Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J Tools
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / Server Manager
406 Dateien
core-20261024T135205Z-7c487d719de2
Hinzugefügt: · Erstellt:
1
"use strict";2
// End-to-end UI check of the chj.users plugin: real plugin runtime, preload, IPC, SSH session and3
// sudo flow against a disposable Docker container built from test/fixtures/usersIntegration.4
// Run: CHJ_USERS_IMAGE=chj-users-it-debian npx electron scripts/smokeUsersUi.js [screenshot-dir]5
const { app, BrowserWindow, ipcMain, protocol } = require("electron");6
const assert = require("node:assert/strict");7
const fs = require("node:fs");8
const os = require("node:os");9
const path = require("node:path");10
const { execFileSync, spawnSync } = require("node:child_process");11
const { hardenSession } = require("../src/main/bootstrap/createMainWindow");12
const { PluginRegistry } = require("../src/main/plugins/pluginRegistry");13
const { PluginRuntime } = require("../src/main/plugins/pluginRuntime");14
const { SessionManager } = require("../src/main/sessions/sessionManager");15
const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");16
const { UserAdminService, createUserAdminApi } = require("../src/main/users/userAdminService");17
const { KeyGeneratorService } = require("../src/main/keys/keyGeneratorService");18
const { WindowsUserAdminService } = require("../src/main/users/windowsUserAdminService");19
const { WindowsExecutor } = require("../src/main/sessions/windowsPrivilegedExec");20
const { detectPlatform } = require("../src/main/sessions/platformDetect");22
const image = process.env.CHJ_USERS_IMAGE || "chj-users-it-debian";23
const container = `chj-users-ui-${process.pid}`;24
const root = fs.mkdtempSync(path.join(os.tmpdir(), "chj-users-ui-"));25
const shots = process.argv.find((arg, index) => index > 1 && !arg.startsWith("-") && !arg.endsWith(".js")) || null;26
app.setPath("userData", root);27
protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);28
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));29
const errors = [];30
let window;31
const js = (source) => window.webContents.executeJavaScript(source);32
async function until(source, timeout = 20000) {33
const started = Date.now();34
while (Date.now() - started < timeout) { if (await js(`(() => { try { return (${source}); } catch { return false; } })()`)) return; await delay(60); }35
throw new Error(`UI condition timed out: ${source}\nstatus: ${await js("document.querySelector('#status').textContent")}`);36
}37
async function shot(name) {38
if (!shots) return;39
fs.mkdirSync(shots, { recursive: true });40
fs.writeFileSync(path.join(shots, `${name}.png`), (await window.webContents.capturePage()).toPNG());41
}42
function inContainer(command) {43
const result = spawnSync("docker", ["exec", container, "sh", "-c", command], { encoding: "utf8" });44
return { code: result.status, stdout: result.stdout };45
}46
async function sudo(password = "AdminPass-1") {47
await until("document.querySelector('#sudoDialog').open");48
await js(`document.querySelector('#sudoPassword').value = ${JSON.stringify(password)}; document.querySelector('#sudoForm').requestSubmit();`);49
}50
async function confirm() {51
await until("document.querySelector('#confirmDialog').open");52
await js("document.querySelector('#confirmForm').requestSubmit()");53
}54
async function openTab(name) { await js(`document.querySelector('#tab-${name}').click()`); }55
async function selectUser(name) {56
await until(`document.querySelector('button[data-username="${name}"]')`);57
await js(`document.querySelector('button[data-username="${name}"]').click()`);58
await until(`document.querySelector('#detailTitle').textContent === "${name}" && !document.querySelector('#detailSubtitle').textContent.includes("Načítám")`);59
}61
async function run() {62
execFileSync("docker", ["run", "-d", "--rm", "--name", container, "--sysctl", "net.ipv6.conf.all.disable_ipv6=0", "-p", "127.0.0.1::22", image]);63
const port = Number(execFileSync("docker", ["port", container, "22/tcp"], { encoding: "utf8" }).trim().split(":").pop());64
await delay(1500);65
await app.whenReady(); hardenSession();66
const hostKeys = new Map();67
const profileService = {68
get: () => ({ id: "it", label: "Docker test", host: "127.0.0.1", port, username: "admin", authMethod: "password" }),69
getHostKey: () => hostKeys.get(port) || null, trustHostKey: (_host, _port, fingerprint) => hostKeys.set(port, fingerprint),70
getStoredPassword: () => "AdminPass-1", markUsed() {}71
};72
const sessionManager = new SessionManager({ profileService, latencyMonitor: { start: () => () => {} } });73
try { await sessionManager.connect({ sessionId: "ui", profileId: "it" }); }74
catch (error) { sessionManager.trustPending({ sessionId: "ui", profileId: "it", fingerprint: error.fingerprint }); await sessionManager.connect({ sessionId: "ui", profileId: "it" }); }75
const windowsService = new WindowsUserAdminService({ sessionManager, windowsExecutor: new WindowsExecutor({ sessionManager }) });76
const userAdmin = createUserAdminApi(new UserAdminService({ sessionManager, privilegedExecutor: new PrivilegedExecutor({ sessionManager }), windowsService, detectPlatform }));77
const keyPath = path.join(root, "id_ed25519_ui");78
const keyGeneratorService = new KeyGeneratorService({ selectSavePath: async () => ({ canceled: false, path: keyPath }) });79
const registry = new PluginRegistry(root, null, { bundledRoot: path.join(__dirname, "../src/main/firstPartyPlugins") });80
// The assertions below check the Czech UI texts.81
const runtime = new PluginRuntime({ BrowserWindow, registry, getLanguage: () => "cs", sessionManager, userAdmin, keyGeneratorService,82
preload: path.join(__dirname, "../src/preload/pluginPreload.js"), getMainWindow: () => null, isVaultUnlocked: () => true });83
runtime.registerIpc(ipcMain);84
runtime.open("chj.users");85
window = runtime.windows.get("chj.users");86
window.webContents.on("console-message", (event) => { if (event.level === "error") errors.push(event.message); });87
window.setSize(1280, 860);88
await until("document.querySelector('button[data-username=\"admin\"]')");89
assert.match(await js("document.querySelector('#privilegeBadge').textContent"), /admin: sudo s heslem/);90
assert.equal(await js("document.querySelector('#elevatedButton').hidden"), false);91
assert.equal(await js("document.querySelectorAll('#sudoPassword').length"), 1, "only the dialog has a sudo password field");92
await shot("01-readonly-list");94
// Wrong password keeps the dialog usable; cancel leaves the plugin working.95
await js("document.querySelector('#elevatedButton').click()");96
await sudo("wrong-password");97
await until("!document.querySelector('#sudoError').hidden", 30000);98
await shot("02-sudo-wrong-password");99
await js("document.querySelector('#sudoDialog [data-close]').click()");100
await until("document.querySelector('#status').textContent.includes('zrušena')");101
await js("document.querySelector('#elevatedButton').click()");102
await sudo();103
await until("document.querySelector('#status').textContent.includes('Načteno') && document.querySelector('#elevatedButton').hidden");104
assert.match(await js("document.querySelector('#usersBody').textContent"), /plné sudo/);105
await shot("03-privileged-list");107
// Create an account through the dialog.108
await js("document.querySelector('#createButton').click()");109
await until("document.querySelector('#createDialog').open");110
await js(`document.querySelector('#createUsername').value = 'uitester'; document.querySelector('#createComment').value = 'UI Tester';111
document.querySelector('#createPassword').value = 'Ui-Pass-123'; document.querySelector('#createPassword2').value = 'Ui-Pass-123';`);112
await shot("04-create-dialog");113
await js("document.querySelector('#createForm').requestSubmit()");114
await sudo();115
await until("document.querySelector('#detailTitle').textContent === 'uitester'", 30000);116
assert.equal(inContainer("getent passwd uitester | cut -d: -f5").stdout.trim(), "UI Tester");117
assert.equal(await js("document.querySelector('#createPassword').value"), "", "password fields are cleared");119
// Load privileged detail, inspect all tabs.120
await selectUser("uitester");121
await js("Array.from(document.querySelectorAll('#detailNotice button')).find(b => b.textContent.includes('sudo')).click()");122
await sudo();123
await until("document.querySelector('#detailBadges').textContent.includes('ověřeno se sudo')", 30000);124
await shot("05-detail-account");125
await openTab("groups"); await shot("06-detail-groups");126
await openTab("ssh");127
assert.match(await js("document.querySelector('#panel-ssh').textContent"), /Skutečně IPv4/);128
await shot("07-detail-ssh");130
// Apply an SSH policy: password off, IPv6 denied.131
await js(`document.querySelector('input[name="policy-password"][value="no"]').click(); document.querySelector('input[name="policy-ipv6"][value="deny"]').click();132
document.querySelector('.policy-form').requestSubmit();`);133
await confirm();134
await sudo();135
await until("document.querySelector('#status').textContent.includes('SSH přístup byl změněn')", 60000);136
assert.match(inContainer("cat /etc/ssh/sshd_config.d/01-chj-users.conf").stdout, /DenyUsers uitester@::\/0[\s\S]*Match User uitester\n\tPasswordAuthentication no/);137
await until("document.querySelector('button[data-username=\"uitester\"]').closest('tr').textContent.includes('bez IPv6')");138
assert.equal(await js("document.querySelector('#panel-account').hidden"), true, "only the selected tab panel is visible");139
await shot("08-ssh-applied");141
// Add a key via the dialog, using the local generator (private key saved locally only).142
await openTab("keys");143
await js("Array.from(document.querySelectorAll('#panel-keys button')).find(b => b.textContent.includes('Přidat Ed25519')).click()");144
await until("document.querySelector('#keyDialog').open");145
await js("document.querySelector('#keyGenerateButton').click(); document.querySelector('#keyGenRun').click();");146
await until("document.querySelector('#keyText').value.startsWith('ssh-ed25519 ')");147
await shot("09-key-dialog");148
await js("document.querySelector('#keyForm').requestSubmit()");149
await sudo();150
await until("document.querySelector('#panel-keys').textContent.includes('SHA256:')", 30000);151
assert.ok(fs.existsSync(keyPath), "private key stored locally");152
assert.equal(inContainer(`grep -c ssh-ed25519 /home/uitester/.ssh/authorized_keys`).stdout.trim(), "1");153
assert.equal(inContainer(`grep -c PRIVATE /home/uitester/.ssh/authorized_keys`).stdout.trim(), "0");154
await shot("10-keys");155
await openTab("diagnostics"); await shot("11-diagnostics");157
// root: protected account, but SSH access (incl. PermitRootLogin) and keys are manageable.158
await selectUser("root");159
assert.equal(await js("Array.from(document.querySelectorAll('#panel-account button')).some(b => b.textContent.includes('Odstranit'))"), false, "no destructive root actions");160
assert.match(await js("document.querySelector('#detailNotice').textContent"), /Chráněný systémový účet root/);161
await js("Array.from(document.querySelectorAll('#detailNotice button')).find(b => b.textContent.includes('sudo')).click()");162
await sudo();163
await until("document.querySelector('#detailBadges').textContent.includes('ověřeno se sudo')", 30000);164
await openTab("ssh");165
assert.match(await js("document.querySelector('#panel-ssh').textContent"), /PermitRootLogin \(root\)[\s\S]*Jen klíč \(doporučeno\)/);166
assert.equal(await js("document.querySelector('input[name=\"policy-permitRootLogin\"]').disabled"), false);167
await shot("11b-root-ssh");168
await openTab("keys");169
assert.equal(await js("Array.from(document.querySelectorAll('#panel-keys button')).some(b => b.textContent.includes('Přidat Ed25519') && !b.disabled)"), true, "root keys can be added");170
await shot("11c-root-keys");172
// Protected account: the session user cannot be deleted from the UI.173
await selectUser("admin");174
assert.equal(await js("Array.from(document.querySelectorAll('#panel-account button')).some(b => b.textContent.includes('Odstranit'))"), false);175
assert.match(await js("document.querySelector('#detailNotice').textContent"), /Tímto účtem je CH-J právě připojeno/);177
// Delete with typed confirmation.178
await selectUser("uitester");179
await openTab("account");180
await js("Array.from(document.querySelectorAll('#panel-account button')).find(b => b.textContent.includes('Odstranit')).click()");181
await until("document.querySelector('#confirmDialog').open");182
await js("document.querySelector('#confirmForm').requestSubmit()");183
assert.equal(await js("document.querySelector('#confirmDialog').open"), true, "typed confirmation required");184
await js("document.querySelector('#confirmTyped').value = 'uitester'; document.querySelector('#confirmForm').requestSubmit();");185
await sudo();186
await until("!document.querySelector('button[data-username=\"uitester\"]')", 60000);187
assert.equal(inContainer("getent passwd uitester").code, 2);188
assert.equal(inContainer("test -e /etc/ssh/sshd_config.d/01-chj-users.conf").code, 1, "SSH policy of the deleted account removed");189
await shot("12-after-delete");190
assert.deepEqual(errors, [], "no renderer errors");191
console.log("chj.users UI smoke passed");192
}194
run().then(() => { cleanup(); app.exit(0); }, (error) => { console.error(error); cleanup(); app.exit(1); });195
function cleanup() {196
try { execFileSync("docker", ["rm", "-f", container], { stdio: "ignore" }); } catch {}197
fs.rmSync(root, { recursive: true, force: true });198
}SHA-256
140a83b75e5091b9ecee39c59148047ecd077d265adc6b79a648a336197892c6CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6