App Engine Python SDK version 1.8.9
[gae.git] / python / google / appengine / tools / dev_appserver_oauth.py
blob16f2e6926a7eeae5110d31cc04219d898ec5b683
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
21 """Helper CGI for OAuth in the development app server."""
25 import cgi
29 _GET_REQUEST_TOKEN_URL = '/_ah/OAuthGetRequestToken'
30 _AUTHORIZE_TOKEN_URL = '/_ah/OAuthAuthorizeToken'
31 _GET_ACCESS_TOKEN_URL = '/_ah/OAuthGetAccessToken'
34 _OAUTH_CALLBACK_PARAM = 'oauth_callback'
37 OAUTH_URL_PATTERN = (_GET_REQUEST_TOKEN_URL
38 + '|' + _AUTHORIZE_TOKEN_URL
39 + '|' + _GET_ACCESS_TOKEN_URL)
43 TOKEN_APPROVAL_TEMPLATE = """<html>
44 <head>
45 <title>OAuth Access Request</title>
46 </head>
47 <body>
49 <form method="POST">
50 <div style="width: 20em; margin: 1em auto;
51 text-align: left;
52 padding: 0 2em 1.25em 2em;
53 background-color: #d6e9f8;
54 font: 13px sans-serif;
55 border: 2px solid #67a7e3">
56 <h3>OAuth Access Request</h3>
57 <input type="hidden" name="oauth_callback" value="%(oauth_callback)s"/>
58 <p style="margin-left: 3em;">
59 <input name="action" type="submit" value="Grant Access"/>
60 </p>
61 </div>
62 </form>
64 </body>
65 </html>
66 """
69 TOKEN_APPROVED_TEMPLATE = """<html>
70 <head>
71 <title>OAuth Access Granted</title>
72 </head>
73 <body>
75 <div style="width: 20em; margin: 1em auto;
76 text-align: left;
77 padding: 0 2em 1.25em 2em;
78 background-color: #d6e9f8;
79 font: 13px sans-serif;
80 border: 2px solid #67a7e3">
81 <h3>OAuth Access Granted</h3>
82 </div>
84 </body>
85 </html>
86 """
89 def RenderTokenApprovalTemplate(oauth_callback):
90 """Renders the token approval page.
92 Args:
93 oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI.
95 Returns:
96 String containing the contents of the token approval page.
97 """
98 template_dict = {
99 'oauth_callback': cgi.escape(oauth_callback, quote=True),
102 return TOKEN_APPROVAL_TEMPLATE % template_dict
105 def RenderTokenApprovedTemplate():
106 """Renders the token approved page.
108 Returns:
109 String containing the contents of the token approved page.
111 return TOKEN_APPROVED_TEMPLATE
114 def OAuthGetRequestTokenCGI(outfile):
115 """Runs the OAuthGetRequestToken CGI.
117 Args:
118 outfile: File-like object to which all output data should be written.
120 outfile.write('Status: 200\r\n')
121 outfile.write('Content-Type: text/plain\r\n')
122 outfile.write('\r\n')
123 outfile.write('oauth_token=REQUEST_TOKEN')
124 outfile.write('&')
125 outfile.write('oauth_token_secret=REQUEST_TOKEN_SECRET')
128 def OAuthAuthorizeTokenCGI(method, parameters, outfile):
129 """Runs the OAuthAuthorizeToken CGI.
131 Args:
132 method: HTTP method
133 parameters: Dictionary of parameters from the request.
134 outfile: File-like object to which all output data should be written.
136 oauth_callback = GetFirst(parameters, _OAUTH_CALLBACK_PARAM, '')
137 if method == 'GET':
138 outfile.write('Status: 200\r\n')
139 outfile.write('Content-Type: text/html\r\n')
140 outfile.write('\r\n')
141 outfile.write(RenderTokenApprovalTemplate(oauth_callback))
142 elif method == 'POST':
143 if oauth_callback:
144 outfile.write('Status: 302 Redirecting to callback URL\r\n')
145 outfile.write('Location: %s\r\n' % oauth_callback)
146 outfile.write('\r\n')
147 else:
148 outfile.write('Status: 200\r\n')
149 outfile.write('Content-Type: text/html\r\n')
150 outfile.write('\r\n')
151 outfile.write(RenderTokenApprovedTemplate())
152 else:
153 outfile.write('Status: 400 Unsupported method\r\n')
156 def OAuthGetAccessTokenCGI(outfile):
157 """Runs the OAuthGetAccessToken CGI.
159 Args:
160 outfile: File-like object to which all output data should be written.
162 outfile.write('Status: 200\r\n')
163 outfile.write('Content-Type: text/plain\r\n')
164 outfile.write('\r\n')
165 outfile.write('oauth_token=ACCESS_TOKEN')
166 outfile.write('&')
167 outfile.write('oauth_token_secret=ACCESS_TOKEN_SECRET')
170 def GetFirst(parameters, key, default=None):
171 """Returns the first value of the given key.
173 Args:
174 parameters: A dictionary of lists, {key: [value1, value2]}
175 key: name of parameter to retrieve
176 default: value to return if the key isn't found
178 Returns:
179 The first value in the list, or default.
181 if key in parameters:
182 if parameters[key]:
183 return parameters[key][0]
184 return default
187 def MainCGI(method, path, unused_headers, parameters, outfile):
188 """CGI for all OAuth handlers.
190 Args:
191 method: HTTP method
192 path: Path of the request
193 unused_headers: Instance of mimetools.Message with headers from the request.
194 parameters: Dictionary of parameters from the request.
195 outfile: File-like object to which all output data should be written.
197 if method != 'GET' and method != 'POST':
198 outfile.write('Status: 400\r\n')
199 return
201 if path == _GET_REQUEST_TOKEN_URL:
202 OAuthGetRequestTokenCGI(outfile)
203 elif path == _AUTHORIZE_TOKEN_URL:
204 OAuthAuthorizeTokenCGI(method, parameters, outfile)
205 elif path == _GET_ACCESS_TOKEN_URL:
206 OAuthGetAccessTokenCGI(outfile)
207 else:
208 outfile.write('Status: 404 Unknown OAuth handler\r\n')
211 def CreateOAuthDispatcher():
212 """Function to create OAuth dispatcher.
214 Returns:
215 New dispatcher capable of handling requests to the built-in OAuth handlers.
220 from google.appengine.tools import dev_appserver
222 class OAuthDispatcher(dev_appserver.URLDispatcher):
223 """Dispatcher that handles requests to the built-in OAuth handlers."""
225 def Dispatch(self,
226 request,
227 outfile,
228 base_env_dict=None):
229 """Handles dispatch to OAuth handlers.
231 Args:
232 request: AppServerRequest.
233 outfile: The response file.
234 base_env_dict: Dictionary of CGI environment parameters if available.
235 Defaults to None.
237 if not base_env_dict:
238 outfile.write('Status: 500\r\n')
239 return
240 method, path, headers, parameters = self._Parse(request, base_env_dict)
241 MainCGI(method, path, headers, parameters, outfile)
243 def _Parse(self, request, base_env_dict):
244 """Parses a request into convenient pieces.
246 Args:
247 request: AppServerRequest.
248 base_env_dict: Dictionary of CGI environment parameters.
250 Returns:
251 A tuple (method, path, headers, parameters) of the HTTP method, the
252 path (minus query string), an instance of mimetools.Message with
253 headers from the request, and a dictionary of parameter lists from the
254 body or query string (in the form of {key :[value1, value2]}).
256 method = base_env_dict['REQUEST_METHOD']
257 path, query = dev_appserver.SplitURL(request.relative_url)
258 parameters = {}
259 if method == 'POST':
260 form = cgi.FieldStorage(fp=request.infile,
261 headers=request.headers,
262 environ=base_env_dict)
263 for key in form:
264 if key not in parameters:
265 parameters[key] = []
266 for value in form.getlist(key):
267 parameters[key].append(value)
268 elif method == 'GET':
269 parameters = cgi.parse_qs(query)
270 return method, path, request.headers, parameters
272 return OAuthDispatcher()