953 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			953 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python
 | 
						|
#
 | 
						|
# Copyright (C) 2007 - 2009 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.
 | 
						|
 | 
						|
 | 
						|
import cgi
 | 
						|
import math
 | 
						|
import random
 | 
						|
import re
 | 
						|
import time
 | 
						|
import types
 | 
						|
import urllib
 | 
						|
import atom.http_interface
 | 
						|
import atom.token_store
 | 
						|
import atom.url
 | 
						|
import gdata.oauth as oauth
 | 
						|
import gdata.oauth.rsa as oauth_rsa
 | 
						|
import gdata.tlslite.utils.keyfactory as keyfactory
 | 
						|
import gdata.tlslite.utils.cryptomath as cryptomath
 | 
						|
 | 
						|
import gdata.gauth
 | 
						|
 | 
						|
__author__ = 'api.jscudder (Jeff Scudder)'
 | 
						|
 | 
						|
 | 
						|
PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth='
 | 
						|
AUTHSUB_AUTH_LABEL = 'AuthSub token='
 | 
						|
 | 
						|
 | 
						|
"""This module provides functions and objects used with Google authentication.
 | 
						|
 | 
						|
Details on Google authorization mechanisms used with the Google Data APIs can
 | 
						|
be found here: 
 | 
						|
http://code.google.com/apis/gdata/auth.html
 | 
						|
http://code.google.com/apis/accounts/
 | 
						|
 | 
						|
The essential functions are the following.
 | 
						|
Related to ClientLogin:
 | 
						|
  generate_client_login_request_body: Constructs the body of an HTTP request to
 | 
						|
                                      obtain a ClientLogin token for a specific
 | 
						|
                                      service. 
 | 
						|
  extract_client_login_token: Creates a ClientLoginToken with the token from a
 | 
						|
                              success response to a ClientLogin request.
 | 
						|
  get_captcha_challenge: If the server responded to the ClientLogin request
 | 
						|
                         with a CAPTCHA challenge, this method extracts the
 | 
						|
                         CAPTCHA URL and identifying CAPTCHA token.
 | 
						|
 | 
						|
Related to AuthSub:
 | 
						|
  generate_auth_sub_url: Constructs a full URL for a AuthSub request. The 
 | 
						|
                         user's browser must be sent to this Google Accounts
 | 
						|
                         URL and redirected back to the app to obtain the
 | 
						|
                         AuthSub token.
 | 
						|
  extract_auth_sub_token_from_url: Once the user's browser has been 
 | 
						|
                                   redirected back to the web app, use this
 | 
						|
                                   function to create an AuthSubToken with
 | 
						|
                                   the correct authorization token and scope.
 | 
						|
  token_from_http_body: Extracts the AuthSubToken value string from the 
 | 
						|
                        server's response to an AuthSub session token upgrade
 | 
						|
                        request.
 | 
						|
"""
 | 
						|
 | 
						|
def generate_client_login_request_body(email, password, service, source, 
 | 
						|
    account_type='HOSTED_OR_GOOGLE', captcha_token=None, 
 | 
						|
    captcha_response=None):
 | 
						|
  """Creates the body of the autentication request
 | 
						|
 | 
						|
  See http://code.google.com/apis/accounts/AuthForInstalledApps.html#Request
 | 
						|
  for more details.
 | 
						|
 | 
						|
  Args:
 | 
						|
    email: str
 | 
						|
    password: str
 | 
						|
    service: str
 | 
						|
    source: str
 | 
						|
    account_type: str (optional) Defaul is 'HOSTED_OR_GOOGLE', other valid
 | 
						|
        values are 'GOOGLE' and 'HOSTED'
 | 
						|
    captcha_token: str (optional)
 | 
						|
    captcha_response: str (optional)
 | 
						|
 | 
						|
  Returns:
 | 
						|
    The HTTP body to send in a request for a client login token.
 | 
						|
  """
 | 
						|
  return gdata.gauth.generate_client_login_request_body(email, password,
 | 
						|
      service, source, account_type, captcha_token, captcha_response)
 | 
						|
 | 
						|
 | 
						|
GenerateClientLoginRequestBody = generate_client_login_request_body
 | 
						|
 | 
						|
 | 
						|
def GenerateClientLoginAuthToken(http_body):
 | 
						|
  """Returns the token value to use in Authorization headers.
 | 
						|
 | 
						|
  Reads the token from the server's response to a Client Login request and
 | 
						|
  creates header value to use in requests.
 | 
						|
 | 
						|
  Args:
 | 
						|
    http_body: str The body of the server's HTTP response to a Client Login
 | 
						|
        request
 | 
						|
 
 | 
						|
  Returns:
 | 
						|
    The value half of an Authorization header.
 | 
						|
  """
 | 
						|
  token = get_client_login_token(http_body)
 | 
						|
  if token:
 | 
						|
    return 'GoogleLogin auth=%s' % token
 | 
						|
  return None
 | 
						|
 | 
						|
 | 
						|
def get_client_login_token(http_body):
 | 
						|
  """Returns the token value for a ClientLoginToken.
 | 
						|
 | 
						|
  Reads the token from the server's response to a Client Login request and
 | 
						|
  creates the token value string to use in requests.
 | 
						|
 | 
						|
  Args:
 | 
						|
    http_body: str The body of the server's HTTP response to a Client Login
 | 
						|
        request
 | 
						|
 
 | 
						|
  Returns:
 | 
						|
    The token value string for a ClientLoginToken.
 | 
						|
  """
 | 
						|
  return gdata.gauth.get_client_login_token_string(http_body)
 | 
						|
 | 
						|
 | 
						|
