@@ -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