1 #!/usr/bin/env python 2 3 "Login redirection." 4 5 from WebStack.Helpers.Auth import get_token 6 import WebStack.Generic 7 8 class LoginRedirectResource: 9 10 "A resource redirecting to a login URL." 11 12 def __init__(self, login_url, app_url, resource, authenticator, anonymous_parameter_name=None, 13 anonymous_username="anonymous", logout_parameter_name=None, logout_url="/", 14 use_logout_redirect=1): 15 16 """ 17 Initialise the resource with a 'login_url', an 'app_url' where the 'resource' for 18 the application being protected should be reachable, and an 'authenticator'. 19 20 If the optional 'anonymous_parameter_name' is set, clients providing a parameter 21 of that name in the URL will not be authenticated, but then such clients will get 22 a predefined user identity associated with them, configurable using the optional 23 'anonymous_username'. 24 25 If the optional 'logout_parameter_name' is set, clients providing a parameter of 26 that name in the URL will become logged out. After logging out, clients are 27 redirected to a location which can be configured by the optional 'logout_url'. 28 29 If the optional 'use_logout_redirect' flag is set to 0, a confirmation screen is 30 given instead of redirecting the user to the 'logout_url'. 31 """ 32 33 self.login_url = login_url 34 self.app_url = app_url 35 self.resource = resource 36 self.authenticator = authenticator 37 self.anonymous_parameter_name = anonymous_parameter_name 38 self.anonymous_username = anonymous_username 39 self.logout_parameter_name = logout_parameter_name 40 self.logout_url = logout_url 41 self.use_logout_redirect = use_logout_redirect 42 43 def respond(self, trans): 44 45 "Respond using the given transaction 'trans'." 46 47 fields_path = trans.get_fields_from_path() 48 49 # Check for the logout parameter, if appropriate. 50 51 if self.logout_parameter_name is not None and fields_path.has_key(self.logout_parameter_name): 52 53 # Remove the special cookie token, then pass on the transaction. 54 55 self.authenticator.unset_token(trans) 56 57 # Redirect to the logout URL. 58 59 if self.use_logout_redirect: 60 trans.set_header_value("Location", self.logout_url) 61 trans.set_response_code(307) 62 63 # Show the logout confirmation anyway. 64 65 self._show_logout(trans, self.logout_url) 66 67 # Check the authentication details with the specified authenticator. 68 69 elif self.authenticator.authenticate(trans): 70 71 # If successful, pass on the transaction. 72 73 self.resource.respond(trans) 74 75 # Check for the anonymous parameter, if appropriate. 76 77 elif self.anonymous_parameter_name is not None and fields_path.has_key(self.anonymous_parameter_name): 78 79 # Make a special cookie token, then pass on the transaction. 80 81 self.authenticator.set_token(trans, self.anonymous_username) 82 self.resource.respond(trans) 83 84 else: 85 86 # Redirect to the login URL. 87 88 trans.set_header_value("Location", "%s?redirect=%s%s" % ( 89 self.login_url, self.app_url, self._encode(trans.get_path())) 90 ) 91 trans.set_response_code(307) 92 93 def _encode(self, url): 94 95 "Encode the given 'url' for redirection purposes." 96 97 return url.replace("?", "%3f").replace("&", "%26") 98 99 def _show_logout(self, trans, redirect): 100 101 """ 102 Write a confirmation page to 'trans' containing the 'redirect' URL which the 103 client should be sent to upon logout. 104 """ 105 106 # When logout takes place, show the login screen. 107 108 trans.set_content_type(WebStack.Generic.ContentType("text/html")) 109 out = trans.get_response_stream() 110 out.write(""" 111 <html> 112 <head> 113 <title>Logout</title> 114 </head> 115 <body> 116 <h1>Logout Successful</h1> 117 <p>Please proceed <a href="%s">to the application</a>.</p> 118 </body> 119 </html> 120 """ % redirect) 121 122 class LoginRedirectAuthenticator: 123 124 """ 125 An authenticator which verifies the credentials provided in a special login cookie. 126 """ 127 128 def __init__(self, secret_key, cookie_name=None): 129 130 "Initialise the authenticator with a 'secret_key' and an optional 'cookie_name'." 131 132 self.secret_key = secret_key 133 self.cookie_name = cookie_name or "LoginAuthenticator" 134 135 def authenticate(self, trans): 136 137 "Authenticate the originator of 'trans', updating the object if successful." 138 139 cookie = trans.get_cookie(self.cookie_name) 140 if cookie is None or cookie.value is None: 141 return 0 142 143 # Test the token from the cookie against a recreated token using the 144 # given information. 145 146 username, code = cookie.value.split(":") 147 if cookie.value == get_token(username, self.secret_key): 148 149 # Update the transaction with the user details. 150 151 trans.set_user(username) 152 return 1 153 else: 154 return 0 155 156 def set_token(self, trans, username): 157 158 "Set an authentication token in 'trans' with the given 'username'." 159 160 trans.set_cookie_value( 161 self.cookie_name, 162 get_token(username, self.secret_key) 163 ) 164 165 # Update the transaction with the user details. 166 167 trans.set_user(username) 168 169 def unset_token(self, trans): 170 171 "Unset the authentication token in 'trans'." 172 173 trans.delete_cookie(self.cookie_name) 174 175 # vim: tabstop=4 expandtab shiftwidth=4