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