Commit b9097101 authored by Jérome Perrin's avatar Jérome Perrin

xhtml_style/authentication_policy: merge "logged_in" implementation

This simplify code and also bring the functionnality of c484f8aa
(erp5_xhtml_style Base_cancel and logged_in: do not allow redirection outside
ERP5 site., 2016-02-12) for the cases where authentication_policy is
installed.

This also fixes a problem with translations of "Your password will expire at
 {date}", which was using different messages for every possible date.

Tests needed to be updated because we now redirect with properly URL encoded
parameters.
parent 40cfe242
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>logged_in</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>iso-8859-15</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<tal:block tal:condition="here/portal_skins/updateSkinCookie | nothing"
tal:define="dummy here/setupCurrentSkin;" />
<tal:block tal:define="response request/RESPONSE;
mtool here/portal_membership;
isAnon mtool/isAnonymousUser|nothing;">
<tal:block tal:condition="isAnon">
<tal:block tal:define="dummy python: response.expireCookie('__ac', path='/');
is_user_account_blocked python: request.get('is_user_account_blocked', False);
is_user_account_password_expired python: request.get('is_user_account_password_expired', False);">
<!-- Login and/or password is incorrect. -->
<tal:block tal:condition="python: not is_user_account_blocked and not is_user_account_password_expired">
<tal:block tal:define="url python: '%s/login_form?portal_status_message=%s' % (here.absolute_url(), here.Base_translateString('Login and/or password is incorrect.'));
url python: request.get('came_from') and '%s&amp;came_from=%s' % (url, request['came_from']) or url;
dummy python: response.redirect(url);"/>
</tal:block>
<!-- Login is blocked. -->
<tal:block tal:condition="is_user_account_blocked">
<tal:block tal:define="url python: '%s/login_form?portal_status_message=%s' % (here.absolute_url(), here.Base_translateString('Account is blocked.'));
url python: request.get('came_from') and '%s&amp;came_from=%s' % (url, request['came_from']) or url;
dummy python: response.redirect(url);"/>
</tal:block>
<!-- Password is expired permanently. -->
<tal:block tal:condition="is_user_account_password_expired">
<tal:block tal:define="message python: {False: 'Password is expired.',
True: 'Password is expired. You will soon receive an email with details about how you can recover it.'}.get(here.getPortalObject().portal_preferences.isPreferredSystemRecoverExpiredPassword());
url python: '%s/login_form?portal_status_message=%s' % (here.absolute_url(), here.Base_translateString(message));
url python: request.get('came_from') and '%s&amp;came_from=%s' % (url, request['came_from']) or url;
dummy python: response.redirect(url);"/>
</tal:block>
</tal:block>
</tal:block>
<tal:block tal:condition="not: isAnon"
tal:define="is_user_account_password_expired_expire_date python:request.get('is_user_account_password_expired_expire_date', 0);">
<!-- Password will expire soon just warn user ? -->
<tal:block tal:condition="is_user_account_password_expired_expire_date">
<tal:block tal:define="came_from python: request.get('came_from') or here.absolute_url();
dummy python: response.redirect('%s/ERP5Site_viewNewPersonCredentialUpdateDialog?portal_status_message=%s&amp;cancel_url=%s' %(came_from, here.Base_translateString('Your password will expire at %s. You are advised to change it as soon as possible.' %context.Base_FormatDate(is_user_account_password_expired_expire_date, hour_minute=1)), came_from));" />
</tal:block>
<tal:block tal:condition="not: is_user_account_password_expired_expire_date">
<tal:block tal:define="came_from python: request.get('came_from') or here.absolute_url();
dummy python: response.redirect(came_from);" />
</tal:block>
</tal:block>
</tal:block>
</tal:block>
\ No newline at end of file
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
from functools import partial from functools import partial
import unittest import unittest
import urllib import urllib
import urlparse
from StringIO import StringIO from StringIO import StringIO
import time import time
import httplib import httplib
...@@ -711,7 +712,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -711,7 +712,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
basic='test-05:used_ALREADY_1234', basic='test-05:used_ALREADY_1234',
) )
response = publish() response = publish()
self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Account is blocked.")) redirect_url = urlparse.urlparse(response.getHeader("Location"))
self.assertEqual(redirect_url.path, '{}/login_form'.format(portal.absolute_url_path()))
redirect_url_params = urlparse.parse_qsl(redirect_url.query)
self.assertEqual(redirect_url_params, [('portal_status_message', 'Account is blocked.')] )
# test expire password message, first unblock it # test expire password message, first unblock it
login.Login_unblockLogin() login.Login_unblockLogin()
...@@ -719,7 +723,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -719,7 +723,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic() self.tic()
self._clearCache() self._clearCache()
response = publish() response = publish()
self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Password is expired.")) redirect_url = urlparse.urlparse(response.getHeader("Location"))
self.assertEqual(redirect_url.path, '{}/login_form'.format(portal.absolute_url_path()))
redirect_url_params = urlparse.parse_qsl(redirect_url.query)
self.assertEqual(redirect_url_params, [('portal_status_message', 'Password is expired.')] )
self.assertTrue(login.isPasswordExpired()) self.assertTrue(login.isPasswordExpired())
# test we're redirected to update password due to soon expire # test we're redirected to update password due to soon expire
...@@ -728,9 +735,11 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -728,9 +735,11 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic() self.tic()
self._clearCache() self._clearCache()
response = publish() response = publish()
redirect_url = urlparse.urlparse(response.getHeader("Location"))
self.assertTrue('Your password will expire' in response.getHeader("Location")) self.assertEqual(redirect_url.path, '{}/ERP5Site_viewNewPersonCredentialUpdateDialog'.format(portal.absolute_url_path()))
self.assertTrue('You are advised to change it as soon as possible' in response.getHeader("Location")) redirect_url_params = urlparse.parse_qs(redirect_url.query)
self.assertIn('Your password will expire', redirect_url_params['portal_status_message'][0])
self.assertIn('You are advised to change it as soon as possible', redirect_url_params['portal_status_message'][0])
# test proper login # test proper login
preference.setPreferredPasswordLifetimeExpireWarningDuration(12) preference.setPreferredPasswordLifetimeExpireWarningDuration(12)
...@@ -742,6 +751,18 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -742,6 +751,18 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
) )
self.assertTrue('Welcome to ERP5' in response.getBody()) self.assertTrue('Welcome to ERP5' in response.getBody())
# test external redirection prevention
response = self.publish(
portal.absolute_url_path() + '/logged_in',
basic='test-05:used_ALREADY_1234',
stdin=StringIO(urllib.urlencode({'came_from': 'https://www.erp5.com'})),
request_method='POST',
)
redirect_url = urlparse.urlparse(response.getHeader("Location"))
self.assertEqual(redirect_url.path, portal.absolute_url_path())
redirect_url_params = urlparse.parse_qsl(redirect_url.query)
self.assertEqual(redirect_url_params, [('portal_status_message', 'Redirection to an external site prevented.')] )
def test_ExpireOldAuthenticationEventList(self): def test_ExpireOldAuthenticationEventList(self):
""" """
Check that expiring old Authentication Event list works. Check that expiring old Authentication Event list works.
......
portal = context.getPortalObject() portal = context.getPortalObject()
translateString = portal.Base_translateString
if portal.portal_skins.updateSkinCookie(): if portal.portal_skins.updateSkinCookie():
portal.setupCurrentSkin() portal.setupCurrentSkin()
url = REQUEST.get("came_from") url = REQUEST.get("came_from")
if portal.portal_membership.isAnonymousUser(): if portal.portal_membership.isAnonymousUser():
RESPONSE.expireCookie("__ac", path="/") RESPONSE.expireCookie("__ac", path="/")
keep_item_dict = { keep_item_dict = {
'portal_status_message': context.Base_translateString("Login and/or password is incorrect."), 'portal_status_message': translateString("Login and/or password is incorrect."),
} }
if url: if url:
keep_item_dict['came_from'] = url keep_item_dict['came_from'] = url
# handle authentication policy requests parameters
if REQUEST.get('is_user_account_blocked'):
keep_item_dict['portal_status_message'] = translateString('Account is blocked.')
if REQUEST.get('is_user_account_password_expired'):
keep_item_dict['portal_status_message'] = translateString('Password is expired.')
if portal.portal_preferences.isPreferredSystemRecoverExpiredPassword():
keep_item_dict['portal_status_message'] = translateString(
'Password is expired. You will soon receive an email with details about how you can recover it.')
context.Base_redirect( context.Base_redirect(
form_id='login_form', form_id='login_form',
keep_items=keep_item_dict, keep_items=keep_item_dict,
...@@ -16,11 +25,25 @@ if portal.portal_membership.isAnonymousUser(): ...@@ -16,11 +25,25 @@ if portal.portal_membership.isAnonymousUser():
return return
if not url: if not url:
url = context.absolute_url() url = context.absolute_url()
if REQUEST.get('is_user_account_password_expired_expire_date'):
return context.Base_redirect(
'ERP5Site_viewNewPersonCredentialUpdateDialog',
keep_items={
'cancel_url': url,
'portal_status_message': translateString(
'Your password will expire at {date}. '
'You are advised to change it as soon as possible.',
mapping={'date':
portal.Base_FormatDate(
REQUEST.get('is_user_account_password_expired_expire_date'),
hour_minute=1)})})
topmost_url_document = context.Base_getURLTopmostDocumentValue() topmost_url_document = context.Base_getURLTopmostDocumentValue()
if not topmost_url_document.isURLAncestorOf(url): if not topmost_url_document.isURLAncestorOf(url):
return topmost_url_document.Base_redirect( return topmost_url_document.Base_redirect(
keep_items={ keep_items={
'portal_status_message': context.Base_translateString('Redirection to an external site prevented.'), 'portal_status_message': translateString('Redirection to an external site prevented.'),
} }
) )
return RESPONSE.redirect(url) return RESPONSE.redirect(url)
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