def extract_client_login_token(http_body, scopes):
 | 
						|
  """Parses the server's response and returns a ClientLoginToken.
 | 
						|
  
 | 
						|
  Args:
 | 
						|
    http_body: str The body of the server's HTTP response to a Client Login
 | 
						|
               request. It is assumed that the login request was successful.
 | 
						|
    scopes: list containing atom.url.Urls or strs. The scopes list contains
 | 
						|
            all of the partial URLs under which the client login token is
 | 
						|
            valid. For example, if scopes contains ['http://example.com/foo']
 | 
						|
            then the client login token would be valid for 
 | 
						|
            http://example.com/foo/bar/baz
 | 
						|
 | 
						|
  Returns:
 | 
						|
    A ClientLoginToken which is valid for the specified scopes.
 | 
						|
  """
 | 
						|
  token_string = get_client_login_token(http_body)
 | 
						|
  token = ClientLoginToken(scopes=scopes)
 | 
						|
  token.set_token_string(token_string)
 | 
						|
  return token
 | 
						|
 | 
						|
 | 
						|
def get_captcha_challenge(http_body, 
 | 
						|
    captcha_base_url='http://www.google.com/accounts/'):
 | 
						|
  """Returns the URL and token for a CAPTCHA challenge issued by the server.
 | 
						|
 | 
						|
  Args:
 | 
						|
    http_body: str The body of the HTTP response from the server which 
 | 
						|
        contains the CAPTCHA challenge.
 | 
						|
    captcha_base_url: str This function returns a full URL for viewing the 
 | 
						|
        challenge image which is built from the server's response. This
 | 
						|
        base_url is used as the beginning of the URL because the server
 | 
						|
        only provides the end of the URL. For example the server provides
 | 
						|
        'Captcha?ctoken=Hi...N' and the URL for the image is
 | 
						|
        'http://www.google.com/accounts/Captcha?ctoken=Hi...N'
 | 
						|
 | 
						|
  Returns:
 | 
						|
    A dictionary containing the information needed to repond to the CAPTCHA
 | 
						|
    challenge, the image URL and the ID token of the challenge. The 
 | 
						|
    dictionary is in the form:
 | 
						|
    {'token': string identifying the CAPTCHA image,
 | 
						|
     'url': string containing the URL of the image}
 | 
						|
    Returns None if there was no CAPTCHA challenge in the response.
 | 
						|
  """
 | 
						|
  return gdata.gauth.get_captcha_challenge(http_body, captcha_base_url)
 | 
						|
 | 
						|
 | 
						|
GetCaptchaChallenge = get_captcha_challenge
 | 
						|
 | 
						|
 | 
						|
def GenerateOAuthRequestTokenUrl(
 | 
						|
    oauth_input_params, scopes,
 | 
						|
    request_token_url='https://www.google.com/accounts/OAuthGetRequestToken',
 | 
						|
    extra_parameters=None):
 | 
						|
  """Generate a URL at which a request for OAuth request token is to be sent.
 | 
						|
  
 | 
						|
  Args:
 | 
						|
    oauth_input_params: OAuthInputParams OAuth input parameters.
 | 
						|
    scopes: list of strings The URLs of the services to be accessed.
 | 
						|
    request_token_url: string The beginning of the request token URL. This is
 | 
						|
        normally 'https://www.google.com/accounts/OAuthGetRequestToken' or
 | 
						|
        '/accounts/OAuthGetRequestToken'
 | 
						|
    extra_parameters: dict (optional) key-value pairs as any additional
 | 
						|
        parameters to be included in the URL and signature while making a
 | 
						|
        request for fetching an OAuth request token. All the OAuth parameters
 | 
						|
        are added by default. But if provided through this argument, any
 | 
						|
        default parameters will be overwritten. For e.g. a default parameter
 | 
						|
        oauth_version 1.0 can be overwritten if
 | 
						|
        extra_parameters = {'oauth_version': '2.0'}
 | 
						|
  
 | 
						|
  Returns:
 | 
						|
    atom.url.Url OAuth request token URL.
 | 
						|
  """
 | 
						|
  scopes_string = ' '.join([str(scope) for scope in scopes])
 | 
						|
  parameters = {'scope': scopes_string}
 | 
						|
  if extra_parameters:
 | 
						|
    parameters.update(extra_parameters)
 | 
						|
  oauth_request = oauth.OAuthRequest.from_consumer_and_token(
 | 
						|
      oauth_input_params.GetConsumer(), http_url=request_token_url,
 | 
						|
      parameters=parameters)
 | 
						|
  oauth_request.sign_request(oauth_input_params.GetSignatureMethod(),
 | 
						|
                             oauth_input_params.GetConsumer(), None)
 | 
						|
  return atom.url.parse_url(oauth_request.to_url())
 | 
						|
 | 
						|
 | 
						|
