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."""
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>
45 <title>OAuth Access Request</title>
50 <div style="width: 20em; margin: 1em auto;
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"/>
69 TOKEN_APPROVED_TEMPLATE
= """<html>
71 <title>OAuth Access Granted</title>
75 <div style="width: 20em; margin: 1em auto;
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>
89 def RenderTokenApprovalTemplate(oauth_callback
):
90 """Renders the token approval page.
93 oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI.
96 String containing the contents of the token approval page.
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.
109 String containing the contents of the token approved page.
111 return TOKEN_APPROVED_TEMPLATE
114 def OAuthGetRequestTokenCGI(outfile
):
115 """Runs the OAuthGetRequestToken CGI.
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')
125 outfile
.write('oauth_token_secret=REQUEST_TOKEN_SECRET')
128 def OAuthAuthorizeTokenCGI(method
, parameters
, outfile
):
129 """Runs the OAuthAuthorizeToken CGI.
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
, '')
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':
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')
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())
153 outfile
.write('Status: 400 Unsupported method\r\n')
156 def OAuthGetAccessTokenCGI(outfile
):
157 """Runs the OAuthGetAccessToken CGI.
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')
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.
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
179 The first value in the list, or default.
181 if key
in parameters
:
183 return parameters
[key
][0]
187 def MainCGI(method
, path
, unused_headers
, parameters
, outfile
):
188 """CGI for all OAuth handlers.
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')
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
)
208 outfile
.write('Status: 404 Unknown OAuth handler\r\n')
211 def CreateOAuthDispatcher():
212 """Function to create OAuth dispatcher.
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."""
229 """Handles dispatch to OAuth handlers.
232 request: AppServerRequest.
233 outfile: The response file.
234 base_env_dict: Dictionary of CGI environment parameters if available.
237 if not base_env_dict
:
238 outfile
.write('Status: 500\r\n')
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.
247 request: AppServerRequest.
248 base_env_dict: Dictionary of CGI environment parameters.
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
)
260 form
= cgi
.FieldStorage(fp
=request
.infile
,
261 headers
=request
.headers
,
262 environ
=base_env_dict
)
264 if key
not in parameters
:
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()