#!/usr/bin/env python3

# This file is part of Cockpit.
#
# Copyright (C) 2022 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import unittest

import parent  # noqa: F401
from testlib import MachineCase, nondestructive, test_main
from testvm import DEFAULT_IMAGE

HOST = "host.containers.internal"


@unittest.skipUnless(DEFAULT_IMAGE in ["fedora-coreos", "rhel4edge"], "no cockpit/ws container on this image")
@nondestructive
class TestWsBastionContainer(MachineCase):
    def setUp(self):
        super().setUp()
        # stop ws container from previous runs
        self.machine.stop_cockpit()
        # undo cockpit/ws install steps
        self.restore_file("/etc/systemd/system/cockpit.service")
        self.machine.execute("rm /etc/systemd/system/cockpit.service")
        self.addCleanup(self.machine.execute, "podman rm -f --all")

    def approve_key(self, b, hostname):
        b.wait_visible("#hostkey-group")
        b.wait_in_text("#hostkey-message-1", f"You are connecting to {hostname} for the first time.")
        b.click("#login-button")

    def testPasswordLogin(self):
        m = self.machine
        b = self.browser
        m.execute("podman run -d --name cockpit-bastion -p 9090:9090 localhost/cockpit/ws")
        m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")

        b.ignore_ssl_certificate_errors(True)
        b.open("/", tls=True)

        b.wait_visible("#login")
        # should be pre-configured to RequireHost
        b.wait_not_visible("#option-group")
        b.wait_visible("#server-field")
        # LoginTitle from default-bastion.conf
        b.wait_text("#server-name", "Cockpit Bastion")
        # No branding by default
        b.wait_text("#brand", "")

        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")

        # Requires a host
        b.click("#login-button")
        b.wait_in_text("#login-error-message", "host to connect")
        # so connect to our own container host
        b.set_val("#server-field", HOST)
        b.set_val("#login-password-input", "foobar")
        # key is unknown
        b.click("#login-button")
        self.approve_key(b, HOST)

        b.wait_visible('#content')
        b.wait_text('#current-username', 'admin')
        b.logout()

        # remembers the last host via URL, server field should be pre-filled
        self.assertEqual(b.eval_js("window.location.pathname"), f"/={HOST}/system")
        # FIXME: login page does not really set this in the DOM? DOM has empty value, but browser shows the value
        # b.wait_text("#server-field", host)
        # this is only for Cockpit Client
        b.wait_not_visible("#recent-hosts")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        # second time SSH key is known
        b.click("#login-button")
        b.wait_visible('#content')
        b.logout()

    def testKnownHosts(self):
        m = self.machine
        b = self.browser

        m.execute(f"ssh-keyscan localhost | sed 's/^localhost/{HOST}/' > /root/known_hosts")
        self.addCleanup(m.execute, "rm /root/known_hosts")
        b.ignore_ssl_certificate_errors(True)

        def check_login():
            m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")
            b.open("/", tls=True)
            b.set_val("#login-user-input", "admin")
            b.set_val("#login-password-input", "foobar")
            b.set_val("#server-field", HOST)
            b.click("#login-button")
            b.wait_visible('#content')
            b.logout()
            m.execute("podman rm -f cockpit-bastion")

        # default location
        m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
                  "-v /root/known_hosts:/etc/ssh/ssh_known_hosts:ro,Z "
                  "localhost/cockpit/ws")
        check_login()

        # custom location
        m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
                  "-v /root/known_hosts:/known_hosts:ro,Z "
                  "-e COCKPIT_SSH_KNOWN_HOSTS_FILE=/known_hosts "
                  "localhost/cockpit/ws")
        check_login()

        # connect to all unknown hosts
        # FIXME: this does not work
        # m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
        #           "-e COCKPIT_SSH_CONNECT_TO_UNKNOWN_HOSTS=1 "
        #           "localhost/cockpit/ws")
        # check_login()

    def testCustomConf(self):
        m = self.machine
        b = self.browser

        # custom cockpit.conf and pretend we are Fedora CoreOS/RHEL
        self.write_file("/root/cockpit.conf", """[WebService]
            LoginTitle = My Walden
""")
        m.execute("cp /etc/os-release /root; "
                  "podman run -d --name cockpit-bastion -p 9090:9090 "
                  "-v /root/cockpit.conf:/etc/cockpit/cockpit.conf:ro,Z "
                  "-v /root/os-release:/etc/os-release:ro,Z "
                  "localhost/cockpit/ws")
        m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")

        b.ignore_ssl_certificate_errors(True)
        b.open("/", tls=True)

        b.wait_visible("#login")
        b.wait_text("#server-name", "My Walden")
        # custom conf does not have RequireHost
        b.wait_visible("#option-group")
        # Shows os-release branding
        b.wait_in_text("#brand", "Fedora" if m.image == "fedora-coreos" else "Red Hat Enterprise Linux")

        # pre-fill target host
        b.open(f"/={HOST}/", tls=True)
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")

        b.click("#login-button")
        self.approve_key(b, HOST)

        b.wait_visible('#content')
        b.wait_text('#current-username', 'admin')

    def testKeyLogin(self):
        m = self.machine
        b = self.browser

        KEY_PASSWORD = "sshfoobar"
        # old RSA/PEM format
        m.execute(f"ssh-keygen -q -f /root/id_bastion -t rsa -m PEM -N {KEY_PASSWORD}")
        m.execute(f"ssh-keyscan localhost | sed 's/^localhost/{HOST}/' > /root/known_hosts")
        self.addCleanup(m.execute, "rm /root/known_hosts /root/id_bastion /root/id_bastion.pub")

        m.execute("podman run -d --name cockpit-bastion -p 9090:9090 "
                  "-v /root/known_hosts:/etc/ssh/ssh_known_hosts:ro,Z "
                  "-v /root/id_bastion:/id_bastion:ro,Z "
                  "-e COCKPIT_SSH_KEY_PATH=/id_bastion "
                  "-e G_MESSAGES_DEBUG=cockpit-ssh "
                  "localhost/cockpit/ws")
        m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")

        b.ignore_ssl_certificate_errors(True)
        b.open("/", tls=True)
        b.set_val("#login-user-input", "admin")
        b.set_val("#server-field", HOST)

        # the account password does not work
        b.set_val("#login-password-input", "foobar")
        b.click("#login-button")
        b.wait_text_not("#login-error-message", "")

        # SSH key password, but key is not authorized
        b.set_val("#login-password-input", KEY_PASSWORD)
        b.click("#login-button")
        b.wait_text_not("#login-error-message", "")

        # authorize key
        self.restore_file("/home/admin/.ssh/authorized_keys")
        # Do not use authorized_keys.d as that does not work on rhel4edge
        # Do not append but overwrite so we are sure the right key is used
        m.execute("cat /root/id_bastion.pub > /home/admin/.ssh/authorized_keys")

        # fails with wrong key password
        b.set_val("#login-password-input", "notthispassword")
        b.click("#login-button")
        b.wait_text_not("#login-error-message", "")

        # works with correct key password
        b.set_val("#login-password-input", KEY_PASSWORD)
        b.click("#login-button")
        b.wait_visible('#content')
        b.logout()

        # now test with current OpenSSH format
        m.execute(f"yes | ssh-keygen -q -f /root/id_bastion -t rsa -N {KEY_PASSWORD}")
        m.execute("cat /root/id_bastion.pub > /home/admin/.ssh/authorized_keys")
        b.set_val("#login-user-input", "admin")
        b.set_val("#server-field", HOST)
        b.set_val("#login-password-input", KEY_PASSWORD)
        b.click("#login-button")
        b.wait_visible('#content')


