Commit b9a33c40 authored by Bryton Lacquement's avatar Bryton Lacquement 🚪

patches/CookieCrumbler: backport the Unauthorized handling and redirects

Products.CMFCore.CookieCrumbler's redirect support was moved to
Products.CMFDefault in CMF 2.3, cf:
- CMFCore: 387bd379fd9655aa1462715a04b4664f5c54974a
- CMFDefault: d319a68e8299e42a76f605ad79383bb78db14031

The absence of this backport when using CMF 2.3 creates regressions:
some redirects become Unauthorized.
parent aaf5fb18
......@@ -30,6 +30,7 @@ from App.class_init import InitializeClass
from Products.CMFCore.CookieCrumbler import CookieCrumbler
from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled
from urllib import quote, unquote
from zExceptions import Redirect
from ZPublisher.HTTPRequest import HTTPRequest
ATTEMPT_NONE = 0 # No attempt at authentication
......@@ -47,6 +48,10 @@ class PatchedCookieCrumbler(CookieCrumbler):
security = ClassSecurityInfo()
CookieCrumbler.auto_login_page = 'login_form'
CookieCrumbler.unauth_page = ''
CookieCrumbler.logout_page = 'logged_out'
def getLoginURL(self):
'''
Redirects to the login page.
......@@ -194,5 +199,157 @@ def credentialsChanged(self, user, name, pw, request=None):
CookieCrumbler.credentialsChanged = credentialsChanged
# The following patches are to keep the original behaviour of automatic
# redirection to login page. Recent CMF uses a view that is implemented
# in CMFDefault (UnauthorizedView, on zExceptions.Unauthorized).
class ResponseCleanup:
def __init__(self, resp):
self.resp = resp
def __del__(self):
# Free the references.
#
# No errors of any sort may propagate, and we don't care *what*
# they are, even to log them.
try:
del self.resp.unauthorized
except:
pass
try:
del self.resp._unauthorized
except:
pass
try:
del self.resp
except:
pass
if 1:
def __call__(self, container, req):
'''The __before_publishing_traverse__ hook.'''
resp = req['RESPONSE']
try:
attempt = self.modifyRequest(req, resp)
except CookieCrumblerDisabled:
return
# <patch>
if req.get('disable_cookie_login__', 0):
return
if (self.unauth_page or
attempt == ATTEMPT_LOGIN or attempt == ATTEMPT_NONE):
# Modify the "unauthorized" response.
req._hold(ResponseCleanup(resp))
resp.unauthorized = self.unauthorized
resp._unauthorized = self._unauthorized
# </patch>
if attempt != ATTEMPT_NONE:
# Trying to log in or resume a session
if self.cache_header_value:
# we don't want caches to cache the resulting page
resp.setHeader('Cache-Control', self.cache_header_value)
# demystify this in the response.
resp.setHeader('X-Cache-Control-Hdr-Modified-By',
'CookieCrumbler')
phys_path = self.getPhysicalPath()
# <patch>
if self.logout_page:
# Cookies are in use.
page = getattr(container, self.logout_page, None)
if page is not None:
# Provide a logout page.
req._logout_path = phys_path + ('logout',)
req._credentials_changed_path = (
phys_path + ('credentialsChanged',))
# </patch>
def _cleanupResponse(self):
# XXX: this method violates the rules for tools/utilities:
# it depends on self.REQUEST
resp = self.REQUEST['RESPONSE']
# No errors of any sort may propagate, and we don't care *what*
# they are, even to log them.
try: del resp.unauthorized
except: pass
try: del resp._unauthorized
except: pass
return resp
security.declarePrivate('unauthorized')
def unauthorized(self):
resp = self._cleanupResponse()
# If we set the auth cookie before, delete it now.
if resp.cookies.has_key(self.auth_cookie):
del resp.cookies[self.auth_cookie]
# Redirect if desired.
url = self.getUnauthorizedURL()
if url is not None:
raise Redirect, url
# Fall through to the standard unauthorized() call.
resp.unauthorized()
def _unauthorized(self):
resp = self._cleanupResponse()
# If we set the auth cookie before, delete it now.
if resp.cookies.has_key(self.auth_cookie):
del resp.cookies[self.auth_cookie]
# Redirect if desired.
url = self.getUnauthorizedURL()
if url is not None:
resp.redirect(url, lock=1)
# We don't need to raise an exception.
return
# Fall through to the standard _unauthorized() call.
resp._unauthorized()
security.declarePublic('getUnauthorizedURL')
def getUnauthorizedURL(self):
'''
Redirects to the login page.
'''
# XXX: this method violates the rules for tools/utilities:
# it depends on self.REQUEST
req = self.REQUEST
resp = req['RESPONSE']
attempt = getattr(req, '_cookie_auth', ATTEMPT_NONE)
if attempt == ATTEMPT_NONE:
# An anonymous user was denied access to something.
page_id = self.auto_login_page
retry = ''
elif attempt == ATTEMPT_LOGIN:
# The login attempt failed. Try again.
page_id = self.auto_login_page
retry = '1'
else:
# An authenticated user was denied access to something.
page_id = self.unauth_page
retry = ''
if page_id:
page = self.restrictedTraverse(page_id, None)
if page is not None:
came_from = req.get('came_from', None)
if came_from is None:
came_from = req.get('ACTUAL_URL')
query = req.get('QUERY_STRING')
if query:
# Include the query string in came_from
if not query.startswith('?'):
query = '?' + query
came_from = came_from + query
url = '%s?came_from=%s&retry=%s&disable_cookie_login__=1' % (
page.absolute_url(), quote(came_from), retry)
return url
return None
CookieCrumbler.__call__ = __call__
CookieCrumbler._cleanupResponse = _cleanupResponse
CookieCrumbler.unauthorized = unauthorized
CookieCrumbler._unauthorized = _unauthorized
CookieCrumbler.getUnauthorizedURL = getUnauthorizedURL
###
CookieCrumbler.security = security
InitializeClass(CookieCrumbler)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment