Back to index

plone3  3.1.7
test_CookieCrumbler.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """CookieCrumbler tests.
00014 
00015 $Id: test_CookieCrumbler.py 71020 2006-11-01 15:42:13Z yuppie $
00016 """
00017 
00018 import unittest
00019 import Testing
00020 
00021 from zope.app.testing.placelesssetup import PlacelessSetup
00022 
00023 def makerequest(root, stdout, stdin=None):
00024     # Customized version of Testing.makerequest.makerequest()
00025     from cStringIO import StringIO
00026     from ZPublisher.HTTPRequest import HTTPRequest
00027     from ZPublisher.HTTPResponse import HTTPResponse
00028 
00029     resp = HTTPResponse(stdout=stdout)
00030     environ = {}
00031     environ['SERVER_NAME'] = 'example.com'
00032     environ['SERVER_PORT'] = '80'
00033     environ['REQUEST_METHOD'] = 'GET'
00034     if stdin is None:
00035         stdin = StringIO('')  # Empty input
00036     req = HTTPRequest(stdin, environ, resp)
00037     req['PARENTS'] = [root]
00038     return req
00039 
00040 
00041 class CookieCrumblerTests(unittest.TestCase, PlacelessSetup):
00042 
00043     _CC_ID = 'cookie_authentication'
00044 
00045     def _getTargetClass(self):
00046         from Products.CMFCore.CookieCrumbler  import CookieCrumbler
00047         return CookieCrumbler
00048 
00049     def _makeOne(self, *args, **kw):
00050         return self._getTargetClass()(*args, **kw)
00051 
00052     def setUp(self):
00053         from zope.component import provideHandler
00054         from zope.component.interfaces import IObjectEvent
00055         from Products.CMFCore.interfaces import ICookieCrumbler
00056         from Products.CMFCore.CookieCrumbler import handleCookieCrumblerEvent
00057 
00058         PlacelessSetup.setUp(self)
00059         self._finally = None
00060 
00061         provideHandler(handleCookieCrumblerEvent,
00062                        adapts=(ICookieCrumbler, IObjectEvent))
00063 
00064     def tearDown(self):
00065         from AccessControl.SecurityManagement import noSecurityManager
00066 
00067         if self._finally is not None:
00068             self._finally()
00069 
00070         noSecurityManager()
00071         PlacelessSetup.tearDown(self)
00072 
00073     def _makeSite(self):
00074         import base64
00075         from cStringIO import StringIO
00076         import urllib
00077 
00078         from AccessControl.User import UserFolder
00079         from OFS.Folder import Folder
00080         from OFS.DTMLMethod import DTMLMethod
00081 
00082         root = Folder()
00083         root.isTopLevelPrincipiaApplicationObject = 1  # User folder needs this
00084         root.getPhysicalPath = lambda: ()  # hack
00085         root._View_Permission = ('Anonymous',)
00086 
00087         users = UserFolder()
00088         users._setId('acl_users')
00089         users._doAddUser('abraham', 'pass-w', ('Patriarch',), ())
00090         users._doAddUser('isaac', 'pass-w', ('Son',), ())
00091         root._setObject(users.id, users)
00092 
00093         cc = self._makeOne()
00094         cc.id = self._CC_ID
00095         root._setObject(cc.id, cc)
00096 
00097         index = DTMLMethod()
00098         index.munge('This is the default view')
00099         index._setId('index_html')
00100         root._setObject(index.getId(), index)
00101 
00102         login = DTMLMethod()
00103         login.munge('Please log in first.')
00104         login._setId('login_form')
00105         root._setObject(login.getId(), login)
00106 
00107         protected = DTMLMethod()
00108         protected._View_Permission = ('Manager',)
00109         protected.munge('This is the protected view')
00110         protected._setId('protected')
00111         root._setObject(protected.getId(), protected)
00112 
00113         req = makerequest(root, StringIO())
00114         self._finally = req.close
00115 
00116         credentials = urllib.quote(
00117             base64.encodestring('abraham:pass-w').rstrip())
00118 
00119         return root, cc, req, credentials
00120 
00121     def test_z3interfaces(self):
00122         from zope.interface.verify import verifyClass
00123         from Products.CMFCore.interfaces import ICookieCrumbler
00124 
00125         verifyClass(ICookieCrumbler, self._getTargetClass())
00126 
00127     def testNoCookies(self):
00128         # verify the cookie crumbler doesn't break when no cookies are given
00129         root, cc, req, credentials = self._makeSite()
00130         req.traverse('/')
00131         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00132                          'Anonymous User')
00133 
00134     def testCookieLogin(self):
00135         # verify the user and auth cookie get set
00136         root, cc, req, credentials = self._makeSite()
00137 
00138         req.cookies['__ac_name'] = 'abraham'
00139         req.cookies['__ac_password'] = 'pass-w'
00140         req.traverse('/')
00141 
00142         self.failUnless(req.has_key('AUTHENTICATED_USER'))
00143         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00144                          'abraham')
00145         resp = req.response
00146         self.failUnless(resp.cookies.has_key('__ac'))
00147         self.assertEqual(resp.cookies['__ac']['value'],
00148                          credentials)
00149         self.assertEqual(resp.cookies['__ac']['path'], '/')
00150 
00151     def testCookieResume(self):
00152         # verify the cookie crumbler continues the session
00153         root, cc, req, credentials = self._makeSite()
00154         req.cookies['__ac'] = credentials
00155         req.traverse('/')
00156         self.failUnless(req.has_key('AUTHENTICATED_USER'))
00157         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00158                          'abraham')
00159 
00160     def testPasswordShredding(self):
00161         # verify the password is shredded before the app gets the request
00162         root, cc, req, credentials = self._makeSite()
00163         req.cookies['__ac_name'] = 'abraham'
00164         req.cookies['__ac_password'] = 'pass-w'
00165         self.failUnless(req.has_key('__ac_password'))
00166         req.traverse('/')
00167         self.failIf( req.has_key('__ac_password'))
00168         self.failIf( req.has_key('__ac'))
00169 
00170     def testCredentialsNotRevealed(self):
00171         # verify the credentials are shredded before the app gets the request
00172         root, cc, req, credentials = self._makeSite()
00173         req.cookies['__ac'] = credentials
00174         self.failUnless(req.has_key('__ac'))
00175         req.traverse('/')
00176         self.failIf( req.has_key('__ac'))
00177 
00178     def testAutoLoginRedirection(self):
00179         # Redirect unauthorized anonymous users to the login page
00180         from Products.CMFCore.CookieCrumbler  import Redirect
00181 
00182         root, cc, req, credentials = self._makeSite()
00183         self.assertRaises(Redirect, req.traverse, '/protected')
00184 
00185     def testDisabledAutoLoginRedirection(self):
00186         # When disable_cookie_login__ is set, don't redirect.
00187         from zExceptions.unauthorized import Unauthorized
00188 
00189         root, cc, req, credentials = self._makeSite()
00190         req['disable_cookie_login__'] = 1
00191         self.assertRaises(Unauthorized, req.traverse, '/protected')
00192 
00193 
00194     def testNoRedirectAfterAuthenticated(self):
00195         # Don't redirect already-authenticated users to the login page,
00196         # even when they try to access things they can't get.
00197         from zExceptions.unauthorized import Unauthorized
00198 
00199         root, cc, req, credentials = self._makeSite()
00200         req.cookies['__ac'] = credentials
00201         self.assertRaises(Unauthorized, req.traverse, '/protected')
00202 
00203     def testRetryLogin(self):
00204         # After a failed login, CookieCrumbler should give the user an
00205         # opportunity to try to log in again.
00206         from Products.CMFCore.CookieCrumbler  import Redirect
00207 
00208         root, cc, req, credentials = self._makeSite()
00209         req.cookies['__ac_name'] = 'israel'
00210         req.cookies['__ac_password'] = 'pass-w'
00211         try:
00212             req.traverse('/protected')
00213         except Redirect, s:
00214             # Test passed
00215             if hasattr(s, 'args'):
00216                 s = s.args[0]
00217             self.failUnless(s.find('came_from=') >= 0)
00218             self.failUnless(s.find('retry=1') >= 0)
00219             self.failUnless(s.find('disable_cookie_login__=1') >= 0)
00220         else:
00221             self.fail('Did not redirect')
00222 
00223 
00224     def testLoginRestoresQueryString(self):
00225         # When redirecting for login, the came_from form field should
00226         # include the submitted URL as well as the query string.
00227         import urllib
00228         from Products.CMFCore.CookieCrumbler  import Redirect
00229 
00230         root, cc, req, credentials = self._makeSite()
00231         req['PATH_INFO'] = '/protected'
00232         req['QUERY_STRING'] = 'a:int=1&x:string=y'
00233         try:
00234             req.traverse('/protected')
00235         except Redirect, s:
00236             if hasattr(s, 'args'):
00237                 s = s.args[0]
00238             to_find = urllib.quote('/protected?' + req['QUERY_STRING'])
00239             self.failUnless(s.find(to_find) >= 0, s)
00240         else:
00241             self.fail('Did not redirect')
00242 
00243     def testCacheHeaderAnonymous(self):
00244         # Should not set cache-control
00245         root, cc, req, credentials = self._makeSite()
00246         req.traverse('/')
00247         self.assertEqual(
00248             req.response.headers.get('cache-control', ''), '')
00249 
00250     def testCacheHeaderLoggingIn(self):
00251         # Should set cache-control
00252         root, cc, req, credentials = self._makeSite()
00253         req.cookies['__ac_name'] = 'abraham'
00254         req.cookies['__ac_password'] = 'pass-w'
00255         req.traverse('/')
00256         self.assertEqual(req.response['cache-control'], 'private')
00257 
00258     def testCacheHeaderAuthenticated(self):
00259         # Should set cache-control
00260         root, cc, req, credentials = self._makeSite()
00261         req.cookies['__ac'] = credentials
00262         req.traverse('/')
00263         self.assertEqual(req.response['cache-control'], 'private')
00264 
00265     def testCacheHeaderDisabled(self):
00266         # Should not set cache-control
00267         root, cc, req, credentials = self._makeSite()
00268         cc.cache_header_value = ''
00269         req.cookies['__ac'] = credentials
00270         req.traverse('/')
00271         self.assertEqual(
00272             req.response.headers.get('cache-control', ''), '')
00273 
00274     def testDisableLoginDoesNotPreventPasswordShredding(self):
00275         # Even if disable_cookie_login__ is set, read the cookies
00276         # anyway to avoid revealing the password to the app.
00277         # (disable_cookie_login__ does not mean disable cookie
00278         # authentication, it only means disable the automatic redirect
00279         # to the login page.)
00280         root, cc, req, credentials = self._makeSite()
00281         req.cookies['__ac_name'] = 'abraham'
00282         req.cookies['__ac_password'] = 'pass-w'
00283         req['disable_cookie_login__'] = 1
00284         req.traverse('/')
00285         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00286                          'abraham')
00287         # Here is the real test: the password should have been shredded.
00288         self.failIf( req.has_key('__ac_password'))
00289 
00290     def testDisableLoginDoesNotPreventPasswordShredding2(self):
00291         root, cc, req, credentials = self._makeSite()
00292         req.cookies['__ac'] = credentials
00293         req['disable_cookie_login__'] = 1
00294         req.traverse('/')
00295         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00296                          'abraham')
00297         self.failIf( req.has_key('__ac'))
00298 
00299     def testMidApplicationAutoLoginRedirection(self):
00300         # Redirect anonymous users to login page if Unauthorized
00301         # occurs in the middle of the app
00302         from zExceptions.unauthorized import Unauthorized
00303 
00304         root, cc, req, credentials = self._makeSite()
00305         req.traverse('/')
00306         try:
00307             raise Unauthorized
00308         except:
00309             req.response.exception()
00310             self.assertEqual(req.response.status, 302)
00311 
00312     def testMidApplicationAuthenticationButUnauthorized(self):
00313         # Don't redirect already-authenticated users to the login page,
00314         # even when Unauthorized happens in the middle of the app.
00315         from zExceptions.unauthorized import Unauthorized
00316 
00317         root, cc, req, credentials = self._makeSite()
00318         req.cookies['__ac'] = credentials
00319         req.traverse('/')
00320         try:
00321             raise Unauthorized
00322         except:
00323             req.response.exception()
00324             self.assertEqual(req.response.status, 401)
00325 
00326     def testRedirectOnUnauthorized(self):
00327         # Redirect already-authenticated users to the unauthorized
00328         # handler page if that's what the sysadmin really wants.
00329         from Products.CMFCore.CookieCrumbler  import Redirect
00330 
00331         root, cc, req, credentials = self._makeSite()
00332         cc.unauth_page = 'login_form'
00333         req.cookies['__ac'] = credentials
00334         self.assertRaises(Redirect, req.traverse, '/protected')
00335 
00336     def testLoginRatherThanResume(self):
00337         # When the user presents both a session resume and new
00338         # credentials, choose the new credentials (so that it's
00339         # possible to log in without logging out)
00340         root, cc, req, credentials = self._makeSite()
00341         req.cookies['__ac_name'] = 'isaac'
00342         req.cookies['__ac_password'] = 'pass-w'
00343         req.cookies['__ac'] = credentials
00344         req.traverse('/')
00345 
00346         self.failUnless(req.has_key('AUTHENTICATED_USER'))
00347         self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
00348                          'isaac')
00349 
00350     def testCreateForms(self):
00351         # Verify the factory creates the login forms.
00352         from Products.CMFCore.CookieCrumbler  import manage_addCC
00353 
00354         if 'CMFCore' in self._getTargetClass().__module__:
00355             # This test is disabled in CMFCore.
00356             return
00357 
00358         root, cc, req, credentials = self._makeSite()
00359         root._delObject('cookie_authentication')
00360         manage_addCC(root, 'login', create_forms=1)
00361         ids = root.login.objectIds()
00362         ids.sort()
00363         self.assertEqual(tuple(ids), (
00364             'index_html', 'logged_in', 'logged_out', 'login_form',
00365             'standard_login_footer', 'standard_login_header'))
00366 
00367     def test_before_traverse_hooks(self):
00368         from OFS.Folder import Folder
00369         container = Folder()
00370         cc = self._makeOne()
00371         cc._setId(self._CC_ID)
00372 
00373         marker = []
00374         bt_before = getattr(container, '__before_traverse__', marker)
00375         self.failUnless(bt_before is marker)
00376 
00377         container._setObject(self._CC_ID, cc)
00378 
00379         bt_added = getattr(container, '__before_traverse__')
00380         self.assertEqual(len(bt_added.items()), 1)
00381         k, v = bt_added.items()[0]
00382         self.failUnless(k[1].startswith(self._getTargetClass().meta_type))
00383         self.assertEqual(v.name, self._CC_ID)
00384 
00385         container._delObject(self._CC_ID)
00386 
00387         bt_removed = getattr(container, '__before_traverse__')
00388         self.assertEqual(len(bt_removed.items()), 0)
00389 
00390 
00391 def test_suite():
00392     return unittest.TestSuite((
00393         unittest.makeSuite(CookieCrumblerTests),
00394         ))
00395 
00396 if __name__ == '__main__':
00397     unittest.main(defaultTest='test_suite')