Skip to content

Commit 490a3a8

Browse files
committed
feat(platform): Add shell-native node control plane
Add Platform API control-plane routes for config, security, updates, and the first-time wizard, then wire the Web Shell to use those routes for common operator workflows under /app/node/. Preserve the existing admin form-password bridge and legacy fallback pages while tightening updater and wizard runtime behavior to match daemon semantics. How to test:\n- ./gradlew test
1 parent 56b9e69 commit 490a3a8

29 files changed

Lines changed: 5005 additions & 96 deletions

File tree

adapter-http-legacy-admin/src/main/java/network/crypta/clients/http/PlatformApiToadlet.java

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ public final class PlatformApiToadlet extends Toadlet {
5252
private static final String DELETE_METHOD = "DELETE";
5353
private static final String FORM_PASSWORD_PARAMETER = "formPassword";
5454
private static final int MAX_PLATFORM_API_FORM_FIELD_LENGTH = QueueToadlet.MAX_KEY_LENGTH;
55+
private static final String CONFIG_SEGMENT = "config";
5556
private static final String PEERS_SEGMENT = "peers";
5657
private static final String QUEUE_SEGMENT = "queue";
58+
private static final String SECURITY_LEVELS_SEGMENT = "security-levels";
59+
private static final String UPDATES_SEGMENT = "updates";
60+
private static final String WIZARD_SEGMENT = "wizard";
5761

5862
/**
5963
* Transport-neutral router that owns endpoint selection, validation, and JSON payload creation.
@@ -294,43 +298,87 @@ private void writePlatformApiResponse(
294298
* <p>The legacy shell only auto-checks form passwords for POST before dispatch. The Platform API
295299
* bridge keeps full-access checks ahead of password checks and applies the same legacy password
296300
* guard to every currently supported Platform API mutation family. DELETE remains relevant only
297-
* for installed-app removal, while queue and peer mutations currently use POST.
301+
* for installed-app removal, while the current config, security-levels, updater, wizard, queue,
302+
* peer, and app actions use POST.
298303
*
299304
* @param method HTTP method name forwarded into the router
300305
* @param uri request target supplied by the legacy HTTP shell
301306
* @return {@code true} when the request must present the legacy form password
302307
*/
303308
private static boolean requiresFormPassword(String method, URI uri) {
304-
if (!"POST".equals(method) && !DELETE_METHOD.equals(method)) {
305-
return false;
306-
}
307-
308-
List<String> pathSegments;
309-
try {
310-
pathSegments = relativeApiPath(requestPath(uri));
311-
} catch (URLEncodedFormatException _) {
309+
if (!requiresFormPasswordEligibleMethod(method)) {
312310
return false;
313311
}
314312

313+
List<String> pathSegments = decodeRelativeApiPath(uri);
315314
if (pathSegments.isEmpty()) {
316315
return false;
317316
}
318317
if ("apps".equals(pathSegments.getFirst())) {
319-
if (DELETE_METHOD.equals(method)) {
320-
return pathSegments.size() == 2;
321-
}
322-
if (pathSegments.size() == 2 && "install".equals(pathSegments.get(1))) {
323-
return true;
324-
}
325-
return pathSegments.size() == 3
326-
&& ("start".equals(pathSegments.get(2))
327-
|| "stop".equals(pathSegments.get(2))
328-
|| "update".equals(pathSegments.get(2)));
318+
return requiresAppsFormPassword(method, pathSegments);
329319
}
330-
if (requiresQueueFormPassword(method, pathSegments)) {
320+
return requiresNonAppFormPassword(method, pathSegments);
321+
}
322+
323+
/**
324+
* Returns whether the current request method participates in the legacy form-password guard.
325+
*
326+
* @param method HTTP method name forwarded into the router
327+
* @return {@code true} when the bridge should inspect the request path for a mutating route
328+
*/
329+
private static boolean requiresFormPasswordEligibleMethod(String method) {
330+
return "POST".equals(method) || DELETE_METHOD.equals(method);
331+
}
332+
333+
/**
334+
* Decodes one relative API path while treating malformed percent-encoding as a non-matching
335+
* mutation route.
336+
*
337+
* @param uri request target supplied by the legacy HTTP shell
338+
* @return decoded relative API path, or an empty list when decoding fails
339+
*/
340+
private static List<String> decodeRelativeApiPath(URI uri) {
341+
try {
342+
return relativeApiPath(requestPath(uri));
343+
} catch (URLEncodedFormatException _) {
344+
return List.of();
345+
}
346+
}
347+
348+
/**
349+
* Returns whether the current request targets one of the mutating app-management routes.
350+
*
351+
* @param method HTTP method name forwarded into the router
352+
* @param pathSegments decoded path segments beneath the Platform API mount point
353+
* @return {@code true} when the request must present the legacy form password
354+
*/
355+
private static boolean requiresAppsFormPassword(String method, List<String> pathSegments) {
356+
if (DELETE_METHOD.equals(method)) {
357+
return pathSegments.size() == 2;
358+
}
359+
if (pathSegments.size() == 2 && "install".equals(pathSegments.get(1))) {
331360
return true;
332361
}
333-
return requiresPeersFormPassword(method, pathSegments);
362+
return pathSegments.size() == 3
363+
&& ("start".equals(pathSegments.get(2))
364+
|| "stop".equals(pathSegments.get(2))
365+
|| "update".equals(pathSegments.get(2)));
366+
}
367+
368+
/**
369+
* Returns whether the current request targets one of the non-app mutating Platform API routes.
370+
*
371+
* @param method HTTP method name forwarded into the router
372+
* @param pathSegments decoded path segments beneath the Platform API mount point
373+
* @return {@code true} when the request must present the legacy form password
374+
*/
375+
private static boolean requiresNonAppFormPassword(String method, List<String> pathSegments) {
376+
return requiresQueueFormPassword(method, pathSegments)
377+
|| requiresPeersFormPassword(method, pathSegments)
378+
|| requiresConfigFormPassword(method, pathSegments)
379+
|| requiresSecurityLevelsFormPassword(method, pathSegments)
380+
|| requiresUpdatesFormPassword(method, pathSegments)
381+
|| requiresWizardFormPassword(method, pathSegments);
334382
}
335383

336384
/**
@@ -383,6 +431,65 @@ private static boolean requiresPeersFormPassword(String method, List<String> pat
383431
|| "remove".equals(pathSegments.get(2)));
384432
}
385433

434+
/**
435+
* Returns whether the current request targets one of the mutating config routes.
436+
*
437+
* @param method HTTP method name forwarded into the router
438+
* @param pathSegments decoded path segments beneath the Platform API mount point
439+
* @return {@code true} when the request must present the legacy form password
440+
*/
441+
private static boolean requiresConfigFormPassword(String method, List<String> pathSegments) {
442+
return "POST".equals(method)
443+
&& pathSegments.size() == 2
444+
&& CONFIG_SEGMENT.equals(pathSegments.getFirst())
445+
&& ("overrides".equals(pathSegments.get(1)) || "persist".equals(pathSegments.get(1)));
446+
}
447+
448+
/**
449+
* Returns whether the current request targets one of the mutating security-level routes.
450+
*
451+
* @param method HTTP method name forwarded into the router
452+
* @param pathSegments decoded path segments beneath the Platform API mount point
453+
* @return {@code true} when the request must present the legacy form password
454+
*/
455+
private static boolean requiresSecurityLevelsFormPassword(
456+
String method, List<String> pathSegments) {
457+
return "POST".equals(method)
458+
&& pathSegments.size() == 2
459+
&& SECURITY_LEVELS_SEGMENT.equals(pathSegments.getFirst())
460+
&& ("network".equals(pathSegments.get(1)) || "physical".equals(pathSegments.get(1)));
461+
}
462+
463+
/**
464+
* Returns whether the current request targets the mutating updater download route.
465+
*
466+
* @param method HTTP method name forwarded into the router
467+
* @param pathSegments decoded path segments beneath the Platform API mount point
468+
* @return {@code true} when the request must present the legacy form password
469+
*/
470+
private static boolean requiresUpdatesFormPassword(String method, List<String> pathSegments) {
471+
return "POST".equals(method)
472+
&& pathSegments.size() == 3
473+
&& UPDATES_SEGMENT.equals(pathSegments.getFirst())
474+
&& "core".equals(pathSegments.get(1))
475+
&& "download".equals(pathSegments.get(2));
476+
}
477+
478+
/**
479+
* Returns whether the current request targets the mutating first-time-wizard route.
480+
*
481+
* @param method HTTP method name forwarded into the router
482+
* @param pathSegments decoded path segments beneath the Platform API mount point
483+
* @return {@code true} when the request must present the legacy form password
484+
*/
485+
private static boolean requiresWizardFormPassword(String method, List<String> pathSegments) {
486+
return "POST".equals(method)
487+
&& pathSegments.size() == 3
488+
&& WIZARD_SEGMENT.equals(pathSegments.getFirst())
489+
&& "first-time".equals(pathSegments.get(1))
490+
&& "apply".equals(pathSegments.get(2));
491+
}
492+
386493
/**
387494
* Enforces the legacy form-password requirement for mutating requests.
388495
*

0 commit comments

Comments
 (0)