#
# Copyright (C) 2020  FreeIPA Contributors see COPYING for license
#

"""
Module provides tests for ipa-cert-fix CLI.
"""
import pytest
import time

import logging
from ipaplatform.paths import paths
from ipatests.pytest_ipa.integration import tasks
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration.test_caless import CALessBase, ipa_certs_cleanup


logger = logging.getLogger(__name__)


def server_install_teardown(func):
    def wrapped(*args):
        master = args[0].master
        try:
            func(*args)
        finally:
            ipa_certs_cleanup(master)
    return wrapped


def check_status(host, cert_count, state, timeout=600):
    """Helper method to check that if all the certs are in given state
    :param host: the host
    :param cert_count: no of cert to look for
    :param state: state to check for
    :param timeout: max time in seconds to wait for the state
    """
    for _i in range(0, timeout, 10):
        result = host.run_command(['getcert', 'list'])
        count = result.stdout_text.count(f"status: {state}")
        logger.info("cert count in %s state : %s", state, count)
        if int(count) == cert_count:
            break
        time.sleep(10)
    else:
        raise RuntimeError("request timed out")

    return count


@pytest.fixture
def expire_cert_critical():
    """
    Fixture to expire the certs by moving the system date using
    date -s command and revert it back
    """

    hosts = dict()

    def _expire_cert_critical(host, setup_kra=False):
        hosts['host'] = host
        # Do not install NTP as the test plays with the date
        tasks.install_master(host, setup_dns=False,
                             extra_args=['--no-ntp'])
        if setup_kra:
            tasks.install_kra(host)
        host.run_command(['systemctl', 'stop', 'chronyd'])
        host.run_command(['date', '-s', '+3Years+1day'])

    yield _expire_cert_critical

    host = hosts.pop('host')
    tasks.uninstall_master(host)
    host.run_command(['date', '-s', '-3Years-1day'])
    host.run_command(['systemctl', 'start', 'chronyd'])


class TestIpaCertFix(IntegrationTest):
    @classmethod
    def uninstall(cls, mh):
        # Uninstall method is empty as the uninstallation is done in
        # the fixture
        pass

    def test_missing_csr(self, expire_cert_critical):
        """
        Test that ipa-cert-fix succeeds when CSR is missing from CS.cfg

        Test case for https://pagure.io/freeipa/issue/8618
        Scenario:
        - move the date so that ServerCert cert-pki-ca is expired
        - remove the ca.sslserver.certreq directive from CS.cfg
        - call getcert resubmit in order to create the CSR in certmonger file
        - use ipa-cert-fix, no issue should be seen
        """
        expire_cert_critical(self.master)
        # pki must be stopped in order to edit CS.cfg
        self.master.run_command(['ipactl', 'stop'])
        self.master.run_command(['sed', '-i', r'/ca\.sslserver\.certreq=/d',
                                 paths.CA_CS_CFG_PATH])
        # dirsrv needs to be up in order to run ipa-cert-fix
        self.master.run_command(['ipactl', 'start',
                                 '--ignore-service-failures'])

        # It's the call to getcert resubmit that creates the CSR in certmonger.
        # In normal operations it would be launched automatically when the
        # expiration date is near but in the test we force the CSR creation.
        self.master.run_command(['getcert', 'resubmit',
                                 '-n', 'Server-Cert cert-pki-ca',
                                 '-d', paths.PKI_TOMCAT_ALIAS_DIR])
        # Wait a few secs
        time.sleep(3)

        # Now the real test, call ipa-cert-fix and ensure it doesn't
        # complain about missing sslserver.crt
        result = self.master.run_command(['ipa-cert-fix', '-v'],
                                         stdin_text='yes\n',
                                         raiseonerr=False)
        msg = ("No such file or directory: "
               "'/etc/pki/pki-tomcat/certs/sslserver.crt'")
        assert msg not in result.stderr_text

        # Because of BZ 1897120, pki-cert-fix fails on pki-core 10.10.0
        # https://bugzilla.redhat.com/show_bug.cgi?id=1897120
        if tasks.get_pki_version(self.master) != tasks.parse_version('10.10.0'):
            assert result.returncode == 0

            # get the number of certs track by certmonger
            cmd = self.master.run_command(['getcert', 'list'])
            certs = cmd.stdout_text.count('Request ID')
            timeout = 600
            renewed = 0
            start = time.time()
            # wait up to 10 min for all certs to renew
            while time.time() - start < timeout:
                cmd = self.master.run_command(['getcert', 'list'])
                renewed = cmd.stdout_text.count('status: MONITORING')
                if renewed == certs:
                    break
                time.sleep(100)
            else:
                # timeout
                raise AssertionError('Timeout: Failed to renew all the certs')

    def test_renew_expired_cert_on_master(self, expire_cert_critical):
        """Test if ipa-cert-fix renews expired certs

        Test moves system date to expire certs. Then calls ipa-cert-fix
        to renew them. This certs include subsystem, audit-signing,
        OCSP signing, Dogtag HTTPS, IPA RA agent, LDAP and KDC certs.

        related: https://pagure.io/freeipa/issue/7885
        """
        expire_cert_critical(self.master)

        # wait for cert expiry
        check_status(self.master, 8, "CA_UNREACHABLE")

        self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')

        check_status(self.master, 9, "MONITORING")

        # second iteration of ipa-cert-fix
        result = self.master.run_command(
            ['ipa-cert-fix', '-v'],
            stdin_text='yes\n'
        )
        assert "Nothing to do" in result.stdout_text
        check_status(self.master, 9, "MONITORING")

    def test_ipa_cert_fix_non_ipa(self):
        """Test ipa-cert-fix doesn't work on non ipa system

        ipa-cert-fix tool should not work on non ipa system.

        related: https://pagure.io/freeipa/issue/7885
        """
        result = self.master.run_command(['ipa-cert-fix', '-v'],
                                         stdin_text='yes\n',
                                         raiseonerr=False)
        assert result.returncode == 2


