/* * Copyright (C) 2020 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React, { useState } from "react"; import { useObject, useInit, useEvent } from "hooks"; import { useDialogs } from "dialogs.jsx"; import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js"; import { Modal, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core/dist/esm/components/Modal/index.js'; import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js"; import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js"; import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { ModalError } from 'cockpit-components-inline-notification.jsx'; import { LockIcon } from '@patternfly/react-icons'; const _ = cockpit.gettext; export function host_superuser_storage_key(host: string | undefined) { if (!host) host = cockpit.transport.host; const local_key = window.localStorage.getItem("superuser-key"); if (host === "localhost") return local_key; else if (host.indexOf("@") >= 0) return "superuser:" + host; else if (local_key) return local_key + "@" + host; else return null; } function sudo_polish(msg: string): string; function sudo_polish(msg: null): null; function sudo_polish(msg: string | null): string | null { if (!msg) return msg; msg = msg.replace(/^\[sudo(: authenticate)?] /, ""); msg = msg[0].toUpperCase() + msg.slice(1); return msg; } interface Method { v: { label: { v: string; } }; } export interface SuperuserProxy extends cockpit.DBusProxy { Current: string; Bridges: string[]; Methods: Record; Start(method: string): Promise; Stop(): Promise; Answer(val: string): Promise; } export function superuser_proxy(bus?: cockpit.DBusClient) { if (!bus) bus = cockpit.dbus(null, { bus: "internal" }); return bus.proxy("cockpit.Superuser", "/superuser") as SuperuserProxy; } const UnlockDialog = ({ proxy, host } : { proxy: SuperuserProxy, host: string | undefined }) => { const D = useDialogs(); useInit(init, [proxy, host]); const [methods, setMethods] = useState(null); const [method, setMethod] = useState(false); const [busy, setBusy] = useState(false); const [cancel, setCancel] = useState(() => D.close); const [prompt, setPrompt] = useState<{ message: string, prompt: string, echo: boolean } | null>(null); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const [errorVariant, setErrorVariant] = useState<"danger" | "warning" | null>(null); const [value, setValue] = useState(""); function start(method: string) { setBusy(true); setCancel(() => () => { proxy.Stop(); D.close(); }); let did_prompt = false; const onprompt = (_event: Event, message: string, prompt: string, def: string, echo: boolean, error: string) => { setBusy(false); setPrompt({ message: sudo_polish(message), prompt: sudo_polish(prompt), echo }); setValue(def); if (error) { setError(sudo_polish(error)); setErrorVariant(did_prompt ? 'danger' : 'warning'); } did_prompt = true; }; proxy.addEventListener("Prompt", onprompt); proxy.Start(method) .then(() => { proxy.removeEventListener("Prompt", onprompt); const key = host_superuser_storage_key(host); if (key) window.localStorage.setItem(key, method); if (did_prompt) { D.close(); } else { setBusy(false); setPrompt(null); setMessage(_("You now have administrative access.")); setCancel(() => D.close); } }) .catch((err: cockpit.BasicError) => { console.warn(err); proxy.removeEventListener("Prompt", onprompt); if (err && err.message !== "cancelled") { setBusy(false); setPrompt(null); setError(sudo_polish(err.toString())); setCancel(() => D.close); } else D.close(); }); } function init() { return proxy.Stop().finally(() => { if (proxy.Bridges.length === 0) { setError(_("No methods to gain administrative access are available (sudo -A, pkexec).")); } else if (proxy.Methods) { const ids = Object.keys(proxy.Methods); if (ids.length === 0) start(proxy.Bridges[0]); else if (ids.length === 1) start(ids[0]); else { setMethods(ids); setMethod(ids[0]); } } else start(proxy.Bridges[0]); }); } const validated = errorVariant === "danger" ? "error" : errorVariant; let title = null; let title_icon: null | "danger" = null; let body = null; let footer = null; if (prompt) { if (!prompt.message && !prompt.prompt) { prompt.message = _("Please authenticate to gain administrative access"); prompt.prompt = _("Password"); } const apply = () => { proxy.Answer(value); setError(null); setBusy(true); }; title = _("Switch to administrative access"); body = (
{ apply(); event.preventDefault(); return false }}> { error && } { prompt.message && {prompt.message} } setValue(value)} type={!prompt.echo ? 'password' : 'text'} validated={!error ? "default" : validated || "error"} value={value} /> ); footer = ( <> ); } else if (message) { title = _("Administrative access"); body =

{message}

; footer = ( ); } else if (error) { title_icon = "danger"; title = _("Problem becoming administrator"); body =

{error}

; footer = ( ); } else if (methods && method) { title = _("Switch to administrative access"); body = (
setMethod(method)} isDisabled={busy}> { methods.map(m => ) }
); footer = ( <> ); } if (body === null) return null; return ( {body} {footer} ); }; const LockDialog = ({ proxy, host } : { proxy: SuperuserProxy, host: string | undefined }) => { const D = useDialogs(); const [error, setError] = useState(null); const apply = () => { setError(null); proxy.Stop() .then(() => { const key = host_superuser_storage_key(host); if (key) window.localStorage.setItem(key, "none"); D.close(); }) .catch(err => { setError(err.toString()); }); }; const footer = ( ); return ( {error && }

{_("Limited access mode restricts administrative privileges. Some parts of the web console will have reduced functionality.")}

{_("Your browser will remember your access level across sessions.")}

{footer}
); }; const SuperuserDialogs = ({ superuser_proxy, host = undefined, create_trigger } : { superuser_proxy: SuperuserProxy; host: string | undefined, create_trigger: (unlocked: boolean, onclick: () => void) => React.ReactNode; }) => { const D = useDialogs(); useEvent(superuser_proxy, "changed", () => { const key = host_superuser_storage_key(host); if (key) { // Reset wanted state if we fail to gain admin privs. // Failing to gain admin privs might take a noticeable // time, and we don't want to suffer through the // associated intermediate UI state on every login. const want = window.localStorage.getItem(key); if (superuser_proxy.Current === "none" && superuser_proxy.Current !== want) window.localStorage.setItem(key, superuser_proxy.Current); } }); const show = superuser_proxy.Current !== "root" && superuser_proxy.Current !== "init"; const unlocked = superuser_proxy.Current !== "none"; function unlock() { D.show(); } function lock() { D.show(); } if (!show) return null; return create_trigger(unlocked, unlocked ? lock : unlock); }; export const SuperuserIndicator = ({ proxy, host } : { proxy: SuperuserProxy | null, host?: string }) => { if (!proxy || !proxy.valid) return null; function create_trigger(unlocked: boolean, onclick: () => void) { return ( ); } return ; }; export const SuperuserButton = () => { const proxy = useObject( () => superuser_proxy(), null, []); const create_trigger = (unlocked: boolean, onclick: () => void) => ; return ; };