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 logins/logout in the development application server.
19 This CGI has these parameters:
21 continue: URL to redirect to after a login or logout has completed.
22 email: Email address to set for the client.
23 admin: If 'True', the client should be logged in as an admin.
24 action: What action to take ('Login' or 'Logout').
26 To view the current user information and a form for logging in and out,
42 CONTINUE_PARAM
= 'continue'
45 ACTION_PARAM
= 'action'
48 LOGOUT_ACTION
= 'Logout'
49 LOGIN_ACTION
= 'Login'
52 LOGOUT_PARAM
= 'action=%s' % LOGOUT_ACTION
55 COOKIE_NAME
= 'dev_appserver_login'
59 def GetUserInfo(http_cookie
, cookie_name
=COOKIE_NAME
):
60 """Get the requestor's user info from the HTTP cookie in the CGI environment.
63 http_cookie: Value of the HTTP_COOKIE environment variable.
64 cookie_name: Name of the cookie that stores the user info.
67 Tuple (email, admin, user_id) where:
68 email: The user's email address, if any.
69 admin: True if the user is an admin; False otherwise.
70 user_id: The user ID, if any.
73 cookie
= Cookie
.SimpleCookie(http_cookie
)
74 except Cookie
.CookieError
:
78 if cookie_name
in cookie
:
79 cookie_value
= cookie
[cookie_name
].value
81 email
, admin
, user_id
= (cookie_value
.split(':') + ['', '', ''])[:3]
82 return email
, (admin
== 'True'), user_id
85 def CreateCookieData(email
, admin
):
86 """Creates cookie payload data.
89 email, admin: Parameters to incorporate into the cookie.
92 String containing the cookie payload.
94 admin_string
= 'False'
98 user_id_digest
= hashlib
.md5(email
.lower()).digest()
99 user_id
= '1' + ''.join(['%02d' % ord(x
) for x
in user_id_digest
])[:20]
102 return '%s:%s:%s' % (email
, admin_string
, user_id
)
105 def SetUserInfoCookie(email
, admin
, cookie_name
=COOKIE_NAME
):
106 """Creates a cookie to set the user information for the requestor.
109 email: Email to set for the user.
110 admin: True if the user should be admin; False otherwise.
111 cookie_name: Name of the cookie that stores the user info.
114 'Set-Cookie' header for setting the user info of the requestor.
116 cookie_value
= CreateCookieData(email
, admin
)
117 set_cookie
= Cookie
.SimpleCookie()
118 set_cookie
[cookie_name
] = cookie_value
119 set_cookie
[cookie_name
]['path'] = '/'
120 return '%s\r\n' % set_cookie
123 def ClearUserInfoCookie(cookie_name
=COOKIE_NAME
):
124 """Clears the user info cookie from the requestor, logging them out.
127 cookie_name: Name of the cookie that stores the user info.
130 'Set-Cookie' header for clearing the user info of the requestor.
132 set_cookie
= Cookie
.SimpleCookie()
133 set_cookie
[cookie_name
] = ''
134 set_cookie
[cookie_name
]['path'] = '/'
135 set_cookie
[cookie_name
]['max-age'] = '0'
136 return '%s\r\n' % set_cookie
140 LOGIN_TEMPLATE
= """<html>
146 <form method="get" action="%(login_url)s"
147 style="text-align:center; font: 13px sans-serif">
148 <div style="width: 20em; margin: 1em auto;
150 padding: 0 2em 1.25em 2em;
151 background-color: #d6e9f8;
152 border: 2px solid #67a7e3">
153 <h3>%(login_message)s</h3>
154 <p style="padding: 0; margin: 0">
155 <label for="email" style="width: 3em">Email:</label>
156 <input name="email" type="text" value="%(email)s" id="email"/>
158 <p style="margin: .5em 0 0 3em; font-size:12px">
159 <input name="admin" type="checkbox" value="True"
160 %(admin_checked)s id="admin"/>
161 <label for="admin">Sign in as Administrator</label>
163 <p style="margin-left: 3em">
164 <input name="action" value="Login" type="submit"
166 <input name="action" value="Logout" type="submit"
167 id="submit-logout" />
170 <input name="continue" type="hidden" value="%(continue_url)s"/>
178 def RenderLoginTemplate(login_url
, continue_url
, email
, admin
):
179 """Renders the login page.
182 login_url, continue_url, email, admin: Parameters passed to
186 String containing the contents of the login page.
188 login_message
= 'Not logged in'
190 login_message
= 'Logged in'
193 admin_checked
= 'checked'
198 'email': (cgi
.escape(email
, quote
=1) or 'test\x40example.com'),
199 'admin_checked': admin_checked
,
200 'login_message': login_message
,
201 'login_url': cgi
.escape(login_url
, quote
=1),
202 'continue_url': cgi
.escape(continue_url
, quote
=1)
205 return LOGIN_TEMPLATE
% template_dict
209 def LoginRedirect(login_url
,
214 """Writes a login redirection URL to a user.
217 login_url: Relative URL which should be used for handling user logins.
218 hostname: Name of the host on which the webserver is running.
219 port: Port on which the webserver is running.
220 relative_url: String containing the URL accessed.
221 outfile: File-like object to which the response should be written.
223 dest_url
= "http://%s:%s%s" % (hostname
, port
, relative_url
)
224 redirect_url
= 'http://%s:%s%s?%s=%s' % (hostname
,
228 urllib
.quote(dest_url
))
229 outfile
.write('Status: 302 Requires login\r\n')
230 outfile
.write('Location: %s\r\n\r\n' % redirect_url
)
233 def LoginCGI(login_url
,
241 """Runs the login CGI.
243 This CGI does not care about the method at all. For both POST and GET the
244 client will be redirected to the continue URL.
247 login_url: URL used to run the CGI.
248 email: Current email address of the requesting user.
249 admin: True if the requesting user is an admin; False otherwise.
250 action: The action used to run the CGI; 'Login' for a login action, 'Logout'
251 for when a logout should occur.
252 set_email: Email to set for the user; Empty if no email should be set.
253 set_admin: True if the user should be an admin; False otherwise.
254 continue_url: URL to which the user should be redirected when the CGI
255 finishes loading; defaults to the login_url with no parameters (showing
256 current status) if not supplied.
257 outfile: File-like object to which all output data should be written.
263 if action
.lower() == LOGOUT_ACTION
.lower():
264 output_headers
.append(ClearUserInfoCookie())
266 output_headers
.append(SetUserInfoCookie(set_email
, set_admin
))
268 redirect_url
= continue_url
or login_url
271 outfile
.write('Status: 302 Redirecting to continue URL\r\n')
272 for header
in output_headers
:
273 outfile
.write(header
)
274 outfile
.write('Location: %s\r\n' % redirect_url
)
275 outfile
.write('\r\n')
277 outfile
.write('Status: 200\r\n')
278 outfile
.write('Content-Type: text/html\r\n')
279 outfile
.write('\r\n')
280 outfile
.write(RenderLoginTemplate(login_url
,
288 """Runs the login and logout CGI script."""
289 form
= cgi
.FieldStorage(environ
=os
.environ
)
290 login_url
= os
.environ
['PATH_INFO']
291 email
= os
.environ
.get('USER_EMAIL', '')
292 admin
= os
.environ
.get('USER_IS_ADMIN', '0') == '1'
294 action
= form
.getfirst(ACTION_PARAM
)
295 set_email
= form
.getfirst(EMAIL_PARAM
, '')
296 set_admin
= form
.getfirst(ADMIN_PARAM
, '') == 'True'
297 continue_url
= form
.getfirst(CONTINUE_PARAM
, '')
311 if __name__
== '__main__':