[Erp5-report] r23321 - in /erp5/tags/version-5.1.2/products/ERP5Type: patches/ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Sep 1 15:25:03 CEST 2008


Author: yusei
Date: Mon Sep  1 15:24:47 2008
New Revision: 23321

URL: http://svn.erp5.org?rev=23321&view=rev
Log:
Add cookie crumbler bug fix.

Added:
    erp5/tags/version-5.1.2/products/ERP5Type/tests/testCookieCrumbler.py
Modified:
    erp5/tags/version-5.1.2/products/ERP5Type/patches/CookieCrumbler.py
    erp5/tags/version-5.1.2/products/ERP5Type/tests/ERP5TypeTestCase.py

Modified: erp5/tags/version-5.1.2/products/ERP5Type/patches/CookieCrumbler.py
URL: http://svn.erp5.org/erp5/tags/version-5.1.2/products/ERP5Type/patches/CookieCrumbler.py?rev=23321&r1=23320&r2=23321&view=diff
==============================================================================
--- erp5/tags/version-5.1.2/products/ERP5Type/patches/CookieCrumbler.py (original)
+++ erp5/tags/version-5.1.2/products/ERP5Type/patches/CookieCrumbler.py Mon Sep  1 15:24:47 2008
@@ -26,7 +26,16 @@
 """
 
 from Products.CMFCore.CookieCrumbler import CookieCrumbler
-from urllib import quote
+from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled
+from urllib import quote, unquote
+from ZPublisher.HTTPRequest import HTTPRequest
+
+ATTEMPT_NONE = 0       # No attempt at authentication
+ATTEMPT_LOGIN = 1      # Attempt to log in
+ATTEMPT_RESUME = 2     # Attempt to resume session
+
+from base64 import encodestring, decodestring
+from DateTime import DateTime
 
 class PatchedCookieCrumbler(CookieCrumbler):
   """
@@ -60,3 +69,86 @@
     return None
 
 CookieCrumbler.getLoginURL = getLoginURL
+
+def modifyRequest(self, req, resp):
+  """Copies cookie-supplied credentials to the basic auth fields.
+  
+  Returns a flag indicating what the user is trying to do with
+  cookies: ATTEMPT_NONE, ATTEMPT_LOGIN, or ATTEMPT_RESUME.  If
+  cookie login is disabled for this request, raises
+  CookieCrumblerDisabled.
+  """
+  if (req.__class__ is not HTTPRequest
+      or not req['REQUEST_METHOD'] in ('HEAD', 'GET', 'PUT', 'POST')
+      or req.environ.has_key('WEBDAV_SOURCE_PORT')):
+      raise CookieCrumblerDisabled
+
+  # attempt may contain information about an earlier attempt to
+  # authenticate using a higher-up cookie crumbler within the
+  # same request.
+  attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE)
+
+  if attempt == ATTEMPT_NONE:
+    if req._auth:
+      # An auth header was provided and no cookie crumbler
+      # created it.  The user must be using basic auth.
+      raise CookieCrumblerDisabled
+
+    if req.has_key(self.pw_cookie) and req.has_key(self.name_cookie):
+      # Attempt to log in and set cookies.
+      attempt = ATTEMPT_LOGIN
+      name = req[self.name_cookie]
+      pw = req[self.pw_cookie]
+      #ac = encodestring('%s:%s' % (name, pw)).rstrip() => changed for remove all newlines
+      ac = encodestring('%s:%s' % (name, pw)).replace('\012','')
+      self._setAuthHeader(ac, req, resp)
+      if req.get(self.persist_cookie, 0):
+        # Persist the user name (but not the pw or session)
+        expires = (DateTime() + 365).toZone('GMT').rfc822()
+        resp.setCookie(self.name_cookie, name,
+                       path=self.getCookiePath(),
+                       expires=expires)
+      else:
+        # Expire the user name
+        resp.expireCookie(self.name_cookie,
+                          path=self.getCookiePath())
+      method = self.getCookieMethod( 'setAuthCookie'
+                                     , self.defaultSetAuthCookie )
+      method( resp, self.auth_cookie, quote( ac ) )
+      self.delRequestVar(req, self.name_cookie)
+      self.delRequestVar(req, self.pw_cookie)
+
+    elif req.has_key(self.auth_cookie):
+      # Attempt to resume a session if the cookie is valid.
+      # Copy __ac to the auth header.
+      ac = unquote(req[self.auth_cookie])
+      if ac and ac != 'deleted':
+        try:
+          decodestring(ac)
+        except:
+          # Not a valid auth header.
+          pass
+        else:
+          attempt = ATTEMPT_RESUME
+          self._setAuthHeader(ac, req, resp)
+          self.delRequestVar(req, self.auth_cookie)
+          method = self.getCookieMethod(
+            'twiddleAuthCookie', None)
+          if method is not None:
+            method(resp, self.auth_cookie, quote(ac))
+              
+  req._cookie_auth = attempt
+  return attempt
+
+CookieCrumbler.modifyRequest = modifyRequest
+
+
+def credentialsChanged(self, user, name, pw):
+  #ac = encodestring('%s:%s' % (name, pw)).rstrip() => changed for remove all newlines
+  ac = encodestring('%s:%s' % (name, pw)).replace('\012','')
+  method = self.getCookieMethod( 'setAuthCookie'
+                                 , self.defaultSetAuthCookie )
+  resp = self.REQUEST['RESPONSE']
+  method( resp, self.auth_cookie, quote( ac ) )
+
+CookieCrumbler.credentialsChanged = credentialsChanged

