Commit 47c4e226 authored by Rafael Monnerat's avatar Rafael Monnerat

Introduce Cerficate Login Document

See merge request nexedi/erp5!1486
parents 34ee4bdc 563ee3a1
Pipeline #18050 failed with stage
in 0 seconds
<?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}/CertificateLogin_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from AccessControl import ClassSecurityInfo, Unauthorized, getSecurityManager from AccessControl import ClassSecurityInfo, Unauthorized, getSecurityManager
from erp5.component.document.erp5_version.Person import Person as ERP5Person from erp5.component.document.erp5_version.Person import Person as ERP5Person
from Products.ERP5Type import Permissions
class Person(ERP5Person): class Person(ERP5Person):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declarePublic('getCertificate') security.declarePublic('getCertificate')
def _getCertificateLoginDocument(self):
for _erp5_login in self.objectValues(
portal_type=["ERP5 Login"]):
if _erp5_login.getValidationState() == "validated" and \
_erp5_login.getReference() == self.getUserId():
# The user already created a Login document as UserId, so
# So just use this one.
return _erp5_login
for _certificate_login in self.objectValues(
portal_type=["Certificate Login"]):
if _certificate_login.getValidationState() == "validated":
return _certificate_login
certificate_login = self.newContent(
portal_type="Certificate Login",
# For now use UserId as easy way.
reference=self.getUserId()
)
certificate_login.validate()
return certificate_login
def _checkCertificateRequest(self): def _checkCertificateRequest(self):
try: try:
self.checkUserCanChangePassword() self.checkUserCanChangePassword()
...@@ -12,7 +36,7 @@ class Person(ERP5Person): ...@@ -12,7 +36,7 @@ class Person(ERP5Person):
# in ERP5 user has no SetOwnPassword permission on Person document # in ERP5 user has no SetOwnPassword permission on Person document
# referring himself, so implement "security" by checking that currently # referring himself, so implement "security" by checking that currently
# logged in user is trying to get/revoke his own certificate # logged in user is trying to get/revoke his own certificate
user_id = self.Person_getUserId() user_id = self.getUserId()
if not user_id: if not user_id:
raise raise
if getSecurityManager().getUser().getId() != user_id: if getSecurityManager().getUser().getId() != user_id:
...@@ -20,11 +44,11 @@ class Person(ERP5Person): ...@@ -20,11 +44,11 @@ class Person(ERP5Person):
def _getCertificate(self): def _getCertificate(self):
return self.getPortalObject().portal_certificate_authority\ return self.getPortalObject().portal_certificate_authority\
.getNewCertificate(self.Person_getUserId()) .getNewCertificate(self._getCertificateLoginDocument().getReference())
def _revokeCertificate(self): def _revokeCertificate(self):
return self.getPortalObject().portal_certificate_authority\ return self.getPortalObject().portal_certificate_authority\
.revokeCertificateByCommonName(self.Person_getUserId()) .revokeCertificateByCommonName(self._getCertificateLoginDocument().getReference())
def getCertificate(self): def getCertificate(self):
"""Returns new SSL certificate""" """Returns new SSL certificate"""
...@@ -36,3 +60,17 @@ class Person(ERP5Person): ...@@ -36,3 +60,17 @@ class Person(ERP5Person):
"""Revokes existing certificate""" """Revokes existing certificate"""
self._checkCertificateRequest() self._checkCertificateRequest()
self._revokeCertificate() self._revokeCertificate()
security.declareProtected(Permissions.AccessContentsInformation,
'getTitle')
def getTitle(self, **kw):
"""
Returns the title if it exists or a combination of
first name and last name
"""
title = ERP5Person.getTitle(self, **kw)
test_title = title.replace(' ', '')
if test_title == '':
return self.getDefaultEmailCoordinateText(test_title)
else:
return title
<allowed_content_type_list>
<portal_type id="Person">
<item>Certificate Login</item>
</portal_type>
</allowed_content_type_list>
\ No newline at end of file
<?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>Certificate 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>
<workflow_chain>
<chain>
<type>Certificate Login</type>
<workflow>edit_workflow, login_validation_workflow</workflow>
</chain>
</workflow_chain>
\ No newline at end of file
...@@ -9,7 +9,18 @@ ...@@ -9,7 +9,18 @@
<item> <item>
<key> <string>_local_properties</string> </key> <key> <string>_local_properties</string> </key>
<value> <value>
<tuple/> <tuple>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>business_template_skin_layer_priority</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>float</string> </value>
</item>
</dictionary>
</tuple>
</value> </value>
</item> </item>
<item> <item>
...@@ -18,6 +29,10 @@ ...@@ -18,6 +29,10 @@
<tuple/> <tuple/>
</value> </value>
</item> </item>
<item>
<key> <string>business_template_skin_layer_priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>erp5_certificate_authority</string> </value> <value> <string>erp5_certificate_authority</string> </value>
......
<?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_translated_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>CertificateLogin_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>description</string>
<string>title</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>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. The system will check that there isn\'t another user with the same username.</string> </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>User Login</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>id</string> </key>
<value> <string>my_translated_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_translated_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/>
</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>
portal_type = context.getPortalType()
# Define backward compatibility for do not duplicate Certificate and ERP5 Login
if portal_type in ("ERP5 Login", "Certificate Login"):
portal_type = ("ERP5 Login", "Certificate Login")
return context.getPortalObject().Base_checkLoginExistence(
portal_type=portal_type,
reference=context.getReference(),
ignore_uid=context.getUid(),
ignore_user_uid=context.getParentValue().getUid(),
)
<?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>_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>
...@@ -31,6 +31,7 @@ import os ...@@ -31,6 +31,7 @@ import os
import random import random
import unittest import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.DCWorkflow.DCWorkflow import ValidationFailed
from AccessControl import Unauthorized from AccessControl import Unauthorized
class TestCertificateAuthority(ERP5TypeTestCase): class TestCertificateAuthority(ERP5TypeTestCase):
...@@ -39,6 +40,16 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -39,6 +40,16 @@ class TestCertificateAuthority(ERP5TypeTestCase):
return "Test Certificate Authority" return "Test Certificate Authority"
def afterSetUp(self): def afterSetUp(self):
if getattr(self.portal.portal_types.Person,
'user_can_see_himself', None) is None:
self.portal.portal_types.Person.newContent(
id="user_can_see_himself",
title="The User Himself",
role_name=("Assignee",),
role_base_category_script_id="ERP5Type_getSecurityCategoryFromSelf",
role_base_category="group",
portal_type="Role Information")
if "TEST_CA_PATH" in os.environ:
self.portal.portal_certificate_authority.certificate_authority_path = \ self.portal.portal_certificate_authority.certificate_authority_path = \
os.environ['TEST_CA_PATH'] os.environ['TEST_CA_PATH']
...@@ -51,6 +62,7 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -51,6 +62,7 @@ class TestCertificateAuthority(ERP5TypeTestCase):
reference=login, password=login) reference=login, password=login)
person.newContent(portal_type='Assignment').open() person.newContent(portal_type='Assignment').open()
person.newContent(portal_type='ERP5 Login', reference=login).validate() person.newContent(portal_type='ERP5 Login', reference=login).validate()
person.updateLocalRolesOnSecurityGroups()
self.tic() self.tic()
return person.getUserId(), login return person.getUserId(), login
...@@ -59,6 +71,30 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -59,6 +71,30 @@ class TestCertificateAuthority(ERP5TypeTestCase):
self.loginByUserName(login) self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate() certificate = person.getCertificate()
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertEquals(certificate_login.getValidationState(), "validated")
self.assertTrue('CN=%s' % user_id in certificate['certificate'])
def test_person_duplicated_login(self):
user_id, login = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
person.newContent(portal_type='ERP5 Login', reference=user_id).validate()
self.tic()
certificate = person.getCertificate()
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
# If a erp5_login is already using the User ID, just reuse it for now
self.assertEquals(len(certificate_login_list), 0)
self.assertTrue('CN=%s' % user_id in certificate['certificate']) self.assertTrue('CN=%s' % user_id in certificate['certificate'])
def test_person_revoke_certificate(self): def test_person_revoke_certificate(self):
...@@ -72,6 +108,14 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -72,6 +108,14 @@ class TestCertificateAuthority(ERP5TypeTestCase):
self.loginByUserName(login) self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate() certificate = person.getCertificate()
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertEquals(certificate_login.getValidationState(), "validated")
self.assertTrue('CN=%s' % user_id in certificate['certificate']) self.assertTrue('CN=%s' % user_id in certificate['certificate'])
person.revokeCertificate() person.revokeCertificate()
...@@ -80,9 +124,56 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -80,9 +124,56 @@ class TestCertificateAuthority(ERP5TypeTestCase):
self.loginByUserName(login) self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate() certificate = person.getCertificate()
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertTrue('CN=%s' % user_id in certificate['certificate']) self.assertTrue('CN=%s' % user_id in certificate['certificate'])
self.assertEquals(certificate_login.getValidationState(), "validated")
self.assertRaises(ValueError, person.getCertificate) self.assertRaises(ValueError, person.getCertificate)
# Ensure it don't create a second object
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertEquals(certificate_login.getValidationState(), "validated")
def test_person_request_revoke_request_certificate(self):
user_id, login = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate()
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertTrue('CN=%s' % user_id in certificate['certificate'])
self.assertEquals(certificate_login.getValidationState(), "validated")
person.revokeCertificate()
certificate = person.getCertificate()
# Ensure it don't create a second object
certificate_login_list = person.objectValues(
portal_type="Certificate Login"
)
self.assertEquals(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEquals(certificate_login.getReference(), user_id)
self.assertEquals(certificate_login.getValidationState(), "validated")
def test_person_request_certificate_for_another(self): def test_person_request_certificate_for_another(self):
_, login = self._createPerson() _, login = self._createPerson()
_, login2 = self._createPerson() _, login2 = self._createPerson()
...@@ -91,6 +182,25 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -91,6 +182,25 @@ class TestCertificateAuthority(ERP5TypeTestCase):
self.loginByUserName(login2) self.loginByUserName(login2)
self.assertRaises(Unauthorized, person.getCertificate) self.assertRaises(Unauthorized, person.getCertificate)
def test_person_duplicated_login_from_another_user(self):
user_id, login = self._createPerson()
person = self.portal.person_module.newContent(portal_type='Person',
reference=str(random.random()), password=login)
person.newContent(portal_type='Assignment').open()
# Try to create a login with other person user_id to cheat the system
person.newContent(portal_type='ERP5 Login', reference=user_id).validate()
self.tic()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.assertRaises(ValidationFailed, person.getCertificate)
certificate_login_list = [ i for i in person.objectValues(
portal_type="Certificate Login"
) if i.getValidationState() == "validated"]
self.assertEquals(len(certificate_login_list), 0)
def test_person_revoke_certificate_for_another(self): def test_person_revoke_certificate_for_another(self):
user_id, login = self._createPerson() user_id, login = self._createPerson()
_, login2 = self._createPerson() _, login2 = self._createPerson()
......
Certificate Login | view
Person | get_certificate Person | get_certificate
Person | revoke_certificate Person | revoke_certificate
\ No newline at end of file
Certificate Authority Tool Certificate Authority Tool
Certificate Login
Certificate Login | edit_workflow
Certificate Login | login_validation_workflow
\ No newline at end of file
...@@ -42,10 +42,10 @@ manage_addERP5ExternalAuthenticationPluginForm = PageTemplateFile( ...@@ -42,10 +42,10 @@ manage_addERP5ExternalAuthenticationPluginForm = PageTemplateFile(
__name__='manage_addERP5ExternalAuthenticationPluginForm') __name__='manage_addERP5ExternalAuthenticationPluginForm')
def addERP5ExternalAuthenticationPlugin(dispatcher, id, title=None, user_id_key='', def addERP5ExternalAuthenticationPlugin(dispatcher, id, title=None, user_id_key='',
REQUEST=None): login_portal_type_list=None, REQUEST=None):
""" Add a ERP5ExternalAuthenticationPlugin to a Pluggable Auth Service. """ """ Add a ERP5ExternalAuthenticationPlugin to a Pluggable Auth Service. """
plugin = ERP5ExternalAuthenticationPlugin(id, title, user_id_key) plugin = ERP5ExternalAuthenticationPlugin(id, title, user_id_key, login_portal_type_list)
dispatcher._setObject(plugin.getId(), plugin) dispatcher._setObject(plugin.getId(), plugin)
if REQUEST is not None: if REQUEST is not None:
...@@ -76,16 +76,28 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin): ...@@ -76,16 +76,28 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin):
'mode':'w', 'mode':'w',
'label':'HTTP request header key where the user_id is stored' 'label':'HTTP request header key where the user_id is stored'
}, },
{'id': 'login_portal_type_list',
'type':'lines',
'mode':'w',
'label': 'List of Login Portal Types to search'
},
) )
+ BasePlugin._properties[:] + BasePlugin._properties[:]
) )
def __init__(self, id, title=None, user_id_key=''): def __init__(self, id, title=None, user_id_key='', login_portal_type_list=None):
#Register value #Register value
self._setId(id) self._setId(id)
self.title = title self.title = title
self.user_id_key = user_id_key self.user_id_key = user_id_key
if login_portal_type_list is None:
# Keep at least one portal type as Login
login_portal_type_list = ["ERP5 Login"]
self.login_portal_type_list = login_portal_type_list
#################################### ####################################
#ILoginPasswordHostExtractionPlugin# #ILoginPasswordHostExtractionPlugin#
#################################### ####################################
...@@ -97,9 +109,10 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin): ...@@ -97,9 +109,10 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin):
if getHeader is None: if getHeader is None:
# use get_header instead for Zope-2.8 # use get_header instead for Zope-2.8
getHeader = request.get_header getHeader = request.get_header
user_id = getHeader(self.user_id_key) external_login = getHeader(self.user_id_key)
if user_id is not None: if external_login is not None:
creds['external_login'] = user_id creds['external_login'] = external_login
creds['login_portal_type'] = self.login_portal_type_list
else: else:
# fallback to default way # fallback to default way
return DumbHTTPExtractor().extractCredentials(request) return DumbHTTPExtractor().extractCredentials(request)
...@@ -125,7 +138,7 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin): ...@@ -125,7 +138,7 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin):
__name__='manage_editERP5ExternalAuthenticationPluginForm') __name__='manage_editERP5ExternalAuthenticationPluginForm')
security.declareProtected(ManageUsers, 'manage_editERP5ExternalAuthenticationPlugin') security.declareProtected(ManageUsers, 'manage_editERP5ExternalAuthenticationPlugin')
def manage_editERP5ExternalAuthenticationPlugin(self, user_id_key, RESPONSE=None): def manage_editERP5ExternalAuthenticationPlugin(self, user_id_key, login_portal_type_list, RESPONSE=None):
"""Edit the object""" """Edit the object"""
error_message = '' error_message = ''
...@@ -135,6 +148,11 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin): ...@@ -135,6 +148,11 @@ class ERP5ExternalAuthenticationPlugin(BasePlugin):
else: else:
self.user_id_key = user_id_key self.user_id_key = user_id_key
if login_portal_type_list == '' or login_portal_type_list is None:
error_message += 'Invalid portal type value '
else:
self.login_portal_type_list = login_portal_type_list
#Redirect #Redirect
if RESPONSE is not None: if RESPONSE is not None:
if error_message != '': if error_message != '':
......
...@@ -36,6 +36,17 @@ ...@@ -36,6 +36,17 @@
<input type="text" name="user_id_key" size="40" /> <input type="text" name="user_id_key" size="40" />
</td> </td>
</tr> </tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
List of Login Portal Types (One per line)
</div>
</td>
<td align="left" valign="top">
<textarea name="login_portal_type_list:lines" rows="6" cols="35">ERP5 Login
</textarea>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <input type="submit" value="add plugin"/> <td colspan="2"> <input type="submit" value="add plugin"/>
</td> </td>
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
<form action="manage_editERP5ExternalAuthenticationPlugin" method="POST"> <form action="manage_editERP5ExternalAuthenticationPlugin" method="POST">
<table tal:define="user_id_key request/user_id_key|context/user_id_key|string:;"> <table tal:define="user_id_key request/user_id_key|context/user_id_key|string:;
login_portal_type_list_ request/login_portal_type_list|context/login_portal_type_list|string:;
login_portal_type_list python: '\n'.join(login_portal_type_list_)">
<tr> <tr>
<td>HTTP request header key where the user_id is stored</td> <td>HTTP request header key where the user_id is stored</td>
...@@ -16,6 +18,14 @@ ...@@ -16,6 +18,14 @@
tal:attributes="value user_id_key;" /> tal:attributes="value user_id_key;" />
</td> </td>
</tr> </tr>
<tr>
<td>List of Login Portal Types (One per Line)</td>
<td>
<textarea name="login_portal_type_list:lines" rows="6" cols="35"
tal:content="login_portal_type_list">
</textarea>
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<input type="submit" value="save"/> <input type="submit" value="save"/>
......
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