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