1.9.30 sync.
[gae.git] / python / google / appengine / tools / dev_appserver_oauth.py
blob93ed47019609964e15e0c06f5ee16aa6ee7bd275
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.
17 """Helper CGI for OAuth in the development app server."""
22 import cgi
26 _GET_REQUEST_TOKEN_URL = '/_ah/OAuthGetRequestToken'
27 _AUTHORIZE_TOKEN_URL = '/_ah/OAuthAuthorizeToken'
28 _GET_ACCESS_TOKEN_URL = '/_ah/OAuthGetAccessToken'
31 _OAUTH_CALLBACK_PARAM = 'oauth_callback'
34 OAUTH_URL_PATTERN = (_GET_REQUEST_TOKEN_URL
35 + '|' + _AUTHORIZE_TOKEN_URL
36 + '|' + _GET_ACCESS_TOKEN_URL)
40 TOKEN_APPROVAL_TEMPLATE = """<html>
41 <head>
42 <title>OAuth Access Request</title>
43 </head>
44 <body>
46 <form method="POST">
47 <div style="width: 20em; margin: 1em auto;
48 text-align: left;
49 padding: 0 2em 1.25em 2em;
50 background-color: #d6e9f8;
51 font: 13px sans-serif;
52 border: 2px solid #67a7e3">
53 <h3>OAuth Access Request</h3>
54 <input type="hidden" name="oauth_callback" value="%(oauth_callback)s"/>
55 <p style="margin-left: 3em;">
56 <input name="action" type="submit" value="Grant Access"/>
57 </p>
58 </div>
59 </form>
61 </body>
62 </html>
63 """
66 TOKEN_APPROVED_TEMPLATE = """<html>
67 <head>
68 <title>OAuth Access Granted</title>
69 </head>
70 <body>
72 <div style="width: 20em; margin: 1em auto;
73 text-align: left;
74 padding: 0 2em 1.25em 2em;
75 background-color: #d6e9f8;
76 font: 13px sans-serif;
77 border: 2px solid #67a7e3">
78 <h3>OAuth Access Granted</h3>
79 </div>
81 </body>
82 </html>
83 """
86 def RenderTokenApprovalTemplate(oauth_callback):
87 """Renders the token approval page.
89 Args:
90 oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI.
92 Returns:
93 String containing the contents of the token approval page.
94 """
95 template_dict = {
96 'oauth_callback': cgi.escape(oauth_callback, quote=True),
99 return TOKEN_APPROVAL_TEMPLATE % template_dict
102 def RenderTokenApprovedTemplate():
103 """Renders the token approved page.
105 Returns:
106 String containing the contents of the token approved page.
108 return TOKEN_APPROVED_TEMPLATE
111 def OAuthGetRequestTokenCGI(outfile):
112 """Runs the OAuthGetRequestToken CGI.
114 Args:
115 outfile: File-like object to which all output data should be written.
117 outfile.write('Status: 200\r\n')
118 outfile.write('Content-Type: text/plain\r\n')
119 outfile.write('\r\n')
120 outfile.write('oauth_token=REQUEST_TOKEN')
121 outfile.write('&')
122 outfile.write('oauth_token_secret=REQUEST_TOKEN_SECRET')
125 def OAuthAuthorizeTokenCGI(method, parameters, outfile):
126 """Runs the OAuthAuthorizeToken CGI.
128 Args:
129 method: HTTP method
130 parameters: Dictionary of parameters from the request.
131 outfile: File-like object to which all output data should be written.
133 oauth_callback = GetFirst(parameters, _OAUTH_CALLBACK_PARAM, '')
134 if method == 'GET':
135 outfile.write('Status: 200\r\n')
136 outfile.write('Content-Type: text/html\r\n')
137 outfile.write('\r\n')
138 outfile.write(RenderTokenApprovalTemplate(oauth_callback))
139 elif method == 'POST':
140 if oauth_callback:
141 outfile.write('Status: 302 Redirecting to callback URL\r\n')
142 outfile.write('Location: %s\r\n' % oauth_callback)
143 outfile.write('\r\n')
144 else:
145 outfile.write('Status: 200\r\n')
146 outfile.write('Content-Type: text/html\r\n')
147 outfile.write('\r\n')
148 outfile.write(RenderTokenApprovedTemplate())
149 else:
150 outfile.write('Status: 400 Unsupported method\r\n')
153 def OAuthGetAccessTokenCGI(outfile):
154 """Runs the OAuthGetAccessToken CGI.
156 Args:
157 outfile: File-like object to which all output data should be written.
159 outfile.write('Status: 200\r\n')
160 outfile.write('Content-Type: text/plain\r\n')
161 outfile.write('\r\n')
162 outfile.write('oauth_token=ACCESS_TOKEN')
163 outfile.write('&')
164 outfile.write('oauth_token_secret=ACCESS_TOKEN_SECRET')
167 def GetFirst(parameters, key, default=None):
168 """Returns the first value of the given key.
170 Args:
171 parameters: A dictionary of lists, {key: [value1, value2]}
172 key: name of parameter to retrieve
173 default: value to return if the key isn't found
175 Returns:
176 The first value in the list, or default.
178 if key in parameters:
179 if parameters[key]:
180 return parameters[key][0]
181 return default
184 def MainCGI(method, path, unused_headers, parameters, outfile):
185 """CGI for all OAuth handlers.
187 Args:
188 method: HTTP method
189 path: Path of the request
190 unused_headers: Instance of mimetools.Message with headers from the request.
191 parameters: Dictionary of parameters from the request.
192 outfile: File-like object to which all output data should be written.
194 if method != 'GET' and method != 'POST':
195 outfile.write('Status: 400\r\n')
196 return
198 if path == _GET_REQUEST_TOKEN_URL:
199 OAuthGetRequestTokenCGI(outfile)
200 elif path == _AUTHORIZE_TOKEN_URL:
201 OAuthAuthorizeTokenCGI(method, parameters, outfile)
202 elif path == _GET_ACCESS_TOKEN_URL:
203 OAuthGetAccessTokenCGI(outfile)
204 else:
205 outfile.write('Status: 404 Unknown OAuth handler\r\n')
208 def CreateOAuthDispatcher():
209 """Function to create OAuth dispatcher.
211 Returns:
212 New dispatcher capable of handling requests to the built-in OAuth handlers.
217 from google.appengine.tools import old_dev_appserver
219 class OAuthDispatcher(old_dev_appserver.URLDispatcher):
220 """Dispatcher that handles requests to the built-in OAuth handlers."""
222 def Dispatch(self,
223 request,
224 outfile,
225 base_env_dict=None):
226 """Handles dispatch to OAuth handlers.
228 Args:
229 request: AppServerRequest.
230 outfile: The response file.
231 base_env_dict: Dictionary of CGI environment parameters if available.
232 Defaults to None.
234 if not base_env_dict:
235 outfile.write('Status: 500\r\n')
236 return
237 method, path, headers, parameters = self._Parse(request, base_env_dict)
238 MainCGI(method, path, headers, parameters, outfile)
240 def _Parse(self, request, base_env_dict):
241 """Parses a request into convenient pieces.
243 Args:
244 request: AppServerRequest.
245 base_env_dict: Dictionary of CGI environment parameters.
247 Returns:
248 A tuple (method, path, headers, parameters) of the HTTP method, the
249 path (minus query string), an instance of mimetools.Message with
250 headers from the request, and a dictionary of parameter lists from the
251 body or query string (in the form of {key :[value1, value2]}).
253 method = base_env_dict['REQUEST_METHOD']
254 path, query = old_dev_appserver.SplitURL(request.relative_url)
255 parameters = {}
256 if method == 'POST':
257 form = cgi.FieldStorage(fp=request.infile,
258 headers=request.headers,
259 environ=base_env_dict)
260 for key in form:
261 if key not in parameters:
262 parameters[key] = []
263 for value in form.getlist(key):
264 parameters[key].append(value)
265 elif method == 'GET':
266 parameters = cgi.parse_qs(query)
267 return method, path, request.headers, parameters
269 return OAuthDispatcher()