def GenerateOAuthAuthorizationUrl(
 | 
						|
    request_token,
 | 
						|
    authorization_url='https://www.google.com/accounts/OAuthAuthorizeToken',
 | 
						|
    callback_url=None, extra_params=None,
 | 
						|
    include_scopes_in_callback=False, scopes_param_prefix='oauth_token_scope'):
 | 
						|
  """Generates URL at which user will login to authorize the request token.
 | 
						|
  
 | 
						|
  Args:
 | 
						|
    request_token: gdata.auth.OAuthToken OAuth request token.
 | 
						|
    authorization_url: string The beginning of the authorization URL. This is
 | 
						|
        normally 'https://www.google.com/accounts/OAuthAuthorizeToken' or
 | 
						|
        '/accounts/OAuthAuthorizeToken'
 | 
						|
    callback_url: string (optional) The URL user will be sent to after
 | 
						|
        logging in and granting access.
 | 
						|
    extra_params: dict (optional) Additional parameters to be sent.
 | 
						|
    include_scopes_in_callback: Boolean (default=False) if set to True, and
 | 
						|
        if 'callback_url' is present, the 'callback_url' will be modified to
 | 
						|
        include the scope(s) from the request token as a URL parameter. The
 | 
						|
        key for the 'callback' URL's scope parameter will be
 | 
						|
        OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as
 | 
						|
        a parameter to the 'callback' URL, is that the page which receives
 | 
						|
        the OAuth token will be able to tell which URLs the token grants
 | 
						|
        access to.
 | 
						|
    scopes_param_prefix: string (default='oauth_token_scope') The URL
 | 
						|
        parameter key which maps to the list of valid scopes for the token.
 | 
						|
        This URL parameter will be included in the callback URL along with
 | 
						|
        the scopes of the token as value if include_scopes_in_callback=True.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    atom.url.Url OAuth authorization URL.
 | 
						|
  """
 | 
						|
  scopes = request_token.scopes
 | 
						|
  if isinstance(scopes, list):
 | 
						|
    scopes = ' '.join(scopes)  
 | 
						|
  if include_scopes_in_callback and callback_url:
 | 
						|
    if callback_url.find('?') > -1:
 | 
						|
      callback_url += '&'
 | 
						|
    else:
 | 
						|
      callback_url += '?'
 | 
						|
    callback_url += urllib.urlencode({scopes_param_prefix:scopes})  
 | 
						|
  oauth_token = oauth.OAuthToken(request_token.key, request_token.secret)
 | 
						|
  oauth_request = oauth.OAuthRequest.from_token_and_callback(
 | 
						|
      token=oauth_token, callback=callback_url,
 | 
						|
      http_url=authorization_url, parameters=extra_params)
 | 
						|
  return atom.url.parse_url(oauth_request.to_url())
 | 
						|
 | 
						|
 | 
						|
def GenerateOAuthAccessTokenUrl(
 | 
						|
    authorized_request_token,
 | 
						|
    oauth_input_params,
 | 
						|
    access_token_url='https://www.google.com/accounts/OAuthGetAccessToken',
 | 
						|
    oauth_version='1.0',
 | 
						|
    oauth_verifier=None):
 | 
						|
  """Generates URL at which user will login to authorize the request token.
 | 
						|
  
 | 
						|
  Args:
 | 
						|
    authorized_request_token: gdata.auth.OAuthToken OAuth authorized request
 | 
						|
        token.
 | 
						|
    oauth_input_params: OAuthInputParams OAuth input parameters.    
 | 
						|
    access_token_url: string The beginning of the authorization URL. This is
 | 
						|
        normally 'https://www.google.com/accounts/OAuthGetAccessToken' or
 | 
						|
        '/accounts/OAuthGetAccessToken'
 | 
						|
    oauth_version: str (default='1.0') oauth_version parameter.
 | 
						|
    oauth_verifier: str (optional) If present, it is assumed that the client
 | 
						|
        will use the OAuth v1.0a protocol which includes passing the
 | 
						|
        oauth_verifier (as returned by the SP) in the access token step.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    atom.url.Url OAuth access token URL.
 | 
						|
  """
 | 
						|
  oauth_token = oauth.OAuthToken(authorized_request_token.key,
 | 
						|
                                 authorized_request_token.secret)
 | 
						|
  parameters = {'oauth_version': oauth_version}
 | 
						|
  if oauth_verifier is not None:
 | 
						|
    parameters['oauth_verifier'] = oauth_verifier
 | 
						|
  oauth_request = oauth.OAuthRequest.from_consumer_and_token(
 | 
						|
      oauth_input_params.GetConsumer(), token=oauth_token,
 | 
						|
      http_url=access_token_url, parameters=parameters)
 | 
						|
  oauth_request.sign_request(oauth_input_params.GetSignatureMethod(),
 | 
						|
                             oauth_input_params.GetConsumer(), oauth_token)
 | 
						|
  return atom.url.parse_url(oauth_request.to_url())
 | 
						|
 | 
						|
 | 
						|
def GenerateAuthSubUrl(next, scope, secure=False, session=True, 
 | 
						|
    request_url='https://www.google.com/accounts/AuthSubRequest',
 | 
						|
    domain='default'):
 | 
						|
  """Generate a URL at which the user will login and be redirected back.
 | 
						|
 | 
						|
  Users enter their credentials on a Google login page and a token is sent
 | 
						|
  to the URL specified in next. See documentation for AuthSub login at:
 | 
						|
  http://code.google.com/apis/accounts/AuthForWebApps.html
 | 
						|
 | 
						|
  Args:
 | 
						|
    request_url: str The beginning of the request URL. This is normally
 | 
						|
        'http://www.google.com/accounts/AuthSubRequest' or 
 | 
						|
        '/accounts/AuthSubRequest'
 | 
						|
    next: string The URL user will be sent to after logging in.
 | 
						|
    scope: string The URL of the service to be accessed.
 | 
						|
    secure: boolean (optional) Determines whether or not the issued token
 | 
						|
            is a secure token.
 | 
						|
    session: boolean (optional) Determines whether or not the issued token
 | 
						|
             can be upgraded to a session token.
 | 
						|
    domain: str (optional) The Google Apps domain for this account. If this
 | 
						|
            is not a Google Apps account, use 'default' which is the default
 | 
						|
            value.
 | 
						|
  """
 | 
						|
  # Translate True/False values for parameters into numeric values acceoted
 | 
						|
  # by the AuthSub service.
 | 
						|
  if secure:
 | 
						|
    secure = 1
 | 
						|
  else:
 | 
						|
    secure = 0
 | 
						|
 | 
						|
  if session:
 | 
						|
    session = 1
 | 
						|
  else:
 | 
						|
    session = 0
 | 
						|
 | 
						|
  request_params = urllib.urlencode({'next': next, 'scope': scope,
 | 
						|
                                     'secure': secure, 'session': session, 
 | 
						|
                                     'hd': domain})
 | 
						|
  if request_url.find('?') == -1:
 | 
						|
    return '%s?%s' % (request_url, request_params)
 | 
						|
  else:
 | 
						|
    # The request URL already contained url parameters so we should add
 | 
						|
    # the parameters using the & seperator
 | 
						|
    return '%s&%s' % (request_url, request_params)
 | 
						|
 | 
						|
 | 
						|
