1 #!/usr/bin/env python 2 3 """ 4 Login resources which redirect clients back to an application after a successful 5 login. 6 7 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 8 9 This library is free software; you can redistribute it and/or 10 modify it under the terms of the GNU Lesser General Public 11 License as published by the Free Software Foundation; either 12 version 2.1 of the License, or (at your option) any later version. 13 14 This library is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 Lesser General Public License for more details. 18 19 You should have received a copy of the GNU Lesser General Public 20 License along with this library; if not, write to the Free Software 21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 """ 23 24 import WebStack.Generic 25 from WebStack.Helpers.Auth import get_token 26 27 class LoginResource: 28 29 "A resource providing a login screen." 30 31 def __init__(self, authenticator, use_redirect=1): 32 33 """ 34 Initialise the resource with an 'authenticator'. 35 36 If the optional 'use_redirect' flag is set to 0, a confirmation screen is given 37 instead of redirecting the user back to the original application. 38 """ 39 40 self.authenticator = authenticator 41 self.use_redirect = use_redirect 42 43 def respond(self, trans): 44 45 "Respond using the transaction 'trans'." 46 47 fields_path = trans.get_fields_from_path() 48 fields_body = trans.get_fields_from_body() 49 50 # NOTE: Handle missing redirects better. 51 52 if fields_body.has_key("redirect"): 53 redirects = fields_body["redirect"] 54 redirect = redirects[0] 55 elif fields_path.has_key("redirect"): 56 redirects = fields_path["redirect"] 57 redirect = redirects[0] 58 else: 59 redirect = "" 60 61 # Check for a submitted login form. 62 63 if fields_body.has_key("login"): 64 if self.authenticator.authenticate(trans): 65 self._redirect(trans, redirect) 66 return 67 68 # Otherwise, show the login form. 69 70 self._show_login(trans, redirect) 71 72 def _redirect(self, trans, redirect): 73 74 "Redirect the client using 'trans' and the given 'redirect' URL." 75 76 if self.use_redirect: 77 trans.set_header_value("Location", redirect) 78 trans.set_response_code(302) # was 307 79 80 # Show the success page anyway. 81 82 self._show_success(trans, redirect) 83 84 def _show_login(self, trans, redirect): 85 86 """ 87 Writes a login screen using the transaction 'trans', including details of the 88 'redirect' URL which the client was attempting to access. 89 """ 90 91 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 92 out = trans.get_response_stream() 93 out.write(""" 94 <html> 95 <head> 96 <title>Login Example</title> 97 </head> 98 <body> 99 <h1>Login</h1> 100 <form method="POST"> 101 <p>Username: <input name="username" type="text" size="12"/></p> 102 <p>Password: <input name="password" type="text" size="12"/></p> 103 <p><input name="login" type="submit" value="Login"/></p> 104 <input name="redirect" type="hidden" value="%s"/> 105 </form> 106 </body> 107 </html> 108 """ % redirect) 109 110 def _show_success(self, trans, redirect): 111 112 # When authentication fails or is yet to take place, show the login 113 # screen. 114 115 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 116 out = trans.get_response_stream() 117 out.write(""" 118 <html> 119 <head> 120 <title>Login Example</title> 121 </head> 122 <body> 123 <h1>Login Successful</h1> 124 <p>Please proceed <a href="%s">to the application</a>.</p> 125 </body> 126 </html> 127 """ % redirect) 128 129 def _decode(self, url): 130 131 "Decode the given 'url' for redirection purposes." 132 133 return url.replace("%3f", "?").replace("%26", "&") 134 135 class LoginAuthenticator: 136 137 def __init__(self, secret_key, credentials, cookie_name=None): 138 139 """ 140 Initialise the authenticator with a 'secret_key', the authenticator's registry of 141 'credentials' and an optional 'cookie_name'. 142 143 The 'credentials' must be an object which supports tests of the form 144 '(username, password) in credentials'. 145 """ 146 147 self.secret_key = secret_key 148 self.credentials = credentials 149 self.cookie_name = cookie_name or "LoginAuthenticator" 150 151 def authenticate(self, trans): 152 153 """ 154 Authenticate the sender of the transaction 'trans', returning 1 (true) if they are 155 recognised, 0 (false) otherwise. 156 """ 157 158 # Process any supplied parameters. 159 160 fields = trans.get_fields_from_body() 161 162 if fields.has_key("username") and fields.has_key("password"): 163 usernames, passwords = fields["username"], fields["password"] 164 165 # Insist on only one username and password. 166 167 if len(usernames) == 1 and len(passwords) == 1: 168 username, password = usernames[0], passwords[0] 169 170 # Check against the class's credentials. 171 172 if (username, password) in self.credentials: 173 174 # Make a special cookie token. 175 176 self.set_token(trans, username) 177 return 1 178 179 return 0 180 181 def set_token(self, trans, username): 182 183 "Set an authentication token in 'trans' with the given 'username'." 184 185 trans.set_cookie_value( 186 self.cookie_name, 187 get_token(username, self.secret_key), 188 path="/" 189 ) 190 191 # vim: tabstop=4 expandtab shiftwidth=4