#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)

# This file is part of Cockpit.
#
# Copyright (C) 2020 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 time

import testlib


def allow_old_cockpit_ws_messages(test):
    # noisy debug message from old cockpit-ws/polkit
    test.allow_journal_messages("logged in user session",
                                "New connection to session from.*",
                                "pam_unix(polkit-1:session): session opened for user root by .*uid=.*",
                                "admin: Executing command .*COMMAND=.*cockpit-bridge --privileged.*")


@testlib.nondestructive
@testlib.skipDistroPackage()
class TestSuperuser(testlib.MachineCase):
    def testBasic(self):
        b = self.browser
        m = self.machine

        # Log in with all defaults
        self.login_and_go()
        b.check_superuser_indicator("Administrative access")

        b.assert_pixels("#topnav", "topnav-privileged")

        # Drop privileges
        b.open_superuser_dialog()
        b.click(".pf-v5-c-modal-box:contains('Switch to limited access') button:contains('Limit access')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to limited access')")
        b.check_superuser_indicator("Limited access")

        b.assert_pixels("#topnav", "topnav-unprivileged")

        # Check they are still gone after logout
        b.relogin()
        b.leave_page()
        b.check_superuser_indicator("Limited access")

        # We want to be lectured again
        self.restore_file("/var/db/sudo/lectured/admin")
        self.restore_file("/var/lib/sudo/lectured/admin")
        m.execute("rm -rf /var/{db,lib}/sudo/lectured/admin")

        # Get the privileges back, this time in the mobile layout
        b.set_layout("mobile")
        b.open_superuser_dialog()
        if "ubuntu" not in m.image and "debian" not in m.image:
            b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')",
                           "We trust you have received the usual lecture")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.focus(".pf-v5-c-modal-box button:contains('Cancel')")
        b.assert_pixels(".pf-v5-c-modal-box:contains('Switch to administrative access')", "superuser-modal")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.check_superuser_indicator("Administrative access")
        b.set_layout("desktop")

        # Check we still have them after logout
        b.relogin()
        b.leave_page()
        b.check_superuser_indicator("Administrative access")

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

        self.write_file("/etc/sudoers.d/log", "Defaults log_output\n")

        # with explicitly becoming superuser
        self.login_and_go(superuser=False)
        b.check_superuser_indicator("Limited access")
        b.become_superuser()
        self.assertIn("00\n", m.execute("ls /var/log/sudo-io"))
        b.logout()

        # with immediate superuser at login
        b.login_and_go()
        b.check_superuser_indicator("Administrative access")
        b.logout()

    def testNoPasswd(self):
        b = self.browser

        # Log in with limited access
        self.login_and_go(superuser=False)
        b.check_superuser_indicator("Limited access")

        # Give us password-less sudo and use it
        self.write_file("/etc/sudoers.d/admin", "admin ALL=(ALL) NOPASSWD:ALL")
        b.become_superuser(passwordless=True)

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

        # Log in with limited access
        self.login_and_go(superuser=False)
        b.check_superuser_indicator("Limited access")

        # Configure the sudo PAM stack to make two prompts
        if "debian" in m.image or "ubuntu" in m.image:
            self.write_file("/etc/pam.d/sudo", """
auth required pam_unix.so
auth required mock-pam-conv-mod.so
@include common-account
@include common-session-noninteractive
""")
        else:
            self.write_file("/etc/pam.d/sudo", """
auth required pam_unix.so
auth required mock-pam-conv-mod.so
account include system-auth
password include system-auth
session include system-auth
""")

        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        # Let the dialog sit there for 45 seconds, to test that this doesn't trigger a D-Bus timeout.
        time.sleep(45)
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "universe and everything")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "42")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.check_superuser_indicator("Administrative access")

    def testWrongPasswd(self):
        b = self.browser

        # Log in with limited access
        self.login_and_go(superuser=False)
        b.check_superuser_indicator("Limited access")

        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wrong")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Sorry, try again")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wronger")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Sorry, try again")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "wrongest")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Problem becoming administrator')", "Sudo: 3 incorrect password attempts")
        b.click(".pf-v5-c-modal-box:contains('Problem becoming administrator') button:contains('Close')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Problem becoming administrator')")
        b.check_superuser_indicator("Limited access")

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

        # Remove special treatment of the "admin" group on Ubuntu.
        # Our main test user is unfortunately called "admin" and has
        # "admin" as its primary group.
        #
        if "ubuntu" in m.image:
            self.sed_file("/^%admin/d", "/etc/sudoers")

        m.execute(f"gpasswd -d admin {m.get_admin_group()}")

        self.login_and_go()
        b.check_superuser_indicator("Limited access")

        b.open_superuser_dialog()
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Problem becoming administrator')", "Admin is not in the sudoers file.")
        b.click(".pf-v5-c-modal-box:contains('Problem becoming administrator') button:contains('Close')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Problem becoming administrator')")

        # no stray/hanging sudo process
        testlib.wait(lambda: "sudo" not in m.execute("loginctl --lines=0 user-status admin"), tries=5)

        # cancelling auth dialog stops sudo
        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box", "Switch to administrative access")
        b.wait_in_text(".pf-v5-c-modal-box", "Password for admin")
        status = m.execute("loginctl --lines=0 user-status admin")
        self.assertIn("sudo", status)
        self.assertIn("cockpit-askpass", status)

        b.click(".pf-v5-c-modal-box button:contains('Cancel')")
        b.wait_not_present(".pf-v5-c-modal-box")

        testlib.wait(lambda: "sudo" not in m.execute("loginctl --lines=0 user-status admin"), tries=5)

        # logging out cleans up pending sudo auth; user should either go to "State: closing" or disappear completely
        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box", "Password for admin")
        self.assertIn("cockpit-askpass", m.execute("loginctl --lines=0 user-status admin"))
        b.logout()
        testlib.wait(lambda: "sudo" not in m.execute("loginctl --lines=0 user-status admin || true"), tries=10)
        self.assertNotIn("cockpit", m.execute("loginctl --lines=0 user-status admin || true"))

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

        self.write_file("/etc/cockpit/shell.override.json", """
{
  "bridges": [
    {
      "privileged": true,
      "spawn": [
        "sudo",
        "-k",
        "-A",
        "cockpit-bridge",
        "--privileged"
      ]
    }
  ]
}
""")

        # We don't want to be lectured in this test just to control
        # the content of the dialog better.
        m.execute("touch /var/{db,lib}/sudo/lectured/admin 2>/dev/null || true")

        self.login_and_go(superuser=False)
        b.check_superuser_indicator("Limited access")

        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box:contains('Problem becoming administrator')", "Sudo: no askpass program specified, try setting SUDO_ASKPASS")
        b.click(".pf-v5-c-modal-box:contains('Problem becoming administrator') button:contains('Close')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Problem becoming administrator')")
        b.check_superuser_indicator("Limited access")

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

        self.login_and_go("/playground/pkgs", superuser=True)
        b.leave_page()
        b.check_superuser_indicator("Administrative access")
        # superuser bridge is running
        m.execute("pgrep -u root -a cockpit-bridge")

        self.write_file("/etc/cockpit/shell.override.json", """
{
  "bridges": [ ]
}
""")

        b.enter_page("/playground/pkgs")
        b.click("#reload")
        b.leave_page()
        if self.is_pybridge():
            b.check_superuser_indicator("")
        else:
            # C bridge gets that wrong: Its .Bridges property remains at [sudo, pkexec], showing a broken indicator
            b.check_superuser_indicator("Limited access")
        # superuser bridge goes away
        m.execute("while pgrep -u root -a cockpit-bridge; do sleep 1; done", timeout=5)

    def testSingleLabelBridgeConfig(self):
        b = self.browser

        # When there is a single labeled privileged bridge, Cockit will start it automatically.

        self.write_file("/etc/cockpit/shell.override.json", """
{
  "bridges": [
    {
      "privileged": true,
      "label": "Always fails",
      "spawn": [
        "/bin/bash", "-c", "echo >&2 'Hello from the bash method'; exit 1"
      ]
    }
  ]
}
""")

        self.login_and_go("/playground/pkgs", superuser=False)
        b.leave_page()
        b.check_superuser_indicator("Limited access")
        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box:contains('Problem becoming administrator')", "Hello from the bash method")
        b.click(".pf-v5-c-modal-box button:contains('Close')")
        b.check_superuser_indicator("Limited access")

    def testMultipleBridgeConfig(self):
        b = self.browser

        self.write_file("/etc/cockpit/shell.override.json", """
{
  "bridges": [
    {
      "privileged": true,
      "label": "Sudo",
      "environ": [
        "SUDO_ASKPASS=${libexecdir}/cockpit-askpass"
      ],
      "spawn": [
        "sudo",
        "-k",
        "-A",
        "cockpit-bridge",
        "--privileged"
      ]
    },
    {
        "privileged": true,
        "label": "Polkit",
        "spawn": [
            "pkexec",
            "--disable-internal-agent",
            "cockpit-bridge",
            "--privileged"
        ]
    }
  ]
}
""")

        self.login_and_go("/playground/pkgs", superuser=False)
        b.leave_page()
        b.check_superuser_indicator("Limited access")

        # Get admin rights with Polkit method
        b.open_superuser_dialog()
        b.set_val(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Polkit")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Polkit")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Please authenticate")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.check_superuser_indicator("Administrative access")

        # Drop them
        b.open_superuser_dialog()
        b.click(".pf-v5-c-modal-box:contains('Switch to limited access') button:contains('Limit access')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to limited access')")
        b.check_superuser_indicator("Limited access")

        # Run the regular sudo method, which should work as always
        b.open_superuser_dialog()
        b.set_val(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Sudo")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access') select", "Sudo")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.check_superuser_indicator("Administrative access")

    def testOverview(self):
        b = self.browser

        self.login_and_go("/system", superuser=False)
        b.wait_visible(".pf-v5-c-alert:contains('Web console is running in limited access mode.')")
        b.click(".pf-v5-c-alert:contains('Web console is running in limited access mode.') button:contains('Turn on')")
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.wait_not_present(".pf-v5-c-alert:contains('Web console is running in limited access mode.')")