def generate_auth_sub_url(next, scopes, secure=False, session=True,
 | 
						|
    request_url='https://www.google.com/accounts/AuthSubRequest', 
 | 
						|
    domain='default', scopes_param_prefix='auth_sub_scopes'):
 | 
						|
  """Constructs a URL string for requesting a multiscope AuthSub token.
 | 
						|
 | 
						|
  The generated token will contain a URL parameter to pass along the 
 | 
						|
  requested scopes to the next URL. When the Google Accounts page 
 | 
						|
  redirects the broswser to the 'next' URL, it appends the single use
 | 
						|
  AuthSub token value to the URL as a URL parameter with the key 'token'.
 | 
						|
  However, the information about which scopes were requested is not
 | 
						|
  included by Google Accounts. This method adds the scopes to the next
 | 
						|
  URL before making the request so that the redirect will be sent to 
 | 
						|
  a page, and both the token value and the list of scopes can be 
 | 
						|
  extracted from the request URL. 
 | 
						|
 | 
						|
  Args:
 | 
						|
    next: atom.url.URL or string The URL user will be sent to after
 | 
						|
          authorizing this web application to access their data.
 | 
						|
    scopes: list containint strings The URLs of the services to be accessed.
 | 
						|
    secure: boolean (optional) Determines whether or not the issued token
 | 
						|
            is a secure token.
 | 
						|
    session: boolean (optional) Determines whether or not the issued token
 | 
						|
             can be upgraded to a session token.
 | 
						|
    request_url: atom.url.Url or str The beginning of the request URL. This
 | 
						|
        is normally 'http://www.google.com/accounts/AuthSubRequest' or 
 | 
						|
        '/accounts/AuthSubRequest'
 | 
						|
    domain: The domain which the account is part of. This is used for Google
 | 
						|
        Apps accounts, the default value is 'default' which means that the
 | 
						|
        requested account is a Google Account (@gmail.com for example)
 | 
						|
    scopes_param_prefix: str (optional) The requested scopes are added as a 
 | 
						|
        URL parameter to the next URL so that the page at the 'next' URL can
 | 
						|
        extract the token value and the valid scopes from the URL. The key
 | 
						|
        for the URL parameter defaults to 'auth_sub_scopes'
 | 
						|
 | 
						|
  Returns:
 | 
						|
    An atom.url.Url which the user's browser should be directed to in order
 | 
						|
    to authorize this application to access their information.
 | 
						|
  """
 | 
						|
  if isinstance(next, (str, unicode)):
 | 
						|
    next = atom.url.parse_url(next)
 | 
						|
  scopes_string = ' '.join([str(scope) for scope in scopes])
 | 
						|
  next.params[scopes_param_prefix] = scopes_string
 | 
						|
 | 
						|
  if isinstance(request_url, (str, unicode)):
 | 
						|
    request_url = atom.url.parse_url(request_url)
 | 
						|
  request_url.params['next'] = str(next)
 | 
						|
  request_url.params['scope'] = scopes_string
 | 
						|
  if session:
 | 
						|
    request_url.params['session'] = 1
 | 
						|
  else:
 | 
						|
    request_url.params['session'] = 0
 | 
						|
  if secure:
 | 
						|
    request_url.params['secure'] = 1
 | 
						|
  else:
 | 
						|
    request_url.params['secure'] = 0
 | 
						|
  request_url.params['hd'] = domain
 | 
						|
  return request_url
 | 
						|
 | 
						|
 | 
						|
def AuthSubTokenFromUrl(url):
 | 
						|
  """Extracts the AuthSub token from the URL. 
 | 
						|
 | 
						|
  Used after the AuthSub redirect has sent the user to the 'next' page and
 | 
						|
  appended the token to the URL. This function returns the value to be used
 | 
						|
  in the Authorization header. 
 | 
						|
 | 
						|
  Args:
 | 
						|
    url: str The URL of the current page which contains the AuthSub token as
 | 
						|
        a URL parameter.
 | 
						|
  """
 | 
						|
  token = TokenFromUrl(url)
 | 
						|
  if token:
 | 
						|
    return 'AuthSub token=%s' % token
 | 
						|
  return None
 | 
						|
 | 
						|
 | 
						|
def TokenFromUrl(url):
 | 
						|
  """Extracts the AuthSub token from the URL.
 | 
						|
 | 
						|
  Returns the raw token value.
 | 
						|
 | 
						|
  Args:
 | 
						|
    url: str The URL or the query portion of the URL string (after the ?) of
 | 
						|
        the current page which contains the AuthSub token as a URL parameter.
 | 
						|
  """
 | 
						|
  if url.find('?') > -1:
 | 
						|
    query_params = url.split('?')[1]
 | 
						|
  else:
 | 
						|
    query_params = url
 | 
						|
  for pair in query_params.split('&'):
 | 
						|
    if pair.startswith('token='):
 | 
						|
      return pair[6:]
 | 
						|
  return None
 | 
						|
 | 
						|
 | 
						|
