blob: 93ed47019609964e15e0c06f5ee16aa6ee7bd275 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 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.
#
"""Helper CGI for OAuth in the development app server."""
import cgi
_GET_REQUEST_TOKEN_URL = '/_ah/OAuthGetRequestToken'
_AUTHORIZE_TOKEN_URL = '/_ah/OAuthAuthorizeToken'
_GET_ACCESS_TOKEN_URL = '/_ah/OAuthGetAccessToken'
_OAUTH_CALLBACK_PARAM = 'oauth_callback'
OAUTH_URL_PATTERN = (_GET_REQUEST_TOKEN_URL
+ '|' + _AUTHORIZE_TOKEN_URL
+ '|' + _GET_ACCESS_TOKEN_URL)
TOKEN_APPROVAL_TEMPLATE = """<html>
<head>
<title>OAuth Access Request</title>
</head>
<body>
<form method="POST">
<div style="width: 20em; margin: 1em auto;
text-align: left;
padding: 0 2em 1.25em 2em;
background-color: #d6e9f8;
font: 13px sans-serif;
border: 2px solid #67a7e3">
<h3>OAuth Access Request</h3>
<input type="hidden" name="oauth_callback" value="%(oauth_callback)s"/>
<p style="margin-left: 3em;">
<input name="action" type="submit" value="Grant Access"/>
</p>
</div>
</form>
</body>
</html>
"""
TOKEN_APPROVED_TEMPLATE = """<html>
<head>
<title>OAuth Access Granted</title>
</head>
<body>
<div style="width: 20em; margin: 1em auto;
text-align: left;
padding: 0 2em 1.25em 2em;
background-color: #d6e9f8;
font: 13px sans-serif;
border: 2px solid #67a7e3">
<h3>OAuth Access Granted</h3>
</div>
</body>
</html>
"""
def RenderTokenApprovalTemplate(oauth_callback):
"""Renders the token approval page.
Args:
oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI.
Returns:
String containing the contents of the token approval page.
"""
template_dict = {
'oauth_callback': cgi.escape(oauth_callback, quote=True),
}
return TOKEN_APPROVAL_TEMPLATE % template_dict
def RenderTokenApprovedTemplate():
"""Renders the token approved page.
Returns:
String containing the contents of the token approved page.
"""
return TOKEN_APPROVED_TEMPLATE
def OAuthGetRequestTokenCGI(outfile):
"""Runs the OAuthGetRequestToken CGI.
Args:
outfile: File-like object to which all output data should be written.
"""
outfile.write('Status: 200\r\n')
outfile.write('Content-Type: text/plain\r\n')
outfile.write('\r\n')
outfile.write('oauth_token=REQUEST_TOKEN')
outfile.write('&')
outfile.write('oauth_token_secret=REQUEST_TOKEN_SECRET')
def OAuthAuthorizeTokenCGI(method, parameters, outfile):
"""Runs the OAuthAuthorizeToken CGI.
Args:
method: HTTP method
parameters: Dictionary of parameters from the request.
outfile: File-like object to which all output data should be written.
"""
oauth_callback = GetFirst(parameters, _OAUTH_CALLBACK_PARAM, '')
if method == 'GET':
outfile.write('Status: 200\r\n')
outfile.write('Content-Type: text/html\r\n')
outfile.write('\r\n')
outfile.write(RenderTokenApprovalTemplate(oauth_callback))
elif method == 'POST':
if oauth_callback:
outfile.write('Status: 302 Redirecting to callback URL\r\n')
outfile.write('Location: %s\r\n' % oauth_callback)
outfile.write('\r\n')
else:
outfile.write('Status: 200\r\n')
outfile.write('Content-Type: text/html\r\n')
outfile.write('\r\n')
outfile.write(RenderTokenApprovedTemplate())
else:
outfile.write('Status: 400 Unsupported method\r\n')
def OAuthGetAccessTokenCGI(outfile):
"""Runs the OAuthGetAccessToken CGI.
Args:
outfile: File-like object to which all output data should be written.
"""
outfile.write('Status: 200\r\n')
outfile.write('Content-Type: text/plain\r\n')
outfile.write('\r\n')
outfile.write('oauth_token=ACCESS_TOKEN')
outfile.write('&')
outfile.write('oauth_token_secret=ACCESS_TOKEN_SECRET')
def GetFirst(parameters, key, default=None):
"""Returns the first value of the given key.
Args:
parameters: A dictionary of lists, {key: [value1, value2]}
key: name of parameter to retrieve
default: value to return if the key isn't found
Returns:
The first value in the list, or default.
"""
if key in parameters:
if parameters[key]:
return parameters[key][0]
return default
def MainCGI(method, path, unused_headers, parameters, outfile):
"""CGI for all OAuth handlers.
Args:
method: HTTP method
path: Path of the request
unused_headers: Instance of mimetools.Message with headers from the request.
parameters: Dictionary of parameters from the request.
outfile: File-like object to which all output data should be written.
"""
if method != 'GET' and method != 'POST':
outfile.write('Status: 400\r\n')
return
if path == _GET_REQUEST_TOKEN_URL:
OAuthGetRequestTokenCGI(outfile)
elif path == _AUTHORIZE_TOKEN_URL:
OAuthAuthorizeTokenCGI(method, parameters, outfile)
elif path == _GET_ACCESS_TOKEN_URL:
OAuthGetAccessTokenCGI(outfile)
else:
outfile.write('Status: 404 Unknown OAuth handler\r\n')
def CreateOAuthDispatcher():
"""Function to create OAuth dispatcher.
Returns:
New dispatcher capable of handling requests to the built-in OAuth handlers.
"""
from google.appengine.tools import old_dev_appserver
class OAuthDispatcher(old_dev_appserver.URLDispatcher):
"""Dispatcher that handles requests to the built-in OAuth handlers."""
def Dispatch(self,
request,
outfile,
base_env_dict=None):
"""Handles dispatch to OAuth handlers.
Args:
request: AppServerRequest.
outfile: The response file.
base_env_dict: Dictionary of CGI environment parameters if available.
Defaults to None.
"""
if not base_env_dict:
outfile.write('Status: 500\r\n')
return
method, path, headers, parameters = self._Parse(request, base_env_dict)
MainCGI(method, path, headers, parameters, outfile)
def _Parse(self, request, base_env_dict):
"""Parses a request into convenient pieces.
Args:
request: AppServerRequest.
base_env_dict: Dictionary of CGI environment parameters.
Returns:
A tuple (method, path, headers, parameters) of the HTTP method, the
path (minus query string), an instance of mimetools.Message with
headers from the request, and a dictionary of parameter lists from the
body or query string (in the form of {key :[value1, value2]}).
"""
method = base_env_dict['REQUEST_METHOD']
path, query = old_dev_appserver.SplitURL(request.relative_url)
parameters = {}
if method == 'POST':
form = cgi.FieldStorage(fp=request.infile,
headers=request.headers,
environ=base_env_dict)
for key in form:
if key not in parameters:
parameters[key] = []
for value in form.getlist(key):
parameters[key].append(value)
elif method == 'GET':
parameters = cgi.parse_qs(query)
return method, path, request.headers, parameters
return OAuthDispatcher()