CH-J Server ManagerSSH کے ذریعے سرور کا انتظام
مینو
شائع شدہ سورس

CH-J Server Manager

ایپلیکیشن کی کسی مخصوص ریلیز کی ڈائریکٹریز اور فائلیں دیکھیں۔

سورس ZIP ڈاؤن لوڈ کریں
CH-J / Server Manager 368 فائلیں
core-20261017T201717Z-057348c4a70f شامل کیا گیا: تاریخ درج نہیں · بلڈ کیا گیا:
smokeUsersUi.js شامل کیا گیا: تاریخ درج نہیں 13.1 KB · 197 لائنیں
کوڈ فائل ڈاؤن لوڈ کریں
1"use strict";
2// End-to-end UI check of the chj.users plugin: real plugin runtime, preload, IPC, SSH session and
3// 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]
5const { app, BrowserWindow, ipcMain, protocol } = require("electron");
6const assert = require("node:assert/strict");
7const fs = require("node:fs");
8const os = require("node:os");
9const path = require("node:path");
10const { execFileSync, spawnSync } = require("node:child_process");
11const { hardenSession } = require("../src/main/bootstrap/createMainWindow");
12const { PluginRegistry } = require("../src/main/plugins/pluginRegistry");
13const { PluginRuntime } = require("../src/main/plugins/pluginRuntime");
14const { SessionManager } = require("../src/main/sessions/sessionManager");
15const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");
16const { UserAdminService, createUserAdminApi } = require("../src/main/users/userAdminService");
17const { KeyGeneratorService } = require("../src/main/keys/keyGeneratorService");
18const { WindowsUserAdminService } = require("../src/main/users/windowsUserAdminService");
19const { WindowsExecutor } = require("../src/main/sessions/windowsPrivilegedExec");
20const { detectPlatform } = require("../src/main/sessions/platformDetect");
22const image = process.env.CHJ_USERS_IMAGE || "chj-users-it-debian";
23const container = `chj-users-ui-${process.pid}`;
24const root = fs.mkdtempSync(path.join(os.tmpdir(), "chj-users-ui-"));
25const shots = process.argv.find((arg, index) => index > 1 && !arg.startsWith("-") && !arg.endsWith(".js")) || null;
26app.setPath("userData", root);
27protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);
28const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29const errors = [];
30let window;
31const js = (source) => window.webContents.executeJavaScript(source);
32async 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")}`);
37async 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());
42function inContainer(command) {
43 const result = spawnSync("docker", ["exec", container, "sh", "-c", command], { encoding: "utf8" });
44 return { code: result.status, stdout: result.stdout };
46async 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();`);
50async function confirm() {
51 await until("document.querySelector('#confirmDialog').open");
52 await js("document.querySelector('#confirmForm').requestSubmit()");
54async function openTab(name) { await js(`document.querySelector('#tab-${name}').click()`); }
55async 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")`);
61async 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 const runtime = new PluginRuntime({ BrowserWindow, registry, sessionManager, userAdmin, keyGeneratorService,
81 preload: path.join(__dirname, "../src/preload/pluginPreload.js"), getMainWindow: () => null, isVaultUnlocked: () => true });
82 runtime.registerIpc(ipcMain);
83 runtime.open("chj.users");
84 window = runtime.windows.get("chj.users");
85 window.webContents.on("console-message", (event) => { if (event.level === "error") errors.push(event.message); });
86 window.setSize(1280, 860);
87 await until("document.querySelector('button[data-username=\"admin\"]')");
88 assert.match(await js("document.querySelector('#privilegeBadge').textContent"), /admin: sudo s heslem/);
89 assert.equal(await js("document.querySelector('#elevatedButton').hidden"), false);
90 assert.equal(await js("document.querySelectorAll('#sudoPassword').length"), 1, "only the dialog has a sudo password field");
91 await shot("01-readonly-list");
93 // Wrong password keeps the dialog usable; cancel leaves the plugin working.
94 await js("document.querySelector('#elevatedButton').click()");
95 await sudo("wrong-password");
96 await until("!document.querySelector('#sudoError').hidden", 30000);
97 await shot("02-sudo-wrong-password");
98 await js("document.querySelector('#sudoDialog [data-close]').click()");
99 await until("document.querySelector('#status').textContent.includes('zrušena')");
100 await js("document.querySelector('#elevatedButton').click()");
101 await sudo();
102 await until("document.querySelector('#status').textContent.includes('Načteno') && document.querySelector('#elevatedButton').hidden");
103 assert.match(await js("document.querySelector('#usersBody').textContent"), /plné sudo/);
104 await shot("03-privileged-list");
106 // Create an account through the dialog.
107 await js("document.querySelector('#createButton').click()");
108 await until("document.querySelector('#createDialog').open");
109 await js(`document.querySelector('#createUsername').value = 'uitester'; document.querySelector('#createComment').value = 'UI Tester';
110 document.querySelector('#createPassword').value = 'Ui-Pass-123'; document.querySelector('#createPassword2').value = 'Ui-Pass-123';`);
111 await shot("04-create-dialog");
112 await js("document.querySelector('#createForm').requestSubmit()");
113 await sudo();
114 await until("document.querySelector('#detailTitle').textContent === 'uitester'", 30000);
115 assert.equal(inContainer("getent passwd uitester | cut -d: -f5").stdout.trim(), "UI Tester");
116 assert.equal(await js("document.querySelector('#createPassword').value"), "", "password fields are cleared");
118 // Load privileged detail, inspect all tabs.
119 await selectUser("uitester");
120 await js("Array.from(document.querySelectorAll('#detailNotice button')).find(b => b.textContent.includes('sudo')).click()");
121 await sudo();
122 await until("document.querySelector('#detailBadges').textContent.includes('ověřeno se sudo')", 30000);
123 await shot("05-detail-account");
124 await openTab("groups"); await shot("06-detail-groups");
125 await openTab("ssh");
126 assert.match(await js("document.querySelector('#panel-ssh').textContent"), /Skutečně IPv4/);
127 await shot("07-detail-ssh");
129 // Apply an SSH policy: password off, IPv6 denied.
130 await js(`document.querySelector('input[name="policy-password"][value="no"]').click(); document.querySelector('input[name="policy-ipv6"][value="deny"]').click();
131 document.querySelector('.policy-form').requestSubmit();`);
132 await confirm();
133 await sudo();
134 await until("document.querySelector('#status').textContent.includes('SSH přístup byl změněn')", 60000);
135 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/);
136 await until("document.querySelector('button[data-username=\"uitester\"]').closest('tr').textContent.includes('bez IPv6')");
137 assert.equal(await js("document.querySelector('#panel-account').hidden"), true, "only the selected tab panel is visible");
138 await shot("08-ssh-applied");
140 // Add a key via the dialog, using the local generator (private key saved locally only).
141 await openTab("keys");
142 await js("Array.from(document.querySelectorAll('#panel-keys button')).find(b => b.textContent.includes('Přidat Ed25519')).click()");
143 await until("document.querySelector('#keyDialog').open");
144 await js("document.querySelector('#keyGenerateButton').click(); document.querySelector('#keyGenRun').click();");
145 await until("document.querySelector('#keyText').value.startsWith('ssh-ed25519 ')");
146 await shot("09-key-dialog");
147 await js("document.querySelector('#keyForm').requestSubmit()");
148 await sudo();
149 await until("document.querySelector('#panel-keys').textContent.includes('SHA256:')", 30000);
150 assert.ok(fs.existsSync(keyPath), "private key stored locally");
151 assert.equal(inContainer(`grep -c ssh-ed25519 /home/uitester/.ssh/authorized_keys`).stdout.trim(), "1");
152 assert.equal(inContainer(`grep -c PRIVATE /home/uitester/.ssh/authorized_keys`).stdout.trim(), "0");
153 await shot("10-keys");
154 await openTab("diagnostics"); await shot("11-diagnostics");
156 // root: protected account, but SSH access (incl. PermitRootLogin) and keys are manageable.
157 await selectUser("root");
158 assert.equal(await js("Array.from(document.querySelectorAll('#panel-account button')).some(b => b.textContent.includes('Odstranit'))"), false, "no destructive root actions");
159 assert.match(await js("document.querySelector('#detailNotice').textContent"), /Chráněný systémový účet root/);
160 await js("Array.from(document.querySelectorAll('#detailNotice button')).find(b => b.textContent.includes('sudo')).click()");
161 await sudo();
162 await until("document.querySelector('#detailBadges').textContent.includes('ověřeno se sudo')", 30000);
163 await openTab("ssh");
164 assert.match(await js("document.querySelector('#panel-ssh').textContent"), /PermitRootLogin \(root\)[\s\S]*Jen klíč \(doporučeno\)/);
165 assert.equal(await js("document.querySelector('input[name=\"policy-permitRootLogin\"]').disabled"), false);
166 await shot("11b-root-ssh");
167 await openTab("keys");
168 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");
169 await shot("11c-root-keys");
171 // Protected account: the session user cannot be deleted from the UI.
172 await selectUser("admin");
173 assert.equal(await js("Array.from(document.querySelectorAll('#panel-account button')).some(b => b.textContent.includes('Odstranit'))"), false);
174 assert.match(await js("document.querySelector('#detailNotice').textContent"), /Tímto účtem je CH-J právě připojeno/);
176 // Delete with typed confirmation.
177 await selectUser("uitester");
178 await openTab("account");
179 await js("Array.from(document.querySelectorAll('#panel-account button')).find(b => b.textContent.includes('Odstranit')).click()");
180 await until("document.querySelector('#confirmDialog').open");
181 await js("document.querySelector('#confirmForm').requestSubmit()");
182 assert.equal(await js("document.querySelector('#confirmDialog').open"), true, "typed confirmation required");
183 await js("document.querySelector('#confirmTyped').value = 'uitester'; document.querySelector('#confirmForm').requestSubmit();");
184 await sudo();
185 await until("!document.querySelector('button[data-username=\"uitester\"]')", 60000);
186 assert.equal(inContainer("getent passwd uitester").code, 2);
187 assert.equal(inContainer("test -e /etc/ssh/sshd_config.d/01-chj-users.conf").code, 1, "SSH policy of the deleted account removed");
188 await shot("12-after-delete");
189 assert.deepEqual(errors, [], "no renderer errors");
190 console.log("chj.users UI smoke passed");
193run().then(() => { cleanup(); app.exit(0); }, (error) => { console.error(error); cleanup(); app.exit(1); });
194function cleanup() {
195 try { execFileSync("docker", ["rm", "-f", container], { stdio: "ignore" }); } catch {}
196 fs.rmSync(root, { recursive: true, force: true });
SHA-256da92fa9292063eaf68ac9119467a1626e9ca841792e1d5b0c46b505176ab4c0d

CH-J Proprietary Software License 1.14 · سورس CH-J Proprietary Software License 1.14 کے تحت فراہم کیا جاتا ہے۔ اس کی دستیابی لائسنس کی شرائط نہیں بدلتی اور نہ ہی اضافی حقوق دیتی ہے۔

آرکائیو کا SHA-256cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3
فائلیں