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