mirror of
https://github.com/GAM-team/GAM.git
synced 2025-07-07 21:23:34 +00:00
Add existing GAM 3.21 code
This commit is contained in:
952
gdata/auth.py
Normal file
952
gdata/auth.py
Normal file
@ -0,0 +1,952 @@
|
||||
#!/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)
|
Reference in New Issue
Block a user