def extract_auth_sub_token_from_url(url, 
 | 
						|
    scopes_param_prefix='auth_sub_scopes', rsa_key=None):
 | 
						|
  """Creates an AuthSubToken and sets the token value and scopes from the URL.
 | 
						|
  
 | 
						|
  After the Google Accounts AuthSub pages redirect the user's broswer back to 
 | 
						|
  the web application (using the 'next' URL from the request) the web app must
 | 
						|
  extract the token from the current page's URL. The token is provided as a 
 | 
						|
  URL parameter named 'token' and if generate_auth_sub_url was used to create
 | 
						|
  the request, the token's valid scopes are included in a URL parameter whose
 | 
						|
  name is specified in scopes_param_prefix.
 | 
						|
 | 
						|
  Args:
 | 
						|
    url: atom.url.Url or str representing the current URL. The token value
 | 
						|
         and valid scopes should be included as URL parameters.
 | 
						|
    scopes_param_prefix: str (optional) The URL parameter key which maps to
 | 
						|
                         the list of valid scopes for the token.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    An AuthSubToken with the token value from the URL and set to be valid for
 | 
						|
    the scopes passed in on the URL. If no scopes were included in the URL,
 | 
						|
    the AuthSubToken defaults to being valid for no scopes. If there was no
 | 
						|
    'token' parameter in the URL, this function returns None.
 | 
						|
  """
 | 
						|
  if isinstance(url, (str, unicode)):
 | 
						|
    url = atom.url.parse_url(url)
 | 
						|
  if 'token' not in url.params:
 | 
						|
    return None
 | 
						|
  scopes = []
 | 
						|
  if scopes_param_prefix in url.params:
 | 
						|
    scopes = url.params[scopes_param_prefix].split(' ')
 | 
						|
  token_value = url.params['token']
 | 
						|
  if rsa_key:
 | 
						|
    token = SecureAuthSubToken(rsa_key, scopes=scopes)
 | 
						|
  else:
 | 
						|
    token = AuthSubToken(scopes=scopes)
 | 
						|
  token.set_token_string(token_value)
 | 
						|
  return token
 | 
						|
 | 
						|
 | 
						|
def AuthSubTokenFromHttpBody(http_body):
 | 
						|
  """Extracts the AuthSub token from an HTTP body string.
 | 
						|
 | 
						|
  Used to find the new session token after making a request to upgrade a
 | 
						|
  single use AuthSub token.
 | 
						|
 | 
						|
  Args:
 | 
						|
    http_body: str The repsonse from the server which contains the AuthSub
 | 
						|
        key. For example, this function would find the new session token
 | 
						|
        from the server's response to an upgrade token request.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    The header value to use for Authorization which contains the AuthSub
 | 
						|
    token.
 | 
						|
  """
 | 
						|
  token_value = token_from_http_body(http_body)
 | 
						|
  if token_value:
 | 
						|
    return '%s%s' % (AUTHSUB_AUTH_LABEL, token_value)
 | 
						|
  return None
 | 
						|
 | 
						|
 | 
						|
def token_from_http_body(http_body):
 | 
						|
  """Extracts the AuthSub token from an HTTP body string.
 | 
						|
 | 
						|
  Used to find the new session token after making a request to upgrade a 
 | 
						|
  single use AuthSub token.
 | 
						|
 | 
						|
  Args:
 | 
						|
    http_body: str The repsonse from the server which contains the AuthSub 
 | 
						|
        key. For example, this function would find the new session token
 | 
						|
        from the server's response to an upgrade token request.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    The raw token value to use in an AuthSubToken object.
 | 
						|
  """
 | 
						|
  for response_line in http_body.splitlines():
 | 
						|
    if response_line.startswith('Token='):
 | 
						|
      # Strip off Token= and return the token value string.
 | 
						|
      return response_line[6:]
 | 
						|
  return None
 | 
						|
 | 
						|
 | 
						|
TokenFromHttpBody = token_from_http_body
 | 
						|
 | 
						|
 | 
						|
def OAuthTokenFromUrl(url, scopes_param_prefix='oauth_token_scope'):
 | 
						|
  """Creates an OAuthToken and sets token key and scopes (if present) from URL.
 | 
						|
  
 | 
						|
  After the Google Accounts OAuth pages redirect the user's broswer back to 
 | 
						|
  the web application (using the 'callback' URL from the request) the web app
 | 
						|
  can extract the token from the current page's URL. The token is same as the
 | 
						|
  request token, but it is either authorized (if user grants access) or
 | 
						|
  unauthorized (if user denies access). The token is provided as a 
 | 
						|
  URL parameter named 'oauth_token' and if it was chosen to use
 | 
						|
  GenerateOAuthAuthorizationUrl with include_scopes_in_param=True, the token's
 | 
						|
  valid scopes are included in a URL parameter whose name is specified in
 | 
						|
  scopes_param_prefix.
 | 
						|
 | 
						|
  Args:
 | 
						|
    url: atom.url.Url or str representing the current URL. The token value
 | 
						|
        and valid scopes should be included as URL parameters.
 | 
						|
    scopes_param_prefix: str (optional) The URL parameter key which maps to
 | 
						|
        the list of valid scopes for the token.
 | 
						|
 | 
						|
  Returns:
 | 
						|
    An OAuthToken with the token key from the URL and set to be valid for
 | 
						|
    the scopes passed in on the URL. If no scopes were included in the URL,
 | 
						|
    the OAuthToken defaults to being valid for no scopes. If there was no
 | 
						|
    'oauth_token' parameter in the URL, this function returns None.
 | 
						|
  """
 | 
						|
  if isinstance(url, (str, unicode)):
 | 
						|
    url = atom.url.parse_url(url)
 | 
						|
  if 'oauth_token' not in url.params:
 | 
						|
    return None
 | 
						|
  scopes = []
 | 
						|
  if scopes_param_prefix in url.params:
 | 
						|
    scopes = url.params[scopes_param_prefix].split(' ')
 | 
						|
  token_key = url.params['oauth_token']
 | 
						|
  token = OAuthToken(key=token_key, scopes=scopes)
 | 
						|
  return token
 | 
						|
 | 
						|
 | 
						|
def OAuthTokenFromHttpBody(http_body):
 | 
						|
  """Parses the HTTP response body and returns an OAuth token.
 | 
						|
  
 | 
						|
  The returned OAuth token will just have key and secret parameters set.
 | 
						|
  It won't have any knowledge about the scopes or oauth_input_params. It is
 | 
						|
  your responsibility to make it aware of the remaining parameters.
 | 
						|
  
 | 
						|
  Returns:
 | 
						|
    OAuthToken OAuth token.
 | 
						|
  """
 | 
						|
  token = oauth.OAuthToken.from_string(http_body)
 | 
						|
  oauth_token = OAuthToken(key=token.key, secret=token.secret)
 | 
						|
  return oauth_token
 | 
						|
  
 | 
						|
 | 
						|
