Commit 73e08c0b authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki Committed by Cédric Le Ninivin

ERP5Security: Use a dedicated Login document to handle authentication.

parent c064d05e
<workflow_chain> <workflow_chain>
<chain> <chain>
<type>Person</type> <type>ERP5 Login</type>
<workflow>password_interaction_workflow</workflow> <workflow>password_interaction_workflow</workflow>
</chain> </chain>
</workflow_chain> </workflow_chain>
\ No newline at end of file
...@@ -160,7 +160,7 @@ return result_code_list\n ...@@ -160,7 +160,7 @@ return result_code_list\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_analyzePassword</string> </value> <value> <string>Login_analyzePassword</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -62,7 +62,7 @@ return brain.url\n ...@@ -62,7 +62,7 @@ return brain.url\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_getListboxUrl</string> </value> <value> <string>Login_getListboxUrl</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -121,7 +121,7 @@ return 0\n ...@@ -121,7 +121,7 @@ return 0\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_isLoginBlocked</string> </value> <value> <string>Login_isLoginBlocked</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<value> <string encoding="cdata"><![CDATA[ <value> <string encoding="cdata"><![CDATA[
"""\n """\n
Returns if user account is Person\'s password is expired.\n Returns if user account Login\'s password is expired.\n
Start password recovery process for expired password (if configured).\n Start password recovery process for expired password (if configured).\n
"""\n """\n
from Products.ERP5Type.Cache import CachingMethod\n from Products.ERP5Type.Cache import CachingMethod\n
...@@ -88,7 +88,7 @@ def _isPasswordExpired():\n ...@@ -88,7 +88,7 @@ def _isPasswordExpired():\n
return False, expire_date_warning\n return False, expire_date_warning\n
\n \n
_isPasswordExpired = CachingMethod(_isPasswordExpired,\n _isPasswordExpired = CachingMethod(_isPasswordExpired,\n
id=\'Person_isPasswordExpired_%s\' %context.getReference(),\n id=\'%s_%s\' % (script.getId(), context.getReference()),\n
cache_factory=\'erp5_content_short\')\n cache_factory=\'erp5_content_short\')\n
is_password_expired, expire_date = _isPasswordExpired()\n is_password_expired, expire_date = _isPasswordExpired()\n
\n \n
...@@ -114,7 +114,7 @@ return is_password_expired\n ...@@ -114,7 +114,7 @@ return is_password_expired\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_isPasswordExpired</string> </value> <value> <string>Login_isPasswordExpired</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -65,30 +65,16 @@ message_dict = { 0: \'Unknown error\',\n ...@@ -65,30 +65,16 @@ message_dict = { 0: \'Unknown error\',\n
-4: \'You have already used this password.\',\n -4: \'You have already used this password.\',\n
-5: \'You can not use any parts of your first and last name in password.\'}\n -5: \'You can not use any parts of your first and last name in password.\'}\n
\n \n
def doValidation(person, password):\n def doValidation(login, password):\n
# raise so Formulator shows proper message\n # raise so Formulator shows proper message\n
result_code_list = person.Person_analyzePassword(password)\n result_code_list = login.analyzePassword(password)\n
if result_code_list!=[]:\n if result_code_list!=[]:\n
translateString = context.Base_translateString\n translateString = context.Base_translateString\n
message = \' \'.join([translateString(message_dict[x]) for x in result_code_list])\n message = \' \'.join([translateString(message_dict[x]) for x in result_code_list])\n
raise ValidationError(\'external_validator_failed\', context, error_text=message)\n raise ValidationError(\'external_validator_failed\', context, error_text=message)\n
return 1\n return 1\n
\n \n
user_login = request.get(\'field_user_login\', None)\n return doValidation(context, password)\n
# find Person object (or authenticated member) and validate it on it (password recovered for an existing account)\n
person = context.ERP5Site_getAuthenticatedMemberPersonValue(user_login)\n
if person is not None:\n
return doValidation(person, password)\n
\n
# use a temp object (new account created)\n
first_name = request.get(\'field_your_first_name\', None) \n
last_name = request.get(\'field_your_last_name\', None) \n
kw = {\'title\': \'%s %s\' %(first_name, last_name),\n
\'first_name\': first_name,\n
\'last_name\': last_name}\n
person = newTempBase(portal, kw[\'title\'], **kw)\n
\n
return doValidation(person, password)\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
...@@ -105,7 +91,7 @@ return doValidation(person, password)\n ...@@ -105,7 +91,7 @@ return doValidation(person, password)\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Base_isPasswordValid</string> </value> <value> <string>Login_isPasswordValid</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -85,7 +85,7 @@ return authentication_event\n ...@@ -85,7 +85,7 @@ return authentication_event\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_notifyLoginFailure</string> </value> <value> <string>Login_notifyLoginFailure</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -107,7 +107,7 @@ credential_recovery.submit()\n ...@@ -107,7 +107,7 @@ credential_recovery.submit()\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_notifyPasswordExpire</string> </value> <value> <string>Login_notifyPasswordExpire</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -96,7 +96,7 @@ return\n ...@@ -96,7 +96,7 @@ return\n
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_unblockLogin</string> </value> <value> <string>Login_unblockLogin</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -77,20 +77,21 @@ kw = {\'portal_type\': \'Authentication Event\',\n ...@@ -77,20 +77,21 @@ kw = {\'portal_type\': \'Authentication Event\',\n
\'validation_state\' : \'confirmed\'}\n \'validation_state\' : \'confirmed\'}\n
failure_list = portal.portal_catalog(**kw)\n failure_list = portal.portal_catalog(**kw)\n
for failure in failure_list:\n for failure in failure_list:\n
person = failure.getDestinationValue()\n login = failure.getDestinationValue()\n
if person not in all_blocked_user_login_dict.keys():\n if login not in all_blocked_user_login_dict.keys():\n
all_blocked_user_login_dict[person] = []\n all_blocked_user_login_dict[login] = []\n
all_blocked_user_login_dict[person].append(failure)\n all_blocked_user_login_dict[login].append(failure)\n
\n \n
# leave only ones that are blocked:\n # leave only ones that are blocked:\n
for person, failure_list in all_blocked_user_login_dict.items():\n for login, failure_list in all_blocked_user_login_dict.items():\n
if len(failure_list) >= max_authentication_failures:\n if len(failure_list) >= max_authentication_failures:\n
title = login.getParentTitle()\n
blocked_user_login_list.append(newTempBase(portal, \n blocked_user_login_list.append(newTempBase(portal, \n
person.getTitle(), \n title, \n
**{\'title\': person.getTitle(),\n **{\'title\': title,\n
\'count\':len(failure_list),\n \'count\':len(failure_list),\n
\'reference\': person.getReference(),\n \'reference\': login.getReference(),\n
\'url\': person.absolute_url()}))\n \'url\': login.absolute_url()}))\n
return blocked_user_login_list\n return blocked_user_login_list\n
......
...@@ -174,15 +174,15 @@ ...@@ -174,15 +174,15 @@
<list> <list>
<tuple> <tuple>
<string>title</string> <string>title</string>
<string>Person_getListboxUrl</string> <string>Login_getListboxUrl</string>
</tuple> </tuple>
<tuple> <tuple>
<string>reference</string> <string>reference</string>
<string>Person_getListboxUrl</string> <string>Login_getListboxUrl</string>
</tuple> </tuple>
<tuple> <tuple>
<string>count</string> <string>count</string>
<string>Person_getListboxUrl</string> <string>Login_getListboxUrl</string>
</tuple> </tuple>
</list> </list>
</value> </value>
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<key> <string>after_script_name</string> </key> <key> <string>after_script_name</string> </key>
<value> <value>
<list> <list>
<string>Person_changePassword</string> <string>afterChangePassword</string>
</list> </list>
</value> </value>
</item> </item>
...@@ -72,10 +72,16 @@ ...@@ -72,10 +72,16 @@
<key> <string>portal_type_filter</string> </key> <key> <string>portal_type_filter</string> </key>
<value> <value>
<list> <list>
<string>Person</string> <string>ERP5 Login</string>
</list> </list>
</value> </value>
</item> </item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>script_name</string> </key> <key> <string>script_name</string> </key>
<value> <value>
......
...@@ -50,21 +50,20 @@ ...@@ -50,21 +50,20 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>from DateTime import DateTime\n <value> <string>login = state_change[\'object\']\n
portal = context.getPortalObject()\n portal = login.getPortalObject()\n
person = state_change[\'object\']\n
\n \n
# check preferences and save only if set\n # check preferences and save only if set\n
number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck()\n number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck()\n
\n \n
if number_of_last_password_to_check is not None and number_of_last_password_to_check:\n if number_of_last_password_to_check is not None and number_of_last_password_to_check:\n
# save password and modification date\n # save password and modification date\n
current_password = person.getPassword()\n current_password = login.getPassword()\n
if current_password is not None:\n if current_password is not None:\n
password_event = portal.system_event_module.newContent(portal_type = \'Password Event\',\n password_event = portal.system_event_module.newContent(portal_type=\'Password Event\',\n
source_value = person,\n source_value=login,\n
destination_value = person,\n destination_value=login,\n
password = current_password)\n password=current_password)\n
password_event.confirm()\n password_event.confirm()\n
# Person_isPasswordExpired cache the wrong result if document is not in catalog.\n # Person_isPasswordExpired cache the wrong result if document is not in catalog.\n
# As the document is created in the same transaction, it is possible to force reindexation\n # As the document is created in the same transaction, it is possible to force reindexation\n
...@@ -86,7 +85,7 @@ if number_of_last_password_to_check is not None and number_of_last_password_to_c ...@@ -86,7 +85,7 @@ if number_of_last_password_to_check is not None and number_of_last_password_to_c
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_changePassword</string> </value> <value> <string>afterChangePassword</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
Person | password_interaction_workflow ERP5 Login | password_interaction_workflow
\ No newline at end of file \ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Login_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -6,6 +6,53 @@ ...@@ -6,6 +6,53 @@
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>_count</string> </key> <key> <string>_count</string> </key>
<value> <value>
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
############################################################################## ##############################################################################
import erp5 import erp5
import re
from Products.ERP5.Extensions.CheckPortalTypes import changeObjectClass from Products.ERP5.Extensions.CheckPortalTypes import changeObjectClass
def migrateToEmbeddedFile(self, force=0): def migrateToEmbeddedFile(self, force=0):
...@@ -37,7 +38,50 @@ def migrateToEmbeddedFile(self, force=0): ...@@ -37,7 +38,50 @@ def migrateToEmbeddedFile(self, force=0):
if portal_type in ('File', 'Image') and self.getValidationState()=='embedded': if portal_type in ('File', 'Image') and self.getValidationState()=='embedded':
embedded_type = 'Embedded File' embedded_type = 'Embedded File'
container = self.getParentValue() container = self.getParentValue()
id = self.id
if force == 1: if force == 1:
changeObjectClass(container, id, getattr(erp5.portal_type, embedded_type)) changeObjectClass(container, self.id, getattr(erp5.portal_type, embedded_type))
return '%s: %s -> %s' % (self.getRelativeUrl(), portal_type, embedded_type), return '%s: %s -> %s' % (self.getRelativeUrl(), portal_type, embedded_type),
def migrateToLogin(self):
assert self.getPortalType() == 'Person'
if len(self.objectValues(portal_type=self.getPortalObject().getPortalLoginTypeList())):
# already migrated
return
reference = self.getReference()
if not reference:
# no login is required
return
login_list = []
if re.match(r'^fb_\d+$', reference):
login = self.newContent(
portal_type='Facebook Login',
reference=reference,
)
login_list.append(login)
elif re.match(r'^go_\d+$', reference):
login = self.newContent(
portal_type='Google Login',
reference=reference,
)
login_list.append(login)
elif re.match('^bid_[^@]+@[^@]+$', reference):
login = self.newContent(
portal_type='Persona Login',
reference=reference,
)
login_list.append(login)
if self.hasPassword():
login = self.newContent(
portal_type='ERP5 Login',
reference=reference,
)
login._setEncodedPassword(self.getPassword())
login_list.append(login)
if not login_list:
login = self.newContent(
portal_type='ERP5 Login',
reference=reference,
)
login_list.append(login)
for login in login_list:
login.validate()
...@@ -6,10 +6,22 @@ ...@@ -6,10 +6,22 @@
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>default_reference</string> </key>
<value> <string>BaseMigration</string> </value> <value> <string>BaseMigration</string> </value>
</item> </item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>extension.erp5.BaseMigration</string> </value> <value> <string>extension.erp5.BaseMigration</string> </value>
...@@ -24,6 +36,18 @@ ...@@ -24,6 +36,18 @@
<none/> <none/>
</value> </value>
</item> </item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item> <item>
<key> <string>version</string> </key> <key> <string>version</string> </key>
<value> <string>erp5</string> </value> <value> <string>erp5</string> </value>
...@@ -31,13 +55,28 @@ ...@@ -31,13 +55,28 @@
<item> <item>
<key> <string>workflow_history</string> </key> <key> <string>workflow_history</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="PersistentMapping" module="Persistence.mapping"/> <global name="PersistentMapping" module="Persistence.mapping"/>
</pickle> </pickle>
...@@ -50,7 +89,7 @@ ...@@ -50,7 +89,7 @@
<item> <item>
<key> <string>component_validation_workflow</string> </key> <key> <string>component_validation_workflow</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -59,7 +98,7 @@ ...@@ -59,7 +98,7 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="3" aka="AAAAAAAAAAM="> <record id="4" aka="AAAAAAAAAAQ=">
<pickle> <pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/> <global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle> </pickle>
......
def checkPersonLoginExistenceConsistency(self, fixit=False):
assert self.getPortalType() == 'Person'
reference = self.getReference()
if not reference:
# no login is required
return []
if not self.hasPassword():
# no login is required, but possibly another Login type object is required if implemented
return []
if len(self.objectValues(portal_type=self.getPortalObject().getPortalLoginTypeList())):
# already migrated
return []
if fixit:
login = self.newContent(
portal_type='ERP5 Login',
reference=reference,
)
login._setEncodedPassword(self.getPassword())
login.validate()
return ['%s has no Login type sub document.' % self.getRelativeUrl()]
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Extension Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>PersonLoginMigration</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.PersonLoginMigration</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Extension Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
...@@ -105,6 +105,7 @@ ...@@ -105,6 +105,7 @@
<item>Career</item> <item>Career</item>
<item>Chat Address</item> <item>Chat Address</item>
<item>Credit Card</item> <item>Credit Card</item>
<item>ERP5 Login</item>
<item>Email</item> <item>Email</item>
<item>Embedded File</item> <item>Embedded File</item>
<item>Fax</item> <item>Fax</item>
...@@ -123,4 +124,4 @@ ...@@ -123,4 +124,4 @@
<portal_type id="Solver Tool"> <portal_type id="Solver Tool">
<item>Solver Type</item> <item>Solver Type</item>
</portal_type> </portal_type>
</allowed_content_type_list> </allowed_content_type_list>
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
</portal_type> </portal_type>
<portal_type id="Person"> <portal_type id="Person">
<item>DefaultImage</item> <item>DefaultImage</item>
<item>PersonUpgradeConstraint</item>
</portal_type> </portal_type>
<portal_type id="Preference"> <portal_type id="Preference">
<item>TelephonePreference</item> <item>TelephonePreference</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>content_icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple>
<string>login</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5 Login</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Base Type</string> </value>
</item>
<item>
<key> <string>searchable_text_property_id</string> </key>
<value>
<tuple>
<string>reference</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -35,6 +35,10 @@ ...@@ -35,6 +35,10 @@
<type>Currency Exchange Line</type> <type>Currency Exchange Line</type>
<workflow>currency_exchange_line_interaction_workflow, edit_workflow, validation_workflow</workflow> <workflow>currency_exchange_line_interaction_workflow, edit_workflow, validation_workflow</workflow>
</chain> </chain>
<chain>
<type>ERP5 Login</type>
<workflow>edit_workflow, login_validation_workflow</workflow>
</chain>
<chain> <chain>
<type>Email</type> <type>Email</type>
<workflow>edit_workflow</workflow> <workflow>edit_workflow</workflow>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>LoginConstraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TALES Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>expression</string> </key>
<value> <string>not: here/Login_checkExistence</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>check_duplicate_constraint</string> </value>
</item>
<item>
<key> <string>message_expression_error</string> </key>
<value> <string>Error while checking the existence of the same User Login: ${error}</string> </value>
</item>
<item>
<key> <string>message_expression_false</string> </key>
<value> <string>The User Login is already used</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>TALES Constraint</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Existence Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>constraint_property</string> </key>
<value>
<tuple>
<string>reference</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>reference_existence_constraint</string> </value>
</item>
<item>
<key> <string>message_no_such_property</string> </key>
<value> <string>User Login must be defined</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Existence Constraint</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PersonUpgradeConstraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Script Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>constraint_type/post_upgrade</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Login Existence_constraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Script Constraint</string> </value>
</item>
<item>
<key> <string>script_id</string> </key>
<value> <string>Person_checkPersonLoginExistenceConsistency</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery, ComplexQuery\n
query = ComplexQuery(\n
SimpleQuery(portal_type=portal_type),\n
SimpleQuery(reference=reference),\n
)\n
login_list = [x for x in context.getPortalObject().portal_catalog(\n
select_list=(\'reference\',),\n
query=query)\n
if x.reference == reference]\n
if ignore_uid is not None:\n
login_list = [x for x in login_list if x.uid != ignore_uid]\n
return len(login_list) > 0\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>portal_type, reference, ignore_uid=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_checkLoginExistence</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -101,6 +101,7 @@ ...@@ -101,6 +101,7 @@
<string>my_view_mode_telephone_number</string> <string>my_view_mode_telephone_number</string>
<string>my_view_mode_email_coordinate_text</string> <string>my_view_mode_email_coordinate_text</string>
<string>my_view_mode_email_url_string</string> <string>my_view_mode_email_url_string</string>
<string>my_view_mode_portal_type</string>
</list> </list>
</value> </value>
</item> </item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_view_mode_portal_type</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Type</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>portal = context.getPortalObject()\n
result = []\n
sql_result = portal.ERP5Site_zGetDuplicateLoginReferenceList()\n
if len(sql_result):\n
from Products.CMFActivity.ActiveResult import ActiveResult\n
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery, ComplexQuery\n
query = ComplexQuery(\n
*[ComplexQuery(SimpleQuery(portal_type=i[\'portal_type\']),\n
SimpleQuery(reference=i[\'reference\'])) for i in sql_result],\n
operator=\'OR\')\n
active_result = ActiveResult()\n
for i in portal.portal_catalog(\n
select_list=(\'portal_type\', \'reference\',),\n
query=query):\n
result.append(\'%r, %r, %r\' % (i[\'portal_type\'], i[\'reference\'], i[\'path\']))\n
active_result.edit(summary=\'Logins having the same reference exist\',\n
severity=len(sql_result),\n
detail=\'\\n\'.join(result))\n
active_process = context.newActiveProcess()\n
active_process.postResult(active_result)\n
return active_process\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_checkDuplicateLoginReferenceLogin</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL" module="Products.ZSQLMethods.SQL"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_col</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>name</string> </key>
<value> <string>reference</string> </value>
</item>
<item>
<key> <string>null</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>t</string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>15</int> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>name</string> </key>
<value> <string>portal_type</string> </value>
</item>
<item>
<key> <string>null</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>t</string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>10</int> </value>
</item>
</dictionary>
</list>
</value>
</item>
<item>
<key> <string>arguments_src</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>connection_id</string> </key>
<value> <string>erp5_sql_connection</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_zGetDuplicateLoginReferenceList</string> </value>
</item>
<item>
<key> <string>src</string> </key>
<value> <string encoding="cdata"><![CDATA[
SELECT\n
reference, portal_type\n
FROM\n
catalog\n
WHERE\n
portal_type in (<dtml-in expr="portal_catalog.getPortalLoginTypeList()"><dtml-sqlvar sequence-item type="string"><dtml-if sequence-end><dtml-else>,</dtml-if></dtml-in>)\n
AND validation_state=\'validated\'\n
GROUP BY\n
reference, portal_type\n
HAVING\n
count(*) > 1\n
]]></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</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/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_portal_type</string>
<string>my_reference</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list>
<string>my_translated_validation_state_title</string>
</list>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ExternalLogin_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Login_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_portal_type</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_portal_type</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewBaseFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -10,14 +10,13 @@ ...@@ -10,14 +10,13 @@
<key> <string>delegated_list</string> </key> <key> <string>delegated_list</string> </key>
<value> <value>
<list> <list>
<string>default</string> <string>enabled</string>
<string>editable</string>
</list> </list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>your_reference</string> </value> <value> <string>my_reference</string> </value>
</item> </item>
<item> <item>
<key> <string>message_values</string> </key> <key> <string>message_values</string> </key>
...@@ -54,15 +53,11 @@ ...@@ -54,15 +53,11 @@
<value> <value>
<dictionary> <dictionary>
<item> <item>
<key> <string>default</string> </key> <key> <string>enabled</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value> </value>
</item> </item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -82,10 +77,6 @@ ...@@ -82,10 +77,6 @@
<key> <string>values</string> </key> <key> <string>values</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string>The username this person will use to log in the system.\n <value> <string>The username this person will use to log in the system.\n
...@@ -93,8 +84,8 @@ ...@@ -93,8 +84,8 @@
The system will check that there isn\'t another user with the same username.</string> </value> The system will check that there isn\'t another user with the same username.</string> </value>
</item> </item>
<item> <item>
<key> <string>editable</string> </key> <key> <string>enabled</string> </key>
<value> <int>0</int> </value> <value> <int>1</int> </value>
</item> </item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
...@@ -120,13 +111,19 @@ The system will check that there isn\'t another user with the same username.</st ...@@ -120,13 +111,19 @@ The system will check that there isn\'t another user with the same username.</st
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle> <pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/> <tuple>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>_text</string> </key> <key> <string>_text</string> </key>
<value> <string>python: here.portal_membership.getAuthenticatedMember().getUserName()</string> </value> <value> <string>python: request.AUTHENTICATED_USER.has_permission(\'Manage users\', here)</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_translated_workflow_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>return context.getPortalObject().Base_checkLoginExistence(\n
portal_type=context.getPortalType(),\n
reference=context.getReference(),\n
ignore_uid=context.getUid(),\n
)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Login_checkExistence</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</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/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_portal_type</string>
<string>my_reference</string>
<string>my_password</string>
<string>password_confirm</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list>
<string>my_translated_validation_state_title</string>
</list>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Login_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Login_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
<string>enabled</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_password</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>Password and confirmation doesn\'t match.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>enabled</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>The password of the user.\n
\n
Leaving this password empty will not modify existing password.</string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_password</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Person_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>User Password</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: request.AUTHENTICATED_USER.has_permission(\'Set own password\', here)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_portal_type</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_portal_type</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewBaseFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>enabled</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>enabled</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>The username this person will use to log in the system.\n
\n
The system will check that there isn\'t another user with the same username.</string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Person_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>User Login</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: request.AUTHENTICATED_USER.has_permission(\'Manage users\', here)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_translated_workflow_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
<string>default</string>
<string>enabled</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>password_confirm</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_password</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Person_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Password Confirmation</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>string:</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<tuple>
<tuple>
<string>Products.Formulator.TALESField</string>
<string>TALESMethod</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: request.AUTHENTICATED_USER.has_permission(\'Set own password\', here)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>checkPersonLoginExistenceConsistency</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>PersonLoginMigration</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_checkPersonLoginExistenceConsistency</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>error_list = []\n
if context.hasReference() and \\\n
not len(context.objectValues(portal_type=context.getPortalObject().getPortalLoginTypeList())):\n
error_list.append(\'%s has no Login type sub document.\' % context.getRelativeUrl())\n
if fixit:\n
context.Person_migrateToLogin()\n
return error_list\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>fixit=False, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_checkPreUpgradeLoginExistenceConsistency</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>def migrateLogin():\n
login = context.newContent(\n
portal_type=\'Login\',\n
reference=context.getReference(),\n
password=context.getPassword(),\n
)\n
login.validate()\n
return login\n
\n
login_list = context.objectValues(portal_type=\'Login\')\n
\n
if not login_list:\n
return [migrateLogin()]\n
elif not [x for x in login_list if x.getValidationState() == \'validated\']:\n
return [migrateLogin()] + login_list\n
else:\n
return login_list\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>**kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_getLoginValueList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>migrateToLogin</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>BaseMigration</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_migrateToLogin</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
<value> <value>
<list> <list>
<string>listbox</string> <string>listbox</string>
<string>login_listbox</string>
</list> </list>
</value> </value>
</item> </item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>columns</string>
<string>portal_types</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>login_listbox</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>portal_types</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>Base_viewSearchResultList</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>all_editable_columns</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>reference</string>
<string>Login</string>
</tuple>
<tuple>
<string>translated_validation_state_title</string>
<string>State</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_listbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewBaseFieldLibrary</string> </value>
</item>
<item>
<key> <string>height</string> </key>
<value> <int>5</int> </value>
</item>
<item>
<key> <string>list_cookie</string> </key>
<value> <string>CONTACT_LIST</string> </value>
</item>
<item>
<key> <string>portal_types</string> </key>
<value>
<list>
<tuple>
<string>ERP5 Login</string>
<string>ERP5 Login</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>reverse</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <int>40</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: [(x, x) for x in here.getPortalLoginTypeList()]</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="DCWorkflowDefinition" module="Products.DCWorkflow.DCWorkflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>creation_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Validation helps confirming the entered data by relevant agents before it is shared.</string> </value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>login_validation_workflow</string> </value>
</item>
<item>
<key> <string>initial_state</string> </key>
<value> <string>draft</string> </value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Access contents information</string>
<string>Modify portal content</string>
<string>View</string>
<string>Add portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>state_var</string> </key>
<value> <string>validation_state</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validation Workflow</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Scripts" module="Products.DCWorkflow.Scripts"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>scripts</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>object = state_change[\'object\']\n
object.Base_checkConsistency()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>checkConsistency</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>obj = state_change[\'object\']\n
obj.activate(tag=\'login:%s:%s\' % (obj.getPortalType(), obj.getReference())).reindexObject()\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>register_login</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>obj = state_change[\'object\']\n
for plugin in obj.getPortalObject().acl_users.objectValues():\n
if getattr(plugin, \'getLoginPortalType\', lambda: None)() == obj.getPortalType():\n
plugin.unregisterLogin(obj)\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>unregister_login</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="States" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>states</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>Documents in this state were deleted by the user as a result of clicking on the trash button or calling the delete action</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>deleted</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Deleted</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Persistence</string>
<string>PersistentMapping</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_container</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>Default state of the document</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>draft</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Draft</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>delete</string>
<string>delete_action</string>
<string>validate</string>
<string>validate_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Persistence</string>
<string>PersistentMapping</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_container</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Author</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>State of a document that has been invalidated in ERP5</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>invalidated</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Invalidated</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>delete</string>
<string>delete_action</string>
<string>validate</string>
<string>validate_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Persistence</string>
<string>PersistentMapping</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_container</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>State of a document that has been validated in ERP5</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>validated</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validated</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>invalidate</string>
<string>invalidate_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Persistence</string>
<string>PersistentMapping</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_container</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Add portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Transitions" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transitions</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Delete a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>delete</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>deleted</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Delete</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>delete</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Delete a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>delete_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Delete Action</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>This action invalidates a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>invalidate</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>invalidated</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Invalidate</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string>Invalidate</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string>%(content_url)s/Base_viewWorkflowActionDialog?workflow_action=invalidate_action</string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>invalidate</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>This action invalidates a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>invalidate_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Invalidate Action</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Validates a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>validated</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string>register_login</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validate</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string>Validate</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string>%(content_url)s/Base_viewWorkflowActionDialog?workflow_action=validate_action</string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Validates a document in ERP5</string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>validate_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string>checkConsistency</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Validate Action</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Variables" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variables</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>action</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.CMFCore.Expression</string>
<string>Expression</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>transition/getId|nothing</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>actor</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.CMFCore.Expression</string>
<string>Expression</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>comment</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.CMFCore.Expression</string>
<string>Expression</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python:state_change.kwargs.get(\'comment\',\'\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>error_message</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>history</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.CMFCore.Expression</string>
<string>Expression</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>state_change/getHistory</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>portal_type</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>time</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<tuple>
<tuple>
<string>Products.CMFCore.Expression</string>
<string>Expression</string>
</tuple>
<none/>
</tuple>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>state_change/getDateTime</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Worklists" module="Products.DCWorkflow.Worklists"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>worklists</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -24,6 +24,7 @@ Delivery Builder | view_predicate_group ...@@ -24,6 +24,7 @@ Delivery Builder | view_predicate_group
Delivery Builder | view_profile Delivery Builder | view_profile
Delivery Causality Assignment Movement Group | view Delivery Causality Assignment Movement Group | view
Delivery Tool | view Delivery Tool | view
ERP5 Login | view
Email | change_function Email | change_function
Email | view Email | view
Embedded File | download Embedded File | download
...@@ -99,4 +100,4 @@ Title Movement Group | view ...@@ -99,4 +100,4 @@ Title Movement Group | view
Variant Movement Group | view Variant Movement Group | view
Variation Property Movement Group | view Variation Property Movement Group | view
portal_actions | jump_query portal_actions | jump_query
portal_actions | post_query portal_actions | post_query
\ No newline at end of file
extension.erp5.BarcodeUtils
extension.erp5.BaseMigration extension.erp5.BaseMigration
extension.erp5.BarcodeUtils extension.erp5.PersonLoginMigration
\ No newline at end of file \ No newline at end of file
...@@ -74,6 +74,7 @@ Person | Bank Account ...@@ -74,6 +74,7 @@ Person | Bank Account
Person | Career Person | Career
Person | Chat Address Person | Chat Address
Person | Credit Card Person | Credit Card
Person | ERP5 Login
Person | Email Person | Email
Person | Embedded File Person | Embedded File
Person | Fax Person | Fax
...@@ -81,4 +82,4 @@ Person | Link ...@@ -81,4 +82,4 @@ Person | Link
Person | Telephone Person | Telephone
Query Module | Query Query Module | Query
Rounding Tool | Rounding Model Rounding Tool | Rounding Model
Solver Tool | Solver Type Solver Tool | Solver Type
\ No newline at end of file
...@@ -18,6 +18,7 @@ Day Movement Group ...@@ -18,6 +18,7 @@ Day Movement Group
Delivery Builder Delivery Builder
Delivery Causality Assignment Movement Group Delivery Causality Assignment Movement Group
Delivery Tool Delivery Tool
ERP5 Login
Email Email
Embedded File Embedded File
Embedded Folder Embedded Folder
...@@ -61,4 +62,4 @@ Split Movement Group ...@@ -61,4 +62,4 @@ Split Movement Group
Telephone Telephone
Title Movement Group Title Movement Group
Variant Movement Group Variant Movement Group
Variation Property Movement Group Variation Property Movement Group
\ No newline at end of file
...@@ -2,6 +2,7 @@ Notification Message | ItemAggregation ...@@ -2,6 +2,7 @@ Notification Message | ItemAggregation
Notification Message | Reference Notification Message | Reference
Organisation | DefaultImage Organisation | DefaultImage
Person | DefaultImage Person | DefaultImage
Person | PersonUpgradeConstraint
Preference | TelephonePreference Preference | TelephonePreference
Previous Causality Movement Group | PreviousCausalityMovementGroup Previous Causality Movement Group | PreviousCausalityMovementGroup
Query | TextDocument Query | TextDocument
\ No newline at end of file
...@@ -13,6 +13,8 @@ Currency Exchange Line | edit_workflow ...@@ -13,6 +13,8 @@ Currency Exchange Line | edit_workflow
Currency Exchange Line | validation_workflow Currency Exchange Line | validation_workflow
Currency | edit_workflow Currency | edit_workflow
Currency | validation_workflow Currency | validation_workflow
ERP5 Login | edit_workflow
ERP5 Login | login_validation_workflow
Email | edit_workflow Email | edit_workflow
Embedded File | document_conversion_interaction_workflow Embedded File | document_conversion_interaction_workflow
Embedded File | edit_workflow Embedded File | edit_workflow
......
LoginConstraint
PersonUpgradeConstraint
PreviousCausalityMovementGroup PreviousCausalityMovementGroup
\ No newline at end of file
...@@ -10,6 +10,7 @@ document_conversion_interaction_workflow ...@@ -10,6 +10,7 @@ document_conversion_interaction_workflow
document_security_interaction_workflow document_security_interaction_workflow
embedded_workflow embedded_workflow
local_permission_interaction_workflow local_permission_interaction_workflow
login_validation_workflow
movement_resource_interaction_workflow movement_resource_interaction_workflow
notification_message_workflow notification_message_workflow
person_interaction_workflow person_interaction_workflow
......
...@@ -225,7 +225,6 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin): ...@@ -225,7 +225,6 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin):
person.getFirstName()) person.getFirstName())
self.assertEqual(user_info["field_your_last_name"], self.assertEqual(user_info["field_your_last_name"],
person.getLastName()) person.getLastName())
self.assertNotEquals(person.getPassword(), None)
self.assertEqual(user_info["field_your_function"], self.assertEqual(user_info["field_your_function"],
person.getFunction()) person.getFunction())
self.assertEqual(user_info["field_your_default_email_text"], self.assertEqual(user_info["field_your_default_email_text"],
...@@ -236,6 +235,9 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin): ...@@ -236,6 +235,9 @@ class StandardConfigurationMixin(TestLiveConfiguratorWorkflowMixin):
assignment_list = person.contentValues(portal_type='Assignment') assignment_list = person.contentValues(portal_type='Assignment')
self.assertEqual(len(assignment_list), 1) self.assertEqual(len(assignment_list), 1)
self.assertEqual('my_group', assignment_list[0].getGroup()) self.assertEqual('my_group', assignment_list[0].getGroup())
login_list = person.contentValues(portal_type='ERP5 Login')
self.assertEqual(len(login_list), 1)
self.assertNotEquals(login_list[0].getPassword(), None)
def stepCheckSocialTitleCategory(self, sequence=None,sequence_list=None, **kw): def stepCheckSocialTitleCategory(self, sequence=None,sequence_list=None, **kw):
"""Check that the social title category is configured. """Check that the social title category is configured.
......
...@@ -102,8 +102,8 @@ ...@@ -102,8 +102,8 @@
<value> <value>
<list> <list>
<tuple> <tuple>
<string>Person</string> <string>ERP5 Login</string>
<string>Person</string> <string>ERP5 Login</string>
</tuple> </tuple>
</list> </list>
</value> </value>
......
...@@ -102,8 +102,8 @@ ...@@ -102,8 +102,8 @@
<value> <value>
<list> <list>
<tuple> <tuple>
<string>Person</string> <string>ERP5 Login</string>
<string>Person</string> <string>ERP5 Login</string>
</tuple> </tuple>
</list> </list>
</value> </value>
......
##############################################################################
#
# Copyright (c) 2015 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5.mixin.encrypted_password import EncryptedPasswordMixin
from Products.ERP5.mixin.login_account_provider import LoginAccountProviderMixin
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.XMLObject import XMLObject
class Login(XMLObject, LoginAccountProviderMixin, EncryptedPasswordMixin):
meta_type = 'ERP5 Login'
portal_type = 'Login'
add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.INode)
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Login
, PropertySheet.LoginConstraint
)
def _setReference(self, value):
"""
Set the user id. This method is defined explicitly, because:
- we want to apply a different permission
- we want to prevent duplicated user ids, but only when
PAS _AND_ ERP5UserManager are used
"""
activate_kw = {}
portal = self.getPortalObject()
if value:
# Encode reference to hex to prevent uppercase/lowercase conflict in
# activity table (when calling countMessageWithTag)
activate_kw['tag'] = tag = \
self.getPortalType() + '_setReference_' + value.encode('hex')
# Check that there no existing user
erp5_users = portal.acl_users.erp5_users
login = erp5_users.getLoginObject(value, self.getPortalType())
if login is not None and login != self and \
login != self.getParentValue():
raise RuntimeError, 'user id %s already exist' % (value,)
# Check that there is no reindexation related to reference indexation
if portal.portal_activities.countMessageWithTag(tag):
raise RuntimeError, 'user id %s already exist' % (value,)
# Prevent concurrent transaction to set the same reference on 2
# different persons
self.getParentValue().getParentValue().serialize()
# Prevent to set the same reference on 2 different persons during the
# same transaction
transactional_variable = getTransactionalVariable()
if tag in transactional_variable:
raise RuntimeError, 'user id %s already exist' % (value,)
else:
transactional_variable[tag] = None
self._baseSetReference(value)
self.reindexObject(activate_kw=activate_kw)
# invalid the cache for ERP5Security
portal_caches = portal.portal_caches
portal_caches.clearCache(cache_factory_list=('erp5_content_short', ))
...@@ -1516,6 +1516,14 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin): ...@@ -1516,6 +1516,14 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
return self._getPortalGroupedTypeList('entity') or\ return self._getPortalGroupedTypeList('entity') or\
self._getPortalConfiguration('portal_entity_type_list') self._getPortalConfiguration('portal_entity_type_list')
security.declareProtected(Permissions.AccessContentsInformation,
'getPortalLoginTypeList')
def getPortalLoginTypeList(self):
"""
Returns Login types.
"""
return self._getPortalGroupedTypeList('login')
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getDefaultModuleId') 'getDefaultModuleId')
def getDefaultModuleId(self, portal_type, default=MARKER, only_visible=False): def getDefaultModuleId(self, portal_type, default=MARKER, only_visible=False):
......
...@@ -53,18 +53,12 @@ def getSecurityCategoryFromAssignment(self, base_category_list, user_name, objec ...@@ -53,18 +53,12 @@ def getSecurityCategoryFromAssignment(self, base_category_list, user_name, objec
category_list = [] category_list = []
person_object_list = self.portal_catalog.unrestrictedSearchResults( person_object = self.getPortalObject().acl_users.erp5_users.getPersonByReference(user_name)
query=SimpleQuery(reference=user_name), portal_type='Person') if person_object is None:
# if a person_object was not found in the module, we do nothing more
if len(person_object_list) != 1: # this happens for example when a manager with no associated person object
if len(person_object_list) > 1: # creates a person_object for a new user
raise ConsistencyError, "Error: There is more than one Person with reference '%s'" % user_name return []
else:
# if a person_object was not found in the module, we do nothing more
# this happens for example when a manager with no associated person object
# creates a person_object for a new user
return []
person_object = person_object_list[0].getObject()
# We look for every valid assignments of this user # We look for every valid assignments of this user
for assignment in person_object.contentValues(filter={'portal_type': 'Assignment'}): for assignment in person_object.contentValues(filter={'portal_type': 'Assignment'}):
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<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_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<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>_body</string> </key>
<value> <string>person = context.ERP5Site_getAuthenticatedMemberPersonValue()\n
if person is None:\n
return []\n
else:\n
return [(x.getReference(), x.getRelativeUrl()) for x in \\\n
person.objectValues(portal_type=\'ERP5 Login\') \\\n
if x.getValidationState() == \'validated\']\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getValidatedLoginItemList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -53,28 +53,26 @@ ...@@ -53,28 +53,26 @@
<value> <string>from AccessControl import getSecurityManager\n <value> <string>from AccessControl import getSecurityManager\n
from Products.ERP5Type.Message import translateString\n from Products.ERP5Type.Message import translateString\n
\n \n
request = context.REQUEST\n portal = context.getPortalObject()\n
new_password = request.get("new_password")\n
former_password = request.get("current_password")\n
\n
user = getSecurityManager().getUser()\n user = getSecurityManager().getUser()\n
persons = context.acl_users.erp5_users.getUserByLogin(user)\n person = context.acl_users.erp5_users.getPersonByReference(user.getId())\n
person = persons[0]\n login = portal.restrictedTraverse(login)\n
\n if login.getParentValue() != person:\n
if not person.checkPassword(former_password):\n msg = translateString(\'You can change your own password only.\')\n
return context.Base_redirect(dialog_id, keep_items=dict(portal_status_message=msg))\n
elif not login.checkPassword(current_password):\n
msg = translateString("Current password is wrong.")\n msg = translateString("Current password is wrong.")\n
else:\n return context.Base_redirect(dialog_id, keep_items=dict(portal_status_message=msg))\n
msg = translateString("Password changed.")\n
person.setPassword(new_password)\n
\n \n
login.setPassword(new_password)\n
# clear erp5_content_short cache (see _authenticateCredentials in Products.ERP5Security.ERP5UserManager)\n # clear erp5_content_short cache (see _authenticateCredentials in Products.ERP5Security.ERP5UserManager)\n
context.portal_caches.clearCache((\'erp5_content_short\',))\n context.portal_caches.clearCache((\'erp5_content_short\',))\n
return context.logout()\n return portal.Base_redirect(\'logout\')\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>form_id=\'view\', **kw</string> </value> <value> <string>new_password, current_password, login, dialog_id=\'view\', **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>_proxy_roles</string> </key> <key> <string>_proxy_roles</string> </key>
......
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<key> <string>left</string> </key> <key> <string>left</string> </key>
<value> <value>
<list> <list>
<string>your_reference</string> <string>your_login</string>
<string>your_current_password</string> <string>your_current_password</string>
<string>your_new_password</string> <string>your_new_password</string>
<string>password_confirm</string> <string>password_confirm</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>items</string>
<string>required</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_login</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_list_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>User Login</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/Base_getValidatedLoginItemList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -66,10 +66,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -66,10 +66,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
reference = 'test') reference = 'test')
if portal.portal_catalog.getResultValue(**kw) is None: if portal.portal_catalog.getResultValue(**kw) is None:
# add a loggable Person # add a loggable Person
person = portal.person_module.newContent(password = 'test', person = self.createUser(
first_name = 'First', kw['reference'],
last_name = 'Last', password='test',
**kw) person_kw={'first_name': 'First',
'last_name': 'Last'},
)
person.validate() person.validate()
assignment = person.newContent(portal_type = 'Assignment') assignment = person.newContent(portal_type = 'Assignment')
assignment.open() assignment.open()
...@@ -91,16 +93,33 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -91,16 +93,33 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
'erp5_content_short', # for authentication cache 'erp5_content_short', # for authentication cache
)) ))
def _getPasswordEventList(self, person): def _getPasswordEventList(self, login):
return [x.getObject() for x in self.portal.portal_catalog( return [x.getObject() for x in self.portal.portal_catalog(
portal_type = 'Password Event', portal_type = 'Password Event',
default_destination_uid = person.getUid(), default_destination_uid = login.getUid(),
sort_on = (('creation_date', 'DESC',),))] sort_on = (('creation_date', 'DESC',),))]
def _cleanUpPerson(self, person): def _cleanUpLogin(self, login):
self.portal.system_event_module.manage_delObjects([x.getId() for x in self._getPasswordEventList(person)]) self.portal.system_event_module.manage_delObjects([x.getId() for x in self._getPasswordEventList(login)])
def createUser(self, reference, password=None, person_kw=None):
"""
Modified version from ERP5TypeTestCase, that does set reference as
password when password is None.
"""
if person_kw is None:
person_kw = {}
person = self.portal.person_module.newContent(portal_type='Person',
reference=reference,
**person_kw)
login = person.newContent(portal_type='ERP5 Login',
reference=reference,
password=password)
login.validate()
return person
def test_01_BlockLogin(self): def test_01_BlockLogin(self):
""" """
Test that a recataloging works for Web Site documents Test that a recataloging works for Web Site documents
...@@ -110,73 +129,74 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -110,73 +129,74 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test') reference = 'test')
login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
# login should be allowed # login should be allowed
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# file some failures so we should detect and block account # file some failures so we should detect and block account
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
self.tic() self.tic()
# should be blocked # should be blocked
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
# set check back interval to actualy disable blocking # set check back interval to actualy disable blocking
preference.setPreferredAuthenticationFailureCheckDuration(0) preference.setPreferredAuthenticationFailureCheckDuration(0)
self._clearCache() self._clearCache()
self.tic() self.tic()
time.sleep(1) # we need to give a moment time.sleep(1) # we need to give a moment
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# .. and revert it back # .. and revert it back
preference.setPreferredAuthenticationFailureCheckDuration(600) preference.setPreferredAuthenticationFailureCheckDuration(600)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
# increase failures attempts # increase failures attempts
preference.setPreferredMaxAuthenticationFailure(4) preference.setPreferredMaxAuthenticationFailure(4)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# .. and revert it back # .. and revert it back
preference.setPreferredMaxAuthenticationFailure(3) preference.setPreferredMaxAuthenticationFailure(3)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
# set short block interval so we can test it as well # set short block interval so we can test it as well
preference.setPreferredAuthenticationFailureBlockDuration(3) preference.setPreferredAuthenticationFailureBlockDuration(3)
self._clearCache() self._clearCache()
self.tic() self.tic()
time.sleep(4) time.sleep(4)
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# test multiple concurrent transactions without waiting for activities to be over # test multiple concurrent transactions without waiting for activities to be over
preference.setPreferredAuthenticationFailureCheckDuration(600) preference.setPreferredAuthenticationFailureCheckDuration(600)
preference.setPreferredAuthenticationFailureBlockDuration(600) preference.setPreferredAuthenticationFailureBlockDuration(600)
preference.setPreferredMaxAuthenticationFailure(3) preference.setPreferredMaxAuthenticationFailure(3)
person.Person_unblockLogin() login.Login_unblockLogin()
self._clearCache() self._clearCache()
self.tic() self.tic()
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
self.commit() self.commit()
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
self.tic() self.tic()
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
# test unblock account # test unblock account
person.Person_unblockLogin() login.Login_unblockLogin()
self.tic() self.tic()
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
def test_02_PasswordHistory(self): def test_02_PasswordHistory(self):
...@@ -186,61 +206,61 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -186,61 +206,61 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
portal = self.getPortal() portal = self.getPortal()
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.person_module.newContent(portal_type = 'Person', person = self.createUser('test-02')
reference = 'test-02') login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
self.tic() self.tic()
# Check that last (X where X is set in preferences) passwords are saved. # Check that last (X where X is set in preferences) passwords are saved.
self.assertEqual([], self._getPasswordEventList(person)) self.assertEqual([], self._getPasswordEventList(login))
preference.setPreferredNumberOfLastPasswordToCheck(10) preference.setPreferredNumberOfLastPasswordToCheck(10)
self.tic() self.tic()
self._clearCache() self._clearCache()
person.setPassword('12345678') login.setPassword('12345678')
self.tic() self.tic()
# password change date should be saved as well hashed old password value # password change date should be saved as well hashed old password value
old_password = person.getPassword() old_password = login.getPassword()
self.assertSameSet([old_password], [x.getPassword() for x in self._getPasswordEventList(person)]) self.assertSameSet([old_password], [x.getPassword() for x in self._getPasswordEventList(login)])
# .. test one more time to check history of password is saved in a list # .. test one more time to check history of password is saved in a list
person.setPassword('123456789') login.setPassword('123456789')
self.tic() self.tic()
old_password1 = person.getPassword() old_password1 = login.getPassword()
# password change date should be saved as well hashed old password value # password change date should be saved as well hashed old password value
self.assertSameSet([old_password1, old_password], [x.getPassword() for x in self._getPasswordEventList(person)]) self.assertSameSet([old_password1, old_password], [x.getPassword() for x in self._getPasswordEventList(login)])
# other methods (_setPassword)... # other methods (_setPassword)...
person._setPassword('123456789-1') login._setPassword('123456789-1')
self.tic() self.tic()
old_password2 = person.getPassword() old_password2 = login.getPassword()
self.assertSameSet([old_password2, old_password1, old_password], \ self.assertSameSet([old_password2, old_password1, old_password], \
[x.getPassword() for x in self._getPasswordEventList(person)]) [x.getPassword() for x in self._getPasswordEventList(login)])
# other methods (_forceSetPassword)... # other methods (_forceSetPassword)...
person._forceSetPassword('123456789-2') login._forceSetPassword('123456789-2')
self.tic() self.tic()
old_password3 = person.getPassword() old_password3 = login.getPassword()
self.assertSameSet([old_password3, old_password2, old_password1, old_password], \ self.assertSameSet([old_password3, old_password2, old_password1, old_password], \
[x.getPassword() for x in self._getPasswordEventList(person)]) [x.getPassword() for x in self._getPasswordEventList(login)])
# other methods (setEncodedPassword)... # other methods (setEncodedPassword)...
person.setEncodedPassword('123456789-3') login.setEncodedPassword('123456789-3')
self.tic() self.tic()
old_password4 = person.getPassword() old_password4 = login.getPassword()
self.assertSameSet([old_password4, old_password3, old_password2, old_password1, old_password], \ self.assertSameSet([old_password4, old_password3, old_password2, old_password1, old_password], \
[x.getPassword() for x in self._getPasswordEventList(person)]) [x.getPassword() for x in self._getPasswordEventList(login)])
# other methods (edit)... # other methods (edit)...
person.edit(password = '123456789-4') login.edit(password = '123456789-4')
self.tic() self.tic()
old_password5 = person.getPassword() old_password5 = login.getPassword()
self.assertSameSet([old_password5, old_password4, old_password3, old_password2, old_password1, old_password], \ self.assertSameSet([old_password5, old_password4, old_password3, old_password2, old_password1, old_password], \
[x.getPassword() for x in self._getPasswordEventList(person)]) [x.getPassword() for x in self._getPasswordEventList(login)])
def test_03_PasswordValidity(self): def test_03_PasswordValidity(self):
...@@ -258,105 +278,107 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -258,105 +278,107 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.person_module.newContent(portal_type = 'Person', person = self.createUser(
reference = 'test-03', 'test-03',
password = 'test', password='test',
first_name = 'First', person_kw={'first_name': 'First',
last_name = 'Last') 'last_name': 'Last'},
)
login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
self.tic() self.tic()
# by default an empty password if nothing set in preferences is OK # by default an empty password if nothing set in preferences is OK
self.assertTrue(person.isPasswordValid('')) self.assertTrue(login.isPasswordValid(''))
# Not long enough passwords used # Not long enough passwords used
self._cleanUpPerson(person) self._cleanUpLogin(login)
preference.setPreferredMinPasswordLength(8) preference.setPreferredMinPasswordLength(8)
preference.setPreferredNumberOfLastPasswordToCheck(0) preference.setPreferredNumberOfLastPasswordToCheck(0)
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertEqual([-1], person.analyzePassword('')) self.assertEqual([-1], login.analyzePassword(''))
self.assertEqual([-1], person.analyzePassword('1234567')) self.assertEqual([-1], login.analyzePassword('1234567'))
self.assertTrue(person.isPasswordValid('12345678')) self.assertTrue(login.isPasswordValid('12345678'))
# not changed in last x days # not changed in last x days
self._cleanUpPerson(person) self._cleanUpLogin(login)
preference.setPreferredMinPasswordLifetimeDuration(24) preference.setPreferredMinPasswordLifetimeDuration(24)
preference.setPreferredNumberOfLastPasswordToCheck(3) preference.setPreferredNumberOfLastPasswordToCheck(3)
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertTrue(person.isPasswordValid('12345678')) self.assertTrue(login.isPasswordValid('12345678'))
person.setPassword('12345678') login.setPassword('12345678')
self.tic() self.tic()
# if we try to change now we should fail with any password # if we try to change now we should fail with any password
self.assertSameSet([-3], person.analyzePassword('87654321')) self.assertSameSet([-3], login.analyzePassword('87654321'))
self.assertSameSet([-1, -3], person.analyzePassword('short')) # multiple failures self.assertSameSet([-1, -3], login.analyzePassword('short')) # multiple failures
self.assertFalse(person.isPasswordValid('short')) # multiple failures self.assertFalse(login.isPasswordValid('short')) # multiple failures
self.assertRaises(ValueError, person.setPassword, '87654321') self.assertRaises(ValueError, login.setPassword, '87654321')
preference.setPreferredMinPasswordLifetimeDuration(0) # remove restriction preference.setPreferredMinPasswordLifetimeDuration(0) # remove restriction
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertTrue(person.isPasswordValid('87654321')) # it's OK to change self.assertTrue(login.isPasswordValid('87654321')) # it's OK to change
# password not used in previous X passwords # password not used in previous X passwords
preference.setPreferredMinPasswordLength(None) # disable for now preference.setPreferredMinPasswordLength(None) # disable for now
self._cleanUpPerson(person) self._cleanUpLogin(login)
self._clearCache() self._clearCache()
self.tic() self.tic()
person.setPassword('12345678-new') login.setPassword('12345678-new')
self.tic() self.tic()
self.assertSameSet([-4], person.analyzePassword('12345678-new')) # if we try to change now we should fail with this EXACT password self.assertSameSet([-4], login.analyzePassword('12345678-new')) # if we try to change now we should fail with this EXACT password
self.assertRaises(ValueError, person.setPassword, '12345678-new') self.assertRaises(ValueError, login.setPassword, '12345678-new')
self.assertTrue(person.isPasswordValid('12345678_')) # it's OK with another one not used yet self.assertTrue(login.isPasswordValid('12345678_')) # it's OK with another one not used yet
for password in ['a','b','c','d', 'e', 'f']: for password in ['a','b','c','d', 'e', 'f']:
# this sleep is not so beautiful, but mysql datetime columns has a # this sleep is not so beautiful, but mysql datetime columns has a
# precision of one second only, and we use creation_date to order # precision of one second only, and we use creation_date to order
# "Password Event" objects. So without this sleep, the test is # "Password Event" objects. So without this sleep, the test is
# failing randomly. # failing randomly.
time.sleep(1) time.sleep(1)
person.setPassword(password) login.setPassword(password)
self.tic() self.tic()
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isPasswordValid('12345678-new')) self.assertTrue(login.isPasswordValid('12345678-new'))
self.assertTrue(person.isPasswordValid('a')) self.assertTrue(login.isPasswordValid('a'))
self.assertTrue(person.isPasswordValid('b')) self.assertTrue(login.isPasswordValid('b'))
self.assertTrue(person.isPasswordValid('c')) self.assertTrue(login.isPasswordValid('c'))
# only last 3 (including current one are invalid) # only last 3 (including current one are invalid)
self.assertSameSet([-4], person.analyzePassword('d')) self.assertSameSet([-4], login.analyzePassword('d'))
self.assertSameSet([-4], person.analyzePassword('e')) self.assertSameSet([-4], login.analyzePassword('e'))
self.assertSameSet([-4], person.analyzePassword('f')) self.assertSameSet([-4], login.analyzePassword('f'))
# if we remove restricted then all password are usable # if we remove restricted then all password are usable
preference.setPreferredNumberOfLastPasswordToCheck(None) preference.setPreferredNumberOfLastPasswordToCheck(None)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isPasswordValid('d')) self.assertTrue(login.isPasswordValid('d'))
self.assertTrue(person.isPasswordValid('e')) self.assertTrue(login.isPasswordValid('e'))
self.assertTrue(person.isPasswordValid('f')) self.assertTrue(login.isPasswordValid('f'))
# if we set only last password to check # if we set only last password to check
preference.setPreferredNumberOfLastPasswordToCheck(1) preference.setPreferredNumberOfLastPasswordToCheck(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isPasswordValid('c')) self.assertTrue(login.isPasswordValid('c'))
self.assertTrue(person.isPasswordValid('d')) self.assertTrue(login.isPasswordValid('d'))
self.assertTrue(person.isPasswordValid('e')) self.assertTrue(login.isPasswordValid('e'))
self.assertSameSet([-4], person.analyzePassword('f')) self.assertSameSet([-4], login.analyzePassword('f'))
preference.setPreferredRegularExpressionGroupList(regular_expression_list) preference.setPreferredRegularExpressionGroupList(regular_expression_list)
preference.setPreferredMinPasswordLength(7) preference.setPreferredMinPasswordLength(7)
preference.setPreferredNumberOfLastPasswordToCheck(None) preference.setPreferredNumberOfLastPasswordToCheck(None)
self._cleanUpPerson(person) self._cleanUpLogin(login)
self._clearCache() self._clearCache()
self.tic() self.tic()
...@@ -370,47 +392,47 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -370,47 +392,47 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
self.tic() self.tic()
for password in four_group_password_list: for password in four_group_password_list:
self.assertTrue(person.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in three_group_password_list+two_group_password_list + one_group_password_list: for password in three_group_password_list+two_group_password_list + one_group_password_list:
self.assertSameSet([-2], person.analyzePassword(password)) self.assertSameSet([-2], login.analyzePassword(password))
# min 3 out of all groups # min 3 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(3) preference.setPreferredMinRegularExpressionGroupNumber(3)
self._clearCache() self._clearCache()
self._cleanUpPerson(person) self._cleanUpLogin(login)
self.tic() self.tic()
for password in four_group_password_list + three_group_password_list: for password in four_group_password_list + three_group_password_list:
self.assertTrue(person.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in two_group_password_list + one_group_password_list: for password in two_group_password_list + one_group_password_list:
self.assertSameSet([-2], person.analyzePassword(password)) self.assertSameSet([-2], login.analyzePassword(password))
# min 2 out of all groups # min 2 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(2) preference.setPreferredMinRegularExpressionGroupNumber(2)
self._clearCache() self._clearCache()
self.tic() self.tic()
for password in four_group_password_list + three_group_password_list + two_group_password_list: for password in four_group_password_list + three_group_password_list + two_group_password_list:
self.assertTrue(person.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in one_group_password_list: for password in one_group_password_list:
self.assertSameSet([-2], person.analyzePassword(password)) self.assertSameSet([-2], login.analyzePassword(password))
# min 1 out of all groups # min 1 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(1) preference.setPreferredMinRegularExpressionGroupNumber(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
for password in four_group_password_list + three_group_password_list + two_group_password_list+one_group_password_list: for password in four_group_password_list + three_group_password_list + two_group_password_list+one_group_password_list:
self.assertTrue(person.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
# not contain the full name of the user # not contain the full name of the user
preference.setPrefferedForceUsernameCheckInPassword(1) preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([-5], person.analyzePassword('abAB#12_%s' %person.getFirstName())) self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getFirstName()))
self.assertSameSet([-5], person.analyzePassword('abAB#12_%s' %person.getLastName())) self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getLastName()))
preference.setPrefferedForceUsernameCheckInPassword(0) preference.setPrefferedForceUsernameCheckInPassword(0)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertTrue(person.isPasswordValid('abAB#12_%s' %person.getFirstName())) self.assertTrue(login.isPasswordValid('abAB#12_%s' %person.getFirstName()))
self.assertTrue(person.isPasswordValid('abAB#12_%s' %person.getLastName())) self.assertTrue(login.isPasswordValid('abAB#12_%s' %person.getLastName()))
# check on temp objects just passworrd length( i.e. simulating a new user account creation) # check on temp objects just passworrd length( i.e. simulating a new user account creation)
first_name = 'John' first_name = 'John'
...@@ -425,8 +447,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -425,8 +447,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
self.tic() self.tic()
# in this case which is basically used in new account creation only length of password matters # in this case which is basically used in new account creation only length of password matters
self.assertSameSet([-1], temp_person.Person_analyzePassword('onlyNine1')) self.assertSameSet([-1], temp_person.Login_analyzePassword('onlyNine1'))
self.assertSameSet([], temp_person.Person_analyzePassword('longEnough1')) self.assertSameSet([], temp_person.Login_analyzePassword('longEnough1'))
# make sure re check works on temp as well ( i.e. min 3 out of all groups) # make sure re check works on temp as well ( i.e. min 3 out of all groups)
preference.setPreferredRegularExpressionGroupList(regular_expression_list) preference.setPreferredRegularExpressionGroupList(regular_expression_list)
...@@ -435,22 +457,22 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -435,22 +457,22 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
self.tic() self.tic()
for password in four_group_password_list + three_group_password_list: for password in four_group_password_list + three_group_password_list:
self.assertSameSet([], temp_person.Person_analyzePassword(password)) self.assertSameSet([], temp_person.Login_analyzePassword(password))
for password in two_group_password_list + one_group_password_list: for password in two_group_password_list + one_group_password_list:
self.assertSameSet([-2], temp_person.Person_analyzePassword(password)) self.assertSameSet([-2], temp_person.Login_analyzePassword(password))
# make sure peron's check on username works on temp as well (i.e. not contain the full name of the user) # make sure peron's check on username works on temp as well (i.e. not contain the full name of the user)
preference.setPrefferedForceUsernameCheckInPassword(1) preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([-5], temp_person.Person_analyzePassword('abAB#12_%s' %first_name)) self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %first_name))
self.assertSameSet([-5], temp_person.Person_analyzePassword('abAB#12_%s' %last_name)) self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %last_name))
preference.setPrefferedForceUsernameCheckInPassword(0) preference.setPrefferedForceUsernameCheckInPassword(0)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([], temp_person.Person_analyzePassword('abAB#12_%s' %first_name)) self.assertSameSet([], temp_person.Login_analyzePassword('abAB#12_%s' %first_name))
self.assertSameSet([], temp_person.Person_analyzePassword('abAB#12_%s' %last_name)) self.assertSameSet([], temp_person.Login_analyzePassword('abAB#12_%s' %last_name))
# check Base_isPasswordValid is able to work in Anonymous User fashion # check Base_isPasswordValid is able to work in Anonymous User fashion
# but with already create Person object (i.e. recover password case) # but with already create Person object (i.e. recover password case)
...@@ -461,20 +483,20 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -461,20 +483,20 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
self.tic() self.tic()
person.setPassword('used_ALREADY_1234') login.setPassword('used_ALREADY_1234')
self._clearCache() self._clearCache()
self.tic() self.tic()
# emulate Anonymous User # emulate Anonymous User
self.logout() self.logout()
request.set('field_user_login', person.getReference()) request.set('field_user_login', login.getReference())
self.assertRaises(ValidationError, portal.Base_isPasswordValid, 'abAB#12_%s' %person.getFirstName(), request) # contains name self.assertRaises(ValidationError, login.Login_isPasswordValid, 'abAB#12_%s' %person.getFirstName(), request) # contains name
self.assertRaises(ValidationError, portal.Base_isPasswordValid, 'abAB#12_%s' %person.getLastName(), request) # contains name self.assertRaises(ValidationError, login.Login_isPasswordValid, 'abAB#12_%s' %person.getLastName(), request) # contains name
self.assertRaises(ValidationError, portal.Base_isPasswordValid, 'abAB#1', request) # too short self.assertRaises(ValidationError, login.Login_isPasswordValid, 'abAB#1', request) # too short
self.assertRaises(ValidationError, portal.Base_isPasswordValid, 'abABCDEFG', request) # too few groups self.assertRaises(ValidationError, login.Login_isPasswordValid, 'abABCDEFG', request) # too few groups
self.assertRaises(ValidationError, portal.Base_isPasswordValid, 'used_ALREADY_1234', request) # already used self.assertRaises(ValidationError, login.Login_isPasswordValid, 'used_ALREADY_1234', request) # already used
self.assertEqual(1, portal.Base_isPasswordValid('abAB#12_', request)) self.assertEqual(1, login.Login_isPasswordValid('abAB#12_', request))
self.assertEqual(1, portal.Base_isPasswordValid('not_used_ALREADY_1234', request)) self.assertEqual(1, login.Login_isPasswordValid('not_used_ALREADY_1234', request))
def test_04_PasswordExpire(self): def test_04_PasswordExpire(self):
""" """
...@@ -485,16 +507,16 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -485,16 +507,16 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.person_module.newContent(portal_type = 'Person', person = self.createUser('test-04',
reference = 'test-04', password='used_ALREADY_1234')
password = 'used_ALREADY_1234') login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
preference.setPreferredMaxPasswordLifetimeDuration(24) preference.setPreferredMaxPasswordLifetimeDuration(24)
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertFalse(person.isPasswordExpired()) self.assertFalse(login.isPasswordExpired())
self.assertFalse(request['is_user_account_password_expired']) self.assertFalse(request['is_user_account_password_expired'])
...@@ -502,21 +524,21 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -502,21 +524,21 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPreferredMaxPasswordLifetimeDuration(4*24) # password expire in 4 days preference.setPreferredMaxPasswordLifetimeDuration(4*24) # password expire in 4 days
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertFalse(person.isPasswordExpired()) self.assertFalse(login.isPasswordExpired())
self.assertFalse(request['is_user_account_password_expired']) self.assertFalse(request['is_user_account_password_expired'])
# test early warning password expire notification is detected # test early warning password expire notification is detected
preference.setPreferredPasswordLifetimeExpireWarningDuration(4*24) # password expire notification appear immediately preference.setPreferredPasswordLifetimeExpireWarningDuration(4*24) # password expire notification appear immediately
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertFalse(person.isPasswordExpired()) self.assertFalse(login.isPasswordExpired())
self.assertTrue(request['is_user_account_password_expired_expire_date']) self.assertTrue(request['is_user_account_password_expired_expire_date'])
# test early warning password expire notification is detected # test early warning password expire notification is detected
preference.setPreferredPasswordLifetimeExpireWarningDuration(4*24-24) # password expire notification appear 3 days befor time preference.setPreferredPasswordLifetimeExpireWarningDuration(4*24-24) # password expire notification appear 3 days befor time
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertFalse(person.isPasswordExpired()) self.assertFalse(login.isPasswordExpired())
self.assertFalse(request['is_user_account_password_expired_expire_date']) self.assertFalse(request['is_user_account_password_expired_expire_date'])
def test_05_HttpRequest(self): def test_05_HttpRequest(self):
...@@ -525,50 +547,52 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -525,50 +547,52 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
""" """
portal = self.getPortal() portal = self.getPortal()
request = self.app.REQUEST request = self.app.REQUEST
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = self.createUser('test-05')
reference = 'test') assignment = person.newContent(portal_type = 'Assignment')
assignment.open()
login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
person.setPassword('used_ALREADY_1234') login.setPassword('used_ALREADY_1234')
self.tic() self.tic()
path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test', 'used_ALREADY_1234') path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test-05', 'used_ALREADY_1234')
response = self.publish(path) response = self.publish(path)
self.assertTrue('Welcome to ERP5' in response.getBody()) self.assertTrue('Welcome to ERP5' in response.getBody())
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# fail request #1 # fail request #1
path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test', 'bad_test') path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test-05', 'bad_test')
response = self.publish(path) response = self.publish(path)
self.assertTrue(response.getHeader("Location").endswith("login_form")) self.assertTrue(response.getHeader("Location").endswith("login_form"))
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# fail request #2 # fail request #2
response = self.publish(path) response = self.publish(path)
self.assertTrue(response.getHeader("Location").endswith("login_form")) self.assertTrue(response.getHeader("Location").endswith("login_form"))
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# fail request #3 # fail request #3
response = self.publish(path) response = self.publish(path)
self.assertTrue(response.getHeader("Location").endswith("login_form")) self.assertTrue(response.getHeader("Location").endswith("login_form"))
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
self.tic() self.tic()
# test message that account is blocked # test message that account is blocked
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
path = portal.absolute_url_path() + '/logged_in?__ac_name=%s&__ac_password=%s' %('test', 'used_ALREADY_1234') path = portal.absolute_url_path() + '/logged_in?__ac_name=%s&__ac_password=%s' %('test-05', 'used_ALREADY_1234')
response = self.publish(path) response = self.publish(path)
self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Account is blocked.")) self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Account is blocked."))
# test expire password message, first unblock it # test expire password message, first unblock it
person.Person_unblockLogin() login.Login_unblockLogin()
preference.setPreferredMaxPasswordLifetimeDuration(0) preference.setPreferredMaxPasswordLifetimeDuration(0)
self.tic() self.tic()
self._clearCache() self._clearCache()
response = self.publish(path) response = self.publish(path)
self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Password is expired.")) self.assertTrue(response.getHeader("Location").endswith("login_form?portal_status_message=Password is expired."))
self.assertTrue(person.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
preference.setPreferredMaxPasswordLifetimeDuration(24) preference.setPreferredMaxPasswordLifetimeDuration(24)
...@@ -584,7 +608,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -584,7 +608,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPreferredPasswordLifetimeExpireWarningDuration(12) preference.setPreferredPasswordLifetimeExpireWarningDuration(12)
self.tic() self.tic()
self._clearCache() self._clearCache()
path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test', 'used_ALREADY_1234') path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test-05', 'used_ALREADY_1234')
response = self.publish(path) response = self.publish(path)
self.assertTrue('Welcome to ERP5' in response.getBody()) self.assertTrue('Welcome to ERP5' in response.getBody())
...@@ -593,18 +617,18 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -593,18 +617,18 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
Check that expiring old Authentication Event list works. Check that expiring old Authentication Event list works.
""" """
portal = self.getPortal() portal = self.getPortal()
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = self.createUser('test-06')
reference = 'test') login = person.objectValues(portal_type='ERP5 Login')[0]
preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference', preference = portal.portal_catalog.getResultValue(portal_type = 'System Preference',
title = 'Authentication',) title = 'Authentication',)
# file some failures so we should detect and block account # file some failures so we should detect and block account
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
person.notifyLoginFailure() login.notifyLoginFailure()
self.tic() self.tic()
# should be blocked # should be blocked
self.assertTrue(person.isLoginBlocked()) self.assertTrue(login.isLoginBlocked())
# set 0 check interval # set 0 check interval
preference.setPreferredAuthenticationFailureCheckDuration(0) preference.setPreferredAuthenticationFailureCheckDuration(0)
...@@ -612,14 +636,14 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -612,14 +636,14 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
time.sleep(1) # we need to give a moment time.sleep(1) # we need to give a moment
self.assertFalse(person.isLoginBlocked()) self.assertFalse(login.isLoginBlocked())
# expire manually old # expire manually old
portal.system_event_module.SystemEventModule_expireAuthenticationEventList() portal.system_event_module.SystemEventModule_expireAuthenticationEventList()
self.tic() self.tic()
self.assertEqual(3, len(portal.portal_catalog(portal_type ="Authentication Event", self.assertEqual(3, len(portal.portal_catalog(portal_type ="Authentication Event",
default_destination_uid = person.getUid(), default_destination_uid = login.getUid(),
validation_state = "expired"))) validation_state = "expired")))
......
...@@ -1108,6 +1108,7 @@ Hé Hé Hé!""", page.asText().strip()) ...@@ -1108,6 +1108,7 @@ Hé Hé Hé!""", page.asText().strip())
# authenticated # authenticated
user = self.createUser('webmaster') user = self.createUser('webmaster')
self.createUserAssignement(user, {}) self.createUserAssignement(user, {})
self.tic()
response = self.publish(path, 'webmaster:webmaster') response = self.publish(path, 'webmaster:webmaster')
last_modified_header = response.getHeader('Last-Modified') last_modified_header = response.getHeader('Last-Modified')
self.assertTrue(last_modified_header) self.assertTrue(last_modified_header)
......
...@@ -88,7 +88,6 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin): ...@@ -88,7 +88,6 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin):
'career_function': self.getFunction(), 'career_function': self.getFunction(),
'last_name': self.getLastName(), 'last_name': self.getLastName(),
'reference': self.getReference(), 'reference': self.getReference(),
'password': self.getPassword(),
}) })
assignment = person.newContent(portal_type="Assignment", assignment = person.newContent(portal_type="Assignment",
...@@ -96,6 +95,10 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin): ...@@ -96,6 +95,10 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin):
group = group_id, group = group_id,
site = site_id) site = site_id)
login = person.newContent(portal_type='ERP5 Login',
reference=self.getReference(),
password=self.getPassword())
# Set dates are required to create valid assigments. # Set dates are required to create valid assigments.
now = DateTime() now = DateTime()
assignment.setStartDate(now) assignment.setStartDate(now)
...@@ -103,9 +106,10 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin): ...@@ -103,9 +106,10 @@ class PersonConfiguratorItem(XMLObject, ConfiguratorItemMixin):
# Define valid for 10 years. # Define valid for 10 years.
assignment.setStopDate(now + (365*10)) assignment.setStopDate(now + (365*10))
# Validate the Person and Assigment # Validate the Person, Assigment and Login
person.validate(comment=translateString("Validated by Configurator")) person.validate(comment=translateString("Validated by Configurator"))
assignment.open(comment=translateString("Open by Configuration")) assignment.open(comment=translateString("Open by Configuration"))
login.validate(comment=translateString("Validated by Configurator"))
## add to customer template ## add to customer template
business_configuration = self.getBusinessConfigurationValue() business_configuration = self.getBusinessConfigurationValue()
......
...@@ -82,6 +82,7 @@ class ERP5AccessTokenExtractionPlugin(BasePlugin): ...@@ -82,6 +82,7 @@ class ERP5AccessTokenExtractionPlugin(BasePlugin):
if external_login is not None: if external_login is not None:
creds['external_login'] = external_login creds['external_login'] = external_login
creds['login_portal_type'] = 'Person'
creds['remote_host'] = request.get('REMOTE_HOST', '') creds['remote_host'] = request.get('REMOTE_HOST', '')
try: try:
creds['remote_address'] = request.getClientAddr() creds['remote_address'] = request.getClientAddr()
......
...@@ -32,14 +32,13 @@ from AccessControl import ClassSecurityInfo ...@@ -32,14 +32,13 @@ from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PluggableAuthService.interfaces import plugins from Products.PluggableAuthService.interfaces import plugins
from Products.PluggableAuthService.utils import classImplements from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.ERP5Security.ERP5UserManager import SUPER_USER from Products.ERP5Security.ERP5UserManager import SUPER_USER
from Products.PluggableAuthService.PluggableAuthService import DumbHTTPExtractor from Products.PluggableAuthService.PluggableAuthService import DumbHTTPExtractor
from AccessControl.SecurityManagement import getSecurityManager, \ from AccessControl.SecurityManagement import getSecurityManager, \
setSecurityManager, newSecurityManager setSecurityManager, newSecurityManager
from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE
import socket import socket
from Products.ERP5Security.ERP5UserManager import getUserByLogin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from zLOG import LOG, ERROR, INFO from zLOG import LOG, ERROR, INFO
try: try:
...@@ -90,7 +89,7 @@ def addERP5GoogleExtractionPlugin(dispatcher, id, title=None, REQUEST=None): ...@@ -90,7 +89,7 @@ def addERP5GoogleExtractionPlugin(dispatcher, id, title=None, REQUEST=None):
'ERP5GoogleExtractionPlugin+added.' 'ERP5GoogleExtractionPlugin+added.'
% dispatcher.absolute_url()) % dispatcher.absolute_url())
class ERP5ExternalOauth2ExtractionPlugin: class ERP5ExternalOauth2ExtractionPluginBase(BasePlugin):
cache_factory_name = 'extrenal_oauth2_token_cache_factory' cache_factory_name = 'extrenal_oauth2_token_cache_factory'
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -143,7 +142,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -143,7 +142,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
Base_createOauth2User = getattr(self.getPortalObject(), Base_createOauth2User = getattr(self.getPortalObject(),
'Base_createOauth2User', None) 'Base_createOauth2User', None)
if Base_createOauth2User is None: if Base_createOauth2User is None:
LOG('ERP5ExternalOauth2ExtractionPlugin', INFO, LOG(self.getId(), INFO,
'No Base_createOauth2User script available, install ' 'No Base_createOauth2User script available, install '
'erp5_credential_oauth2, disabled authentication.') 'erp5_credential_oauth2, disabled authentication.')
return DumbHTTPExtractor().extractCredentials(request) return DumbHTTPExtractor().extractCredentials(request)
...@@ -158,8 +157,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -158,8 +157,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
token = l[1] token = l[1]
if token is None: if token is None:
# no token return creds
return DumbHTTPExtractor().extractCredentials(request)
# token is available # token is available
user = None user = None
...@@ -172,8 +170,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -172,8 +170,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
user = user_entry['reference'] user = user_entry['reference']
if user is None: if user is None:
# fallback to default way return creds
return DumbHTTPExtractor().extractCredentials(request)
tag = '%s_user_creation_in_progress' % user.encode('hex') tag = '%s_user_creation_in_progress' % user.encode('hex')
...@@ -181,7 +178,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -181,7 +178,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
self.REQUEST['USER_CREATION_IN_PROGRESS'] = user self.REQUEST['USER_CREATION_IN_PROGRESS'] = user
else: else:
# create the user if not found # create the user if not found
person_list = getUserByLogin(self.getPortalObject(), user) person_list = self.erp5_users.getPersonByReference(user)
if len(person_list) == 0: if len(person_list) == 0:
sm = getSecurityManager() sm = getSecurityManager()
if sm.getUser().getId() != SUPER_USER: if sm.getUser().getId() != SUPER_USER:
...@@ -193,7 +190,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -193,7 +190,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
try: try:
self.Base_createOauth2User(tag, **user_entry) self.Base_createOauth2User(tag, **user_entry)
except Exception: except Exception:
LOG('ERP5ExternalOauth2ExtractionPlugin', ERROR, LOG(self.getId(), ERROR,
'Issue while calling creation script:', error=True) 'Issue while calling creation script:', error=True)
raise raise
finally: finally:
...@@ -204,6 +201,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -204,6 +201,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
# allow to work w/o cache # allow to work w/o cache
pass pass
creds['external_login'] = user creds['external_login'] = user
creds['login_portal_type'] = self.login_portal_type
creds['remote_host'] = request.get('REMOTE_HOST', '') creds['remote_host'] = request.get('REMOTE_HOST', '')
try: try:
creds['remote_address'] = request.getClientAddr() creds['remote_address'] = request.getClientAddr()
...@@ -211,7 +209,7 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -211,7 +209,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
creds['remote_address'] = request.get('REMOTE_ADDR', '') creds['remote_address'] = request.get('REMOTE_ADDR', '')
return creds return creds
class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin): class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPluginBase):
""" """
Plugin to authenicate as machines. Plugin to authenicate as machines.
""" """
...@@ -219,6 +217,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi ...@@ -219,6 +217,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi
meta_type = "ERP5 Facebook Extraction Plugin" meta_type = "ERP5 Facebook Extraction Plugin"
prefix = 'fb_' prefix = 'fb_'
header_string = 'facebook' header_string = 'facebook'
login_portal_type = 'Facebook Login'
def getUserEntry(self, token): def getUserEntry(self, token):
if facebook is None: if facebook is None:
...@@ -250,7 +249,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi ...@@ -250,7 +249,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi
user_entry = None user_entry = None
return user_entry return user_entry
class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin): class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPluginBase):
""" """
Plugin to authenicate as machines. Plugin to authenicate as machines.
""" """
...@@ -258,6 +257,7 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin) ...@@ -258,6 +257,7 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin)
meta_type = "ERP5 Google Extraction Plugin" meta_type = "ERP5 Google Extraction Plugin"
prefix = 'go_' prefix = 'go_'
header_string = 'google' header_string = 'google'
login_portal_type = 'Google Login'
def getUserEntry(self, token): def getUserEntry(self, token):
if httplib2 is None: if httplib2 is None:
......
...@@ -25,14 +25,13 @@ from Products.ERP5Type.Cache import CachingMethod ...@@ -25,14 +25,13 @@ from Products.ERP5Type.Cache import CachingMethod
from Products.ERP5Type.ERP5Type \ from Products.ERP5Type.ERP5Type \
import ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT import ERP5TYPE_SECURITY_GROUP_ID_GENERATION_SCRIPT
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
import sys import sys
from zLOG import LOG, WARNING from zLOG import LOG, WARNING
from ERP5UserManager import SUPER_USER from ERP5UserManager import SUPER_USER, getUserByLogin
# It can be useful to set NO_CACHE_MODE to 1 in order to debug # It can be useful to set NO_CACHE_MODE to 1 in order to debug
# complex security issues related to caching groups. For example, # complex security issues related to caching groups. For example,
...@@ -45,8 +44,8 @@ NO_CACHE_MODE = 0 ...@@ -45,8 +44,8 @@ NO_CACHE_MODE = 0
class ConsistencyError(Exception): pass class ConsistencyError(Exception): pass
manage_addERP5GroupManagerForm = PageTemplateFile( manage_addERP5GroupManagerForm = PageTemplateFile(
'www/ERP5Security_addERP5GroupManager', globals(), 'www/ERP5Security_addERP5GroupManager', globals(),
__name__='manage_addERP5GroupManagerForm' ) __name__='manage_addERP5GroupManagerForm' )
def addERP5GroupManager( dispatcher, id, title=None, REQUEST=None ): def addERP5GroupManager( dispatcher, id, title=None, REQUEST=None ):
""" Add a ERP5GroupManager to a Pluggable Auth Service. """ """ Add a ERP5GroupManager to a Pluggable Auth Service. """
...@@ -56,10 +55,10 @@ def addERP5GroupManager( dispatcher, id, title=None, REQUEST=None ): ...@@ -56,10 +55,10 @@ def addERP5GroupManager( dispatcher, id, title=None, REQUEST=None ):
if REQUEST is not None: if REQUEST is not None:
REQUEST['RESPONSE'].redirect( REQUEST['RESPONSE'].redirect(
'%s/manage_workspace' '%s/manage_workspace'
'?manage_tabs_message=' '?manage_tabs_message='
'ERP5GroupManager+added.' 'ERP5GroupManager+added.'
% dispatcher.absolute_url()) % dispatcher.absolute_url())
class ERP5GroupManager(BasePlugin): class ERP5GroupManager(BasePlugin):
...@@ -117,17 +116,10 @@ class ERP5GroupManager(BasePlugin): ...@@ -117,17 +116,10 @@ class ERP5GroupManager(BasePlugin):
else: else:
security_definition_list = mapping_method() security_definition_list = mapping_method()
# get the person from its reference - no security check needed # get the person from its login - no security check needed
catalog_result = self.portal_catalog.unrestrictedSearchResults( person_object = self.erp5_users.getPersonByReference(user_name)
portal_type="Person", query=SimpleQuery(reference=user_name)) if person_object is None: # no person is linked to this user login
if len(catalog_result) != 1: # we won't proceed with groups return ()
if len(catalog_result) > 1: # configuration is screwed
raise ConsistencyError, 'There is more than one Person whose \
login is %s : %s' % (user_name,
repr([r.getObject() for r in catalog_result]))
else: # no person is linked to this user login
return ()
person_object = catalog_result[0].getObject()
# Fetch category values from defined scripts # Fetch category values from defined scripts
for (method_name, base_category_list) in security_definition_list: for (method_name, base_category_list) in security_definition_list:
......
...@@ -46,8 +46,7 @@ from Products.PluggableAuthService.plugins.CookieAuthHelper import CookieAuthHel ...@@ -46,8 +46,7 @@ from Products.PluggableAuthService.plugins.CookieAuthHelper import CookieAuthHel
from Products.ERP5Type.Cache import CachingMethod from Products.ERP5Type.Cache import CachingMethod
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from Products.ERP5Security.ERP5UserManager import ERP5UserManager, \ from Products.ERP5Security.ERP5UserManager import SUPER_USER,\
SUPER_USER, \
_AuthenticationFailure _AuthenticationFailure
from Crypto.Cipher import AES from Crypto.Cipher import AES
...@@ -130,8 +129,8 @@ class ILoginEncryptionPlugin(Interface): ...@@ -130,8 +129,8 @@ class ILoginEncryptionPlugin(Interface):
#Form for new plugin in ZMI #Form for new plugin in ZMI
manage_addERP5KeyAuthPluginForm = PageTemplateFile( manage_addERP5KeyAuthPluginForm = PageTemplateFile(
'www/ERP5Security_addERP5KeyAuthPlugin', globals(), 'www/ERP5Security_addERP5KeyAuthPlugin', globals(),
__name__='manage_addERP5KeyAuthPluginForm') __name__='manage_addERP5KeyAuthPluginForm')
def addERP5KeyAuthPlugin(dispatcher, id, title=None, def addERP5KeyAuthPlugin(dispatcher, id, title=None,
encryption_key='', cipher='AES', cookie_name='', encryption_key='', cipher='AES', cookie_name='',
...@@ -150,7 +149,7 @@ def addERP5KeyAuthPlugin(dispatcher, id, title=None, ...@@ -150,7 +149,7 @@ def addERP5KeyAuthPlugin(dispatcher, id, title=None,
'ERP5KeyAuthPlugin+added.' 'ERP5KeyAuthPlugin+added.'
% dispatcher.absolute_url()) % dispatcher.absolute_url())
class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): class ERP5KeyAuthPlugin(CookieAuthHelper):
""" """
Key authentification PAS plugin which support key authentication in URL. Key authentification PAS plugin which support key authentication in URL.
...@@ -237,38 +236,17 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): ...@@ -237,38 +236,17 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper):
#Search __ac_key #Search __ac_key
key = request.get('__ac_key', None) key = request.get('__ac_key', None)
if key is not None: if key is not None:
creds['key'] = key creds['external_login'] = self.decrypt(key)
#Save this in cookie #Save this in cookie
self.updateCredentials(request, request["RESPONSE"], None, None) self.updateCredentials(request, request["RESPONSE"], None, None)
else: else:
# Look in the request for the names coming from the login form #search in cookies
#It's default method cookie = request.get(self.cookie_name, None)
login_pw = request._authUserPW() if cookie is not None:
#Cookie is found
if login_pw is not None: cookie_val = unquote(cookie)
name, password = login_pw creds['external_login'] = self.decrypt(cookie_val)
creds[ 'login' ] = name
creds[ 'password' ] = password
#Save this in cookie
self.updateCredentials(request, request["RESPONSE"], name, password)
else:
#search in cookies
cookie = request.get(self.cookie_name, None)
if cookie is not None:
#Cookie is found
cookie_val = unquote(cookie)
creds['key'] = cookie_val
else:
#Default cookie if needed
default_cookie = request.get(self.default_cookie_name, None)
if default_cookie is not None:
#Cookie is found
cookie_val = decodestring(unquote(default_cookie))
if cookie_val is not None:
login, password = cookie_val.split(':')
creds['login'] = login
creds['password'] = password
#Complete credential with some information #Complete credential with some information
if creds: if creds:
...@@ -315,78 +293,15 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper): ...@@ -315,78 +293,15 @@ class ERP5KeyAuthPlugin(ERP5UserManager, CookieAuthHelper):
response.expireCookie(self.default_cookie_name, path='/') response.expireCookie(self.default_cookie_name, path='/')
################################
# IAuthenticationPlugin #
################################
security.declarePrivate('authenticateCredentials')
def authenticateCredentials( self, credentials ):
"""Authentificate with credentials"""
key = credentials.get('key', None)
if key != None:
login = self.decrypt(key)
# Forbidden the usage of the super user.
if login == SUPER_USER:
return None
#Function to allow cache
@UnrestrictedMethod
def _authenticateCredentials(login):
if not login:
return None
#Search the user by his login
user_list = self.getUserByLogin(login)
if len(user_list) != 1:
raise _AuthenticationFailure()
user = user_list[0]
if True:
try:
# get assignment list
assignment_list = [x for x in user.contentValues(portal_type="Assignment") \
if x.getValidationState() == "open"]
valid_assignment_list = []
# check dates if exist
login_date = DateTime()
for assignment in assignment_list:
if assignment.getStartDate() is not None and \
assignment.getStartDate() > login_date:
continue
if assignment.hasStopDate() and \
assignment.getStopDate() < login_date:
continue
valid_assignment_list.append(assignment)
# validate
if len(valid_assignment_list) > 0:
return (login, login)
finally:
pass
raise _AuthenticationFailure()
#Cache Method for best performance
_authenticateCredentials = CachingMethod(_authenticateCredentials,
id='ERP5KeyAuthPlugin_authenticateCredentials',
cache_factory='erp5_content_short')
try:
return _authenticateCredentials(login=login)
except _AuthenticationFailure:
return None
except StandardError, e:
#Log standard error
LOG('ERP5KeyAuthPlugin.authenticateCredentials', PROBLEM, str(e))
return None
################################ ################################
# Properties for ZMI managment # # Properties for ZMI managment #
################################ ################################
#'Edit' option form #'Edit' option form
manage_editERP5KeyAuthPluginForm = PageTemplateFile( manage_editERP5KeyAuthPluginForm = PageTemplateFile(
'www/ERP5Security_editERP5KeyAuthPlugin', 'www/ERP5Security_editERP5KeyAuthPlugin',
globals(), globals(),
__name__='manage_editERP5KeyAuthPluginForm' ) __name__='manage_editERP5KeyAuthPluginForm' )
security.declareProtected( ManageUsers, 'manage_editKeyAuthPlugin' ) security.declareProtected( ManageUsers, 'manage_editKeyAuthPlugin' )
def manage_editKeyAuthPlugin(self, encryption_key, cipher, cookie_name, def manage_editKeyAuthPlugin(self, encryption_key, cipher, cookie_name,
......
...@@ -97,6 +97,7 @@ class ERP5UserManager(BasePlugin): ...@@ -97,6 +97,7 @@ class ERP5UserManager(BasePlugin):
""" """
meta_type = 'ERP5 User Manager' meta_type = 'ERP5 User Manager'
login_portal_type = 'ERP5 Login'
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -105,6 +106,53 @@ class ERP5UserManager(BasePlugin): ...@@ -105,6 +106,53 @@ class ERP5UserManager(BasePlugin):
self._id = self.id = id self._id = self.id = id
self.title = title self.title = title
def getLoginPortalType(self):
return self.login_portal_type
def getPersonByReference(self, reference):
def _getPersonRelativeUrlFromReference(reference):
person_url = self.REQUEST.get('_login_cache', {}).get(reference)
portal = self.getPortalObject()
if person_url is not None:
return person_url
else:
person_list = portal.portal_catalog.unrestrictedSearchResults(
select_list=('relative_url',),
portal_type='Person',
reference={'query': reference, 'key': 'ExactMatch'},
limit=2
)
l = len(person_list)
if l > 1:
raise RuntimeError, 'More than one Person have login %r' % \
(reference,)
elif l == 1:
return person_list[0]['relative_url']
_getPersonRelativeUrlFromReference = CachingMethod(
_getPersonRelativeUrlFromReference,
id='ERP5UserManager._getPersonRelativeUrlFromReference',
cache_factory='erp5_content_short')
person_relative_url = _getPersonRelativeUrlFromReference(reference)
if person_relative_url is not None:
return self.getPortalObject().unrestrictedTraverse(
person_relative_url)
def checkPersonValidity(self, person):
if person.getValidationState() in ('deleted',):
return False
now = DateTime()
for assignment in person.contentValues(portal_type="Assignment"):
if assignment.getValidationState() != "open":
continue
if assignment.hasStartDate() and \
assignment.getStartDate() > now:
continue
if assignment.hasStopDate() and \
assignment.getStopDate() < now:
continue
return True
return False
# #
# IAuthenticationPlugin implementation # IAuthenticationPlugin implementation
# #
...@@ -128,82 +176,80 @@ class ERP5UserManager(BasePlugin): ...@@ -128,82 +176,80 @@ class ERP5UserManager(BasePlugin):
return None return None
@UnrestrictedMethod @UnrestrictedMethod
def _authenticateCredentials(login, password, path, def _authenticateCredentials(login, password, portal_type,
ignore_password=False): ignore_password=False):
if not login or not (password or ignore_password): if not login or not (password or ignore_password):
return None return None, None
user_list = self.getUserByLogin(login) login_object = self.getLoginObject(login, portal_type)
if not user_list: if not login_object:
raise _AuthenticationFailure() raise _AuthenticationFailure(None)
user = user_list[0] if login_object.getPortalType() == 'Person':
# BBB
user = login_object
else:
user = login_object.getParentValue()
try: try:
# get assignment if self.checkPersonValidity(user) and \
assignment_list = [x for x in user.contentValues(portal_type="Assignment") if x.getValidationState() == "open"] (ignore_password or self._validatePassword(login_object, password)):
valid_assignment_list = [] return user.getReference(), login_object.getRelativeUrl()
# check dates if exist
login_date = DateTime()
for assignment in assignment_list:
if assignment.getStartDate() is not None and \
assignment.getStartDate() > login_date:
continue
if assignment.hasStopDate() and \
assignment.getStopDate() < login_date:
continue
valid_assignment_list.append(assignment)
if (ignore_password or pw_validate(user.getPassword(), password)) and \
len(valid_assignment_list) and user \
.getValidationState() != 'deleted': #user.getCareerRole() == 'internal':
return login, login # use same for user_id and login
finally: finally:
pass pass
raise _AuthenticationFailure() raise _AuthenticationFailure(login_object.getRelativeUrl())
_authenticateCredentials = CachingMethod( _authenticateCredentials = CachingMethod(
_authenticateCredentials, _authenticateCredentials,
id='ERP5UserManager_authenticateCredentials', id=self.__class__.__name__ + '_authenticateCredentials',
cache_factory='erp5_content_short') cache_factory='erp5_content_short')
try: try:
authentication_result = _authenticateCredentials( user_reference, login_url = _authenticateCredentials(
login=login, login=login,
password=credentials.get('password'), password=credentials.get('password'),
path=self.getPhysicalPath(), portal_type=credentials.get('login_portal_type',
self.login_portal_type),
ignore_password=ignore_password) ignore_password=ignore_password)
except _AuthenticationFailure, exception:
user_reference = None
login_url = exception.message or None
except _AuthenticationFailure: if user_reference and '_login_cache' not in self.REQUEST:
authentication_result = None self.REQUEST.set('_login_cache', {})
self.REQUEST['_login_cache'][user_reference] = login_url
if not self.getPortalObject().portal_preferences.isAuthenticationPolicyEnabled(): if not self.getPortalObject().portal_preferences.isAuthenticationPolicyEnabled():
# stop here, no authentication policy enabled # stop here, no authentication policy enabled
# so just return authentication check result # so just return authentication check result
return authentication_result if user_reference:
return (user_reference, user_reference)
else:
return None
# authentication policy enabled, we need person object anyway if login_url is None:
user_list = self.getUserByLogin(credentials.get('login'))
if not user_list:
# not an ERP5 Person object
return None return None
user = user_list[0]
if authentication_result is None: # authentication policy enabled, we need person object anyway
login = self.getPortalObject().unrestrictedTraverse(login_url)
if user_reference is None:
# file a failed authentication attempt # file a failed authentication attempt
user.notifyLoginFailure() login.notifyLoginFailure()
return None return None
# check if password is expired # check if password is expired
if user.isPasswordExpired(): if login.isPasswordExpired():
user.notifyPasswordExpire() login.notifyPasswordExpire()
return None return None
# check if user account is blocked # check if login is blocked
if user.isLoginBlocked(): if login.isLoginBlocked():
return None return None
return authentication_result return (user_reference, user_reference)
def _validatePassword(self, login_object, password):
return pw_validate(login_object.getPassword(), password)
# #
# IUserEnumerationPlugin implementation # IUserEnumerationPlugin implementation
...@@ -213,7 +259,7 @@ class ERP5UserManager(BasePlugin): ...@@ -213,7 +259,7 @@ class ERP5UserManager(BasePlugin):
sort_by=None, max_results=None, **kw): sort_by=None, max_results=None, **kw):
""" See IUserEnumerationPlugin. """ See IUserEnumerationPlugin.
""" """
if id is None: if not id:
id = login id = login
if isinstance(id, str): if isinstance(id, str):
id = (id,) id = (id,)
...@@ -226,25 +272,70 @@ class ERP5UserManager(BasePlugin): ...@@ -226,25 +272,70 @@ class ERP5UserManager(BasePlugin):
id_list = [] id_list = []
for user_id in id: for user_id in id:
if SUPER_USER == user_id: if SUPER_USER == user_id:
info = { 'id' : SUPER_USER info = {'id' : SUPER_USER,
, 'login' : SUPER_USER 'login' : SUPER_USER,
, 'pluginid' : plugin_id 'pluginid' : plugin_id,
} }
user_info.append(info) user_info.append(info)
else: else:
id_list.append(user_id) id_list.append(user_id)
if id_list: if id_list:
for user in self.getUserByLogin(tuple(id_list), exact_match=exact_match): if exact_match:
info = { 'id' : user.getReference() for reference in id_list:
, 'login' : user.getReference() user = self.getPersonByReference(reference)
, 'pluginid' : plugin_id if user is not None:
} info = {'id': reference,
'login' : reference,
user_info.append(info) 'pluginid': plugin_id,
}
user_info.append(info)
else:
for user in self.getPortalObject().portal_catalog.unrestrictedSearchResults(
select_list=('reference',),
portal_type='Person',
reference={'query': id_list, 'key': 'Keyword'},
):
info = {'id': user['reference'],
'login' : user['reference'],
'pluginid' : plugin_id,
}
user_info.append(info)
return tuple(user_info) return tuple(user_info)
@transactional_cached(lambda self, *args: args)
def getLoginObject(self, login, portal_type):
try:
if not login:
return
catalog_result = self.getPortalObject().portal_catalog.unrestrictedSearchResults(
select_expression=('portal_type', 'reference', 'validation_state'),
portal_type=(portal_type, 'Person'),
reference=dict(query=login, key='ExactMatch'),
sort_on=(('portal_type',),),
)
for x in catalog_result:
if x['portal_type'] != 'Person' and x['validation_state'] != 'validated':
continue
if x['reference'] != login:
continue
x = x.getObject()
if x.objectIds(spec='ERP5 Login'):
continue # Already migrated.
return x
except ConflictError:
raise
except:
LOG('ERP5Security', PROBLEM, 'getLoginObject failed', error=sys.exc_info())
# Here we must raise an exception to prevent callers from caching
# a result of a degraded situation.
# The kind of exception does not matter as long as it's catched by
# PAS and causes a lookup using another plugin or user folder.
# As PAS does not define explicitely such exception, we must use
# the _SWALLOWABLE_PLUGIN_EXCEPTIONS list.
raise _SWALLOWABLE_PLUGIN_EXCEPTIONS[0]
def getUserByLogin(self, login, exact_match=True): def getUserByLogin(self, login, exact_match=True):
# Search the Catalog for login and return a list of person objects # Search the Catalog for login and return a list of person objects
# login can be a string or a list of strings # login can be a string or a list of strings
......
...@@ -94,10 +94,11 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -94,10 +94,11 @@ class TestUserManagement(ERP5TypeTestCase):
newSecurityManager(None, user) newSecurityManager(None, user)
def _makePerson(self, open_assignment=1, assignment_start_date=None, def _makePerson(self, open_assignment=1, assignment_start_date=None,
assignment_stop_date=None, **kw): assignment_stop_date=None, tic=True, **kw):
"""Creates a person in person module, and returns the object, after """Creates a person in person module, and returns the object, after
indexing is done. """ indexing is done. """
person_module = self.getPersonModule() person_module = self.getPersonModule()
password = kw.pop('password', None)
new_person = person_module.newContent( new_person = person_module.newContent(
portal_type='Person', **kw) portal_type='Person', **kw)
assignment = new_person.newContent(portal_type = 'Assignment', assignment = new_person.newContent(portal_type = 'Assignment',
...@@ -105,7 +106,14 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -105,7 +106,14 @@ class TestUserManagement(ERP5TypeTestCase):
stop_date=assignment_stop_date,) stop_date=assignment_stop_date,)
if open_assignment: if open_assignment:
assignment.open() assignment.open()
self.tic() if new_person.hasReference():
login = new_person.newContent(
portal_type='ERP5 Login',
reference=new_person.getReference(),
password=password,)
login.validate()
if tic:
self.tic()
return new_person return new_person
def _assertUserExists(self, login, password): def _assertUserExists(self, login, password):
...@@ -303,9 +311,21 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -303,9 +311,21 @@ class TestUserManagement(ERP5TypeTestCase):
self.tic() self.tic()
self._assertUserExists('the_user', 'secret') self._assertUserExists('the_user', 'secret')
self.loginAsUser('the_user') self.loginAsUser('the_user')
self.portal.REQUEST.set('current_password', 'secret') login = [x for x in pers.objectValues(portal_type='ERP5 Login')][0]
self.portal.REQUEST.set('new_password', 'new_secret') result = self.portal.portal_preferences.PreferenceTool_setNewPassword(
self.portal.portal_preferences.PreferenceTool_setNewPassword() dialog_id='PreferenceTool_viewChangePasswordDialog',
login=login.getRelativeUrl(),
current_password='wrong_secret',
new_password='new_secret',
)
self.assertEqual(result, self.portal.absolute_url()+'/portal_preferences/PreferenceTool_viewChangePasswordDialog?portal_status_message=Current%20password%20is%20wrong.')
result = self.portal.portal_preferences.PreferenceTool_setNewPassword(
dialog_id='PreferenceTool_viewChangePasswordDialog',
login=login.getRelativeUrl(),
current_password='secret',
new_password='new_secret',
)
self.assertEqual(result, self.portal.absolute_url()+'/logout')
self._assertUserExists('the_user', 'new_secret') self._assertUserExists('the_user', 'new_secret')
self._assertUserDoesNotExists('the_user', 'secret') self._assertUserDoesNotExists('the_user', 'secret')
...@@ -325,8 +345,14 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -325,8 +345,14 @@ class TestUserManagement(ERP5TypeTestCase):
self._assertUserDoesNotExists('the_user', 'secret') self._assertUserDoesNotExists('the_user', 'secret')
def test_PersonNotIndexedNotCached(self): def test_PersonNotIndexedNotCached(self):
pers = self._makePerson(password='secret',) pers = self._makePerson()
pers.setReference('the_user') pers.setReference('the_user')
login = pers.newContent(
portal_type='ERP5 Login',
reference='the_user',
password='secret',
)
login.validate()
# not indexed yet # not indexed yet
self._assertUserDoesNotExists('the_user', 'secret') self._assertUserDoesNotExists('the_user', 'secret')
...@@ -337,9 +363,27 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -337,9 +363,27 @@ class TestUserManagement(ERP5TypeTestCase):
def test_PersonNotValidNotCached(self): def test_PersonNotValidNotCached(self):
pers = self._makePerson(reference='the_user', password='other',) pers = self._makePerson(reference='the_user', password='other',)
self._assertUserDoesNotExists('the_user', 'secret') self._assertUserDoesNotExists('the_user', 'secret')
pers.setPassword('secret') login = pers.objectValues(portal_type='ERP5 Login')[0]
login.setPassword('secret')
self._assertUserExists('the_user', 'secret') self._assertUserExists('the_user', 'secret')
def test_PersonLoginMigration(self):
pers = self._makePerson()
pers.setReference('the_user')
pers.setPassword('secret')
self.assertEqual(len(pers.objectValues(portal_type='ERP5 Login')), 0)
self.tic()
self._assertUserExists('the_user', 'secret')
pers.fixConsistency(filter={'constraint_type': 'post_upgrade'})
self.portal.portal_caches.clearAllCache()
self.tic()
self._assertUserExists('the_user', 'secret')
login = pers.objectValues(portal_type='ERP5 Login')[0]
login.setPassword('secret2')
self.portal.portal_caches.clearAllCache()
self.tic()
self._assertUserDoesNotExists('the_user', 'secret')
self._assertUserExists('the_user', 'secret2')
def test_AssignmentWithDate(self): def test_AssignmentWithDate(self):
"""Tests a person with an assignment with correct date is a valid user.""" """Tests a person with an assignment with correct date is a valid user."""
...@@ -400,6 +444,32 @@ class TestUserManagement(ERP5TypeTestCase): ...@@ -400,6 +444,32 @@ class TestUserManagement(ERP5TypeTestCase):
self.tic() self.tic()
self.assertEqual(None, person.getReference()) self.assertEqual(None, person.getReference())
def test_duplicatePersonReference(self):
person1 = self._makePerson(reference='foo', password='secret',)
self.tic()
self.assertRaises(RuntimeError, self._makePerson,
reference='foo', password='secret',)
def test_duplicateLoginReference(self):
person1 = self._makePerson(reference='foo', password='secret',)
self.tic()
person2 = self._makePerson(reference='bar', password='secret',)
login = person2.objectValues(portal_type='ERP5 Login')[0]
self.assertRaises(RuntimeError, login.setReference, 'foo')
def test_duplicateLoginReferenceInSameTransaction(self):
person1 = self._makePerson(reference='foo', password='secret', tic=False)
person2 = self._makePerson(reference='bar', password='secret', tic=False)
login = person2.newContent(portal_type='ERP5 Login')
self.assertRaises(RuntimeError, login.setReference, 'foo')
def test_duplicateLoginReferenceInAnotherTransaction(self):
person1 = self._makePerson(reference='foo', password='secret', tic=False)
person2 = self._makePerson(reference='bar', password='secret', tic=False)
self.commit()
login = person2.newContent(portal_type='ERP5 Login')
self.assertRaises(RuntimeError, login.setReference, 'foo')
class TestUserManagementExternalAuthentication(TestUserManagement): class TestUserManagementExternalAuthentication(TestUserManagement):
def getTitle(self): def getTitle(self):
"""Title of the test.""" """Title of the test."""
......
...@@ -319,7 +319,7 @@ class ERP5TypeInformation(XMLObject, ...@@ -319,7 +319,7 @@ class ERP5TypeInformation(XMLObject,
# Module # Module
'module', 'module',
# Base # Base
'entity', 'entity', 'login',
# LEGACY - needs a warning - XXX-JPS # LEGACY - needs a warning - XXX-JPS
'tax_movement', 'tax_movement',
) )
......
...@@ -422,8 +422,11 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase): ...@@ -422,8 +422,11 @@ class ERP5TypeTestCaseMixin(ProcessingNodeTestCase, PortalTestCase):
person = self.portal.person_module.newContent(portal_type='Person', person = self.portal.person_module.newContent(portal_type='Person',
reference=reference, reference=reference,
password=password,
**person_kw) **person_kw)
login = person.newContent(portal_type='ERP5 Login',
reference=reference,
password=password)
login.validate()
return person return person
def createUserAssignment(self, user, assignment_kw): def createUserAssignment(self, user, assignment_kw):
......
...@@ -55,10 +55,14 @@ class ERP5RemoteUserManager(ERP5UserManager): ...@@ -55,10 +55,14 @@ class ERP5RemoteUserManager(ERP5UserManager):
""" """
meta_type = 'ERP5 Remote User Manager' meta_type = 'ERP5 Remote User Manager'
login_portal_type = 'ERP5 Remote Login'
security = ClassSecurityInfo() security = ClassSecurityInfo()
remote_authentication_cache = None remote_authentication_cache = None
def _doRemoteAuthentication(self, login, password): def checkPersonValidity(self, person):
return True # XXX Really ???
def _validatePassword(self, login_object, password):
# Do remote authentication with local ZODB caching # Do remote authentication with local ZODB caching
# Thanks to this it is possible to login to instance, even # Thanks to this it is possible to login to instance, even
# if master authentication server is down # if master authentication server is down
...@@ -69,6 +73,7 @@ class ERP5RemoteUserManager(ERP5UserManager): ...@@ -69,6 +73,7 @@ class ERP5RemoteUserManager(ERP5UserManager):
# #
# any other error is assumed as fatal and results in disallowing # any other error is assumed as fatal and results in disallowing
# authentication and clearing local cache # authentication and clearing local cache
login = login_object.getReference()
if self.remote_authentication_cache is None: if self.remote_authentication_cache is None:
self.remote_authentication_cache = OOBTree() self.remote_authentication_cache = OOBTree()
portal = self.getPortalObject() portal = self.getPortalObject()
...@@ -122,70 +127,6 @@ class ERP5RemoteUserManager(ERP5UserManager): ...@@ -122,70 +127,6 @@ class ERP5RemoteUserManager(ERP5UserManager):
del self.remote_authentication_cache[login] del self.remote_authentication_cache[login]
return result return result
#
# IAuthenticationPlugin implementation
#
security.declarePrivate( 'authenticateCredentials' )
def authenticateCredentials(self, credentials):
""" See IAuthenticationPlugin.
o We expect the credentials to be those returned by
ILoginPasswordExtractionPlugin.
"""
# Forbidden the usage of the super user.
if credentials.get('login') == SUPER_USER:
return None
def _authenticateCredentials(login, password, path):
if not login or not password:
return None
user_list = self.getUserByLogin(login)
if not user_list:
raise _AuthenticationFailure()
user = user_list[0]
sm = getSecurityManager()
if sm.getUser().getId() != SUPER_USER:
newSecurityManager(self, self.getUser(SUPER_USER))
try:
# get assignment
assignment_list = [x for x in user.contentValues(portal_type="Assignment") \
if x.getValidationState() == "open"]
valid_assignment_list = []
# check dates if exist
login_date = DateTime()
for assignment in assignment_list:
if assignment.getStartDate() is not None and \
assignment.getStartDate() > login_date:
continue
if assignment.getStopDate() is not None and \
assignment.getStopDate() < login_date:
continue
valid_assignment_list.append(assignment)
# validate to remote ERP5 instance
is_authenticated = self._doRemoteAuthentication(login, password)
if is_authenticated:
return login, login
finally:
setSecurityManager(sm)
raise _AuthenticationFailure()
_authenticateCredentials = CachingMethod(_authenticateCredentials,
id='ERP5RemoteUserManager_authenticateCredentials',
cache_factory='erp5_content_short')
try:
return _authenticateCredentials(
login=credentials.get('login'),
password=credentials.get('password'),
path=self.getPhysicalPath())
except _AuthenticationFailure:
return None
classImplements( ERP5RemoteUserManager classImplements( ERP5RemoteUserManager
, IAuthenticationPlugin , IAuthenticationPlugin
, IUserEnumerationPlugin , IUserEnumerationPlugin
......
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