blob: d72bb86334e156a9af8612b30518629f9734163a [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Helper CGI for logins/logout in the development application server.
This CGI has these parameters:
continue: URL to redirect to after a login or logout has completed.
email: Email address to set for the client.
admin: If 'True', the client should be logged in as an admin.
action: What action to take ('Login' or 'Logout').
To view the current user information and a form for logging in and out,
supply no parameters.
"""
import cgi
import Cookie
import os
import sys
import urllib
import hashlib
CONTINUE_PARAM = 'continue'
EMAIL_PARAM = 'email'
ADMIN_PARAM = 'admin'
ACTION_PARAM = 'action'
LOGOUT_ACTION = 'Logout'
LOGIN_ACTION = 'Login'
LOGOUT_PARAM = 'action=%s' % LOGOUT_ACTION
COOKIE_NAME = 'dev_appserver_login'
def GetUserInfo(http_cookie, cookie_name=COOKIE_NAME):
"""Get the requestor's user info from the HTTP cookie in the CGI environment.
Args:
http_cookie: Value of the HTTP_COOKIE environment variable.
cookie_name: Name of the cookie that stores the user info.
Returns:
Tuple (email, admin, user_id) where:
email: The user's email address, if any.
admin: True if the user is an admin; False otherwise.
user_id: The user ID, if any.
"""
try:
cookie = Cookie.SimpleCookie(http_cookie)
except Cookie.CookieError:
return '', False, ''
cookie_value = ''
if cookie_name in cookie:
cookie_value = cookie[cookie_name].value
email, admin, user_id = (cookie_value.split(':') + ['', '', ''])[:3]
return email, (admin == 'True'), user_id
def CreateCookieData(email, admin):
"""Creates cookie payload data.
Args:
email, admin: Parameters to incorporate into the cookie.
Returns:
String containing the cookie payload.
"""
admin_string = 'False'
if admin:
admin_string = 'True'
if email:
user_id_digest = hashlib.md5(email.lower()).digest()
user_id = '1' + ''.join(['%02d' % ord(x) for x in user_id_digest])[:20]
else:
user_id = ''
return '%s:%s:%s' % (email, admin_string, user_id)
def SetUserInfoCookie(email, admin, cookie_name=COOKIE_NAME):
"""Creates a cookie to set the user information for the requestor.
Args:
email: Email to set for the user.
admin: True if the user should be admin; False otherwise.
cookie_name: Name of the cookie that stores the user info.
Returns:
'Set-Cookie' header for setting the user info of the requestor.
"""
cookie_value = CreateCookieData(email, admin)
set_cookie = Cookie.SimpleCookie()
set_cookie[cookie_name] = cookie_value
set_cookie[cookie_name]['path'] = '/'
return '%s\r\n' % set_cookie
def ClearUserInfoCookie(cookie_name=COOKIE_NAME):
"""Clears the user info cookie from the requestor, logging them out.
Args:
cookie_name: Name of the cookie that stores the user info.
Returns:
'Set-Cookie' header for clearing the user info of the requestor.
"""
set_cookie = Cookie.SimpleCookie()
set_cookie[cookie_name] = ''
set_cookie[cookie_name]['path'] = '/'
set_cookie[cookie_name]['max-age'] = '0'
return '%s\r\n' % set_cookie
LOGIN_TEMPLATE = """<html>
<head>
<title>Login</title>
</head>
<body>
<form method="get" action="%(login_url)s"
style="text-align:center; font: 13px sans-serif">
<div style="width: 20em; margin: 1em auto;
text-align:left;
padding: 0 2em 1.25em 2em;
background-color: #d6e9f8;
border: 2px solid #67a7e3">
<h3>%(login_message)s</h3>
<p style="padding: 0; margin: 0">
<label for="email" style="width: 3em">Email:</label>
<input name="email" type="text" value="%(email)s" id="email"/>
</p>
<p style="margin: .5em 0 0 3em; font-size:12px">
<input name="admin" type="checkbox" value="True"
%(admin_checked)s id="admin"/>
<label for="admin">Sign in as Administrator</label>
</p>
<p style="margin-left: 3em">
<input name="action" value="Login" type="submit"
id="submit-login" />
<input name="action" value="Logout" type="submit"
id="submit-logout" />
</p>
</div>
<input name="continue" type="hidden" value="%(continue_url)s"/>
</form>
</body>
</html>
"""
def RenderLoginTemplate(login_url, continue_url, email, admin):
"""Renders the login page.
Args:
login_url, continue_url, email, admin: Parameters passed to
LoginCGI.
Returns:
String containing the contents of the login page.
"""
login_message = 'Not logged in'
if email:
login_message = 'Logged in'
admin_checked = ''
if admin:
admin_checked = 'checked'
template_dict = {
'email': (cgi.escape(email, quote=1) or 'test\x40example.com'),
'admin_checked': admin_checked,
'login_message': login_message,
'login_url': cgi.escape(login_url, quote=1),
'continue_url': cgi.escape(continue_url, quote=1)
}
return LOGIN_TEMPLATE % template_dict
def LoginRedirect(login_url,
hostname,
port,
relative_url,
outfile):
"""Writes a login redirection URL to a user.
Args:
login_url: Relative URL which should be used for handling user logins.
hostname: Name of the host on which the webserver is running.
port: Port on which the webserver is running.
relative_url: String containing the URL accessed.
outfile: File-like object to which the response should be written.
"""
dest_url = "http://%s:%s%s" % (hostname, port, relative_url)
redirect_url = 'http://%s:%s%s?%s=%s' % (hostname,
port,
login_url,
CONTINUE_PARAM,
urllib.quote(dest_url))
outfile.write('Status: 302 Requires login\r\n')
outfile.write('Location: %s\r\n\r\n' % redirect_url)
def LoginCGI(login_url,
email,
admin,
action,
set_email,
set_admin,
continue_url,
outfile):
"""Runs the login CGI.
This CGI does not care about the method at all. For both POST and GET the
client will be redirected to the continue URL.
Args:
login_url: URL used to run the CGI.
email: Current email address of the requesting user.
admin: True if the requesting user is an admin; False otherwise.
action: The action used to run the CGI; 'Login' for a login action, 'Logout'
for when a logout should occur.
set_email: Email to set for the user; Empty if no email should be set.
set_admin: True if the user should be an admin; False otherwise.
continue_url: URL to which the user should be redirected when the CGI
finishes loading; defaults to the login_url with no parameters (showing
current status) if not supplied.
outfile: File-like object to which all output data should be written.
"""
redirect_url = ''
output_headers = []
if action:
if action.lower() == LOGOUT_ACTION.lower():
output_headers.append(ClearUserInfoCookie())
elif set_email:
output_headers.append(SetUserInfoCookie(set_email, set_admin))
redirect_url = continue_url or login_url
if redirect_url:
outfile.write('Status: 302 Redirecting to continue URL\r\n')
for header in output_headers:
outfile.write(header)
outfile.write('Location: %s\r\n' % redirect_url)
outfile.write('\r\n')
else:
outfile.write('Status: 200\r\n')
outfile.write('Content-Type: text/html\r\n')
outfile.write('\r\n')
outfile.write(RenderLoginTemplate(login_url,
continue_url,
email,
admin))
def main():
"""Runs the login and logout CGI script."""
form = cgi.FieldStorage(environ=os.environ)
login_url = os.environ['PATH_INFO']
email = os.environ.get('USER_EMAIL', '')
admin = os.environ.get('USER_IS_ADMIN', '0') == '1'
action = form.getfirst(ACTION_PARAM)
set_email = form.getfirst(EMAIL_PARAM, '')
set_admin = form.getfirst(ADMIN_PARAM, '') == 'True'
continue_url = form.getfirst(CONTINUE_PARAM, '')
LoginCGI(login_url,
email,
admin,
action,
set_email,
set_admin,
continue_url,
sys.stdout)
return 0
if __name__ == '__main__':
main()