@testlib.skipDistroPackage()
class TestSuperuserOldShell(testlib.MachineCase):
    provision = {
        "machine1": {"address": "10.111.113.1/20", "memory_mb": 512},
        "machine2": {"address": "10.111.113.2/20", "image": "centos-7", "memory_mb": 512},
    }

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

        m.start_cockpit()

        # Use m1 to login into m2
        b.open("/")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.click("#show-other-login-options")
        b.wait_visible("#server-group")
        b.set_val("#server-field", "10.111.113.2")
        b.click('#login-button')
        b.wait_in_text("#server-name", "10.111.113.2")
        b.wait_visible("#hostkey-group")
        b.wait_in_text("#hostkey-message-1", "You are connecting to 10.111.113.2 for the first time.")
        b.click('#login-button')
        b.wait_visible('#content')

        # The old shell should have gotten the password from cockpit-ws and it should work
        b.enter_page("/system")
        b.click("#shutdown-group > button:contains('Restart')")
        b.wait_popup("shutdown-dialog")
        b.click("#shutdown-dialog button:contains('Restart')")
        b.wait_popdown("shutdown-dialog")


@testlib.skipImage("TODO: broken on Arch Linux", "arch")
@testlib.skipDistroPackage()
class TestSuperuserOldWebserver(testlib.MachineCase):
    provision = {
        "machine1": {"address": "10.111.113.1/20", "image": "centos-7", "memory_mb": 512},
        "machine2": {"address": "10.111.113.2/20", "memory_mb": 512},
    }

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

        allow_old_cockpit_ws_messages(self)

        m.execute("firewall-cmd --add-service cockpit")
        m.start_cockpit()

        # Use m1 to login into m2
        b.open("/")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.set_checked('#authorized-input', val=True)
        b.click("#show-other-login-options")
        b.wait_visible("#server-group")
        b.set_val("#server-field", "10.111.113.2")
        b.click('#login-button')
        b.wait_in_text("#server-name", "10.111.113.2")
        b.wait_visible("#conversation-group")
        b.wait_in_text("#conversation-prompt", "Fingerprint")
        b.wait_in_text("#conversation-message", "Do you want to proceed this time?")
        b.click('#login-button')
        b.wait_visible('#content')

        if not self.is_pybridge():
            # The C bridge will recognize that it is being started by
            # a old webserver (or old bridge) and will start "any"
            # privileged bridge during startup.
            b.check_superuser_indicator("Administrative access")
            b.go("/playground/test")
            b.enter_page("/playground/test")
            b.click(".super-channel button")
            b.wait_in_text(".super-channel span", 'result: ')
            self.assertIn('result: uid=0', b.text(".super-channel span"))

        else:
            # The Python bridge will not start any privileged bridge
            # if not explicitly told, and a old webserver (or old
            # bridge) does not tell it anything.
            b.check_superuser_indicator("Limited access")
            b.go("/playground/test")
            b.enter_page("/playground/test")
            b.click(".super-channel button")
            b.wait_in_text(".super-channel span", 'access-denied')

            b.become_superuser()
            b.go("/playground/test")
            b.enter_page("/playground/test")
            b.click(".super-channel button")
            b.wait_in_text(".super-channel span", 'result: ')
            self.assertIn('result: uid=0', b.text(".super-channel span"))

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

        allow_old_cockpit_ws_messages(self)

        m.execute("firewall-cmd --add-service cockpit")
        m.start_cockpit()

        # Use m1 to login into m2, but don't reuse the password
        b.open("/")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.set_checked('#authorized-input', val=False)
        b.click("#show-other-login-options")
        b.wait_visible("#server-group")
        b.set_val("#server-field", "10.111.113.2")
        b.click('#login-button')
        b.wait_in_text("#server-name", "10.111.113.2")
        b.wait_visible("#conversation-group")
        b.wait_in_text("#conversation-prompt", "Fingerprint")
        b.wait_in_text("#conversation-message", "Do you want to proceed this time?")
        b.click('#login-button')
        b.wait_visible('#content')

        # We should not have gotten the password from the old
        # cockpit-ws, but we can get it back.

        b.check_superuser_indicator("Limited access")
        b.go("/playground/test")
        b.enter_page("/playground/test")
        b.click(".super-channel button")
        b.wait_in_text(".super-channel span", 'access-denied')

        b.switch_to_top()
        b.open_superuser_dialog()
        b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
        b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
        b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
        b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
        b.check_superuser_indicator("Administrative access")
        b.go("/playground/test")
        b.enter_page("/playground/test")
        b.click(".super-channel button")
        b.wait_in_text(".super-channel span", 'result: ')
        self.assertIn('result: uid=0', b.text(".super-channel span"))


