Commit fba054cb 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 351c6b61
...@@ -30,6 +30,7 @@ from App.class_init import InitializeClass ...@@ -30,6 +30,7 @@ from App.class_init import InitializeClass
from Products.CMFCore.CookieCrumbler import CookieCrumbler from Products.CMFCore.CookieCrumbler import CookieCrumbler
from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled from Products.CMFCore.CookieCrumbler import CookieCrumblerDisabled
from urllib import quote, unquote from urllib import quote, unquote
from zExceptions import Redirect
from ZPublisher.HTTPRequest import HTTPRequest from ZPublisher.HTTPRequest import HTTPRequest
ATTEMPT_NONE = 0 # No attempt at authentication ATTEMPT_NONE = 0 # No attempt at authentication
...@@ -47,6 +48,10 @@ class PatchedCookieCrumbler(CookieCrumbler): ...@@ -47,6 +48,10 @@ class PatchedCookieCrumbler(CookieCrumbler):
security = ClassSecurityInfo() security = ClassSecurityInfo()
CookieCrumbler.auto_login_page = 'login_form'
CookieCrumbler.unauth_page = ''
CookieCrumbler.logout_page = 'logged_out'
def getLoginURL(self): def getLoginURL(self):
''' '''
Redirects to the login page. Redirects to the login page.
...@@ -194,5 +199,157 @@ def credentialsChanged(self, user, name, pw, request=None): ...@@ -194,5 +199,157 @@ def credentialsChanged(self, user, name, pw, request=None):
CookieCrumbler.credentialsChanged = credentialsChanged 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 CookieCrumbler.security = security
InitializeClass(CookieCrumbler) 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