Commit ca74a8ea authored by Rafael Monnerat's avatar Rafael Monnerat

Support multiple certificates per user

See merge request !1811
parents 63254fe4 e80c4d7e
Pipeline #29408 failed with stage
in 0 seconds
...@@ -26,7 +26,9 @@ ...@@ -26,7 +26,9 @@
</item> </item>
<item> <item>
<key> <string>condition</string> </key> <key> <string>condition</string> </key>
<value> <string></string> </value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
...@@ -56,7 +58,7 @@ ...@@ -56,7 +58,7 @@
</item> </item>
<item> <item>
<key> <string>priority</string> </key> <key> <string>priority</string> </key>
<value> <float>10.0</float> </value> <value> <float>11.0</float> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
...@@ -77,7 +79,20 @@ ...@@ -77,7 +79,20 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>text</string> </key> <key> <string>text</string> </key>
<value> <string>string:${object_url}/Person_getCertificate</string> </value> <value> <string>string:${object_url}/CertificateLogin_getCertificate</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: here.getDestinationReference() is None</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -26,7 +26,9 @@ ...@@ -26,7 +26,9 @@
</item> </item>
<item> <item>
<key> <string>condition</string> </key> <key> <string>condition</string> </key>
<value> <string></string> </value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
...@@ -77,7 +79,20 @@ ...@@ -77,7 +79,20 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>text</string> </key> <key> <string>text</string> </key>
<value> <string>string:${object_url}/Person_revokeCertificate</string> </value> <value> <string>string:${object_url}/CertificateLogin_revokeCertificate</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: here.getDestinationReference() is not None</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -5,30 +5,7 @@ from Products.ERP5Type import Permissions ...@@ -5,30 +5,7 @@ from Products.ERP5Type import Permissions
class Person(ERP5Person): class Person(ERP5Person):
security = ClassSecurityInfo() security = ClassSecurityInfo()
def _getCertificateLoginDocument(self): def checkCertificateRequest(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):
try: try:
self.checkUserCanChangePassword() self.checkUserCanChangePassword()
except Unauthorized: except Unauthorized:
...@@ -41,25 +18,20 @@ class Person(ERP5Person): ...@@ -41,25 +18,20 @@ class Person(ERP5Person):
if getSecurityManager().getUser().getId() != user_id: if getSecurityManager().getUser().getId() != user_id:
raise raise
def _getCertificate(self): def _generateCertificate(self):
return self.getPortalObject().portal_certificate_authority\ certificate_login = self.newContent(
.getNewCertificate(self._getCertificateLoginDocument().getReference()) portal_type="Certificate Login",
)
def _revokeCertificate(self): certificate_dict = certificate_login.getCertificate()
return self.getPortalObject().portal_certificate_authority\ certificate_login.validate()
.revokeCertificateByCommonName(self._getCertificateLoginDocument().getReference()) return certificate_dict
security.declarePublic('getCertificate') security.declarePublic('generateCertificate')
def getCertificate(self): def generateCertificate(self):
"""Returns new SSL certificate""" """Returns new SSL certificate
self._checkCertificateRequest() This API was kept for backward compatibility"""
return self._getCertificate() self.checkCertificateRequest()
return self._generateCertificate()
security.declarePublic('revokeCertificate')
def revokeCertificate(self):
"""Revokes existing certificate"""
self._checkCertificateRequest()
self._revokeCertificate()
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getTitle') 'getTitle')
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2023 Nexedi SA and Contributors. All Rights Reserved.
# Rafael Monnerat <rafael@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility 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
# guarantees and support are strongly advised 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
class CertificateLoginMixin:
security = ClassSecurityInfo()
def _getCertificate(self):
portal = self.getPortalObject()
_id = self._generateRandomId()
reference = 'CERTLOGIN-%i-%s' % (
portal.portal_ids.generateNewId(
id_group='certificate_login',
id_generator='non_continuous_integer_increasing',
), _id
)
self.setReference(reference)
certificate_dict = self.getPortalObject().portal_certificate_authority\
.getNewCertificate(self.getReference())
self.setDestinationReference(certificate_dict['id'])
return certificate_dict
def _revokeCertificate(self):
if self.getDestinationReference() is not None:
certificate_dict = self.getPortalObject().portal_certificate_authority\
.revokeCertificate(self.getDestinationReference())
self.setDestinationReference(None)
return certificate_dict
elif self.getReference() is not None:
# Backward compatibility whenever the serial wast set
certificate_dict = self.getPortalObject().portal_certificate_authority\
.revokeCertificateByCommonName(self.getReference())
# Ensure it is None
self.setDestinationReference(None)
return certificate_dict
else:
raise ValueError("No certificate found to revoke!")
security.declarePublic('getCertificate')
def getCertificate(self):
"""Returns new SSL certificate"""
if self.getDestinationReference() is not None:
raise ValueError("Certificate was already issued, please revoke first.")
return self._getCertificate()
security.declarePublic('revokeCertificate')
def revokeCertificate(self):
"""Revokes existing certificate"""
self._revokeCertificate()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Mixin Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>CertificateLoginMixin</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>mixin.erp5.CertificateLoginMixin</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">AAAAAAAAAAI=</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>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -10,22 +10,16 @@ ...@@ -10,22 +10,16 @@
<key> <string>_property_domain_dict</string> </key> <key> <string>_property_domain_dict</string> </key>
<value> <value>
<dictionary> <dictionary>
<item>
<key> <string>description</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>short_title</string> </key> <key> <string>short_title</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -45,9 +39,7 @@ ...@@ -45,9 +39,7 @@
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <value> <string>Certificate Authority Tool contains Certificate Authority.</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>factory</string> </key> <key> <string>factory</string> </key>
...@@ -79,9 +71,15 @@ ...@@ -79,9 +71,15 @@
<none/> <none/>
</value> </value>
</item> </item>
<item>
<key> <string>searchable_text_property_id</string> </key>
<value>
<tuple/>
</value>
</item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Contribution Tool</string> </value> <value> <string></string> </value>
</item> </item>
<item> <item>
<key> <string>type_class</string> </key> <key> <string>type_class</string> </key>
...@@ -104,28 +102,7 @@ ...@@ -104,28 +102,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>domain_name</string> </key> <key> <string>domain_name</string> </key>
<value> <value> <string>erp5_ui</string> </value>
<none/>
</value>
</item>
<item>
<key> <string>property_name</string> </key>
<value> <string>description</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>property_name</string> </key> <key> <string>property_name</string> </key>
...@@ -134,7 +111,7 @@ ...@@ -134,7 +111,7 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="4" aka="AAAAAAAAAAQ="> <record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/> <global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle> </pickle>
...@@ -142,9 +119,7 @@ ...@@ -142,9 +119,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>domain_name</string> </key> <key> <string>domain_name</string> </key>
<value> <value> <string>erp5_ui</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>property_name</string> </key> <key> <string>property_name</string> </key>
......
<type_mixin>
<portal_type id="Certificate Login">
<item>CertificateLoginMixin</item>
</portal_type>
</type_mixin>
\ No newline at end of file
parent = context.getParentValue()
if parent.getPortalType() == "Person":
parent.checkCertificateRequest()
certificate = context.getCertificate() certificate = context.getCertificate()
request = context.REQUEST request = context.REQUEST
request.set('your_certificate', certificate['certificate']) request.set('your_certificate', certificate['certificate'])
request.set('your_key', certificate['key']) request.set('your_key', certificate['key'])
return context.Person_viewCertificateDialog() return context.CertificateLogin_viewCertificateDialog(
keep_items = {'portal_status_message' : 'Certificate generated.'}
)
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_revokeCertificate</string> </value> <value> <string>CertificateLogin_getCertificate</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
parent = context.getParentValue()
if parent.getPortalType() == "Person":
parent.checkCertificateRequest()
context.revokeCertificate() context.revokeCertificate()
return context.Base_redirect(form_id, keep_items = {'portal_status_message' : 'Certificate revoked.'}, **kw) return context.Base_redirect(form_id, keep_items = {'portal_status_message' : 'Certificate revoked.'}, **kw)
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_getCertificate</string> </value> <value> <string>CertificateLogin_revokeCertificate</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
...@@ -106,6 +106,7 @@ ...@@ -106,6 +106,7 @@
<key> <string>right</string> </key> <key> <string>right</string> </key>
<value> <value>
<list> <list>
<string>my_destination_reference</string>
<string>my_translated_validation_state_title</string> <string>my_translated_validation_state_title</string>
</list> </list>
</value> </value>
......
<?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>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_destination_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>field_id</string> </key>
<value> <string>my_view_mode_read_only_reference</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>Authorisation Identity</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
</item> </item>
<item> <item>
<key> <string>field_id</string> </key> <key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value> <value> <string>my_view_mode_read_only_reference</string> </value>
</item> </item>
<item> <item>
<key> <string>form_id</string> </key> <key> <string>form_id</string> </key>
......
...@@ -41,6 +41,10 @@ ...@@ -41,6 +41,10 @@
<key> <string>action</string> </key> <key> <string>action</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
</item> </item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <string></string> </value> <value> <string></string> </value>
...@@ -115,7 +119,7 @@ ...@@ -115,7 +119,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>Person_viewCertificateDialog</string> </value> <value> <string>CertificateLogin_viewCertificateDialog</string> </value>
</item> </item>
<item> <item>
<key> <string>method</string> </key> <key> <string>method</string> </key>
...@@ -123,7 +127,7 @@ ...@@ -123,7 +127,7 @@
</item> </item>
<item> <item>
<key> <string>name</string> </key> <key> <string>name</string> </key>
<value> <string>Person_viewCertificateDialog</string> </value> <value> <string>CertificateLogin_viewCertificateDialog</string> </value>
</item> </item>
<item> <item>
<key> <string>pt</string> </key> <key> <string>pt</string> </key>
...@@ -139,7 +143,7 @@ ...@@ -139,7 +143,7 @@
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>Certificate Request</string> </value> <value> <string>Request Certificate</string> </value>
</item> </item>
<item> <item>
<key> <string>unicode_mode</string> </key> <key> <string>unicode_mode</string> </key>
......
...@@ -211,7 +211,9 @@ ...@@ -211,7 +211,9 @@
<key> <string>default</string> </key> <key> <string>default</string> </key>
<value> <string>Please copy both key and certificate.\n <value> <string>Please copy both key and certificate.\n
\n \n
They are NOT stored anywhere for security reason.</string> </value> They are NOT stored anywhere for security reason.\n
\n
To activate the certificate, you still have to validate the Certificate Login</string> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
......
...@@ -29,15 +29,11 @@ ...@@ -29,15 +29,11 @@
import os import os
import random import random
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.Core.Workflow import ValidationFailed from Products.ERP5Type.Core.Workflow import ValidationFailed
from AccessControl import Unauthorized from AccessControl import Unauthorized
class TestCertificateAuthority(ERP5TypeTestCase): class TestPersonCertificateLogin(ERP5TypeTestCase):
def getTitle(self):
return "Test Certificate Authority"
def afterSetUp(self): def afterSetUp(self):
if getattr(self.portal.portal_types.Person, if getattr(self.portal.portal_types.Person,
...@@ -58,29 +54,30 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -58,29 +54,30 @@ class TestCertificateAuthority(ERP5TypeTestCase):
def _createPerson(self): def _createPerson(self):
login = str(random.random()) login = str(random.random())
person = self.portal.person_module.newContent(portal_type='Person', person = self.portal.person_module.newContent(portal_type='Person')
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() person.updateLocalRolesOnSecurityGroups()
self.tic() self.tic()
return person.getUserId(), login return person.getUserId(), login
def test_person_request_certificate(self): def test_person_generate_certificate(self):
user_id, login = self._createPerson() user_id, login = self._createPerson()
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.generateCertificate()
certificate_login_list = person.objectValues( certificate_login_list = person.objectValues(
portal_type="Certificate Login" portal_type="Certificate Login"
) )
self.assertEqual(len(certificate_login_list), 1) self.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0] certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id) self.assertNotEqual(certificate_login.getReference(), user_id)
self.assertEqual(certificate_login.getValidationState(), "validated") self.assertNotEqual(certificate_login.getReference(), login)
self.assertTrue(certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % user_id, certificate['certificate']) self.assertIn('CN=%s' % certificate_login.getReference(), certificate['certificate'])
self.assertNotIn('CN=%s' % user_id, certificate['certificate'])
def test_person_duplicated_login(self): def test_person_duplicated_login(self):
user_id, login = self._createPerson() user_id, login = self._createPerson()
...@@ -89,98 +86,73 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -89,98 +86,73 @@ class TestCertificateAuthority(ERP5TypeTestCase):
person.newContent(portal_type='ERP5 Login', reference=user_id).validate() person.newContent(portal_type='ERP5 Login', reference=user_id).validate()
self.tic() self.tic()
certificate = person.getCertificate() certificate = person.generateCertificate()
certificate_login_list = person.objectValues( certificate_login_list = person.objectValues(
portal_type="Certificate Login" portal_type="Certificate Login"
) )
# If a erp5_login is already using the User ID, just reuse it for now # If a erp5_login is already using the User ID, just reuse it for now
self.assertEqual(len(certificate_login_list), 0)
self.assertIn('CN=%s' % user_id, certificate['certificate'])
def test_person_revoke_certificate(self):
_, login = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.assertRaises(ValueError, person.revokeCertificate)
def test_person_request_revoke_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.assertEqual(len(certificate_login_list), 1) self.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0] certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id) self.assertNotEqual(certificate_login.getReference(), user_id)
self.assertEqual(certificate_login.getValidationState(), "validated") self.assertNotEqual(certificate_login.getReference(), login)
self.assertTrue(certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % certificate_login.getReference(), certificate['certificate'])
self.assertNotIn('CN=%s' % user_id, certificate['certificate'])
self.assertIn('CN=%s' % user_id, certificate['certificate']) # ERP5 Login dont conflicts
person.revokeCertificate() person.newContent(portal_type='ERP5 Login',
reference=certificate_login.getReference()).validate()
def test_person_request_certificate_twice(self): def test_person_generate_certificate_twice(self):
user_id, login = self._createPerson() user_id, login = self._createPerson()
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.generateCertificate()
certificate_login_list = person.objectValues( certificate_login_list = person.objectValues(
portal_type="Certificate Login" portal_type="Certificate Login"
) )
self.assertEqual(len(certificate_login_list), 1) self.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0] certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id) self.assertNotEqual(certificate_login.getReference(), user_id)
self.assertNotEqual(certificate_login.getReference(), login)
self.assertIn('CN=%s' % user_id, certificate['certificate']) self.assertTrue(certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % certificate_login.getReference(), certificate['certificate'])
self.assertNotIn('CN=%s' % user_id, certificate['certificate'])
self.assertNotIn('CN=%s' % login, certificate['certificate'])
self.assertEqual(certificate_login.getValidationState(), "validated") self.assertEqual(certificate_login.getValidationState(), "validated")
self.assertRaises(ValueError, person.getCertificate) new_certificate = person.generateCertificate()
# Ensure it don't create a second object # Ensure it don't create a second object
certificate_login_list = person.objectValues( certificate_login_list = person.objectValues(
portal_type="Certificate Login" portal_type="Certificate Login"
) )
self.assertEqual(len(certificate_login_list), 1) self.assertEqual(len(certificate_login_list), 2)
certificate_login = certificate_login_list[0] new_certificate_login = [i for i in certificate_login_list
self.assertEqual(certificate_login.getReference(), user_id) if i.getUid() != certificate_login.getUid()][0]
self.assertEqual(certificate_login.getValidationState(), "validated")
def test_person_request_revoke_request_certificate(self): self.assertNotEqual(new_certificate_login.getReference(), user_id)
user_id, login = self._createPerson() self.assertNotEqual(new_certificate_login.getReference(), login)
self.loginByUserName(login) self.assertNotEqual(new_certificate_login.getReference(),
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() certificate_login.getReference())
certificate = person.getCertificate()
self.assertTrue(new_certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % new_certificate_login.getReference(), new_certificate['certificate'])
self.assertNotIn('CN=%s' % user_id, new_certificate['certificate'])
self.assertNotIn('CN=%s' % login, new_certificate['certificate'])
self.assertNotIn('CN=%s' % certificate_login.getReference(), new_certificate['certificate'])
self.assertEqual(new_certificate_login.getValidationState(), "validated")
certificate_login_list = person.objectValues( def test_person_generate_certificate_for_another(self):
portal_type="Certificate Login"
)
self.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id)
self.assertIn('CN=%s' % user_id, certificate['certificate'])
self.assertEqual(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.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id)
self.assertEqual(certificate_login.getValidationState(), "validated")
def test_person_request_certificate_for_another(self):
_, login = self._createPerson() _, login = self._createPerson()
_, login2 = self._createPerson() _, login2 = self._createPerson()
self.loginByUserName(login) self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.loginByUserName(login2) self.loginByUserName(login2)
self.assertRaises(Unauthorized, person.getCertificate) self.assertRaises(Unauthorized, person.generateCertificate)
def test_person_duplicated_login_from_another_user(self): def test_person_duplicated_login_from_another_user(self):
user_id, login = self._createPerson() user_id, login = self._createPerson()
...@@ -192,26 +164,126 @@ class TestCertificateAuthority(ERP5TypeTestCase): ...@@ -192,26 +164,126 @@ class TestCertificateAuthority(ERP5TypeTestCase):
person.newContent(portal_type='ERP5 Login', reference=user_id).validate() person.newContent(portal_type='ERP5 Login', reference=user_id).validate()
self.tic() self.tic()
self.loginByUserName(login) self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue() user = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.assertRaises(ValidationFailed, person.getCertificate) self.assertEqual(user.getUserId(), user_id)
self.assertRaises(Unauthorized, person.generateCertificate)
self.login()
certificate_login_list = [ i for i in person.objectValues( certificate_login_list = [ i for i in person.objectValues(
portal_type="Certificate Login" portal_type="Certificate Login"
) if i.getValidationState() == "validated"] ) if i.getValidationState() == "validated"]
self.assertEqual(len(certificate_login_list), 0) self.assertEqual(len(certificate_login_list), 0)
def test_person_revoke_certificate_for_another(self): def test_certificate_login_cant_validate(self):
user_id, login = self._createPerson() person = self.portal.person_module.newContent(portal_type='Person')
_, login2 = self._createPerson() certificate_login = person.newContent(portal_type='Certificate Login')
self.loginByUserName(login) self.assertRaises(ValidationFailed, certificate_login.validate)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate() def test_certificate_login_get_certificate(self):
self.assertIn('CN=%s' % user_id, certificate['certificate']) person = self.portal.person_module.newContent(portal_type='Person')
self.loginByUserName(login2) certificate_login = person.newContent(portal_type='Certificate Login')
self.assertRaises(Unauthorized, person.revokeCertificate) self.assertEqual(certificate_login.getReference(), None)
certificate_dict = certificate_login.getCertificate()
self.assertNotEqual(certificate_login.getReference(), None)
self.assertNotEqual(certificate_login.getReference(), person.getUserId())
self.assertTrue(certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % certificate_login.getReference(), certificate_dict['certificate'])
self.assertNotIn('CN=%s' % person.getUserId(), certificate_dict['certificate'])
self.assertEqual(certificate_login.getValidationState(), "draft")
certificate_login.validate()
self.assertEqual(certificate_login.getValidationState(), "validated")
def test_certificate_login_get_certificate_set_reference(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login',
reference="FAKEREFERENCE-%s" % (person.getUid()))
self.assertNotEqual(certificate_login.getReference(), None)
certificate_dict = certificate_login.getCertificate()
self.assertNotEqual(certificate_login.getReference(), None)
self.assertNotEqual(certificate_login.getReference(), person.getUserId())
# Reference is reset while setting the generate the certificate.
self.assertTrue(certificate_login.getReference().startswith("CERT"))
self.assertIn('CN=%s' % certificate_login.getReference(), certificate_dict['certificate'])
self.assertNotIn('CN=%s' % person.getUserId(), certificate_dict['certificate'])
self.assertEqual(certificate_login.getValidationState(), "draft")
certificate_login.validate()
self.assertEqual(certificate_login.getValidationState(), "validated")
def test_certificate_login_get_certificate_twice(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login')
self.assertEqual(certificate_login.getReference(), None)
certificate_dict = certificate_login.getCertificate()
reference = certificate_login.getReference()
# Reference is reset while setting the generate the certificate.
self.assertTrue(reference.startswith("CERT"))
self.assertIn('CN=%s' % reference, certificate_dict['certificate'])
self.assertNotIn('CN=%s' % person.getUserId(), certificate_dict['certificate'])
self.assertEqual(certificate_login.getValidationState(), "draft")
self.assertRaises(ValueError, certificate_login.getCertificate)
def test_certificate_login_revoke(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login')
self.assertEqual(certificate_login.getReference(), None)
self.assertRaises(ValueError, certificate_login.revokeCertificate)
certificate_dict = certificate_login.getCertificate()
reference = certificate_login.getReference()
self.assertTrue(reference.startswith("CERT"))
self.assertIn('CN=%s' % reference, certificate_dict['certificate'])
self.assertNotEqual(certificate_login.getDestinationReference(), None)
self.assertEqual(None, certificate_login.revokeCertificate())
self.assertEqual(certificate_login.getDestinationReference(), None)
self.assertEqual(reference, certificate_login.getReference())
# Revoke again must raise
self.assertRaises(ValueError, certificate_login.revokeCertificate)
def test_certificate_login_revoke_backward_compatibility(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login')
self.assertEqual(certificate_login.getReference(), None)
self.assertRaises(ValueError, certificate_login.revokeCertificate)
certificate_dict = certificate_login.getCertificate()
reference = certificate_login.getReference()
self.assertTrue(reference.startswith("CERT"))
self.assertIn('CN=%s' % reference, certificate_dict['certificate'])
self.assertNotEqual(certificate_login.getDestinationReference(), None)
# Older implementation wont set it on the Certificate login
certificate_login.setDestinationReference(None)
self.assertEqual(None, certificate_login.revokeCertificate())
self.assertEqual(certificate_login.getDestinationReference(), None)
self.assertEqual(reference, certificate_login.getReference())
# Still raise since it has no valid certificate anymore
self.assertRaises(ValueError, certificate_login.revokeCertificate)
def test_certificate_login_revoke_no_certificate(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login')
self.assertEqual(certificate_login.getReference(), None)
self.assertRaises(ValueError, certificate_login.revokeCertificate)
certificate_login.setReference("FAKEREFERENCE-%s" % (person.getUid()))
def test_suite(): # Still raise since it has no certificate
suite = unittest.TestSuite() self.assertRaises(ValueError, certificate_login.revokeCertificate)
suite.addTest(unittest.makeSuite(TestCertificateAuthority)) \ No newline at end of file
return suite
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>default_reference</string> </key>
<value> <string>testCertificateAuthorityTool</string> </value> <value> <string>testCertificateAuthorityPerson</string> </value>
</item> </item>
<item> <item>
<key> <string>default_source_reference</string> </key> <key> <string>default_source_reference</string> </key>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>test.erp5_certificate_authority.testCertificateAuthorityTool</string> </value> <value> <string>test.erp5.testCertificateAuthorityPerson</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</item> </item>
<item> <item>
<key> <string>version</string> </key> <key> <string>version</string> </key>
<value> <string>erp5_certificate_authority</string> </value> <value> <string>erp5</string> </value>
</item> </item>
<item> <item>
<key> <string>workflow_history</string> </key> <key> <string>workflow_history</string> </key>
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Ivan Tyagov <ivan@nexedi.com>
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import os
import random
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from erp5.component.tool.CertificateAuthorityTool import CertificateAuthorityBusy
#from AccessControl import Unauthorized
class TestCertificateAuthorityTool(ERP5TypeTestCase):
def afterSetUp(self):
if "TEST_CA_PATH" in os.environ:
self.portal.portal_certificate_authority.certificate_authority_path = \
os.environ['TEST_CA_PATH']
def getBusinessTemplateList(self):
return ('erp5_base', 'erp5_certificate_authority')
def test_lock_unlock(self):
certificate_authority_tool = self.portal.portal_certificate_authority
certificate_authority_tool._checkCertificateAuthority()
try:
certificate_authority_tool._lockCertificateAuthority()
certificate_authority_tool._unlockCertificateAuthority()
certificate_authority_tool._lockCertificateAuthority()
self.assertRaises(CertificateAuthorityBusy, certificate_authority_tool._lockCertificateAuthority)
finally:
certificate_authority_tool._unlockCertificateAuthority()
def test_getNewCertificate(self):
certificate_authority_tool = self.portal.portal_certificate_authority
common_name = str(random.random())
certificate_dict = certificate_authority_tool.getNewCertificate(common_name)
self.assertEqual(common_name, certificate_dict['common_name'])
self.assertNotEqual(None, certificate_dict['id'])
self.assertNotEqual(None, certificate_dict['key'])
self.assertNotEqual(None, certificate_dict['certificate'])
self.assertIn('CN=%s' % common_name, certificate_dict['certificate'])
# Check serial
serial = certificate_authority_tool._getValidSerial(common_name)
self.assertEqual(serial, [certificate_dict['id'].upper()])
self.assertRaises(ValueError,
certificate_authority_tool.getNewCertificate, common_name)
def test_getNewCertificate_locked(self):
certificate_authority_tool = self.portal.portal_certificate_authority
certificate_authority_tool._checkCertificateAuthority()
try:
certificate_authority_tool._lockCertificateAuthority()
common_name = str(random.random())
self.assertRaises(CertificateAuthorityBusy,
certificate_authority_tool.getNewCertificate, common_name)
certificate_authority_tool._unlockCertificateAuthority()
certificate_dict = certificate_authority_tool.getNewCertificate(common_name)
self.assertEqual(common_name, certificate_dict['common_name'])
finally:
certificate_authority_tool._unlockCertificateAuthority()
def test_revokeCertificate_raise(self):
certificate_authority_tool = self.portal.portal_certificate_authority
common_name = str(random.random())
self.assertRaises(ValueError,
certificate_authority_tool.revokeCertificate, common_name)
def test_revokeCertificate(self):
certificate_authority_tool = self.portal.portal_certificate_authority
common_name = str(random.random())
certificate_dict = certificate_authority_tool.getNewCertificate(common_name)
self.assertEqual(common_name, certificate_dict['common_name'])
self.assertNotEqual(None, certificate_dict['id'])
self.assertIn('CN=%s' % common_name, certificate_dict['certificate'])
# Check serial
serial_list = certificate_authority_tool._getValidSerial(common_name)
self.assertEqual(len(serial_list), 1)
self.assertEqual(serial_list[0], certificate_dict['id'].upper())
revoke_dict = certificate_authority_tool.revokeCertificate(serial_list[0])
self.assertNotEqual(revoke_dict['crl'], None)
# No valid certificate anymore
self.assertRaises(ValueError, certificate_authority_tool._getValidSerial, common_name)
def test_revokeCertificateByName(self):
certificate_authority_tool = self.portal.portal_certificate_authority
common_name = str(random.random())
certificate_dict = certificate_authority_tool.getNewCertificate(common_name)
self.assertEqual(common_name, certificate_dict['common_name'])
self.assertNotEqual(None, certificate_dict['id'])
self.assertIn('CN=%s' % common_name, certificate_dict['certificate'])
serial_list = certificate_authority_tool._getValidSerial(common_name)
self.assertEqual(len(serial_list), 1)
self.assertEqual(serial_list[0], certificate_dict['id'].upper())
response = certificate_authority_tool.revokeCertificateByCommonName(common_name)
self.assertEqual(None, response)
# No valid certificate anymore
self.assertRaises(ValueError, certificate_authority_tool._getValidSerial, common_name)
def test_revokeCertificate_locked(self):
certificate_authority_tool = self.portal.portal_certificate_authority
common_name = str(random.random())
certificate_dict = certificate_authority_tool.getNewCertificate(common_name)
self.assertEqual(common_name, certificate_dict['common_name'])
try:
certificate_authority_tool._lockCertificateAuthority()
self.assertRaises(CertificateAuthorityBusy,
certificate_authority_tool.revokeCertificateByCommonName, common_name)
certificate_authority_tool._unlockCertificateAuthority()
response = certificate_authority_tool.revokeCertificateByCommonName(common_name)
self.assertEqual(None, response)
# No valid certificate anymore
self.assertRaises(ValueError, certificate_authority_tool._getValidSerial, common_name)
finally:
certificate_authority_tool._unlockCertificateAuthority()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testCertificateAuthorityTool</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testCertificateAuthorityTool</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test 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">AAAAAAAAAAI=</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>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -281,15 +281,15 @@ class CertificateAuthorityTool(BaseTool): ...@@ -281,15 +281,15 @@ class CertificateAuthorityTool(BaseTool):
index = open(self.index).read().splitlines() index = open(self.index).read().splitlines()
valid_line_list = [q for q in index if q.startswith('V') and valid_line_list = [q for q in index if q.startswith('V') and
('CN=%s/' % common_name in q)] ('CN=%s/' % common_name in q)]
if len(valid_line_list) != 1: if len(valid_line_list) < 1:
raise ValueError('No certificate for %r' % common_name) raise ValueError('No certificate for %r' % common_name)
return valid_line_list[0].split('\t')[3] return [l.split('\t')[3] for l in valid_line_list]
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'revokeCertificate') 'revokeCertificateByCommonName')
def revokeCertificateByCommonName(self, common_name): def revokeCertificateByCommonName(self, common_name):
self._checkCertificateAuthority() self._checkCertificateAuthority()
serial = self._getValidSerial(common_name) for serial in self._getValidSerial(common_name):
self.revokeCertificate(serial) self.revokeCertificate(serial)
InitializeClass(CertificateAuthorityTool) InitializeClass(CertificateAuthorityTool)
Certificate Login | view Certificate Login | get_certificate
Person | get_certificate Certificate Login | revoke_certificate
Person | revoke_certificate Certificate Login | view
\ No newline at end of file \ No newline at end of file
mixin.erp5.CertificateLoginMixin
\ No newline at end of file
Certificate Login | CertificateLoginMixin
\ No newline at end of file
test.erp5_certificate_authority.testCertificateAuthorityTool test.erp5.testCertificateAuthorityPerson
\ No newline at end of file test.erp5.testCertificateAuthorityTool
\ No newline at end of file
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