class OAuthSignatureMethod(object):
 | 
						|
  """Holds valid OAuth signature methods.
 | 
						|
  
 | 
						|
  RSA_SHA1: Class to build signature according to RSA-SHA1 algorithm.
 | 
						|
  HMAC_SHA1: Class to build signature according to HMAC-SHA1 algorithm.
 | 
						|
  """
 | 
						|
  
 | 
						|
  HMAC_SHA1 = oauth.OAuthSignatureMethod_HMAC_SHA1  
 | 
						|
  
 | 
						|
  class RSA_SHA1(oauth_rsa.OAuthSignatureMethod_RSA_SHA1):
 | 
						|
    """Provides implementation for abstract methods to return RSA certs."""
 | 
						|
 | 
						|
    def __init__(self, private_key, public_cert):
 | 
						|
      self.private_key = private_key
 | 
						|
      self.public_cert = public_cert
 | 
						|
  
 | 
						|
    def _fetch_public_cert(self, unused_oauth_request):
 | 
						|
      return self.public_cert
 | 
						|
  
 | 
						|
    def _fetch_private_cert(self, unused_oauth_request):
 | 
						|
      return self.private_key
 | 
						|
  
 | 
						|
 | 
						|
class OAuthInputParams(object):
 | 
						|
  """Stores OAuth input parameters.
 | 
						|
  
 | 
						|
  This class is a store for OAuth input parameters viz. consumer key and secret,
 | 
						|
  signature method and RSA key.
 | 
						|
  """
 | 
						|
  
 | 
						|
  def __init__(self, signature_method, consumer_key, consumer_secret=None,
 | 
						|
               rsa_key=None, requestor_id=None):
 | 
						|
    """Initializes object with parameters required for using OAuth mechanism.
 | 
						|
    
 | 
						|
    NOTE: Though consumer_secret and rsa_key are optional, either of the two
 | 
						|
    is required depending on the value of the signature_method.
 | 
						|
    
 | 
						|
    Args:
 | 
						|
      signature_method: class which provides implementation for strategy class
 | 
						|
          oauth.oauth.OAuthSignatureMethod. Signature method to be used for
 | 
						|
          signing each request. Valid implementations are provided as the
 | 
						|
          constants defined by gdata.auth.OAuthSignatureMethod. Currently
 | 
						|
          they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and
 | 
						|
          gdata.auth.OAuthSignatureMethod.HMAC_SHA1. Instead of passing in
 | 
						|
          the strategy class, you may pass in a string for 'RSA_SHA1' or 
 | 
						|
          'HMAC_SHA1'. If you plan to use OAuth on App Engine (or another
 | 
						|
          WSGI environment) I recommend specifying signature method using a
 | 
						|
          string (the only options are 'RSA_SHA1' and 'HMAC_SHA1'). In these
 | 
						|
          environments there are sometimes issues with pickling an object in 
 | 
						|
          which a member references a class or function. Storing a string to
 | 
						|
          refer to the signature method mitigates complications when
 | 
						|
          pickling.
 | 
						|
      consumer_key: string Domain identifying third_party web application.
 | 
						|
      consumer_secret: string (optional) Secret generated during registration.
 | 
						|
          Required only for HMAC_SHA1 signature method.
 | 
						|
      rsa_key: string (optional) Private key required for RSA_SHA1 signature
 | 
						|
          method.
 | 
						|
      requestor_id: string (optional) User email adress to make requests on
 | 
						|
          their behalf.  This parameter should only be set when performing
 | 
						|
          2 legged OAuth requests.
 | 
						|
    """
 | 
						|
    if (signature_method == OAuthSignatureMethod.RSA_SHA1
 | 
						|
        or signature_method == 'RSA_SHA1'):
 | 
						|
      self.__signature_strategy = 'RSA_SHA1'
 | 
						|
    elif (signature_method == OAuthSignatureMethod.HMAC_SHA1
 | 
						|
        or signature_method == 'HMAC_SHA1'):
 | 
						|
      self.__signature_strategy = 'HMAC_SHA1'
 | 
						|
    else:
 | 
						|
      self.__signature_strategy = signature_method
 | 
						|
    self.rsa_key = rsa_key
 | 
						|
    self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
 | 
						|
    self.requestor_id = requestor_id
 | 
						|
 | 
						|
  def __get_signature_method(self):
 | 
						|
    if self.__signature_strategy == 'RSA_SHA1':
 | 
						|
      return OAuthSignatureMethod.RSA_SHA1(self.rsa_key, None)
 | 
						|
    elif self.__signature_strategy == 'HMAC_SHA1':
 | 
						|
      return OAuthSignatureMethod.HMAC_SHA1()
 | 
						|
    else:
 | 
						|
      return self.__signature_strategy()
 | 
						|
 | 
						|
  def __set_signature_method(self, signature_method):
 | 
						|
    if (signature_method == OAuthSignatureMethod.RSA_SHA1
 | 
						|
        or signature_method == 'RSA_SHA1'):
 | 
						|
      self.__signature_strategy = 'RSA_SHA1'
 | 
						|
    elif (signature_method == OAuthSignatureMethod.HMAC_SHA1
 | 
						|
        or signature_method == 'HMAC_SHA1'):
 | 
						|
      self.__signature_strategy = 'HMAC_SHA1'
 | 
						|
    else:
 | 
						|
      self.__signature_strategy = signature_method
 | 
						|
 | 
						|
  _signature_method = property(__get_signature_method, __set_signature_method,
 | 
						|
      doc="""Returns object capable of signing the request using RSA of HMAC.
 | 
						|
      
 | 
						|
      Replaces the _signature_method member to avoid pickle errors.""")
 | 
						|
 | 
						|
  def GetSignatureMethod(self):
 | 
						|
    """Gets the OAuth signature method.
 | 
						|
 | 
						|
    Returns:
 | 
						|
      object of supertype <oauth.oauth.OAuthSignatureMethod>
 | 
						|
    """
 | 
						|
    return self._signature_method
 | 
						|
 | 
						|
  def GetConsumer(self):
 | 
						|
    """Gets the OAuth consumer.
 | 
						|
 | 
						|
    Returns:
 | 
						|
      object of type <oauth.oauth.Consumer>
 | 
						|
    """
 | 
						|
    return self._consumer
 | 
						|
 | 
						|
 | 
						|
