Ctrl K
ComunidadGuías…

electron/elevated-install.node-test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// The root script runs through a real shell here, with a recording apt-get standing in for the package manager.
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { execFileSync, spawnSync } from "node:child_process";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";

import {
  ELEVATED_INSTALL_SCRIPT,
  PKEXEC,
  canInstallElevated,
  installPackageElevated,
  sha512Hex,
} from "./elevated-install.mjs";

const hex = (text) => createHash("sha512").update(text).digest("hex");
const base64 = (text) => createHash("sha512").update(text).digest("base64");

test("a password prompt is only attempted where one can appear", () => {
  const ok = { platform: "linux", env: { DISPLAY: ":0" }, access: () => {} };
  assert.equal(canInstallElevated(ok), true);
  assert.equal(canInstallElevated({ ...ok, env: { WAYLAND_DISPLAY: "wayland-0" } }), true);
  assert.equal(canInstallElevated({ ...ok, platform: "darwin" }), false);
  assert.equal(canInstallElevated({ ...ok, env: {} }), false, "no graphical session: ssh, a service");
  assert.equal(canInstallElevated({ ...ok, env: { DISPLAY: ":0", OMB_UPDATE_INSTALL: "terminal" } }), false);
  assert.equal(canInstallElevated({ ...ok, access: () => { throw new Error("ENOENT"); } }), false);
});

test("pkexec is trusted only at its system location", () => {
  assert.equal(PKEXEC, "/usr/bin/pkexec");
});

test("the feed's checksum becomes the hex the script compares", () => {
  assert.equal(sha512Hex(base64("deb bytes")), hex("deb bytes"));
  assert.equal(sha512Hex(hex("deb bytes").toUpperCase()), hex("deb bytes"));
  for (const bad of [undefined, null, "", "abc", "x".repeat(88), `${base64("a")}!`, 42]) {
    assert.equal(sha512Hex(bad), null, String(bad));
  }
});

