1 #!/usr/bin/env python 2 3 "Login resources which redirect clients back to an application after a successful login." 4 5 import WebStack.Generic 6 from WebStack.Helpers.Auth import get_token 7 8 class LoginResource: 9 10 "A resource providing a login screen." 11 12 def __init__(self, authenticator, use_redirect=1): 13 14 """ 15 Initialise the resource with an 'authenticator'. 16 17 If the optional 'use_redirect' flag is set to 0, a confirmation screen is given 18 instead of redirecting the user back to the original application. 19 """ 20 21 self.authenticator = authenticator 22 self.use_redirect = use_redirect 23 24 def respond(self, trans): 25 26 "Respond using the transaction 'trans'." 27 28 fields_path = trans.get_fields_from_path() 29 fields_body = trans.get_fields_from_body() 30 31 # NOTE: Handle missing redirects better. 32 33 if fields_body.has_key("redirect"): 34 redirects = fields_body["redirect"] 35 redirect = redirects[0] 36 elif fields_path.has_key("redirect"): 37 redirects = fields_path["redirect"] 38 redirect = redirects[0] 39 else: 40 redirect = "" 41 42 # Check for a submitted login form. 43 44 if fields_body.has_key("login"): 45 if self.authenticator.authenticate(trans): 46 self._redirect(trans, redirect) 47 return 48 49 # Otherwise, show the login form. 50 51 self._show_login(trans, redirect) 52 53 def _redirect(self, trans, redirect): 54 55 "Redirect the client using 'trans' and the given 'redirect' URL." 56 57 if self.use_redirect: 58 trans.set_header_value("Location", redirect) 59 trans.set_response_code(307) 60 61 # Show the success page anyway. 62 63 self._show_success(trans, redirect) 64 65 def _show_login(self, trans, redirect): 66 67 """ 68 Writes a login screen using the transaction 'trans', including details of the 69 'redirect' URL which the client was attempting to access. 70 """ 71 72 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 73 out = trans.get_response_stream() 74 out.write(""" 75 <html> 76 <head> 77 <title>Login Example</title> 78 </head> 79 <body> 80 <h1>Login</h1> 81 <form method="POST"> 82 <p>Username: <input name="username" type="text" size="12"/></p> 83 <p>Password: <input name="password" type="text" size="12"/></p> 84 <p><input name="login" type="submit" value="Login"/></p> 85 <input name="redirect" type="hidden" value="%s"/> 86 </form> 87 </body> 88 </html> 89 """ % redirect) 90 91 def _show_success(self, trans, redirect): 92 93 # When authentication fails or is yet to take place, show the login 94 # screen. 95 96 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 97 out = trans.get_response_stream() 98 out.write(""" 99 <html> 100 <head> 101 <title>Login Example</title> 102 </head> 103 <body> 104 <h1>Login Successful</h1> 105 <p>Please proceed <a href="%s">to the application</a>.</p> 106 </body> 107 </html> 108 """ % redirect) 109 110 def _decode(self, url): 111 112 "Decode the given 'url' for redirection purposes." 113 114 return url.replace("%3f", "?").replace("%26", "&") 115 116 class LoginAuthenticator: 117 118 def __init__(self, secret_key, credentials, cookie_name=None): 119 120 """ 121 Initialise the authenticator with a 'secret_key', the authenticator's registry of 122 'credentials' and an optional 'cookie_name'. 123 124 The 'credentials' must be an object which supports tests of the form 125 '(username, password) in credentials'. 126 """ 127 128 self.secret_key = secret_key 129 self.credentials = credentials 130 self.cookie_name = cookie_name or "LoginAuthenticator" 131 132 def authenticate(self, trans): 133 134 """ 135 Authenticate the sender of the transaction 'trans', returning 1 (true) if they are 136 recognised, 0 (false) otherwise. 137 """ 138 139 # Process any supplied parameters. 140 141 fields = trans.get_fields_from_body() 142 143 if fields.has_key("username") and fields.has_key("password"): 144 usernames, passwords = fields["username"], fields["password"] 145 146 # Insist on only one username and password. 147 148 if len(usernames) == 1 and len(passwords) == 1: 149 username, password = usernames[0], passwords[0] 150 151 # Check against the class's credentials. 152 153 if (username, password) in self.credentials: 154 155 # Make a special cookie token. 156 157 self.set_token(trans, username) 158 return 1 159 160 return 0 161 162 def set_token(self, trans, username): 163 164 "Set an authentication token in 'trans' with the given 'username'." 165 166 trans.set_cookie_value( 167 self.cookie_name, 168 get_token(username, self.secret_key) 169 ) 170 171 # vim: tabstop=4 expandtab shiftwidth=4