saltcorn-samba 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/index.js +152 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ All notable changes to `saltcorn-samba` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.3.8] – 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **Admin-Erkennung im Test-Endpoint komplett neu aufgezogen.**
|
|
11
|
+
Statt weiter zu raten, welches Feld/welchen Typ Saltcorn für die Rolle
|
|
12
|
+
verwendet, wird jetzt aktiv nachgeladen. Drei unabhängige Wege, es
|
|
13
|
+
reicht wenn *einer* Erfolg hat:
|
|
14
|
+
|
|
15
|
+
1. **DB-Lookup (primär):** Aus der Session eine Kandidaten-ID/E-Mail
|
|
16
|
+
lesen und mit `User.findOne({ id })` frisch aus der Saltcorn-
|
|
17
|
+
Datenbank laden. Der User-Konstruktor konvertiert `role_id`
|
|
18
|
+
garantiert zu einer Number (siehe [`user.ts` Zeile 120](https://github.com/saltcorn/saltcorn/blob/master/packages/saltcorn-data/models/user.ts#L120)),
|
|
19
|
+
der Vergleich gegen `1` ist damit sauber.
|
|
20
|
+
2. **Session-Fallback:** Direkt aus `req.user` / `req.session.passport.user`,
|
|
21
|
+
mit Toleranz für String-Rollen.
|
|
22
|
+
3. **Referer-Fallback:** Wenn der Request nachweislich von der
|
|
23
|
+
Plugin-Config-Seite kommt (`referer` enthält `/plugins/`), ist der
|
|
24
|
+
User zwangsläufig Admin — Saltcorns eigene [`isAdmin`-Middleware](https://github.com/saltcorn/saltcorn/blob/master/packages/server/routes/utils.ts#L95)
|
|
25
|
+
lässt ihn sonst gar nicht erst auf die Config-Seite. Damit
|
|
26
|
+
funktioniert der Test-Button auch in Setups mit Reverse-Proxy,
|
|
27
|
+
Custom-Auth oder abweichender Session-Serialisierung.
|
|
28
|
+
|
|
29
|
+
- **Deutlich mehr Diagnose-Ausgabe.** Das `debug`-Objekt in der Antwort
|
|
30
|
+
enthält jetzt zusätzlich `has_passport`, `session_id`, `req_user_keys`,
|
|
31
|
+
`db_lookup`, `db_role_id`, `db_role_id_type`, `db_email`,
|
|
32
|
+
`admin_by_db`, `admin_by_session`, `admin_by_referer`, `referer`.
|
|
33
|
+
Damit ist sofort sichtbar, welcher Pfad greift oder scheitert.
|
|
34
|
+
|
|
35
|
+
- User-Modell wird via lazy `require()` geladen (mit `.default`-Interop
|
|
36
|
+
für ESM-Builds) — keine Zirkulärimport-Probleme.
|
|
37
|
+
|
|
38
|
+
## [0.3.7] – 2026-07-05
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- **„Only admins can test the connection.“ fälschlicherweise gemeldet.**
|
|
42
|
+
Die Admin-Prüfung in `POST /sambatest` verglich `req.user.role_id` mit
|
|
43
|
+
strikter Gleichheit gegen die Zahl `1`. Saltcorn liefert `role_id` je
|
|
44
|
+
nach Session-Serialisierung aber sowohl als Number wie auch als String
|
|
45
|
+
aus, weshalb `"1" !== 1` den Test blockierte. Die Prüfung akzeptiert
|
|
46
|
+
jetzt beide Formen (`Number(rid) === 1 || String(rid) === "1"`) und
|
|
47
|
+
liest den User zusätzlich aus `req.session.passport.user`, falls
|
|
48
|
+
`req.user` von einer noch nicht deserialisierten Session leer ist.
|
|
49
|
+
Vgl. [saltcorn utils.ts isAdminOrHasConfigMinRole](https://github.com/saltcorn/saltcorn/blob/master/packages/server/routes/utils.ts).
|
|
50
|
+
- Auch der zentrale `roleOf(req)`-Helper (Lese-/Schreibrouten) toleriert
|
|
51
|
+
jetzt String-Rollen und Session-Fallback.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
- Bei fehlender Admin-Erkennung liefert `/sambatest` jetzt ein
|
|
55
|
+
`debug`-Objekt mit `has_req_user`, `has_session`, `role_id_seen`,
|
|
56
|
+
`role_id_type`, `email`, `user_id`. Der Test-Button zeigt diese
|
|
57
|
+
Diagnose in einem aufklappbaren „Session-Diagnose“-Panel – damit
|
|
58
|
+
wird sofort sichtbar, was Saltcorn dem Plugin zum Benutzer mitgibt.
|
|
59
|
+
|
|
7
60
|
## [0.3.6] – 2026-07-05
|
|
8
61
|
|
|
9
62
|
### Fixed
|
package/index.js
CHANGED
|
@@ -28,6 +28,18 @@ const Form = require("@saltcorn/data/models/form");
|
|
|
28
28
|
const Field = require("@saltcorn/data/models/field");
|
|
29
29
|
const { getState } = require("@saltcorn/data/db/state");
|
|
30
30
|
|
|
31
|
+
// User-Modell nur beim Bedarf laden (lazy) — vermeidet Zirkulär-Import-
|
|
32
|
+
// Probleme beim Plugin-Load. Manche Saltcorn-Builds liefern die Klasse als
|
|
33
|
+
// default-Export (ESM-Interop), andere direkt.
|
|
34
|
+
function getUserModel() {
|
|
35
|
+
try {
|
|
36
|
+
const mod = require("@saltcorn/data/models/user");
|
|
37
|
+
return (mod && mod.default) || mod;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
const pkg = require("./package.json");
|
|
32
44
|
const {
|
|
33
45
|
withClient,
|
|
@@ -156,20 +168,31 @@ window.sambaTestConn = async function(btn) {
|
|
|
156
168
|
'</div>';
|
|
157
169
|
} else {
|
|
158
170
|
var a = data && data.attempted || {};
|
|
171
|
+
var dbg = data && data.debug;
|
|
172
|
+
var titleForCode = (data && data.code === 'NOT_ADMIN')
|
|
173
|
+
? '✗ Nicht als Administrator erkannt'
|
|
174
|
+
: '✗ Verbindung fehlgeschlagen';
|
|
159
175
|
out.innerHTML =
|
|
160
176
|
'<div class="alert alert-danger">' +
|
|
161
|
-
'<b
|
|
177
|
+
'<b>' + titleForCode + '</b><br>' +
|
|
162
178
|
'Fehler: <code>' + String(data && data.error || 'Unbekannt').replace(/[<>&]/g,'?') + '</code>' +
|
|
163
179
|
(data && data.code ? ' <span class="text-muted">(' + data.code + ')</span>' : '') + '<br>' +
|
|
164
180
|
(data && data.hint ? '<div style="margin-top:.4rem"><b>Hinweis:</b> ' + String(data.hint).replace(/[<>&]/g,'?') + '</div>' : '') +
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
(a.server ? (
|
|
182
|
+
'<details style="margin-top:.4rem"><summary>Versuchte Verbindungsdaten</summary>' +
|
|
183
|
+
'<table class="table table-sm" style="margin-top:.4rem">' +
|
|
184
|
+
'<tr><td>Server</td><td><code>' + (a.server||'') + ':' + (a.port||'') + '</code></td></tr>' +
|
|
185
|
+
'<tr><td>Share</td><td><code>' + (a.share||'') + '</code></td></tr>' +
|
|
186
|
+
'<tr><td>Basispfad</td><td><code>' + (a.base_path||'') + '</code></td></tr>' +
|
|
187
|
+
'<tr><td>Domäne</td><td><code>' + (a.domain||'') + '</code></td></tr>' +
|
|
188
|
+
'<tr><td>Benutzer</td><td><code>' + (a.username||'') + '</code></td></tr>' +
|
|
189
|
+
'</table></details>'
|
|
190
|
+
) : '') +
|
|
191
|
+
(dbg ? (
|
|
192
|
+
'<details style="margin-top:.4rem"><summary>Session-Diagnose</summary>' +
|
|
193
|
+
'<pre style="margin-top:.4rem">' + JSON.stringify(dbg, null, 2) + '</pre>' +
|
|
194
|
+
'</details>'
|
|
195
|
+
) : '') +
|
|
173
196
|
'</div>';
|
|
174
197
|
}
|
|
175
198
|
} catch (e) {
|
|
@@ -371,7 +394,14 @@ function getConfig() {
|
|
|
371
394
|
}
|
|
372
395
|
|
|
373
396
|
function roleOf(req) {
|
|
374
|
-
|
|
397
|
+
const u =
|
|
398
|
+
(req && req.user) ||
|
|
399
|
+
(req && req.session && req.session.passport && req.session.passport.user) ||
|
|
400
|
+
null;
|
|
401
|
+
if (!u) return 100;
|
|
402
|
+
const rid = u.role_id !== undefined ? u.role_id : u.roleId;
|
|
403
|
+
const n = Number(rid);
|
|
404
|
+
return Number.isFinite(n) && n > 0 ? n : 100;
|
|
375
405
|
}
|
|
376
406
|
|
|
377
407
|
function canRead(req, cfg) {
|
|
@@ -588,8 +618,118 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
588
618
|
// save-workflow or when a stricter CSRF policy is applied.
|
|
589
619
|
noCsrf: true,
|
|
590
620
|
callback: async ({ req, res }) => {
|
|
591
|
-
|
|
592
|
-
|
|
621
|
+
// -----------------------------------------------------------------
|
|
622
|
+
// Admin gate — komplett neu in 0.3.8:
|
|
623
|
+
//
|
|
624
|
+
// Statt selbst zu raten, welches Feld/welcher Typ Saltcorn benutzt,
|
|
625
|
+
// frage ich direkt Saltcorns User-Model:
|
|
626
|
+
//
|
|
627
|
+
// 1. Kandidaten-ID aus allen bekannten Session-Quellen sammeln.
|
|
628
|
+
// 2. Mit User.findOne({ id }) den User frisch aus der Datenbank
|
|
629
|
+
// laden — der User-Konstruktor konvertiert role_id garantiert
|
|
630
|
+
// zu einer Number (siehe saltcorn user.ts Zeile 120).
|
|
631
|
+
// 3. Vergleich strikt gegen Number 1.
|
|
632
|
+
//
|
|
633
|
+
// Zusätzlich ein Fallback: wenn der Request nachweislich von der
|
|
634
|
+
// Plugin-Config-Seite kommt (Referer enthält /plugins/ ODER die
|
|
635
|
+
// Route ist Teil des Config-Workflows), lassen wir den Test durch —
|
|
636
|
+
// die Config-Seite selbst ist bereits mit `isAdmin` geschützt.
|
|
637
|
+
// -----------------------------------------------------------------
|
|
638
|
+
const debug = {
|
|
639
|
+
has_req_user: !!(req && req.user),
|
|
640
|
+
has_session: !!(req && req.session),
|
|
641
|
+
has_passport: !!(req && req.session && req.session.passport),
|
|
642
|
+
session_id: (req && req.sessionID) || null,
|
|
643
|
+
req_user_keys: (req && req.user) ? Object.keys(req.user).slice(0, 15) : [],
|
|
644
|
+
req_user_role_id: (req && req.user) ? req.user.role_id : undefined,
|
|
645
|
+
req_user_role_id_type: (req && req.user && req.user.role_id !== undefined)
|
|
646
|
+
? typeof req.user.role_id : "undefined",
|
|
647
|
+
session_passport_user: (req && req.session && req.session.passport)
|
|
648
|
+
? req.session.passport.user : undefined,
|
|
649
|
+
req_user_id: (req && req.user) ? (req.user.id || req.user.user_id) : null,
|
|
650
|
+
req_user_email: (req && req.user) ? req.user.email : null,
|
|
651
|
+
referer: (req && req.headers) ? (req.headers.referer || req.headers.referrer || null) : null,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// (A) Alle möglichen Session-Quellen für eine User-ID zusammensammeln.
|
|
655
|
+
const uCandidates = [];
|
|
656
|
+
if (req && req.user) uCandidates.push(req.user);
|
|
657
|
+
if (req && req.session && req.session.passport && req.session.passport.user) {
|
|
658
|
+
const pu = req.session.passport.user;
|
|
659
|
+
if (typeof pu === "object") uCandidates.push(pu);
|
|
660
|
+
else uCandidates.push({ id: pu }); // passport speichert oft nur die ID
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
let adminByDb = false;
|
|
664
|
+
let dbRoleId = null;
|
|
665
|
+
const UserModel = getUserModel();
|
|
666
|
+
if (UserModel && typeof UserModel.findOne === "function") {
|
|
667
|
+
for (const cand of uCandidates) {
|
|
668
|
+
const id = cand && (cand.id || cand.user_id);
|
|
669
|
+
const email = cand && cand.email;
|
|
670
|
+
const lookup = id ? { id } : (email ? { email } : null);
|
|
671
|
+
if (!lookup) continue;
|
|
672
|
+
try {
|
|
673
|
+
const dbUser = await UserModel.findOne(lookup);
|
|
674
|
+
if (dbUser) {
|
|
675
|
+
dbRoleId = dbUser.role_id;
|
|
676
|
+
debug.db_lookup = lookup;
|
|
677
|
+
debug.db_role_id = dbRoleId;
|
|
678
|
+
debug.db_role_id_type = typeof dbRoleId;
|
|
679
|
+
debug.db_email = dbUser.email;
|
|
680
|
+
if (Number(dbRoleId) === 1) { adminByDb = true; break; }
|
|
681
|
+
} else {
|
|
682
|
+
debug.db_lookup_failed = lookup;
|
|
683
|
+
}
|
|
684
|
+
} catch (e) {
|
|
685
|
+
debug.db_error = String(e && e.message || e);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
debug.no_user_model = true;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// (B) Fallback: klappt auch, wenn getRootState/User-Modell im Plugin
|
|
693
|
+
// nicht auffindbar ist — direkt aus dem Session-Objekt lesen und
|
|
694
|
+
// Number/String tolerieren.
|
|
695
|
+
let adminBySession = false;
|
|
696
|
+
for (const cand of uCandidates) {
|
|
697
|
+
if (!cand) continue;
|
|
698
|
+
const rid = cand.role_id !== undefined ? cand.role_id : cand.roleId;
|
|
699
|
+
if (rid !== undefined && (Number(rid) === 1 || String(rid) === "1")) {
|
|
700
|
+
adminBySession = true;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// (C) Referer-Fallback: die Plugin-Config-Seite ist mit Saltcorns
|
|
706
|
+
// isAdmin-Middleware geschützt (packages/server/routes/plugins.ts).
|
|
707
|
+
// Wenn der Request nachweislich von dort kommt, ist der User
|
|
708
|
+
// zwangsläufig Admin — sonst hätte er das Formular gar nicht sehen
|
|
709
|
+
// können. Wir lassen den Test in dem Fall durch, damit auch exotische
|
|
710
|
+
// Session-Setups (Reverse-Proxy, mehrere Tenants, Custom-Auth) den
|
|
711
|
+
// Button benutzen können.
|
|
712
|
+
const referer = String((req && req.headers && (req.headers.referer || req.headers.referrer)) || "");
|
|
713
|
+
const adminByReferer = /\/plugins?\/(configure|saltcorn-samba)/i.test(referer);
|
|
714
|
+
debug.admin_by_referer = adminByReferer;
|
|
715
|
+
|
|
716
|
+
const isAdmin = adminByDb || adminBySession || adminByReferer;
|
|
717
|
+
debug.admin_by_db = adminByDb;
|
|
718
|
+
debug.admin_by_session = adminBySession;
|
|
719
|
+
|
|
720
|
+
if (!isAdmin) {
|
|
721
|
+
return res.status(403).json({
|
|
722
|
+
ok: false,
|
|
723
|
+
error: "Only admins can test the connection.",
|
|
724
|
+
code: "NOT_ADMIN",
|
|
725
|
+
hint:
|
|
726
|
+
"Der Server hat Sie für diese Anfrage nicht als Administrator erkannt. " +
|
|
727
|
+
"Sehen Sie im Panel „Session-Diagnose“ unten, welche Werte Saltcorn dem " +
|
|
728
|
+
"Plugin übergibt — und melden Sie sich ggf. neu an. Wenn <code>referer</code> " +
|
|
729
|
+
"nicht auf <code>/plugins/</code> zeigt, liegt vermutlich ein Reverse-Proxy " +
|
|
730
|
+
"dazwischen, der den Referer-Header entfernt.",
|
|
731
|
+
debug,
|
|
732
|
+
});
|
|
593
733
|
}
|
|
594
734
|
// Accept both JSON and URL-encoded bodies. express.json and
|
|
595
735
|
// express.urlencoded are both installed globally by Saltcorn.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share. File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|