|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
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 |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
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. |
|
16 # |
|
17 """Helper CGI for logins/logout in the development application server. |
|
18 |
|
19 This CGI has these parameters: |
|
20 |
|
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'). |
|
25 |
|
26 To view the current user information and a form for logging in and out, |
|
27 supply no parameters. |
|
28 """ |
|
29 |
|
30 |
|
31 import Cookie |
|
32 import cgi |
|
33 import os |
|
34 import sys |
|
35 import urllib |
|
36 |
|
37 |
|
38 CONTINUE_PARAM = 'continue' |
|
39 EMAIL_PARAM = 'email' |
|
40 ADMIN_PARAM = 'admin' |
|
41 ACTION_PARAM = 'action' |
|
42 |
|
43 LOGOUT_ACTION = 'Logout' |
|
44 LOGIN_ACTION = 'Login' |
|
45 |
|
46 LOGOUT_PARAM = 'action=%s' % LOGOUT_ACTION |
|
47 |
|
48 COOKIE_NAME = 'dev_appserver_login' |
|
49 |
|
50 |
|
51 def GetUserInfo(http_cookie, cookie_name=COOKIE_NAME): |
|
52 """Get the requestor's user info from the HTTP cookie in the CGI environment. |
|
53 |
|
54 Args: |
|
55 http_cookie: Value of the HTTP_COOKIE environment variable. |
|
56 cookie_name: Name of the cookie that stores the user info. |
|
57 |
|
58 Returns: |
|
59 Tuple (email, admin) where: |
|
60 email: The user's email address, if any. |
|
61 admin: True if the user is an admin; False otherwise. |
|
62 """ |
|
63 cookie = Cookie.SimpleCookie(http_cookie) |
|
64 |
|
65 cookie_value = '' |
|
66 if cookie_name in cookie: |
|
67 cookie_value = cookie[cookie_name].value |
|
68 |
|
69 email, admin = (cookie_value.split(':') + ['', ''])[:2] |
|
70 return email, (admin == 'True') |
|
71 |
|
72 |
|
73 def CreateCookieData(email, admin): |
|
74 """Creates cookie payload data. |
|
75 |
|
76 Args: |
|
77 email, admin: Parameters to incorporate into the cookie. |
|
78 |
|
79 Returns: |
|
80 String containing the cookie payload. |
|
81 """ |
|
82 admin_string = 'False' |
|
83 if admin: |
|
84 admin_string = 'True' |
|
85 return '%s:%s' % (email, admin_string) |
|
86 |
|
87 |
|
88 def SetUserInfoCookie(email, admin, cookie_name=COOKIE_NAME): |
|
89 """Creates a cookie to set the user information for the requestor. |
|
90 |
|
91 Args: |
|
92 email: Email to set for the user. |
|
93 admin: True if the user should be admin; False otherwise. |
|
94 cookie_name: Name of the cookie that stores the user info. |
|
95 |
|
96 Returns: |
|
97 'Set-Cookie' header for setting the user info of the requestor. |
|
98 """ |
|
99 cookie_value = CreateCookieData(email, admin) |
|
100 set_cookie = Cookie.SimpleCookie() |
|
101 set_cookie[cookie_name] = cookie_value |
|
102 set_cookie[cookie_name]['path'] = '/' |
|
103 return '%s\r\n' % set_cookie |
|
104 |
|
105 |
|
106 def ClearUserInfoCookie(cookie_name=COOKIE_NAME): |
|
107 """Clears the user info cookie from the requestor, logging them out. |
|
108 |
|
109 Args: |
|
110 cookie_name: Name of the cookie that stores the user info. |
|
111 |
|
112 Returns: |
|
113 'Set-Cookie' header for clearing the user info of the requestor. |
|
114 """ |
|
115 set_cookie = Cookie.SimpleCookie() |
|
116 set_cookie[cookie_name] = '' |
|
117 set_cookie[cookie_name]['path'] = '/' |
|
118 set_cookie[cookie_name]['max-age'] = '0' |
|
119 return '%s\r\n' % set_cookie |
|
120 |
|
121 |
|
122 LOGIN_TEMPLATE = """<html> |
|
123 <head> |
|
124 <title>Login</title> |
|
125 </head> |
|
126 <body> |
|
127 |
|
128 <form method='get' action='%(login_url)s' |
|
129 style='text-align:center; font: 13px sans-serif'> |
|
130 <div style='width: 20em; margin: 1em auto; |
|
131 text-align:left; |
|
132 padding: 0 2em 1.25em 2em; |
|
133 background-color: #d6e9f8; |
|
134 border: 2px solid #67a7e3'> |
|
135 <h3>%(login_message)s</h3> |
|
136 <p style='padding: 0; margin: 0'> |
|
137 <label for='email' style="width: 3em">Email:</label> |
|
138 <input name='email' type='text' value='%(email)s' id='email'/> |
|
139 </p> |
|
140 <p style='margin: .5em 0 0 3em; font-size:12px'> |
|
141 <input name='admin' type='checkbox' value='True' |
|
142 %(admin_checked)s id='admin'/> |
|
143 <label for='admin'>Sign in as Administrator</label> |
|
144 </p> |
|
145 <p style='margin-left: 3em'> |
|
146 <input name='action' value='Login' type='submit' |
|
147 id='submit-login' /> |
|
148 <input name='action' value='Logout' type='submit' |
|
149 id='submit-logout' /> |
|
150 </p> |
|
151 </div> |
|
152 <input name='continue' type='hidden' value='%(continue_url)s'/> |
|
153 </form> |
|
154 |
|
155 </body> |
|
156 </html> |
|
157 """ |
|
158 |
|
159 |
|
160 def RenderLoginTemplate(login_url, continue_url, email, admin): |
|
161 """Renders the login page. |
|
162 |
|
163 Args: |
|
164 login_url, continue_url, email, admin: Parameters passed to |
|
165 LoginCGI. |
|
166 |
|
167 Returns: |
|
168 String containing the contents of the login page. |
|
169 """ |
|
170 login_message = 'Not logged in' |
|
171 if email: |
|
172 login_message = 'Logged in' |
|
173 admin_checked = '' |
|
174 if admin: |
|
175 admin_checked = 'checked' |
|
176 |
|
177 template_dict = { |
|
178 |
|
179 |
|
180 'email': email or 'test\x40example.com', |
|
181 'admin_checked': admin_checked, |
|
182 'login_message': login_message, |
|
183 'login_url': login_url, |
|
184 'continue_url': continue_url |
|
185 } |
|
186 |
|
187 return LOGIN_TEMPLATE % template_dict |
|
188 |
|
189 |
|
190 def LoginRedirect(login_url, |
|
191 hostname, |
|
192 port, |
|
193 relative_url, |
|
194 outfile): |
|
195 """Writes a login redirection URL to a user. |
|
196 |
|
197 Args: |
|
198 login_url: Relative URL which should be used for handling user logins. |
|
199 hostname: Name of the host on which the webserver is running. |
|
200 port: Port on which the webserver is running. |
|
201 relative_url: String containing the URL accessed. |
|
202 outfile: File-like object to which the response should be written. |
|
203 """ |
|
204 dest_url = "http://%s:%s%s" % (hostname, port, relative_url) |
|
205 redirect_url = 'http://%s:%s%s?%s=%s' % (hostname, |
|
206 port, |
|
207 login_url, |
|
208 CONTINUE_PARAM, |
|
209 urllib.quote(dest_url)) |
|
210 outfile.write('Status: 302 Requires login\r\n') |
|
211 outfile.write('Location: %s\r\n\r\n' % redirect_url) |
|
212 |
|
213 |
|
214 def LoginCGI(login_url, |
|
215 email, |
|
216 admin, |
|
217 action, |
|
218 set_email, |
|
219 set_admin, |
|
220 continue_url, |
|
221 outfile): |
|
222 """Runs the login CGI. |
|
223 |
|
224 This CGI does not care about the method at all. For both POST and GET the |
|
225 client will be redirected to the continue URL. |
|
226 |
|
227 Args: |
|
228 login_url: URL used to run the CGI. |
|
229 email: Current email address of the requesting user. |
|
230 admin: True if the requesting user is an admin; False otherwise. |
|
231 action: The action used to run the CGI; 'Login' for a login action, 'Logout' |
|
232 for when a logout should occur. |
|
233 set_email: Email to set for the user; Empty if no email should be set. |
|
234 set_admin: True if the user should be an admin; False otherwise. |
|
235 continue_url: URL to which the user should be redirected when the CGI |
|
236 finishes loading; defaults to the login_url with no parameters (showing |
|
237 current status) if not supplied. |
|
238 outfile: File-like object to which all output data should be written. |
|
239 """ |
|
240 redirect_url = '' |
|
241 output_headers = [] |
|
242 |
|
243 if action: |
|
244 if action.lower() == LOGOUT_ACTION.lower(): |
|
245 output_headers.append(ClearUserInfoCookie()) |
|
246 elif set_email: |
|
247 output_headers.append(SetUserInfoCookie(set_email, set_admin)) |
|
248 |
|
249 redirect_url = continue_url or login_url |
|
250 elif email and continue_url: |
|
251 redirect_url = continue_url |
|
252 |
|
253 if redirect_url: |
|
254 outfile.write('Status: 302 Redirecting to continue URL\r\n') |
|
255 for header in output_headers: |
|
256 outfile.write(header) |
|
257 outfile.write('Location: %s\r\n' % redirect_url) |
|
258 outfile.write('\r\n') |
|
259 else: |
|
260 outfile.write('Status: 200\r\n') |
|
261 outfile.write('Content-Type: text/html\r\n') |
|
262 outfile.write('\r\n') |
|
263 outfile.write(RenderLoginTemplate(login_url, |
|
264 continue_url, |
|
265 email, |
|
266 admin)) |
|
267 |
|
268 |
|
269 def main(): |
|
270 """Runs the login and logout CGI script.""" |
|
271 form = cgi.FieldStorage() |
|
272 login_url = os.environ['PATH_INFO'] |
|
273 email = os.environ.get('USER_EMAIL', '') |
|
274 admin = os.environ.get('USER_IS_ADMIN', '0') == '1' |
|
275 |
|
276 action = form.getfirst(ACTION_PARAM) |
|
277 set_email = form.getfirst(EMAIL_PARAM, '') |
|
278 set_admin = form.getfirst(ADMIN_PARAM, '') == 'True' |
|
279 continue_url = form.getfirst(CONTINUE_PARAM, '') |
|
280 |
|
281 LoginCGI(login_url, |
|
282 email, |
|
283 admin, |
|
284 action, |
|
285 set_email, |
|
286 set_admin, |
|
287 continue_url, |
|
288 sys.stdout) |
|
289 return 0 |
|
290 |
|
291 |
|
292 if __name__ == '__main__': |
|
293 main() |