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, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 encoding = "utf-8" 32 33 def __init__(self, authenticator, use_redirect=1, urlencoding=None): 34 35 """ 36 Initialise the resource with an 'authenticator'. 37 38 If the optional 'use_redirect' flag is set to 0, a confirmation screen 39 is given instead of redirecting the user back to the original 40 application. 41 42 The optional 'urlencoding' parameter allows a special encoding to be 43 used in producing the redirection path. 44 """ 45 46 self.authenticator = authenticator 47 self.use_redirect = use_redirect 48 self.urlencoding = urlencoding or self.encoding 49 50 def respond(self, trans): 51 52 "Respond using the transaction 'trans'." 53 54 app, path, qs = get_target(trans, self.urlencoding, self.encoding) 55 56 # Check for a submitted login form. 57 58 fields_body = trans.get_fields_from_body(self.encoding) 59 60 if fields_body.has_key("login"): 61 if self.authenticator.authenticate(trans, fields_body.get("username", [None])[0], fields_body.get("password", [None])[0]): 62 self._redirect(trans, app, path, qs) 63 # The above method does not return. 64 65 # Otherwise, show the login form. 66 67 self.show_login(trans, app, path, qs) 68 69 def _redirect(self, trans, app, path, qs): 70 71 """ 72 Redirect the client using 'trans' and the given 'app', 'path' and 'qs' 73 details. 74 """ 75 76 # Show the success page anyway. 77 78 self.show_success(trans, app, path, qs) 79 if self.use_redirect: 80 trans.redirect(app + trans.encode_path(path, self.urlencoding) + qs) 81 else: 82 raise WebStack.Generic.EndOfResponse 83 84 def show_login(self, trans, app, path, qs): 85 86 """ 87 Writes a login screen using the transaction 'trans', including details of the 88 'app', 'path' and 'qs' which the client was attempting to access. 89 """ 90 91 trans.set_content_type(WebStack.Generic.ContentType("text/html", self.encoding)) 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="password" size="12"/></p> 103 <p><input name="login" type="submit" value="Login"/></p> 104 <input name="app" type="hidden" value="%s"/> 105 <input name="path" type="hidden" value="%s"/> 106 <input name="qs" type="hidden" value="%s"/> 107 </form> 108 </body> 109 </html> 110 """ % (app, path, qs)) 111 112 def show_success(self, trans, app, path, qs): 113 114 # When authentication fails or is yet to take place, show the login 115 # screen. 116 117 trans.set_content_type(WebStack.Generic.ContentType("text/html", self.encoding)) 118 out = trans.get_response_stream() 119 out.write(""" 120 <html> 121 <head> 122 <title>Login Example</title> 123 </head> 124 <body> 125 <h1>Login Successful</h1> 126 <p>Please proceed <a href="%s%s%s">to the application</a>.</p> 127 </body> 128 </html> 129 """ % (app, trans.encode_path(path, self.urlencoding), qs)) 130 131 class LoginAuthenticator: 132 133 def __init__(self, secret_key, credentials, cookie_name=None): 134 135 """ 136 Initialise the authenticator with a 'secret_key', the authenticator's registry of 137 'credentials' and an optional 'cookie_name'. 138 139 The 'credentials' must be an object which supports tests of the form 140 '(username, password) in credentials'. 141 """ 142 143 self.secret_key = secret_key 144 self.credentials = credentials 145 self.cookie_name = cookie_name or "LoginAuthenticator" 146 147 def authenticate(self, trans, username, password): 148 149 """ 150 Authenticate the sender of the transaction 'trans', returning 1 (true) if they are 151 recognised, 0 (false) otherwise. Use the 'username' and 'password' supplied as 152 credentials. 153 """ 154 155 # Process any supplied parameters. 156 157 fields = trans.get_fields_from_body() 158 159 # Check against the class's credentials. 160 161 if (username, password) in self.credentials: 162 163 # Make a special cookie token. 164 165 self.set_token(trans, username) 166 return 1 167 168 return 0 169 170 def set_token(self, trans, username): 171 172 "Set an authentication token in 'trans' with the given 'username'." 173 174 trans.set_cookie_value( 175 self.cookie_name, 176 get_token(username, self.secret_key), 177 path="/" 178 ) 179 180 # General functions. 181 182 def get_target(trans, urlencoding, encoding): 183 184 """ 185 Return the application, path and query string for 'trans' using the given 186 'urlencoding' (or path encoding) and request body 'encoding'. 187 """ 188 189 fields_path = trans.get_fields_from_path(urlencoding) 190 fields_body = trans.get_fields_from_body(encoding) 191 192 # NOTE: Handle missing redirects better. 193 194 if fields_body.has_key("app"): 195 apps = fields_body["app"] 196 app = apps[0] 197 elif fields_path.has_key("app"): 198 apps = fields_path["app"] 199 app = apps[0] 200 else: 201 app = u"" 202 203 if fields_body.has_key("path"): 204 paths = fields_body["path"] 205 path = paths[0] 206 elif fields_path.has_key("path"): 207 paths = fields_path["path"] 208 path = paths[0] 209 else: 210 path = u"" 211 212 if fields_body.has_key("qs"): 213 qss = fields_body["qs"] 214 qs = qss[0] 215 elif fields_path.has_key("qs"): 216 qss = fields_path["qs"] 217 qs = qss[0] 218 else: 219 qs = u"" 220 221 return app, path, qs 222 223 # vim: tabstop=4 expandtab shiftwidth=4