class TestIpaCertFixThirdParty(CALessBase):
    """
    Test that ipa-cert-fix works with an installation with custom certs.
    """

    @classmethod
    def install(cls, mh):
        cls.nickname = 'ca1/server'

        super(TestIpaCertFixThirdParty, cls).install(mh)
        tasks.install_master(cls.master, setup_dns=True)

    @server_install_teardown
    def test_third_party_certs(self):
        self.create_pkcs12(self.nickname,
                           password=self.cert_password,
                           filename='server.p12')
        self.prepare_cacert('ca1')

        # We have a chain length of one. If this is extended then the
        # additional cert names will need to be calculated.
        nick_chain = self.nickname.split('/')
        ca_cert = '%s.crt' % nick_chain[0]

        # Add the CA to the IPA store
        self.copy_cert(self.master, ca_cert)
        self.master.run_command(['ipa-cacert-manage', 'install', ca_cert])

        # Apply the new cert chain otherwise ipa-server-certinstall will fail
        self.master.run_command(['ipa-certupdate'])

        # Install the updated certs and restart the world
        self.copy_cert(self.master, 'server.p12')
        args = ['ipa-server-certinstall',
                '-p', self.master.config.dirman_password,
                '--pin', self.master.config.admin_password,
                '-d', 'server.p12']
        self.master.run_command(args)
        self.master.run_command(['ipactl', 'restart',])

        # Run ipa-cert-fix. This is basically a no-op but tests that
        # the DS nickname is used and not a hardcoded value.
        result = self.master.run_command(['ipa-cert-fix', '-v'],)
        assert self.nickname in result.stderr_text


class TestCertFixKRA(IntegrationTest):
    @classmethod
    def uninstall(cls, mh):
        # Uninstall method is empty as the uninstallation is done in
        # the fixture
        pass

    def test_renew_expired_cert_with_kra(self, expire_cert_critical):
        """Test if ipa-cert-fix renews expired certs with kra installed

        This test check if ipa-cert-fix renews certs with kra
        certificate installed.

        related: https://pagure.io/freeipa/issue/7885
        """
        expire_cert_critical(self.master, setup_kra=True)

        # check if all subsystem cert expired
        check_status(self.master, 11, "CA_UNREACHABLE")

        self.master.run_command(['ipa-cert-fix', '-v'], stdin_text='yes\n')

        check_status(self.master, 12, "MONITORING")