function fixture() {
  const dir = mkdtempSync(join(tmpdir(), "omb-elevated-"));
  const cache = join(dir, "cache dir", "o'brien", "pending");
  mkdirSync(cache, { recursive: true });
  const file = join(cache, "OpenMausBot-2.0.0-amd64.deb");
  writeFileSync(file, "deb bytes");
  return { dir, file, cache, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
}

test("the arguments are the system pkexec, a fixed script, and two values", async () => {
  const f = fixture();
  try {
    const calls = [];
    const result = await installPackageElevated({
      file: f.file,
      sha512: base64("deb bytes"),
      run: (program, args, options, callback) => {
        calls.push({ program, args, options });
        callback(null, "", "");
        return { stdin: { end() {} } };
      },
    });
    assert.deepEqual(result, { ok: true });
    assert.equal(calls.length, 1);
    assert.equal(calls[0].program, "/usr/bin/pkexec");
    assert.deepEqual(calls[0].args, ["/bin/sh", "-c", ELEVATED_INSTALL_SCRIPT, "sh", f.file, hex("deb bytes")]);
    assert.ok(calls[0].options.timeout > 60_000);
  } finally {
    f.cleanup();
  }
});

test("each way the install can end maps to a reason the card can act on", async () => {
  const f = fixture();
  try {
    const outcome = (error, stderr = "") =>
      installPackageElevated({
        file: f.file,
        sha512: hex("deb bytes"),
        run: (_p, _a, _o, callback) => {
          callback(error, "", stderr);
          return null;
        },
      });
    const coded = (code, extra = {}) => Object.assign(new Error(`exit ${code}`), { code, ...extra });

    for (const [code, reason, key, detail] of [
      [126, "declined", "native.update.promptClosed", "The password prompt was closed."],
      [127, "refused", "native.update.noAdmin", "This session could not ask for administrator rights."],
      [65, "checksum", "native.update.checksumMismatch", "The downloaded update did not match its checksum. Check again to download it fresh."],
      ["ENOENT", "unavailable", "native.update.noPkexec", "pkexec is not installed."],
    ]) assert.deepEqual(await outcome(coded(code)), { ok: false, reason, detail, detailKey: key });
    const timedOut = await outcome(coded(null, { killed: true }));
    assert.deepEqual(timedOut, { ok: false, reason: "failed", detail: "The install did not finish: The install took too long.", detailKey: "native.update.installFailed", detailParams: { reason: "The install took too long.", timedOut: "true" } });

    const failed = await outcome(coded(100), "W: noise\nE: Unable to acquire the dpkg frontend lock\n");
    assert.equal(failed.reason, "failed");
    assert.match(failed.detail, /Unable to acquire the dpkg frontend lock/);

    const slow = await outcome(coded(null, { killed: true }));
    assert.equal(slow.reason, "failed");
    assert.match(slow.detail, /too long/);

    const thrown = await installPackageElevated({
      file: f.file,
      sha512: hex("deb bytes"),
      run: () => {
        throw new Error("spawn failed");
      },
    });
    assert.equal(thrown.reason, "unavailable");
  } finally {
    f.cleanup();
  }
});

test("nothing is run for a file or checksum that cannot be trusted", async () => {
  const f = fixture();
  try {
    const run = () => assert.fail("pkexec must not be started");
    const link = join(f.dir, "link.deb");
    symlinkSync(f.file, link);
    writeFileSync(join(f.dir, "not-a-deb.txt"), "x");

    for (const file of [undefined, "", "relative.deb", join(f.dir, "gone.deb"), join(f.dir, "not-a-deb.txt"), f.cache, link]) {
      const result = await installPackageElevated({ file, sha512: hex("deb bytes"), run });
      assert.equal(result.ok, false, String(file));
      assert.equal(result.reason, "unverifiable", String(file));
    }
    const noSum = await installPackageElevated({ file: f.file, sha512: undefined, run });
    assert.equal(noSum.reason, "unverifiable");
    assert.match(noSum.detail, /checksum/);
  } finally {
    f.cleanup();
  }
});

// ---- the script itself, in a real shell --------------------------------

// The root script only ever runs on Linux, and the stand-in apt-get uses GNU coreutils (stat -c, find, sha512sum).
const linuxOnly = { skip: process.platform !== "linux" && "the root script runs only on Linux" };

function runScript(file, expected, { aptBody } = {}) {
  const dir = mkdtempSync(join(tmpdir(), "omb-script-"));
  const bin = join(dir, "bin");
  const log = join(dir, "apt.log");
  mkdirSync(bin);
  writeFileSync(
    join(bin, "apt-get"),
    aptBody ?? [
      "#!/bin/sh",
      `printf '%s\\n' "$@" > '${log}'`,
      'for last; do :; done',
      `printf 'dir=%s\\nmode=%s\\ndirmode=%s\\nbytes=%s\\nfront=%s\\n' "$(dirname "$last")" "$(stat -c %a "$last")" "$(stat -c %a "$(dirname "$last")")" "$(cat "$last")" "$DEBIAN_FRONTEND" >> '${log}'`,
      "",
    ].join("\n"),
  );
  chmodSync(join(bin, "apt-get"), 0o755);
  const result = spawnSync("/bin/sh", ["-c", ELEVATED_INSTALL_SCRIPT, "sh", file, expected], {
    encoding: "utf8",
    env: { PATH: `${bin}:${process.env.PATH}`, TMPDIR: dir },
  });
  return {
    result,
    log: existsSync(log) ? readFileSync(log, "utf8") : null,
    leftovers: execFileSync("find", [dir, "-maxdepth", "1", "-name", "tmp.*"], { encoding: "utf8" }).trim(),
    cleanup: () => rmSync(dir, { recursive: true, force: true }),
  };
}

test("root installs its own verified copy, not the file in the user's cache", linuxOnly, () => {
  const f = fixture();
  const run = runScript(f.file, hex("deb bytes"));
  try {
    assert.equal(run.result.status, 0, run.result.stderr);
    const lines = run.log.trim().split("\n");
    assert.deepEqual(lines.slice(0, 4), ["install", "-y", "-o", "Dpkg::Options::=--force-confold"]);
    const installed = lines[4];
    assert.match(installed, /\/tmp\.[^/]+\/update\.deb$/);
    assert.ok(!installed.startsWith(f.cache), "must not install straight from the user-writable cache");
    assert.ok(run.log.includes("mode=644") && run.log.includes("dirmode=755"));
    assert.ok(run.log.includes("bytes=deb bytes") && run.log.includes("front=noninteractive"));
    assert.equal(run.leftovers, "");
  } finally {
    run.cleanup();
    f.cleanup();
  }
});

test("a file swapped after the download is refused before apt-get runs", linuxOnly, () => {
  const f = fixture();
  writeFileSync(f.file, "swapped for something else");
  const run = runScript(f.file, hex("deb bytes"));
  try {
    assert.equal(run.result.status, 65);
    assert.match(run.result.stderr, /checksum/);
    assert.equal(run.log, null, "apt-get must never see an unverified package");
    assert.equal(run.leftovers, "");
  } finally {
    run.cleanup();
    f.cleanup();
  }
});

test("a failing apt-get fails the script and still cleans up", linuxOnly, () => {
  const f = fixture();
  const run = runScript(f.file, hex("deb bytes"), { aptBody: "#!/bin/sh\necho 'E: broken dependencies' >&2\nexit 100\n" });
  try {
    assert.equal(run.result.status, 100);
    assert.match(run.result.stderr, /broken dependencies/);
    assert.equal(run.leftovers, "");
  } finally {
    run.cleanup();
    f.cleanup();
  }
});