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

"""
Module provides tests for the ipa-pkinit-manage command.
"""

from __future__ import absolute_import

from ipalib import x509
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks


SELFSIGNED_CA_HELPER = 'SelfSign'
IPA_CA_HELPER = 'IPA'
PKINIT_STATUS_ENABLED = 'enabled'
PKINIT_STATUS_DISABLED = 'disabled'


def check_pkinit_status(host, status):
    """Ensures that ipa-pkinit-manage status returns the expected state"""
    result = host.run_command(['ipa-pkinit-manage', 'status'],
                              raiseonerr=False)
    assert result.returncode == 0
    assert 'PKINIT is {}'.format(status) in result.stdout_text


def check_pkinit_tracking(host, ca_helper):
    """Ensures that the PKINIT cert is tracked by the expected helper"""
    result = host.run_command(['getcert', 'list', '-f', paths.KDC_CERT],
                              raiseonerr=False)
    assert result.returncode == 0
    # Make sure that only one request exists
    assert result.stdout_text.count('Request ID') == 1
    # Make sure that the right CA helper is used to track the cert
    assert 'CA: {}'.format(ca_helper) in result.stdout_text


def check_pkinit_cert_issuer(host, issuer):
    """Ensures that the PKINIT cert is signed by the expected issuer"""
    data = host.get_file_contents(paths.KDC_CERT)
    pkinit_cert = x509.load_pem_x509_certificate(data)
    # Make sure that the issuer is the expected one
    assert DN(pkinit_cert.issuer) == DN(issuer)
    # KDC cert must have SAN for KDC hostname
    assert host.hostname in pkinit_cert.san_a_label_dns_names
    # at least three SANs, profile adds UPN and KRB principal name
    assert len(pkinit_cert.san_general_names) >= 3


def check_pkinit(host, enabled=True):
    """Checks that PKINIT is configured as expected

    If enabled:
    ipa-pkinit-manage status must return 'PKINIT is enabled'
    the certificate must be tracked by IPA CA helper
    the certificate must be signed by IPA CA
    If disabled:
    ipa-pkinit-manage status must return 'PKINIT is disabled'
    the certificate must be tracked by SelfSign CA helper
    the certificate must be self-signed
    """
    if enabled:
        # When pkinit is enabled:
        # cert is tracked by IPA CA helper
        # cert is signed by IPA CA
        check_pkinit_status(host, PKINIT_STATUS_ENABLED)
        check_pkinit_tracking(host, IPA_CA_HELPER)
        check_pkinit_cert_issuer(
            host,
            'CN=Certificate Authority,O={}'.format(host.domain.realm))
    else:
        # When pkinit is disabled
        # cert is tracked by 'SelfSign' CA helper
        # cert is self-signed
        check_pkinit_status(host, PKINIT_STATUS_DISABLED)
        check_pkinit_tracking(host, SELFSIGNED_CA_HELPER)
        check_pkinit_cert_issuer(
            host,
            'CN={},O={}'.format(host.hostname, host.domain.realm))


class TestPkinitManage(IntegrationTest):
    """Tests the ipa-pkinit-manage command.

    ipa-pkinit-manage can be used to enable, disable or check
    the status of PKINIT.
    When pkinit is enabled, the kerberos server is using a certificate
    signed either externally or by IPA CA. In the latter case, certmonger
    is tracking the cert with IPA helper.
    When pkinit is disabled, the kerberos server is using a self-signed
    certificate that is tracked by certmonger with the SelfSigned helper.
    """

    num_replicas = 1

    @classmethod
    def install(cls, mh):
        # Install the master with PKINIT disabled
        tasks.install_master(cls.master, extra_args=['--no-pkinit'])
        check_pkinit(cls.master, enabled=False)

    def test_pkinit_enable(self):
        self.master.run_command(['ipa-pkinit-manage', 'enable'])
        check_pkinit(self.master, enabled=True)

    def test_pkinit_disable(self):
        self.master.run_command(['ipa-pkinit-manage', 'disable'])
        check_pkinit(self.master, enabled=False)

    def test_pkinit_reenable(self):
        self.master.run_command(['ipa-pkinit-manage', 'enable'])
        check_pkinit(self.master, enabled=True)

    def test_pkinit_on_replica(self):
        """Test pkinit enable on a replica without CA

        Test case for ticket 7795.
        Install a replica with --no-pkinit (without CA)
        then call ipa-pkinit-manage enable. The replica must contact
        a master with a CA instance to get its KDC cert.
        """
        tasks.install_replica(self.master, self.replicas[0], setup_ca=False,
                              extra_args=['--no-pkinit'])
        check_pkinit(self.replicas[0], enabled=False)

        self.replicas[0].run_command(['ipa-pkinit-manage', 'enable'])
        check_pkinit(self.replicas[0], enabled=True)


class TestPkinitInstall(IntegrationTest):
    """Tests that ipa-server-install properly configures pkinit.

    Non-regression test for issue 7795.
    """
    num_replicas = 0

    @classmethod
    def install(cls, mh):
        # Install the master
        tasks.install_master(cls.master)

    def test_pkinit(self):
        # Ensure that pkinit is properly configured
        check_pkinit(self.master, enabled=True)
