Back to index

plone3  3.1.7
CookieAuthHelper.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights
00004 # Reserved.
00005 #
00006 # This software is subject to the provisions of the Zope Public License,
00007 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this
00008 # distribution.
00009 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00010 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00011 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00012 # FOR A PARTICULAR PURPOSE.
00013 #
00014 ##############################################################################
00015 """ Class: CookieAuthHelper
00016 
00017 $Id: CookieAuthHelper.py 75979 2007-05-27 18:24:45Z jens $
00018 """
00019 
00020 from base64 import encodestring, decodestring
00021 from urllib import quote, unquote
00022 
00023 from AccessControl.SecurityInfo import ClassSecurityInfo
00024 from AccessControl.Permissions import view
00025 from OFS.Folder import Folder
00026 from App.class_init import default__class_init__ as InitializeClass
00027 
00028 from zope.interface import Interface
00029 
00030 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00031 from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
00032 
00033 from Products.PluggableAuthService.interfaces.plugins import \
00034         ILoginPasswordHostExtractionPlugin
00035 from Products.PluggableAuthService.interfaces.plugins import \
00036         IChallengePlugin
00037 from Products.PluggableAuthService.interfaces.plugins import \
00038         ICredentialsUpdatePlugin
00039 from Products.PluggableAuthService.interfaces.plugins import \
00040         ICredentialsResetPlugin
00041 from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
00042 from Products.PluggableAuthService.utils import classImplements
00043 
00044 
00045 class ICookieAuthHelper(Interface):
00046     """ Marker interface.
00047     """
00048 
00049 manage_addCookieAuthHelperForm = PageTemplateFile(
00050     'www/caAdd', globals(), __name__='manage_addCookieAuthHelperForm')
00051 
00052 
00053 def addCookieAuthHelper( dispatcher
00054                        , id
00055                        , title=None
00056                        , cookie_name=''
00057                        , REQUEST=None
00058                        ):
00059     """ Add a Cookie Auth Helper to a Pluggable Auth Service. """
00060     sp = CookieAuthHelper(id, title, cookie_name)
00061     dispatcher._setObject(sp.getId(), sp)
00062 
00063     if REQUEST is not None:
00064         REQUEST['RESPONSE'].redirect( '%s/manage_workspace'
00065                                       '?manage_tabs_message='
00066                                       'CookieAuthHelper+added.'
00067                                     % dispatcher.absolute_url() )
00068 
00069 
00070 class CookieAuthHelper(Folder, BasePlugin):
00071     """ Multi-plugin for managing details of Cookie Authentication. """
00072 
00073     meta_type = 'Cookie Auth Helper'
00074     cookie_name = '__ginger_snap'
00075     login_path = 'login_form'
00076     security = ClassSecurityInfo()
00077 
00078     _properties = ( { 'id'    : 'title'
00079                     , 'label' : 'Title'
00080                     , 'type'  : 'string'
00081                     , 'mode'  : 'w'
00082                     }
00083                   , { 'id'    : 'cookie_name'
00084                     , 'label' : 'Cookie Name'
00085                     , 'type'  : 'string'
00086                     , 'mode'  : 'w'
00087                     }
00088                   , { 'id'    : 'login_path'
00089                     , 'label' : 'Login Form'
00090                     , 'type'  : 'string'
00091                     , 'mode'  : 'w'
00092                     }
00093                   )
00094 
00095     manage_options = ( BasePlugin.manage_options[:1]
00096                      + Folder.manage_options[:1]
00097                      + Folder.manage_options[2:]
00098                      )
00099 
00100     def __init__(self, id, title=None, cookie_name=''):
00101         self._setId(id)
00102         self.title = title
00103 
00104         if cookie_name:
00105             self.cookie_name = cookie_name
00106 
00107 
00108     security.declarePrivate('extractCredentials')
00109     def extractCredentials(self, request):
00110         """ Extract credentials from cookie or 'request'. """
00111         creds = {}
00112         cookie = request.get(self.cookie_name, '')
00113         # Look in the request.form for the names coming from the login form
00114         login = request.form.get('__ac_name', '')
00115 
00116         if login and request.form.has_key('__ac_password'):
00117             creds['login'] = login
00118             creds['password'] = request.form.get('__ac_password', '')
00119 
00120         elif cookie and cookie != 'deleted':
00121             cookie_val = decodestring(unquote(cookie))
00122             try:
00123                 login, password = cookie_val.split(':')
00124             except ValueError:
00125                 # Cookie is in a different format, so it is not ours
00126                 return creds
00127 
00128             creds['login'] = login.decode('hex')
00129             creds['password'] = password.decode('hex')
00130 
00131         if creds:
00132             creds['remote_host'] = request.get('REMOTE_HOST', '')
00133 
00134             try:
00135                 creds['remote_address'] = request.getClientAddr()
00136             except AttributeError:
00137                 creds['remote_address'] = request.get('REMOTE_ADDR', '')
00138 
00139         return creds
00140 
00141 
00142     security.declarePrivate('challenge')
00143     def challenge(self, request, response, **kw):
00144         """ Challenge the user for credentials. """
00145         return self.unauthorized()
00146 
00147 
00148     security.declarePrivate('updateCredentials')
00149     def updateCredentials(self, request, response, login, new_password):
00150         """ Respond to change of credentials (NOOP for basic auth). """
00151         cookie_str = '%s:%s' % (login.encode('hex'), new_password.encode('hex'))
00152         cookie_val = encodestring(cookie_str)
00153         cookie_val = cookie_val.rstrip()
00154         response.setCookie(self.cookie_name, quote(cookie_val), path='/')
00155 
00156 
00157     security.declarePrivate('resetCredentials')
00158     def resetCredentials(self, request, response):
00159         """ Raise unauthorized to tell browser to clear credentials. """
00160         response.expireCookie(self.cookie_name, path='/')
00161 
00162 
00163     security.declarePrivate('manage_afterAdd')
00164     def manage_afterAdd(self, item, container):
00165         """ Setup tasks upon instantiation """
00166         if not 'login_form' in self.objectIds():
00167             login_form = ZopePageTemplate( id='login_form'
00168                                            , text=BASIC_LOGIN_FORM
00169                                            )
00170             login_form.title = 'Login Form'
00171             login_form.manage_permission(view, roles=['Anonymous'], acquire=1)
00172             self._setObject( 'login_form', login_form, set_owner=0 )
00173 
00174 
00175     security.declarePrivate('unauthorized')
00176     def unauthorized(self):
00177         req = self.REQUEST
00178         resp = req['RESPONSE']
00179 
00180         # If we set the auth cookie before, delete it now.
00181         if resp.cookies.has_key(self.cookie_name):
00182             del resp.cookies[self.cookie_name]
00183 
00184         # Redirect if desired.
00185         url = self.getLoginURL()
00186         if url is not None:
00187             came_from = req.get('came_from', None)
00188 
00189             if came_from is None:
00190                 came_from = req.get('URL', '')
00191                 query = req.get('QUERY_STRING')
00192                 if query:
00193                     if not query.startswith('?'):
00194                         query = '?' + query
00195                     came_from = came_from + query
00196             else:
00197                 # If came_from contains a value it means the user
00198                 # must be coming through here a second time
00199                 # Reasons could be typos when providing credentials
00200                 # or a redirect loop (see below)
00201                 req_url = req.get('URL', '')
00202 
00203                 if req_url and req_url == url:
00204                     # Oops... The login_form cannot be reached by the user -
00205                     # it might be protected itself due to misconfiguration -
00206                     # the only sane thing to do is to give up because we are
00207                     # in an endless redirect loop.
00208                     return 0
00209 
00210             url = url + '?came_from=%s' % quote(came_from)
00211             resp.redirect(url, lock=1)
00212             return 1
00213 
00214         # Could not challenge.
00215         return 0
00216 
00217 
00218     security.declarePrivate('getLoginURL')
00219     def getLoginURL(self):
00220         """ Where to send people for logging in """
00221         if self.login_path.startswith('/'):
00222             return self.login_path
00223         elif self.login_path != '':
00224             return '%s/%s' % (self.absolute_url(), self.login_path)
00225         else:
00226             return None
00227 
00228     security.declarePublic('login')
00229     def login(self):
00230         """ Set a cookie and redirect to the url that we tried to
00231         authenticate against originally.
00232         """
00233         request = self.REQUEST
00234         response = request['RESPONSE']
00235 
00236         login = request.get('__ac_name', '')
00237         password = request.get('__ac_password', '')
00238 
00239         # In order to use the CookieAuthHelper for its nice login page
00240         # facility but store and manage credentials somewhere else we need
00241         # to make sure that upon login only plugins activated as
00242         # IUpdateCredentialPlugins get their updateCredentials method
00243         # called. If the method is called on the CookieAuthHelper it will
00244         # simply set its own auth cookie, to the exclusion of any other
00245         # plugins that might want to store the credentials.
00246         pas_instance = self._getPAS()
00247 
00248         if pas_instance is not None:
00249             pas_instance.updateCredentials(request, response, login, password)
00250 
00251         came_from = request.form['came_from']
00252 
00253         return response.redirect(came_from)
00254 
00255 classImplements( CookieAuthHelper
00256                , ICookieAuthHelper
00257                , ILoginPasswordHostExtractionPlugin
00258                , IChallengePlugin
00259                , ICredentialsUpdatePlugin
00260                , ICredentialsResetPlugin
00261                )
00262 
00263 InitializeClass(CookieAuthHelper)
00264 
00265 
00266 BASIC_LOGIN_FORM = """<html>
00267   <head>
00268     <title> Login Form </title>
00269   </head>
00270 
00271   <body>
00272 
00273     <h3> Please log in </h3>
00274 
00275     <form method="post" action=""
00276           tal:attributes="action string:${here/absolute_url}/login">
00277 
00278       <input type="hidden" name="came_from" value=""
00279              tal:attributes="value request/came_from | string:"/>
00280       <table cellpadding="2">
00281         <tr>
00282           <td><b>Login:</b> </td>
00283           <td><input type="text" name="__ac_name" size="30" /></td>
00284         </tr>
00285         <tr>
00286           <td><b>Password:</b></td>
00287           <td><input type="password" name="__ac_password" size="30" /></td>
00288         </tr>
00289         <tr>
00290           <td colspan="2">
00291             <br />
00292             <input type="submit" value=" Log In " />
00293           </td>
00294         </tr>
00295       </table>
00296 
00297     </form>
00298 
00299   </body>
00300 
00301 </html>
00302 """
00303