/gdata/auth.py
http://radioappz.googlecode.com/ · Python · 952 lines · 794 code · 29 blank · 129 comment · 58 complexity · 7650c25637770e4937a639a3e0618640 MD5 · raw file
- #!/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)