@testlib.skipDistroPackage()
class TestSuperuserDashboard(testlib.MachineCase):
    provision = {
        "machine1": {"address": "10.111.113.1/20", "memory_mb": 512},
        "machine2": {"address": "10.111.113.2/20", "memory_mb": 512},
    }

    @testlib.todoPybridgeRHEL8()
    def test(self):
        b = self.browser
        self.setup_provisioned_hosts()

        self.login_and_go()
        b.go("/@10.111.113.2")
        b.wait_visible("#machine-troubleshoot")
        b.click('#machine-troubleshoot')
        b.wait_visible('#hosts_setup_server_dialog')
        b.wait_visible('#hosts_setup_server_dialog button:contains("Add")')
        b.click('#hosts_setup_server_dialog button:contains("Add")')
        b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.")
        b.click('#hosts_setup_server_dialog button:contains("Accept key and connect")')
        b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
        b.set_input_text("#login-custom-password", "foobar")
        b.click('#hosts_setup_server_dialog button:contains("Log in")')
        b.wait_not_present('#hosts_setup_server_dialog')

        # There should be no superuser indicator in the Overview

        b.go("/@10.111.113.2/system")
        b.enter_page("/system", host="10.111.113.2")
        b.wait_not_present(".ct-overview-header-actions button:contains('Administrative access')")
        b.wait_not_present(".ct-overview-header-actions button:contains('Limited access')")
        b.leave_page()

        # The superuser indicator in the Shell should apply to machine2

        b.check_superuser_indicator("Limited access")
        b.become_superuser()
        b.go("/@10.111.113.2/playground/test")
        b.enter_page("/playground/test", host="10.111.113.2")
        b.click(".super-channel button")
        b.wait_in_text(".super-channel span", 'result: ')
        self.assertIn('result: uid=0', b.text(".super-channel span"))

        b.drop_superuser()
        b.click(".super-channel button")
        b.wait_in_text(".super-channel span", 'access-denied')

        # back to superuser on machine2
        b.become_superuser()
        user = self.machines["machine2"].execute("loginctl user-status admin")
        self.assertIn("cockpit-bridge --privileged", user)
        # no stray askpass process
        self.assertNotIn("cockpit-askpass", user)
        # logging out cleans up logind sessions on both machines
        b.logout()
        for m in [self.machine, self.machines["machine2"]]:
            m.execute('while [ "$(loginctl show-user --property=State --value admin)" = "active" ]; do sleep 1; done')
            self.assertNotIn("cockpit", "loginctl user-status admin")

        self.allow_hostkey_messages()