class ClientLoginToken(atom.http_interface.GenericToken):
 | 
						|
  """Stores the Authorization header in auth_header and adds to requests.
 | 
						|
 | 
						|
  This token will add it's Authorization header to an HTTP request
 | 
						|
  as it is made. Ths token class is simple but
 | 
						|
  some Token classes must calculate portions of the Authorization header
 | 
						|
  based on the request being made, which is why the token is responsible
 | 
						|
  for making requests via an http_client parameter.
 | 
						|
 | 
						|
  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'
 | 
						|
  """
 | 
						|
  def __init__(self, auth_header=None, scopes=None):
 | 
						|
    self.auth_header = auth_header
 | 
						|
    self.scopes = scopes or []
 | 
						|
 | 
						|
  def __str__(self):
 | 
						|
    return self.auth_header
 | 
						|
 | 
						|
  def perform_request(self, http_client, operation, url, data=None,
 | 
						|
                      headers=None):
 | 
						|
    """Sets the Authorization header and makes the HTTP request."""
 | 
						|
    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 get_token_string(self):
 | 
						|
    """Removes PROGRAMMATIC_AUTH_LABEL to give just the token value."""
 | 
						|
    return self.auth_header[len(PROGRAMMATIC_AUTH_LABEL):]
 | 
						|
 | 
						|
  def set_token_string(self, token_string):
 | 
						|
    self.auth_header = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, token_string)
 | 
						|
  
 | 
						|
  def valid_for_scope(self, url):
 | 
						|
    """Tells the caller if the token authorizes access to the desired URL.
 | 
						|
    """
 | 
						|
    if isinstance(url, (str, unicode)):
 | 
						|
      url = atom.url.parse_url(url)
 | 
						|
    for scope in self.scopes:
 | 
						|
      if scope == atom.token_store.SCOPE_ALL:
 | 
						|
        return True
 | 
						|
      if isinstance(scope, (str, unicode)):
 | 
						|
        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
 | 
						|
 | 
						|
 | 
						|
class AuthSubToken(ClientLoginToken):
 | 
						|
  def get_token_string(self):
 | 
						|
    """Removes AUTHSUB_AUTH_LABEL to give just the token value."""
 | 
						|
    return self.auth_header[len(AUTHSUB_AUTH_LABEL):]
 | 
						|
 | 
						|
  def set_token_string(self, token_string):
 | 
						|
    self.auth_header = '%s%s' % (AUTHSUB_AUTH_LABEL, token_string)
 | 
						|
 | 
						|
 | 
						|
class OAuthToken(atom.http_interface.GenericToken):
 | 
						|
  """Stores the token key, token secret and scopes for which token is valid.
 | 
						|
  
 | 
						|
  This token adds the authorization header to each request made. It
 | 
						|
  re-calculates authorization header for every request since the OAuth
 | 
						|
  signature to be added to the authorization header is dependent on the
 | 
						|
  request parameters.
 | 
						|
  
 | 
						|
  Attributes:
 | 
						|
    key: str The value for the OAuth token i.e. token key.
 | 
						|
    secret: str The value for the OAuth token secret.
 | 
						|
    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'
 | 
						|
    oauth_input_params: OAuthInputParams OAuth input parameters.      
 | 
						|
  """
 | 
						|
  
 | 
						|
  def __init__(self, key=None, secret=None, scopes=None,
 | 
						|
               oauth_input_params=None):
 | 
						|
    self.key = key
 | 
						|
    self.secret = secret
 | 
						|
    self.scopes = scopes or []
 | 
						|
    self.oauth_input_params = oauth_input_params
 | 
						|
  
 | 
						|
  def __str__(self):
 | 
						|
    return self.get_token_string()
 | 
						|
 | 
						|
  def get_token_string(self):
 | 
						|
    """Returns the token string.
 | 
						|
    
 | 
						|
    The token string returned is of format
 | 
						|
    oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings.
 | 
						|
    
 | 
						|
    Returns:
 | 
						|
      A token string of format oauth_token=[0]&oauth_token_secret=[1],
 | 
						|
      where [0] and [1] are some strings. If self.secret is absent, it just
 | 
						|
      returns oauth_token=[0]. If self.key is absent, it just returns
 | 
						|
      oauth_token_secret=[1]. If both are absent, it returns None.
 | 
						|
    """
 | 
						|
    if self.key and self.secret:
 | 
						|
      return urllib.urlencode({'oauth_token': self.key,
 | 
						|
                               'oauth_token_secret': self.secret})
 | 
						|
    elif self.key:
 | 
						|
      return 'oauth_token=%s' % self.key
 | 
						|
    elif self.secret:
 | 
						|
      return 'oauth_token_secret=%s' % self.secret
 | 
						|
    else:
 | 
						|
      return None
 | 
						|
 | 
						|
  def set_token_string(self, token_string):
 | 
						|
    """Sets the token key and secret from the token string.
 | 
						|
    
 | 
						|
    Args:
 | 
						|
      token_string: str Token string of form
 | 
						|
          oauth_token=[0]&oauth_token_secret=[1]. If oauth_token is not present,
 | 
						|
          self.key will be None. If oauth_token_secret is not present,
 | 
						|
          self.secret will be None.
 | 
						|
    """
 | 
						|
    token_params = cgi.parse_qs(token_string, keep_blank_values=False)
 | 
						|
    if 'oauth_token' in token_params:
 | 
						|
      self.key = token_params['oauth_token'][0]
 | 
						|
    if 'oauth_token_secret' in token_params:
 | 
						|
      self.secret = token_params['oauth_token_secret'][0]
 | 
						|
    
 | 
						|
  def GetAuthHeader(self, http_method, http_url, realm=''):
 | 
						|
    """Get the authentication header.
 | 
						|
 | 
						|
    Args:
 | 
						|
      http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc.
 | 
						|
      http_url: string or atom.url.Url HTTP URL to which request is made.
 | 
						|
      realm: string (default='') realm parameter to be included in the
 | 
						|
          authorization header.
 | 
						|
 | 
						|
    Returns:
 | 
						|
      dict Header to be sent with every subsequent request after
 | 
						|
      authentication.
 | 
						|
    """
 | 
						|
    if isinstance(http_url, types.StringTypes):
 | 
						|
      http_url = atom.url.parse_url(http_url)
 | 
						|
    header = None
 | 
						|
    token = None
 | 
						|
    if self.key or self.secret:
 | 
						|
      token = oauth.OAuthToken(self.key, self.secret)
 | 
						|
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
 | 
						|
        self.oauth_input_params.GetConsumer(), token=token,
 | 
						|
        http_url=str(http_url), http_method=http_method,
 | 
						|
        parameters=http_url.params)
 | 
						|
    oauth_request.sign_request(self.oauth_input_params.GetSignatureMethod(),
 | 
						|
                               self.oauth_input_params.GetConsumer(), token)
 | 
						|
    header = oauth_request.to_header(realm=realm)
 | 
						|
    header['Authorization'] = header['Authorization'].replace('+', '%2B')
 | 
						|
    return header
 | 
						|
  
 | 
						|
  def perform_request(self, http_client, operation, url, data=None,
 | 
						|
                      headers=None):
 | 
						|
    """Sets the Authorization header and makes the HTTP request."""
 | 
						|
    if not headers:
 | 
						|
      headers = {}
 | 
						|
    if self.oauth_input_params.requestor_id:
 | 
						|
      url.params['xoauth_requestor_id'] = self.oauth_input_params.requestor_id
 | 
						|
    headers.update(self.GetAuthHeader(operation, url))
 | 
						|
    return http_client.request(operation, url, data=data, headers=headers)
 | 
						|
    
 | 
						|
  def valid_for_scope(self, url):
 | 
						|
    if isinstance(url, (str, unicode)):
 | 
						|
      url = atom.url.parse_url(url)
 | 
						|
    for scope in self.scopes:
 | 
						|
      if scope == atom.token_store.SCOPE_ALL:
 | 
						|
        return True
 | 
						|
      if isinstance(scope, (str, unicode)):
 | 
						|
        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    
 | 
						|
    
 | 
						|
 | 
						|
