saltcorn-samba 0.4.14 → 0.4.15
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 +51 -0
- package/README.md +11 -5
- package/filemanager-view.js +41 -7
- package/index.js +11 -15
- package/package.json +1 -1
- package/tree-view.js +39 -9
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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.4.15] – 2026-07-07
|
|
8
|
+
|
|
9
|
+
### Fixed – CSRF-Fehlermeldung bei Upload / Ordner anlegen / Rename / Delete
|
|
10
|
+
|
|
11
|
+
Bei jedem schreibenden Klick in der File-Manager-View („Ordner anlegen“,
|
|
12
|
+
„Datei hochladen“, „umbenennen“, „löschen“) erschien der Fehler
|
|
13
|
+
`Invalid CSRF token`, obwohl der Client den Token korrekt im Header
|
|
14
|
+
`X-CSRF-Token` mitgeschickt hat.
|
|
15
|
+
|
|
16
|
+
**Root Cause:** Saltcorns globaler `csurf`-Middleware validiert bereits
|
|
17
|
+
alle POST-Routen der Plugins gegen das Session-Secret. Unser Plugin hatte
|
|
18
|
+
zusätzlich eine manuelle `checkCsrf(req)`-Funktion, die den vom Client
|
|
19
|
+
geschickten Token mit `req.csrfToken()` verglich. `req.csrfToken()` gibt
|
|
20
|
+
aber bei jedem Aufruf einen **frisch gesalzenen** Token zurück – der
|
|
21
|
+
Stringvergleich `provided !== req.csrfToken()` schlug daher immer fehl,
|
|
22
|
+
auch bei völlig gültigen Requests.
|
|
23
|
+
|
|
24
|
+
**Fix:** `checkCsrf` ist jetzt ein dokumentierter No-Op. Die eigentliche
|
|
25
|
+
CSRF-Prüfung übernimmt weiterhin Saltcorns Middleware (kein Sicherheits-
|
|
26
|
+
abbau). Betroffene Write-Routen (Upload, Mkdir, Rename, Delete) in
|
|
27
|
+
`index.js` sind unverändert und funktionieren jetzt korrekt.
|
|
28
|
+
|
|
29
|
+
### Added – View-Basispfad (`view_base_path`) für File-Manager und Tree
|
|
30
|
+
|
|
31
|
+
Beide Views (SambaFileManager und SambaTree) haben ein neues optionales
|
|
32
|
+
Konfigurationsfeld **„View-Basispfad (relativ zum Plugin-Basispfad)“**.
|
|
33
|
+
Damit lässt sich der Pfad zweistufig konfigurieren:
|
|
34
|
+
|
|
35
|
+
1. **Plugin-Basispfad** (weiterhin Pflicht) – z. B. `static` – legt fest,
|
|
36
|
+
in welchem Unterverzeichnis der Freigabe der Plugin-Root liegt.
|
|
37
|
+
2. **View-Basispfad** (neu, optional) – z. B. `projekte/2026` – ein
|
|
38
|
+
statischer Präfix, der nur für diese eine View gilt.
|
|
39
|
+
|
|
40
|
+
Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026`
|
|
41
|
+
→ die View listet den effektiven Pfad `static/projekte/2026`.
|
|
42
|
+
|
|
43
|
+
Der bisherige `extra_subpath` in beiden Views ist jetzt konsistent auf
|
|
44
|
+
den `from_field`-Modus beschränkt (dort weiterhin: Suffix hinter dem
|
|
45
|
+
Feldwert der aktuellen Zeile). Im `static`-Modus zählt nur
|
|
46
|
+
`view_base_path`.
|
|
47
|
+
|
|
48
|
+
Der zusammengesetzte Pfad wird pro Segment slash-getrimmt und
|
|
49
|
+
durchläuft `sanitizeRelativePath`, so dass `..`-Traversal, absolute
|
|
50
|
+
Pfade, UNC-Präfixe usw. weiterhin abgelehnt werden.
|
|
51
|
+
|
|
52
|
+
### Tests
|
|
53
|
+
|
|
54
|
+
`test/sanitize.test.js` deckt jetzt zusätzlich die Pfad-Komposition
|
|
55
|
+
(`view_base_path` + Feldwert + `extra_subpath`) inklusive Slash-
|
|
56
|
+
Normalisierung, Modus-Regeln und Traversal-Ablehnung ab.
|
|
57
|
+
|
|
7
58
|
## [0.4.14] – 2026-07-06
|
|
8
59
|
|
|
9
60
|
### Changed – Code-Review / Aufräumen (keine Verhaltensänderung)
|
package/README.md
CHANGED
|
@@ -194,9 +194,10 @@ Optionen:
|
|
|
194
194
|
|
|
195
195
|
| Option | Zweck |
|
|
196
196
|
|---|---|
|
|
197
|
-
| **
|
|
198
|
-
| **Row
|
|
199
|
-
| **
|
|
197
|
+
| **View-Basispfad (relativ zum Plugin-Basispfad)** | Optionaler statischer Präfix nur für diese View. Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026` → View listet `static/projekte/2026`. Zweistufige Konfiguration: einmal serverweit im Plugin, einmal pro View. |
|
|
198
|
+
| **Row-Modus** | `static` = nur der View-Basispfad zählt, `from_field` = zusätzlich wird ein DB-Feldwert der aktuellen Zeile angehängt (z. B. in einer Show-View) |
|
|
199
|
+
| **Feld mit Unterpfad** | Feld der Tabelle mit dem relativen Ordnernamen (nur bei `from_field`) |
|
|
200
|
+
| **Zusätzlicher Suffix (nach dem Feldwert)** | statischer Suffix, z. B. `invoices` (nur bei `from_field`) |
|
|
200
201
|
| **Show hidden files** | `.dotfiles` einblenden |
|
|
201
202
|
| **Allow navigating up** | Up-Button aktivieren (nur bis zum Root, kein Ausbruch) |
|
|
202
203
|
| **Open PDFs / images inline** | Klick öffnet die Datei im integrierten Viewer |
|
|
@@ -204,6 +205,10 @@ Optionen:
|
|
|
204
205
|
| **Page size** | Einträge pro Seite (0 = alle) |
|
|
205
206
|
| **Panel title** | Text im Karten-Header |
|
|
206
207
|
|
|
208
|
+
Der effektive Pfad wird zusammengesetzt aus (jeweils falls gesetzt):
|
|
209
|
+
`Plugin-Basispfad / View-Basispfad / Feldwert / Zusätzlicher Suffix`.
|
|
210
|
+
`..`-Traversal, absolute Pfade und UNC-Präfixe werden abgelehnt.
|
|
211
|
+
|
|
207
212
|
Beispiel-Datenmodell für kunden-spezifische Ordner:
|
|
208
213
|
|
|
209
214
|
```
|
|
@@ -212,12 +217,13 @@ Felder: name (String), akte_dir (String, z. B. "kunde_42/2026")
|
|
|
212
217
|
```
|
|
213
218
|
|
|
214
219
|
Dann eine Show-View von `kunden` bauen und die `SambaFileManager`-View mit
|
|
215
|
-
|
|
220
|
+
Row-Modus = `from_field`, Feld = `akte_dir` einbetten.
|
|
216
221
|
|
|
217
222
|
### View: `SambaTree`
|
|
218
223
|
|
|
219
224
|
Kompaktere Alternative – lazy-loading Baum, ideal in einer Sidebar oder
|
|
220
|
-
neben Formularen. Gleiche Path-
|
|
225
|
+
neben Formularen. Gleiche Path-Optionen wie beim File-Manager (inkl.
|
|
226
|
+
**View-Basispfad**, **Row-Modus**, **Feld**, **Zusätzlicher Suffix**).
|
|
221
227
|
|
|
222
228
|
### Fieldview `samba_pdf` (deaktiviert seit 0.3.2)
|
|
223
229
|
|
package/filemanager-view.js
CHANGED
|
@@ -44,31 +44,42 @@ const configuration_workflow = () =>
|
|
|
44
44
|
.map((f) => f.name);
|
|
45
45
|
return new Form({
|
|
46
46
|
fields: [
|
|
47
|
+
new Field({
|
|
48
|
+
name: "view_base_path",
|
|
49
|
+
label: "View-Basispfad (relativ zum Plugin-Basispfad)",
|
|
50
|
+
type: "String",
|
|
51
|
+
sublabel:
|
|
52
|
+
"Optional. Statischer Pfad relativ zum Plugin-Basispfad, der f\u00fcr diese View gilt. " +
|
|
53
|
+
"Beispiel: Plugin-Basispfad = 'static', View-Basispfad = 'projekte/2026' \u2192 View listet 'static/projekte/2026'. " +
|
|
54
|
+
"Keine f\u00fchrenden/abschlie\u00dfenden Slashes n\u00f6tig. Traversal (\u201e..\u201c) wird abgelehnt.",
|
|
55
|
+
}),
|
|
47
56
|
new Field({
|
|
48
57
|
name: "mode",
|
|
49
|
-
label: "
|
|
58
|
+
label: "Row-Modus",
|
|
50
59
|
type: "String",
|
|
51
60
|
required: true,
|
|
52
61
|
attributes: {
|
|
53
62
|
options: ["static", "from_field"],
|
|
54
63
|
},
|
|
55
64
|
sublabel:
|
|
56
|
-
"static =
|
|
65
|
+
"static = immer nur der View-Basispfad. from_field = h\u00e4nge einen Feldwert der aktuellen Zeile an " +
|
|
66
|
+
"(nur sinnvoll, wenn die View in einer Show-View eingebettet ist).",
|
|
57
67
|
default: "static",
|
|
58
68
|
}),
|
|
59
69
|
new Field({
|
|
60
70
|
name: "path_field",
|
|
61
|
-
label: "
|
|
71
|
+
label: "Feld mit Unterpfad",
|
|
62
72
|
type: "String",
|
|
63
73
|
attributes: { options: stringFieldOptions },
|
|
64
74
|
showIf: { mode: "from_field" },
|
|
65
75
|
}),
|
|
66
76
|
new Field({
|
|
67
77
|
name: "extra_subpath",
|
|
68
|
-
label: "
|
|
78
|
+
label: "Zus\u00e4tzlicher Suffix (nach dem Feldwert)",
|
|
69
79
|
type: "String",
|
|
70
80
|
sublabel:
|
|
71
|
-
"Optional.
|
|
81
|
+
"Optional. Statischer Suffix, der nach dem Feldwert angeh\u00e4ngt wird. Beispiel: 'invoices'.",
|
|
82
|
+
showIf: { mode: "from_field" },
|
|
72
83
|
}),
|
|
73
84
|
new Field({
|
|
74
85
|
name: "show_hidden",
|
|
@@ -122,14 +133,37 @@ async function get_state_fields() {
|
|
|
122
133
|
return [];
|
|
123
134
|
}
|
|
124
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Compose the start path (relative to the plugin base_path) from three
|
|
138
|
+
* optional parts:
|
|
139
|
+
* 1. `view_base_path` — static, per-view prefix
|
|
140
|
+
* 2. row field value — only in "from_field" mode
|
|
141
|
+
* 3. `extra_subpath` — static suffix, only in "from_field" mode
|
|
142
|
+
*
|
|
143
|
+
* Empty parts are dropped. The final joined path is passed through
|
|
144
|
+
* sanitizeRelativePath so traversal / absolute paths / backslashes are
|
|
145
|
+
* rejected consistently.
|
|
146
|
+
*/
|
|
125
147
|
function computeStartPath(configuration, row) {
|
|
126
148
|
const parts = [];
|
|
149
|
+
if (configuration.view_base_path) {
|
|
150
|
+
parts.push(String(configuration.view_base_path));
|
|
151
|
+
}
|
|
127
152
|
if (configuration.mode === "from_field" && configuration.path_field && row) {
|
|
128
153
|
const v = row[configuration.path_field];
|
|
129
154
|
if (v) parts.push(String(v));
|
|
130
155
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
156
|
+
if (
|
|
157
|
+
configuration.mode === "from_field" &&
|
|
158
|
+
configuration.extra_subpath
|
|
159
|
+
) {
|
|
160
|
+
parts.push(String(configuration.extra_subpath));
|
|
161
|
+
}
|
|
162
|
+
const joined = parts
|
|
163
|
+
.map((p) => p.replace(/^\/+|\/+$/g, ""))
|
|
164
|
+
.filter(Boolean)
|
|
165
|
+
.join("/");
|
|
166
|
+
return sanitizeRelativePath(joined);
|
|
133
167
|
}
|
|
134
168
|
|
|
135
169
|
// ---------------------------------------------------------------------------
|
package/index.js
CHANGED
|
@@ -519,22 +519,18 @@ function jsonOk(res, extra) {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
522
|
+
* CSRF verification is handled by Saltcorn's global csurf middleware
|
|
523
|
+
* (packages/server/app.js). By the time our route handler runs, csurf
|
|
524
|
+
* has already validated req.body._csrf / X-CSRF-Token / csrf-token /
|
|
525
|
+
* xsrf-token headers against the session secret. A manual re-check
|
|
526
|
+
* here would be wrong: csrf-tokens returns a NEW salted token on
|
|
527
|
+
* every call to req.csrfToken(), so `provided !== req.csrfToken()`
|
|
528
|
+
* would fail even for perfectly valid requests.
|
|
529
|
+
*
|
|
530
|
+
* We keep this stub as the single documented spot to attach future
|
|
531
|
+
* pre-flight authorisation checks if ever needed.
|
|
525
532
|
*/
|
|
526
|
-
function checkCsrf(
|
|
527
|
-
if (typeof req.csrfToken !== "function") return true; // CSRF disabled globally
|
|
528
|
-
const expected = req.csrfToken();
|
|
529
|
-
const provided =
|
|
530
|
-
(req.body && req.body._csrf) ||
|
|
531
|
-
req.headers["x-csrf-token"] ||
|
|
532
|
-
req.headers["csrf-token"] ||
|
|
533
|
-
req.query._csrf;
|
|
534
|
-
if (!provided || provided !== expected) {
|
|
535
|
-
jsonError(res, 403, "Invalid CSRF token");
|
|
536
|
-
return false;
|
|
537
|
-
}
|
|
533
|
+
function checkCsrf(_req, _res) {
|
|
538
534
|
return true;
|
|
539
535
|
}
|
|
540
536
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.15",
|
|
4
4
|
"description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share via SMB 3.1.1 (AES-CMAC signing, optional encryption). File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/tree-view.js
CHANGED
|
@@ -41,30 +41,42 @@ const configuration_workflow = () =>
|
|
|
41
41
|
.map((f) => f.name);
|
|
42
42
|
return new Form({
|
|
43
43
|
fields: [
|
|
44
|
+
new Field({
|
|
45
|
+
name: "view_base_path",
|
|
46
|
+
label: "View-Basispfad (relativ zum Plugin-Basispfad)",
|
|
47
|
+
type: "String",
|
|
48
|
+
sublabel:
|
|
49
|
+
"Optional. Statischer Pfad relativ zum Plugin-Basispfad, der f\u00fcr diese View gilt. " +
|
|
50
|
+
"Beispiel: Plugin-Basispfad = 'static', View-Basispfad = 'projekte/2026' \u2192 View listet 'static/projekte/2026'. " +
|
|
51
|
+
"Keine f\u00fchrenden/abschlie\u00dfenden Slashes n\u00f6tig. Traversal (\u201e..\u201c) wird abgelehnt.",
|
|
52
|
+
}),
|
|
44
53
|
new Field({
|
|
45
54
|
name: "mode",
|
|
46
|
-
label: "
|
|
55
|
+
label: "Row-Modus",
|
|
47
56
|
type: "String",
|
|
48
57
|
required: true,
|
|
49
58
|
attributes: {
|
|
50
59
|
options: ["static", "from_field"],
|
|
51
60
|
},
|
|
52
61
|
sublabel:
|
|
53
|
-
"static =
|
|
62
|
+
"static = immer nur der View-Basispfad. from_field = h\u00e4nge einen Feldwert der aktuellen Zeile an " +
|
|
63
|
+
"(nur sinnvoll, wenn die View in einer Show-View eingebettet ist).",
|
|
64
|
+
default: "static",
|
|
54
65
|
}),
|
|
55
66
|
new Field({
|
|
56
67
|
name: "path_field",
|
|
57
|
-
label: "
|
|
68
|
+
label: "Feld mit Unterpfad",
|
|
58
69
|
type: "String",
|
|
59
70
|
attributes: { options: stringFieldOptions },
|
|
60
71
|
showIf: { mode: "from_field" },
|
|
61
72
|
}),
|
|
62
73
|
new Field({
|
|
63
74
|
name: "extra_subpath",
|
|
64
|
-
label: "
|
|
75
|
+
label: "Zus\u00e4tzlicher Suffix (nach dem Feldwert)",
|
|
65
76
|
type: "String",
|
|
66
77
|
sublabel:
|
|
67
|
-
"Optional.
|
|
78
|
+
"Optional. Statischer Suffix, der nach dem Feldwert angeh\u00e4ngt wird. Beispiel: 'invoices'.",
|
|
79
|
+
showIf: { mode: "from_field" },
|
|
68
80
|
}),
|
|
69
81
|
new Field({
|
|
70
82
|
name: "show_hidden",
|
|
@@ -104,17 +116,35 @@ async function get_state_fields(table_id, viewname, { mode, path_field }) {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
119
|
+
* Compose the start path (relative to the plugin base_path) from three
|
|
120
|
+
* optional parts:
|
|
121
|
+
* 1. `view_base_path` — static, per-view prefix
|
|
122
|
+
* 2. row field value — only in "from_field" mode
|
|
123
|
+
* 3. `extra_subpath` — static suffix, only in "from_field" mode
|
|
124
|
+
*
|
|
125
|
+
* Empty parts are dropped and slash-trimmed. The final joined path is
|
|
126
|
+
* passed through sanitizeRelativePath so traversal / absolute paths /
|
|
127
|
+
* backslashes are rejected consistently.
|
|
109
128
|
*/
|
|
110
129
|
function computeStartPath(configuration, row) {
|
|
111
130
|
const parts = [];
|
|
131
|
+
if (configuration.view_base_path) {
|
|
132
|
+
parts.push(String(configuration.view_base_path));
|
|
133
|
+
}
|
|
112
134
|
if (configuration.mode === "from_field" && configuration.path_field && row) {
|
|
113
135
|
const v = row[configuration.path_field];
|
|
114
136
|
if (v) parts.push(String(v));
|
|
115
137
|
}
|
|
116
|
-
if (
|
|
117
|
-
|
|
138
|
+
if (
|
|
139
|
+
configuration.mode === "from_field" &&
|
|
140
|
+
configuration.extra_subpath
|
|
141
|
+
) {
|
|
142
|
+
parts.push(String(configuration.extra_subpath));
|
|
143
|
+
}
|
|
144
|
+
const joined = parts
|
|
145
|
+
.map((p) => p.replace(/^\/+|\/+$/g, ""))
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
.join("/");
|
|
118
148
|
// sanitizeRelativePath throws on traversal – we let the caller handle it.
|
|
119
149
|
return sanitizeRelativePath(joined);
|
|
120
150
|
}
|