Modified: erp5/tags/version-5.1.2/products/ERP5Type/tests/ERP5TypeTestCase.py
URL: http://svn.erp5.org/erp5/tags/version-5.1.2/products/ERP5Type/tests/ERP5TypeTestCase.py?rev=23321&r1=23320&r2=23321&view=diff
==============================================================================
--- erp5/tags/version-5.1.2/products/ERP5Type/tests/ERP5TypeTestCase.py (original)
+++ erp5/tags/version-5.1.2/products/ERP5Type/tests/ERP5TypeTestCase.py Mon Sep  1 15:24:47 2008
@@ -163,6 +163,7 @@
 from cStringIO import StringIO
 from urllib import urlretrieve
 from glob import glob
+import sys, re, base64
 
 portal_name = 'erp5_portal'
 
@@ -790,6 +791,100 @@
                             % title) # run_unit_test depends on this string.
         raise
 
+    def publish(self, path, basic=None, env=None, extra=None,
+                request_method='GET', stdin=None, handle_errors=True):
+        '''Publishes the object at 'path' returning a response object.'''
+
+        from StringIO import StringIO
+        from ZPublisher.Response import Response
+        from ZPublisher.Test import publish_module
+
+        from AccessControl.SecurityManagement import getSecurityManager
+        from AccessControl.SecurityManagement import setSecurityManager
+        
+        # Save current security manager
+        sm = getSecurityManager()
+
+        # Commit the sandbox for good measure
+        get_transaction().commit()
+
+        if env is None:
+            env = {}
+        if extra is None:
+            extra = {}
+
+        request = self.app.REQUEST
+
+        env['SERVER_NAME'] = request['SERVER_NAME']
+        env['SERVER_PORT'] = request['SERVER_PORT']
+        env['REQUEST_METHOD'] = request_method
+
+        p = path.split('?')
+        if len(p) == 1:
+            env['PATH_INFO'] = p[0]
+        elif len(p) == 2:
+            [env['PATH_INFO'], env['QUERY_STRING']] = p
+        else:
+            raise TypeError, ''
+
+        if basic:
+            env['HTTP_AUTHORIZATION'] = "Basic %s" % base64.encodestring(basic).replace('\012', '')
+
+        if stdin is None:
+            stdin = StringIO()
+
+        outstream = StringIO()
+        response = Response(stdout=outstream, stderr=sys.stderr)
+
+        publish_module('Zope2',
+                       response=response,
+                       stdin=stdin,
+                       environ=env,
+                       extra=extra,
+                       debug=not handle_errors,
+                      )
+
+        # Restore security manager
+        setSecurityManager(sm)
+
+        return ResponseWrapper(response, outstream, path)
+
+
+class ResponseWrapper:
+    '''Decorates a response object with additional introspective methods.'''
+
+    _bodyre = re.compile('^$^\n(.*)', re.MULTILINE | re.DOTALL)
+
+    def __init__(self, response, outstream, path):
+        self._response = response
+        self._outstream = outstream
+        self._path = path
+
+    def __getattr__(self, name):
+        return getattr(self._response, name)
+
+    def getOutput(self):
+        '''Returns the complete output, headers and all.'''
+        return self._outstream.getvalue()
+
+    def getBody(self):
+        '''Returns the page body, i.e. the output par headers.'''
+        body = self._bodyre.search(self.getOutput())
+        if body is not None:
+            body = body.group(1)
+        return body
+
+    def getPath(self):
+        '''Returns the path used by the request.'''
+        return self._path
+
+    def getHeader(self, name):
+        '''Returns the value of a response header.'''
+        return self.headers.get(name.lower())
+
+    def getCookie(self, name):
+        '''Returns a response cookie.'''
+        return self.cookies.get(name)
 
 class ERP5ReportTestCase(ERP5TypeTestCase):
   """Base class for testing ERP5 Reports

Added: erp5/tags/version-5.1.2/products/ERP5Type/tests/testCookieCrumbler.py
URL: http://svn.erp5.org/erp5/tags/version-5.1.2/products/ERP5Type/tests/testCookieCrumbler.py?rev=23321&view=auto
==============================================================================
--- erp5/tags/version-5.1.2/products/ERP5Type/tests/testCookieCrumbler.py (added)
+++ erp5/tags/version-5.1.2/products/ERP5Type/tests/testCookieCrumbler.py Mon Sep  1 15:24:47 2008
@@ -1,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import base64
+from cStringIO import StringIO
+import unittest
+import urllib
+
+from OFS.DTMLMethod import DTMLMethod
+from OFS.Folder import Folder
+from zExceptions.unauthorized import Unauthorized
+from AccessControl.User import UserFolder
+from AccessControl.SecurityManagement import noSecurityManager
+from ZPublisher.HTTPRequest import HTTPRequest
+from ZPublisher.HTTPResponse import HTTPResponse
+
+from Products.CMFCore.CookieCrumbler \
+     import CookieCrumbler, manage_addCC, Redirect
+
+from Products.CMFCore.tests.testCookieCrumbler import makerequest
+from Products.CMFCore.tests.testCookieCrumbler import CookieCrumblerTests
+
+class ERP5CookieCrumblerTests (CookieCrumblerTests):
+  """ Modify original CMFCore Cookie Crumbler unit test to test long login """
+
+  def setUp(self):
+    root = Folder()
+    self.root = root
+    root.isTopLevelPrincipiaApplicationObject = 1  # User folder needs this
+    root.getPhysicalPath = lambda: ()  # hack
+    root._View_Permission = ('Anonymous',)
+
+    users = UserFolder()
+    users._setId('acl_users')
+    users._doAddUser('abraham', 'pass-w', ('Patriarch',), ())
+    users._doAddUser('isaac', 'pass-w', ('Son',), ())
+    users._doAddUser('abrahammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
+                     'pass-wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
+                     ('Son',), ())
+    root._setObject(users.id, users)
+    
+    cc = CookieCrumbler()
+    cc.id = 'cookie_authentication'
+    root._setObject(cc.id, cc)
+    self.cc = getattr(root, cc.id)
+
+    index = DTMLMethod()
+    index.munge('This is the default view')
+    index._setId('index_html')
+    root._setObject(index.getId(), index)
+
+    login = DTMLMethod()
+    login.munge('Please log in first.')
+    login._setId('login_form')
+    root._setObject(login.getId(), login)
+
+    protected = DTMLMethod()
+    protected._View_Permission = ('Manager',)
+    protected.munge('This is the protected view')
+    protected._setId('protected')
+    root._setObject(protected.getId(), protected)
+
+    self.responseOut = StringIO()
+    self.req = makerequest(root, self.responseOut)
+
+    self.credentials = urllib.quote(
+        base64.encodestring('abraham:pass-w').replace('\012', ''))
+
+  def testCookieLongLogin(self):
+    # verify the user and auth cookie get set
+    long_name = 'abrahammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'
+    long_pass = 'pass-wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'
+    self.req.cookies['__ac_name'] = long_name
+    self.req.cookies['__ac_password'] = long_pass
+    self.req.traverse('/')
+
+    self.assert_(self.req.has_key('AUTHENTICATED_USER'))
+    self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+                         'abrahammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm')
+    resp = self.req.response
+    self.assert_(resp.cookies.has_key('__ac'))
+    self.credentials = base64.encodestring('%s:%s' % (long_name, long_pass)).replace('\012', '')
+    self.assertEqual(resp.cookies['__ac']['value'],
+                         self.credentials)
+    self.assertEqual(resp.cookies['__ac']['path'], '/')
+
+def test_suite():
+    return unittest.makeSuite(ERP5CookieCrumblerTests)
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')




More information about the Erp5-report mailing list