# Copyright (C) 2021 FreeIPA Project Contributors - see LICENSE file from collections import namedtuple from io import BytesIO from lxml.etree import parse as myparse # pylint: disable=no-name-in-module import pytest import textwrap from unittest.mock import mock_open, patch from ipaplatform.constants import constants from ipaserver.install import dogtaginstance class MyDogtagInstance(dogtaginstance.DogtagInstance): """Purpose is to avoid reading configuration files. The real DogtagInstance will open up the system store and try to determine the actual version of tomcat installed. Fake it instead. """ def __init__(self, is_newer): self.service_user = constants.PKI_USER self.ajp_secret = None self.is_newer = is_newer def _is_newer_tomcat_version(self, default=None): return self.is_newer def mock_etree_parse(data): """Convert a string into a file-like object to pass in the XML""" f = BytesIO(data.strip().encode('utf-8')) return myparse(f) def mock_pkiuser_entity(): """Return struct_passwd for mocked pkiuser""" StructPasswd = namedtuple( "StructPasswd", [ "pw_name", "pw_passwd", "pw_uid", "pw_gid", "pw_gecos", "pw_dir", "pw_shell", ] ) pkiuser_entity = StructPasswd( constants.PKI_USER, pw_passwd="x", pw_uid=-1, pw_gid=-1, pw_gecos="", pw_dir="/dev/null", pw_shell="/sbin/nologin", ) return pkiuser_entity # Format of test_data is: # ( # is_newer_tomcat (boolean), # XML input, # expected secret attribute(s), # expected password(s), # rewrite of XML file expected (boolean), # ) test_data = ( ( # Case 1: Upgrade requiredSecret to secret True, textwrap.dedent(""" """), ('secret',), ('testing_ajp_secret',), True, ), ( # Case 2: One connector with secret, no update is needed True, textwrap.dedent(""" """), ('secret',), ('testing_ajp_secret',), False, ), ( # Case 3: Two connectors, old secret attribute, different secrets True, textwrap.dedent(""" """), ('secret', 'secret'), ('testing_ajp_secret', 'testing_ajp_secret'), True, ), ( # Case 4: Two connectors, new secret attribute, same secrets True, textwrap.dedent(""" """), ('secret', 'secret'), ('testing_ajp_secret', 'testing_ajp_secret'), False, ), ( # Case 5: Two connectors, no secrets True, textwrap.dedent(""" """), ('secret', 'secret'), ('RANDOM', 'RANDOM'), True, ), ( # Case 6: Older tomcat, no update needed for requiredSecret False, textwrap.dedent(""" """), ('requiredSecret',), ('testing_ajp_secret',), False, ), ) class TestAJPSecretUpgrade: @patch("ipaplatform.base.constants.pwd.getpwnam") @patch("ipaplatform.base.constants.os.chown") @patch("ipaserver.install.dogtaginstance.lxml.etree.parse") @pytest.mark.parametrize("test_data", test_data) def test_connecter(self, mock_parse, mock_chown, mock_getpwnam, test_data): is_newer, data, secret, expect, rewrite = test_data mock_chown.return_value = None mock_parse.return_value = mock_etree_parse(data) mock_getpwnam.return_value = mock_pkiuser_entity() dogtag = MyDogtagInstance(is_newer) with patch('ipaserver.install.dogtaginstance.open', mock_open()) \ as mocked_file: dogtag.secure_ajp_connector() if rewrite: newdata = mocked_file().write.call_args.args f = BytesIO(newdata[0]) server_xml = myparse(f) doc = server_xml.getroot() connectors = doc.xpath('//Connector[@protocol="AJP/1.3"]') assert len(connectors) == len(secret) i = 0 for connector in connectors: if expect[i] == 'RANDOM': assert connector.attrib[secret[i]] else: assert connector.attrib[secret[i]] == expect[i] i += 1 else: assert mocked_file().write.call_args is None