@unittest.skipUnless(DEFAULT_IMAGE in ["fedora-coreos", "rhel4edge"], "no cockpit/ws container on this image")
@nondestructive
class TestWsPrivileged(MachineCase):
    def testService(self):
        # the install script should have created a cockpit.service, but not started it
        m = self.machine

        self.assertEqual(m.execute("! systemctl is-enabled cockpit.service").strip(), "disabled")
        self.assertEqual(m.execute("! systemctl is-active cockpit.service").strip(), "inactive")
        # stop ws container from previous test runs
        self.machine.stop_cockpit()
        self.assertEqual(m.execute("podman ps --noheading"), "")
        self.addCleanup(m.execute, "systemctl disable --now cockpit.service")

        m.execute("systemctl start cockpit.service")
        self.assertEqual(m.execute("systemctl is-active cockpit.service").strip(), "active")
        firstStartTime = m.execute("podman inspect cockpit-ws| jq '.[0].Created'")
        m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")

        m.execute("systemctl restart cockpit.service")
        self.assertEqual(m.execute("systemctl is-active cockpit.service").strip(), "active")
        secondStartTime = m.execute("podman inspect cockpit-ws| jq '.[0].Created'")
        m.execute("until curl --fail --head -k https://localhost:9090/; do sleep 1; done")

        self.assertNotEqual(firstStartTime, secondStartTime)

        m.execute("systemctl stop cockpit.service")
        self.assertEqual(m.execute("! systemctl is-enabled cockpit.service").strip(), "disabled")
        self.assertEqual(m.execute("! systemctl is-active cockpit.service").strip(), "inactive")
        self.assertEqual(m.execute("podman ps --noheading"), "")

        # current container has `set -x` in its startup script, which ends up in the journal
        self.allow_journal_messages("^[/+'].*")


if __name__ == '__main__':
    test_main()
