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, 2006, 2007, 2008 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 from WebStack.Helpers.Auth import Verifier 25 import WebStack.Generic 26 27 class LoginRedirectResource: 28 29 """ 30 A resource redirecting to a login URL. A number of class attributes can be 31 set or overridden by instance attributes: 32 33 * anonymous_parameter_name - if set to a value other than None, clients 34 providing a parameter of that name in the 35 URL will not be authenticated, but then 36 such clients will get a predefined user 37 identity associated with them, configurable 38 using 'anonymous_username' 39 40 * logout_parameter_name - if set to a value other than None, clients 41 providing a parameter of that name in the URL 42 will become logged out; after logging out, 43 clients are redirected to a location which can 44 be configured by 'logout_url' 45 46 * use_logout_redirect - if set to 0, a confirmation screen is given 47 instead of redirecting the user to 'logout_url' 48 49 * path_encoding' (previously 'urlencoding') allows a special encoding to 50 be used in producing the redirection path 51 52 To change the page used by this resource, either redefine the 53 'logout_page' attribute in instances of this class or a subclass, or 54 override the 'show_logout' method. 55 """ 56 57 anonymous_parameter_name = None 58 anonymous_username = "anonymous" 59 logout_parameter_name = None 60 logout_url = "/" 61 use_logout_redirect = 1 62 path_encoding = None 63 64 def __init__(self, resource, authenticator, login_url=None, app_url=None): 65 66 """ 67 Initialise the resource with a 'resource' for the application being 68 protected and an 'authenticator' protecting the resource. 69 70 If the optional 'login_url' and 'app_url' are provided, these values 71 will be used to locate the login application and protected application 72 respectively. Such values, if not provided, must be otherwise set at a 73 later time or provided by 'get_login_url' and 'get_app_url' methods in 74 a subclass of this class. 75 76 The 'app_url' should be the "bare" reference using a protocol, host 77 and port, not including any path information. 78 """ 79 80 self.login_url = login_url 81 self.app_url = app_url 82 self.resource = resource 83 self.authenticator = authenticator 84 85 def respond(self, trans): 86 87 "Respond using the given transaction 'trans'." 88 89 fields_path = trans.get_fields_from_path(self.path_encoding) 90 91 # Check for the logout parameter, if appropriate. 92 93 if self.logout_parameter_name is not None and fields_path.has_key(self.logout_parameter_name): 94 95 # Remove the special cookie token, then pass on the transaction. 96 97 self.authenticator.unset_token(trans) 98 99 # Redirect to the logout URL. 100 101 if self.use_logout_redirect: 102 trans.set_header_value("Location", self.logout_url) 103 trans.set_response_code(302) # was 307 104 105 # Show the logout confirmation anyway. 106 107 self.show_logout(trans, self.logout_url) 108 109 # Check the authentication details with the specified authenticator. 110 111 elif self.authenticator.authenticate(trans): 112 113 # If successful, pass on the transaction. 114 115 self.resource.respond(trans) 116 117 # Check for the anonymous parameter, if appropriate. 118 119 elif self.anonymous_parameter_name is not None and fields_path.has_key(self.anonymous_parameter_name): 120 121 # Make a special cookie token, then pass on the transaction. 122 123 self.authenticator.set_token(trans, self.anonymous_username) 124 self.resource.respond(trans) 125 126 else: 127 128 # Redirect to the login URL. 129 130 path = trans.get_path(self.path_encoding) 131 trans.redirect("%s?app=%s%s" % ( 132 self.get_login_url(trans), 133 trans.encode_path(self.get_app_url(trans), self.path_encoding), 134 trans.encode_path(path, self.path_encoding), 135 )) 136 137 def get_app_url(self, trans): 138 139 """ 140 Return the application URL, using 'trans' if necessary, in order to 141 provide a complete URL to redirect an authenticated user to their 142 originally requested page. If the application URL is empty, any 143 redirects will be within the same application, rather than to 144 potentially completely different applications residing at arbitrary 145 locations. 146 """ 147 148 return self.app_url 149 150 def get_login_url(self, trans): 151 152 """ 153 Return the login URL, using 'trans' if necessary, in order to 154 provide a complete URL to redirect an authenticated user to their 155 originally requested page. 156 """ 157 158 return self.login_url 159 160 def show_logout(self, trans, redirect): 161 162 """ 163 Write a confirmation page to 'trans' containing the 'redirect' URL which the 164 client should be sent to upon logout. 165 """ 166 167 # When logout takes place, show the logout screen. 168 169 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 170 out = trans.get_response_stream() 171 out.write(self.logout_page % redirect) 172 173 logout_page = """ 174 <html> 175 <head> 176 <title>Logout</title> 177 </head> 178 <body> 179 <h1>Logout Successful</h1> 180 <p>Please proceed <a href="%s">to the application</a>.</p> 181 </body> 182 </html> 183 """ 184 185 class SiteLoginRedirectResource(LoginRedirectResource): 186 187 "Login redirection within a site." 188 189 site_attribute_name = "root" 190 191 def _get_url(self, trans, site_relative_url): 192 193 "Return the URL, using 'trans', for the given 'site_relative_url'." 194 195 return trans.get_attributes()[self.site_attribute_name] + site_relative_url 196 197 def get_login_url(self, trans): 198 199 """ 200 Return the login URL, using 'trans' if necessary, in order to 201 provide a complete URL to redirect an authenticated user to their 202 originally requested page. 203 """ 204 205 return self._get_url(trans, LoginRedirectResource.get_login_url(self, trans)) 206 207 class LoginRedirectAuthenticator(Verifier): 208 209 """ 210 An authenticator which verifies the credentials provided in a special login 211 cookie. 212 """ 213 214 def authenticate(self, trans): 215 216 """ 217 Authenticate the originator of 'trans', updating the object if 218 successful and returning a true value if successful, or a false value 219 otherwise. 220 """ 221 222 valid = Verifier.authenticate(self, trans) 223 224 # Update the transaction with the user details. 225 226 if valid: 227 username, token = self.get_username_and_token(trans) 228 trans.set_user(username) 229 return valid 230 231 def set_token(self, trans, username): 232 233 "Set an authentication token in 'trans' with the given 'username'." 234 235 Verifier.set_token(self, trans, username) 236 237 # Update the transaction with the user details. 238 239 trans.set_user(username) 240 241 # vim: tabstop=4 expandtab shiftwidth=4