class SecureAuthSubToken(AuthSubToken):
 | 
						|
  """Stores the rsa private key, token, and scopes for the secure AuthSub token.
 | 
						|
  
 | 
						|
  This token adds the authorization header to each request made. It
 | 
						|
  re-calculates authorization header for every request since the secure AuthSub
 | 
						|
  signature to be added to the authorization header is dependent on the
 | 
						|
  request parameters.
 | 
						|
  
 | 
						|
  Attributes:
 | 
						|
    rsa_key: string The RSA private key in PEM format that the token will
 | 
						|
             use to sign requests
 | 
						|
    token_string: string (optional) The value for the AuthSub token.
 | 
						|
    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'     
 | 
						|
  """
 | 
						|
  
 | 
						|
  def __init__(self, rsa_key, token_string=None, scopes=None):
 | 
						|
    self.rsa_key = keyfactory.parsePEMKey(rsa_key)
 | 
						|
    self.token_string = token_string or ''
 | 
						|
    self.scopes = scopes or [] 
 | 
						|
   
 | 
						|
  def __str__(self):
 | 
						|
    return self.get_token_string()
 | 
						|
 | 
						|
  def get_token_string(self):
 | 
						|
    return str(self.token_string)
 | 
						|
 | 
						|
  def set_token_string(self, token_string):
 | 
						|
    self.token_string = token_string
 | 
						|
    
 | 
						|
  def GetAuthHeader(self, http_method, http_url):
 | 
						|
    """Generates the Authorization header.
 | 
						|
 | 
						|
    The form of the secure AuthSub Authorization header is
 | 
						|
    Authorization: AuthSub token="token" sigalg="sigalg" data="data" sig="sig"
 | 
						|
    and  data represents a string in the form
 | 
						|
    data = http_method http_url timestamp nonce
 | 
						|
 | 
						|
    Args:
 | 
						|
      http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc.
 | 
						|
      http_url: string or atom.url.Url HTTP URL to which request is made.
 | 
						|
      
 | 
						|
    Returns:
 | 
						|
      dict Header to be sent with every subsequent request after authentication.
 | 
						|
    """
 | 
						|
    timestamp = int(math.floor(time.time()))
 | 
						|
    nonce = '%lu' % random.randrange(1, 2**64)
 | 
						|
    data = '%s %s %d %s' % (http_method, str(http_url), timestamp, nonce)
 | 
						|
    sig = cryptomath.bytesToBase64(self.rsa_key.hashAndSign(data))
 | 
						|
    header = {'Authorization': '%s"%s" data="%s" sig="%s" sigalg="rsa-sha1"' %
 | 
						|
              (AUTHSUB_AUTH_LABEL, self.token_string, data, sig)}
 | 
						|
    return header
 | 
						|
  
 | 
						|
  def perform_request(self, http_client, operation, url, data=None, 
 | 
						|
                      headers=None):
 | 
						|
    """Sets the Authorization header and makes the HTTP request."""
 | 
						|
    if not headers:
 | 
						|
      headers = {}
 | 
						|
    headers.update(self.GetAuthHeader(operation, url))
 | 
						|
    return http_client.request(operation, url, data=data, headers=headers)
 |