Commit ca74a8ea authored by Rafael Monnerat's avatar Rafael Monnerat

Support multiple certificates per user

See merge request nexedi/erp5!1811
parents 63254fe4 e80c4d7e
Pipeline #29408 failed with stage
in 0 seconds
......@@ -26,7 +26,9 @@
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -56,7 +58,7 @@
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>10.0</float> </value>
<value> <float>11.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
......@@ -77,7 +79,20 @@
<dictionary>
<item>
<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>
</dictionary>
</pickle>
......
......@@ -26,7 +26,9 @@
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -77,7 +79,20 @@
<dictionary>
<item>
<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>
</dictionary>
</pickle>
......
......@@ -5,30 +5,7 @@ from Products.ERP5Type import Permissions
class Person(ERP5Person):
security = ClassSecurityInfo()
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:
self.checkUserCanChangePassword()
except Unauthorized:
......@@ -41,25 +18,20 @@ class Person(ERP5Person):
if getSecurityManager().getUser().getId() != user_id:
raise
def _getCertificate(self):
return self.getPortalObject().portal_certificate_authority\
.getNewCertificate(self._getCertificateLoginDocument().getReference())
def _revokeCertificate(self):
return self.getPortalObject().portal_certificate_authority\
.revokeCertificateByCommonName(self._getCertificateLoginDocument().getReference())
security.declarePublic('getCertificate')
def getCertificate(self):
"""Returns new SSL certificate"""
self._checkCertificateRequest()
return self._getCertificate()
security.declarePublic('revokeCertificate')
def revokeCertificate(self):
"""Revokes existing certificate"""
self._checkCertificateRequest()
self._revokeCertificate()
def _generateCertificate(self):
certificate_login = self.newContent(
portal_type="Certificate Login",
)
certificate_dict = certificate_login.getCertificate()
certificate_login.validate()
return certificate_dict
security.declarePublic('generateCertificate')
def generateCertificate(self):
"""Returns new SSL certificate
This API was kept for backward compatibility"""
self.checkCertificateRequest()
return self._generateCertificate()
security.declareProtected(Permissions.AccessContentsInformation,
'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 @@
<key> <string>_property_domain_dict</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
......@@ -45,9 +39,7 @@
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
<value> <string>Certificate Authority Tool contains Certificate Authority.</string> </value>
</item>
<item>
<key> <string>factory</string> </key>
......@@ -79,9 +71,15 @@
<none/>
</value>
</item>
<item>
<key> <string>searchable_text_property_id</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Contribution Tool</string> </value>
<value> <string></string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
......@@ -104,28 +102,7 @@
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<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>
<value> <string>erp5_ui</string> </value>
</item>
<item>
<key> <string>property_name</string> </key>
......@@ -134,7 +111,7 @@
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TranslationInformation" module="Products.ERP5Type.TranslationProviderBase"/>
</pickle>
......@@ -142,9 +119,7 @@
<dictionary>
<item>
<key> <string>domain_name</string> </key>
<value>
<none/>
</value>
<value> <string>erp5_ui</string> </value>
</item>
<item>
<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()
request = context.REQUEST
request.set('your_certificate', certificate['certificate'])
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 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_revokeCertificate</string> </value>
<value> <string>CertificateLogin_getCertificate</string> </value>
</item>
</dictionary>
</pickle>
......
parent = context.getParentValue()
if parent.getPortalType() == "Person":
parent.checkCertificateRequest()
context.revokeCertificate()
return context.Base_redirect(form_id, keep_items = {'portal_status_message' : 'Certificate revoked.'}, **kw)
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_getCertificate</string> </value>
<value> <string>CertificateLogin_revokeCertificate</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -106,6 +106,7 @@
<key> <string>right</string> </key>
<value>
<list>
<string>my_destination_reference</string>
<string>my_translated_validation_state_title</string>
</list>
</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 @@
</item>
<item>
<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>
<key> <string>form_id</string> </key>
......
......@@ -41,6 +41,10 @@
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
......@@ -115,7 +119,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_viewCertificateDialog</string> </value>
<value> <string>CertificateLogin_viewCertificateDialog</string> </value>
</item>
<item>
<key> <string>method</string> </key>
......@@ -123,7 +127,7 @@
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Person_viewCertificateDialog</string> </value>
<value> <string>CertificateLogin_viewCertificateDialog</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
......@@ -139,7 +143,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Certificate Request</string> </value>
<value> <string>Request Certificate</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
......
......@@ -211,7 +211,9 @@
<key> <string>default</string> </key>
<value> <string>Please copy both key and certificate.\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>
<key> <string>description</string> </key>
......
......@@ -29,15 +29,11 @@
import os
import random
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.Core.Workflow import ValidationFailed
from AccessControl import Unauthorized
class TestCertificateAuthority(ERP5TypeTestCase):
def getTitle(self):
return "Test Certificate Authority"
class TestPersonCertificateLogin(ERP5TypeTestCase):
def afterSetUp(self):
if getattr(self.portal.portal_types.Person,
......@@ -58,29 +54,30 @@ class TestCertificateAuthority(ERP5TypeTestCase):
def _createPerson(self):
login = str(random.random())
person = self.portal.person_module.newContent(portal_type='Person',
reference=login, password=login)
person = self.portal.person_module.newContent(portal_type='Person')
person.newContent(portal_type='Assignment').open()
person.newContent(portal_type='ERP5 Login', reference=login).validate()
person.updateLocalRolesOnSecurityGroups()
self.tic()
return person.getUserId(), login
def test_person_request_certificate(self):
def test_person_generate_certificate(self):
user_id, login = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate()
certificate = person.generateCertificate()
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")
self.assertNotEqual(certificate_login.getReference(), user_id)
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):
user_id, login = self._createPerson()
......@@ -89,98 +86,73 @@ class TestCertificateAuthority(ERP5TypeTestCase):
person.newContent(portal_type='ERP5 Login', reference=user_id).validate()
self.tic()
certificate = person.getCertificate()
certificate = person.generateCertificate()
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.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)
certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id)
self.assertEqual(certificate_login.getValidationState(), "validated")
self.assertNotEqual(certificate_login.getReference(), user_id)
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'])
person.revokeCertificate()
# ERP5 Login dont conflicts
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()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate()
certificate = person.generateCertificate()
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.assertNotEqual(certificate_login.getReference(), user_id)
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'])
self.assertNotIn('CN=%s' % login, certificate['certificate'])
self.assertEqual(certificate_login.getValidationState(), "validated")
self.assertRaises(ValueError, person.getCertificate)
new_certificate = person.generateCertificate()
# 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_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.assertEqual(len(certificate_login_list), 1)
certificate_login = certificate_login_list[0]
self.assertEqual(certificate_login.getReference(), user_id)
self.assertEqual(len(certificate_login_list), 2)
new_certificate_login = [i for i in certificate_login_list
if i.getUid() != certificate_login.getUid()][0]
self.assertIn('CN=%s' % user_id, certificate['certificate'])
self.assertEqual(certificate_login.getValidationState(), "validated")
self.assertNotEqual(new_certificate_login.getReference(), user_id)
self.assertNotEqual(new_certificate_login.getReference(), login)
self.assertNotEqual(new_certificate_login.getReference(),
certificate_login.getReference())
person.revokeCertificate()
self.assertTrue(new_certificate_login.getReference().startswith("CERT"))
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")
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")
def test_person_request_certificate_for_another(self):
def test_person_generate_certificate_for_another(self):
_, login = self._createPerson()
_, login2 = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.loginByUserName(login2)
self.assertRaises(Unauthorized, person.getCertificate)
self.assertRaises(Unauthorized, person.generateCertificate)
def test_person_duplicated_login_from_another_user(self):
user_id, login = self._createPerson()
......@@ -192,26 +164,126 @@ class TestCertificateAuthority(ERP5TypeTestCase):
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)
user = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.assertEqual(user.getUserId(), user_id)
self.assertRaises(Unauthorized, person.generateCertificate)
self.login()
certificate_login_list = [ i for i in person.objectValues(
portal_type="Certificate Login"
) if i.getValidationState() == "validated"]
self.assertEqual(len(certificate_login_list), 0)
def test_person_revoke_certificate_for_another(self):
user_id, login = self._createPerson()
_, login2 = self._createPerson()
self.loginByUserName(login)
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
certificate = person.getCertificate()
self.assertIn('CN=%s' % user_id, certificate['certificate'])
self.loginByUserName(login2)
self.assertRaises(Unauthorized, person.revokeCertificate)
def test_certificate_login_cant_validate(self):
person = self.portal.person_module.newContent(portal_type='Person')
certificate_login = person.newContent(portal_type='Certificate Login')
self.assertRaises(ValidationFailed, certificate_login.validate)
def test_certificate_login_get_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)
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():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestCertificateAuthority))
return suite
# Still raise since it has no certificate
self.assertRaises(ValueError, certificate_login.revokeCertificate)
\ No newline at end of file
......@@ -8,7 +8,7 @@
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testCertificateAuthorityTool</string> </value>
<value> <string>testCertificateAuthorityPerson</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
......@@ -22,7 +22,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5_certificate_authority.testCertificateAuthorityTool</string> </value>
<value> <string>test.erp5.testCertificateAuthorityPerson</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -48,7 +48,7 @@
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5_certificate_authority</string> </value>
<value> <string>erp5</string> </value>
</item>
<item>
<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):
index = open(self.index).read().splitlines()
valid_line_list = [q for q in index if q.startswith('V') and
('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)
return valid_line_list[0].split('\t')[3]
return [l.split('\t')[3] for l in valid_line_list]
security.declareProtected(Permissions.AccessContentsInformation,
'revokeCertificate')
'revokeCertificateByCommonName')
def revokeCertificateByCommonName(self, common_name):
self._checkCertificateAuthority()
serial = self._getValidSerial(common_name)
for serial in self._getValidSerial(common_name):
self.revokeCertificate(serial)
InitializeClass(CertificateAuthorityTool)
Certificate Login | get_certificate
Certificate Login | revoke_certificate
Certificate Login | view
\ No newline at end of file
Person | get_certificate
Person | revoke_certificate
\ 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
\ No newline at end of file
test.erp5.testCertificateAuthorityPerson
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