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."""
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>
42 <title>OAuth Access Request</title>
47 <div style="width: 20em; margin: 1em auto;
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"/>
66 TOKEN_APPROVED_TEMPLATE
= """<html>
68 <title>OAuth Access Granted</title>
72 <div style="width: 20em; margin: 1em auto;
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>
86 def RenderTokenApprovalTemplate(oauth_callback
):
87 """Renders the token approval page.
90 oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI.
93 String containing the contents of the token approval page.
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.
106 String containing the contents of the token approved page.
108 return TOKEN_APPROVED_TEMPLATE
111 def OAuthGetRequestTokenCGI(outfile
):
112 """Runs the OAuthGetRequestToken CGI.
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')
122 outfile
.write('oauth_token_secret=REQUEST_TOKEN_SECRET')
125 def OAuthAuthorizeTokenCGI(method
, parameters
, outfile
):
126 """Runs the OAuthAuthorizeToken CGI.
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
, '')
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':
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')
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())
150 outfile
.write('Status: 400 Unsupported method\r\n')
153 def OAuthGetAccessTokenCGI(outfile
):
154 """Runs the OAuthGetAccessToken CGI.
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')
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.
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
176 The first value in the list, or default.
178 if key
in parameters
:
180 return parameters
[key
][0]
184 def MainCGI(method
, path
, unused_headers
, parameters
, outfile
):
185 """CGI for all OAuth handlers.
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')
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
)
205 outfile
.write('Status: 404 Unknown OAuth handler\r\n')
208 def CreateOAuthDispatcher():
209 """Function to create OAuth dispatcher.
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."""
226 """Handles dispatch to OAuth handlers.
229 request: AppServerRequest.
230 outfile: The response file.
231 base_env_dict: Dictionary of CGI environment parameters if available.
234 if not base_env_dict
:
235 outfile
.write('Status: 500\r\n')
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.
244 request: AppServerRequest.
245 base_env_dict: Dictionary of CGI environment parameters.
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
)
257 form
= cgi
.FieldStorage(fp
=request
.infile
,
258 headers
=request
.headers
,
259 environ
=base_env_dict
)
261 if key
not in parameters
:
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()