@testlib.skipDistroPackage()
class TestSuperuserOldDashboard(testlib.MachineCase):
    provision = {
        "machine1": {"address": "10.111.113.1/20", "image": "centos-7", "memory_mb": 512},
        "machine2": {"address": "10.111.113.2/20", "memory_mb": 512},
    }

    def test(self):
        b = self.browser
        m = self.machines["machine1"]

        allow_old_cockpit_ws_messages(self)
        self.allow_hostkey_messages()
        self.setup_provisioned_hosts()

        m.execute("firewall-cmd --add-service cockpit")
        m.start_cockpit()

        # Log into m1 and add m2
        b.open("/")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "admin")
        b.set_val("#login-password-input", "foobar")
        b.set_checked('#authorized-input', val=True)
        b.click('#login-button')
        b.wait_visible('#content')

        b.go("/@10.111.113.2")
        b.wait_visible("#machine-troubleshoot")
        b.click('#machine-troubleshoot')
        b.wait_popup('troubleshoot-dialog')
        b.click('#troubleshoot-dialog button:contains("Add")')
        b.wait_in_text('#troubleshoot-dialog', "Fingerprint")
        b.click('#troubleshoot-dialog button:contains("Connect")')
        b.wait_popdown('troubleshoot-dialog')

        # There should be a superuser button in the Overview of machine2

        def get_admin():
            b.go("/@10.111.113.2")
            b.enter_page("/system", host="10.111.113.2")
            b.click(".ct-overview-header-actions button:contains('Limited access')")
            b.wait_in_text(".pf-v5-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
            b.set_input_text(".pf-v5-c-modal-box:contains('Switch to administrative access') input", "foobar")
            b.click(".pf-v5-c-modal-box button:contains('Authenticate')")
            b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to administrative access')")
            b.wait_visible(".ct-overview-header-actions button:contains('Administrative access')")
            b.go("/@10.111.113.2/playground/test")
            b.enter_page("/playground/test", host="10.111.113.2")
            b.click(".super-channel button")
            b.wait_in_text(".super-channel span", 'result: ')
            self.assertIn('result: uid=0', b.text(".super-channel span"))

        def drop_admin():
            b.go("/@10.111.113.2")
            b.enter_page("/system", host="10.111.113.2")
            b.click(".ct-overview-header-actions button:contains('Administrative access')")
            b.click(".pf-v5-c-modal-box:contains('Switch to limited access') button:contains('Limit access')")
            b.wait_not_present(".pf-v5-c-modal-box:contains('Switch to limited access')")
            b.wait_visible(".ct-overview-header-actions button:contains('Limited access')")
            b.go("/@10.111.113.2/playground/test")
            b.enter_page("/playground/test", host="10.111.113.2")
            b.click(".super-channel button")
            b.wait_in_text(".super-channel span", 'access-denied')

        if not self.is_pybridge():
            # The C bridge will recognize that it is being started by
            # a old bridge (or old webserver) and will start "any"
            # privileged bridge during startup.  So we have admin at
            # this point.
            #
            drop_admin()
            get_admin()

        else:
            # The Python bridge will not start any privileged bridge
            # if not explicitly told, and a old bridge (or old
            # webserver) does not tell it anything.  So we don't have
            # admin at this point.
            #
            get_admin()
            drop_admin()


@testlib.skipDistroPackage()
class TestSuperuserDashboardOldMachine(testlib.MachineCase):
    provision = {
        "machine1": {"address": "10.111.113.1/20"},
        "machine2": {"address": "10.111.113.2/20", "image": "centos-7"},
    }

    @testlib.todoPybridge(reason="https://github.com/cockpit-project/cockpit/issues/18712")
    def test(self):
        b = self.browser

        self.setup_provisioned_hosts()
        self.login_and_go()
        b.go("/@10.111.113.2")
        b.wait_visible("#machine-troubleshoot")
        b.click('#machine-troubleshoot')
        b.wait_visible('#hosts_setup_server_dialog')
        b.click('#hosts_setup_server_dialog button:contains("Add")')
        b.wait_in_text('#hosts_setup_server_dialog', "You are connecting to 10.111.113.2 for the first time.")
        b.click('#hosts_setup_server_dialog button:contains("Accept key and connect")')
        b.wait_in_text('#hosts_setup_server_dialog', "Unable to log in")
        b.set_input_text("#login-custom-password", "foobar")
        b.click('#hosts_setup_server_dialog button:contains("Log in")')
        b.wait_not_present('#hosts_setup_server_dialog')

        # Since user and password are the same on machine2, we should
        # have gotten admin rights.

        b.enter_page("/system", host="10.111.113.2")
        b.click("#shutdown-group > button:contains('Restart')")
        b.wait_popup("shutdown-dialog")
        b.click("#shutdown-dialog button:contains('Restart')")
        b.wait_popdown("shutdown-dialog")

        self.allow_hostkey_messages()


if __name__ == '__main__':
    testlib.test_main()
