1 #!/usr/bin/env python 2 3 """ 4 Login redirection resources, sending unauthenticated users to a login screen 5 URL. 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 from WebStack.Helpers.Auth import get_token 25 import WebStack.Generic 26 27 class LoginRedirectResource: 28 29 "A resource redirecting to a login URL." 30 31 def __init__(self, login_url, app_url, resource, authenticator, anonymous_parameter_name=None, 32 anonymous_username="anonymous", logout_parameter_name=None, logout_url="/", 33 use_logout_redirect=1): 34 35 """ 36 Initialise the resource with a 'login_url', an 'app_url' where the 'resource' for 37 the application being protected should be reachable, and an 'authenticator'. 38 39 If the optional 'anonymous_parameter_name' is set, clients providing a parameter 40 of that name in the URL will not be authenticated, but then such clients will get 41 a predefined user identity associated with them, configurable using the optional 42 'anonymous_username'. 43 44 If the optional 'logout_parameter_name' is set, clients providing a parameter of 45 that name in the URL will become logged out. After logging out, clients are 46 redirected to a location which can be configured by the optional 'logout_url'. 47 48 If the optional 'use_logout_redirect' flag is set to 0, a confirmation screen is 49 given instead of redirecting the user to the 'logout_url'. 50 """ 51 52 self.login_url = login_url 53 self.app_url = app_url 54 self.resource = resource 55 self.authenticator = authenticator 56 self.anonymous_parameter_name = anonymous_parameter_name 57 self.anonymous_username = anonymous_username 58 self.logout_parameter_name = logout_parameter_name 59 self.logout_url = logout_url 60 self.use_logout_redirect = use_logout_redirect 61 62 def respond(self, trans): 63 64 "Respond using the given transaction 'trans'." 65 66 fields_path = trans.get_fields_from_path() 67 68 # Check for the logout parameter, if appropriate. 69 70 if self.logout_parameter_name is not None and fields_path.has_key(self.logout_parameter_name): 71 72 # Remove the special cookie token, then pass on the transaction. 73 74 self.authenticator.unset_token(trans) 75 76 # Redirect to the logout URL. 77 78 if self.use_logout_redirect: 79 trans.set_header_value("Location", self.logout_url) 80 trans.set_response_code(302) # was 307 81 82 # Show the logout confirmation anyway. 83 84 self._show_logout(trans, self.logout_url) 85 86 # Check the authentication details with the specified authenticator. 87 88 elif self.authenticator.authenticate(trans): 89 90 # If successful, pass on the transaction. 91 92 self.resource.respond(trans) 93 94 # Check for the anonymous parameter, if appropriate. 95 96 elif self.anonymous_parameter_name is not None and fields_path.has_key(self.anonymous_parameter_name): 97 98 # Make a special cookie token, then pass on the transaction. 99 100 self.authenticator.set_token(trans, self.anonymous_username) 101 self.resource.respond(trans) 102 103 else: 104 105 # Redirect to the login URL. 106 107 trans.set_header_value("Location", "%s?redirect=%s%s" % ( 108 self.login_url, self.app_url, self._encode(trans.get_path())) 109 ) 110 trans.set_response_code(302) # was 307 111 112 def _encode(self, url): 113 114 "Encode the given 'url' for redirection purposes." 115 116 return url.replace("?", "%3f").replace("&", "%26") 117 118 def _show_logout(self, trans, redirect): 119 120 """ 121 Write a confirmation page to 'trans' containing the 'redirect' URL which the 122 client should be sent to upon logout. 123 """ 124 125 # When logout takes place, show the login screen. 126 127 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 128 out = trans.get_response_stream() 129 out.write(""" 130 <html> 131 <head> 132 <title>Logout</title> 133 </head> 134 <body> 135 <h1>Logout Successful</h1> 136 <p>Please proceed <a href="%s">to the application</a>.</p> 137 </body> 138 </html> 139 """ % redirect) 140 141 class LoginRedirectAuthenticator: 142 143 """ 144 An authenticator which verifies the credentials provided in a special login cookie. 145 """ 146 147 def __init__(self, secret_key, cookie_name=None): 148 149 "Initialise the authenticator with a 'secret_key' and an optional 'cookie_name'." 150 151 self.secret_key = secret_key 152 self.cookie_name = cookie_name or "LoginAuthenticator" 153 154 def authenticate(self, trans): 155 156 """ 157 Authenticate the originator of 'trans', updating the object if successful and 158 returning 1 (true) if successful, 0 (false) otherwise. 159 """ 160 161 cookie = trans.get_cookie(self.cookie_name) 162 if cookie is None or cookie.value is None: 163 return 0 164 165 # Test the token from the cookie against a recreated token using the 166 # given information. 167 168 username = cookie.value.split(":")[0] 169 if cookie.value == get_token(username, self.secret_key): 170 171 # Update the transaction with the user details. 172 173 trans.set_user(username) 174 return 1 175 else: 176 return 0 177 178 def set_token(self, trans, username): 179 180 "Set an authentication token in 'trans' with the given 'username'." 181 182 trans.set_cookie_value( 183 self.cookie_name, 184 get_token(username, self.secret_key), 185 path="/" 186 ) 187 188 # Update the transaction with the user details. 189 190 trans.set_user(username) 191 192 def unset_token(self, trans): 193 194 "Unset the authentication token in 'trans'." 195 196 trans.delete_cookie(self.cookie_name) 197 198 # vim: tabstop=4 expandtab shiftwidth=4