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