| #!/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() |