Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/debugbar-env-quickswitch.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Debug bar: environment quick-switch links (Testing / Maintenance / Production) now render only when switching can actually work — a non-empty `reloadPassword` is configured and `allowEnvironmentSwitchViaUrl` allows it — and prompt for the reload password at click time instead of rendering dead anchors when no password is set (the configuration where `?reload=<environment>` has been a no-op since #2082). The password is never embedded in the page, and the plain `?reload=true` reload anchor keeps its existing behavior ([#3060](https://github.com/wheels-dev/wheels/issues/3060))
20 changes: 18 additions & 2 deletions vendor/wheels/events/onrequestend/debug.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,20 @@ OR (StructKeyExists(url, "format") AND ListFindNoCase("json,xml,csv,pdf", url.fo
<dd>
<span class="wdb-env-dot" style="background:#local.envColor#;"></span>
#capitalize($get("environment"))#
<cfif NOT Len($get("reloadPassword"))>
<!---
Quick-switch links render only when switching can actually work:
since ##2082 the ?reload=<env> switch requires a non-empty
reloadPassword (plus a matching password parameter) and is gated
by allowEnvironmentSwitchViaUrl. The password is never embedded
in the page — wdbEnvSwitch() prompts for it at click time and
builds the documented ?reload=<env>&password=... request.
--->
<cfif Len($get("reloadPassword")) AND $get("allowEnvironmentSwitchViaUrl")>
<cfset local.environments = "development,testing,maintenance,production">
&mdash;
<cfloop list="#local.environments#" index="local.ei">
<cfif $get("environment") IS NOT local.ei>
<a href="#EncodeForHTMLAttribute(local.baseReloadURL & local.ei)#" style="color:##89b4fa;font-size:11px;margin-left:4px;">#capitalize(local.ei)#</a>
<a href="##" data-wdb-reload="#EncodeForHTMLAttribute(local.baseReloadURL & local.ei)#" onclick="return wdbEnvSwitch(this);" title="Switch to #capitalize(local.ei)# (prompts for the reload password)" style="color:##89b4fa;font-size:11px;margin-left:4px;">#capitalize(local.ei)#</a>
</cfif>
</cfloop>
</cfif>
Expand Down Expand Up @@ -545,6 +553,14 @@ OR (StructKeyExists(url, "format") AND ListFindNoCase("json,xml,csv,pdf", url.fo
document.getElementById('wdb-minimized').style.display='none';
try{sessionStorage.removeItem('wdb-hidden');}catch(e){}
};
window.wdbEnvSwitch=function(el){
var target=el.getAttribute('data-wdb-reload');
if(!target)return false;
var pw=window.prompt('Enter the reload password to switch environments:');
if(pw===null||pw==='')return false;
window.location.href=target+'&password='+encodeURIComponent(pw);
return false;
};
try{if(sessionStorage.getItem('wdb-hidden')==='1')wdbMinimize();}catch(e){}
})();
</script>
Expand Down
98 changes: 98 additions & 0 deletions vendor/wheels/tests/specs/events/DebugBarEnvQuickSwitchSpec.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
component extends="wheels.WheelsTest" {

function run() {
describe("debug.cfm environment quick-switch links (issue 3060)", () => {
// Since ##2082, switching environments via ?reload=<env> requires a
// non-empty reloadPassword plus a matching password parameter, and is
// additionally gated by allowEnvironmentSwitchViaUrl. The debug bar's
// Environment panel used to render the quick-switch anchors only when
// NO reloadPassword was set — the exact configuration where switching
// is impossible — and the anchors embedded no password, so clicking
// one silently restarted the app in the same environment. The fixed
// behavior renders the links only when switching CAN work, as
// prompt-based links that never embed the password in the page.

it("renders no quick-switch links when reloadPassword is empty", () => {
var output = $renderDebugBar(reloadPassword = "", allowSwitch = true);
expect(output contains 'data-wdb-reload="').toBeFalse(
"quick-switch links must not render without a reloadPassword (switching cannot work)"
);
expect(output contains "margin-left:4px").toBeFalse(
"the legacy dead quick-switch anchors must not render when reloadPassword is empty"
);
// The plain one-click reload anchor keeps its pre-existing no-password gate.
expect(output contains 'title="Reload Application"').toBeTrue(
"the plain ?reload=true anchor must keep rendering when no reloadPassword is set"
);
});

it("renders prompt-based quick-switch links when a reloadPassword is set and switching is allowed", () => {
var output = $renderDebugBar(reloadPassword = "spec-secret-pw-3060", allowSwitch = true);
expect(output contains 'data-wdb-reload="').toBeTrue(
"quick-switch links must render when a reloadPassword is configured and allowEnvironmentSwitchViaUrl is true"
);
expect(output contains "wdbEnvSwitch").toBeTrue(
"quick-switch links must go through the password prompt handler"
);
expect(output contains "spec-secret-pw-3060").toBeFalse(
"the reload password must never be embedded in the rendered page"
);
// The plain one-click reload anchor keeps its pre-existing password gate.
expect(output contains 'title="Reload Application"').toBeFalse(
"the plain ?reload=true anchor must stay hidden when a reloadPassword is set"
);
});

it("renders no quick-switch links when allowEnvironmentSwitchViaUrl is false even with a password set", () => {
var output = $renderDebugBar(reloadPassword = "spec-secret-pw-3060", allowSwitch = false);
expect(output contains 'data-wdb-reload="').toBeFalse(
"quick-switch links must not render when environment switching via URL is disallowed"
);
});
});
}

/**
* Renders the debug bar template with the given reloadPassword and
* allowEnvironmentSwitchViaUrl applied, restoring all touched state.
* Modeled on debugBarEncodingSpec.cfc.
*/
private string function $renderDebugBar(required string reloadPassword, required boolean allowSwitch) {
var priorReloadPassword = application.wheels.reloadPassword;
var hadAllowSwitch = StructKeyExists(application.wheels, "allowEnvironmentSwitchViaUrl");
var priorAllowSwitch = hadAllowSwitch ? application.wheels.allowEnvironmentSwitchViaUrl : true;
var priorReqWheels = StructKeyExists(request, "wheels") ? Duplicate(request.wheels) : {};
// debug.cfm bails out (cfexit) when url.format is one of json/xml/csv/pdf
// so it never breaks an API response. The test runner is hit with
// format=json — clear it for the duration of the include.
var hadUrlFormat = StructKeyExists(url, "format");
var priorUrlFormat = hadUrlFormat ? url.format : "";
var output = "";
try {
application.wheels.reloadPassword = arguments.reloadPassword;
application.wheels.allowEnvironmentSwitchViaUrl = arguments.allowSwitch;
if (!StructKeyExists(request, "wheels")) {
request.wheels = {};
}
request.wheels.execution = {total = 0};
request.wheels.params = {controller = "wheels", action = "tests", route = ""};
if (hadUrlFormat) {
StructDelete(url, "format");
}
output = application.wo.$includeAndReturnOutput($template = "/wheels/events/onrequestend/debug.cfm");
} finally {
application.wheels.reloadPassword = priorReloadPassword;
if (hadAllowSwitch) {
application.wheels.allowEnvironmentSwitchViaUrl = priorAllowSwitch;
} else {
StructDelete(application.wheels, "allowEnvironmentSwitchViaUrl");
}
request.wheels = priorReqWheels;
if (hadUrlFormat) {
url.format = priorUrlFormat;
}
}
return output;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ A running app can switch environments without a redeploy: request `?reload=<envi

The gate is `allowEnvironmentSwitchViaUrl`. Any explicit boolean you `set()` is honored in every environment. If you never touch it, the default is `false` in `production`, `testing`, and `maintenance`, and `true` everywhere else. Leave it disabled in production.

Two caveats, tracked as open issues:
In development, the debug bar's Environment panel offers the same switch as quick-switch links: they render when a `reloadPassword` is configured and switching via the URL is allowed, and clicking one prompts for the reload password (it is never embedded in the page).

One caveat, tracked as an open issue:

- The debug bar's environment quick-switch links are currently broken ([#3060](https://github.com/wheels-dev/wheels/issues/3060)) — use the URL form above.
- The `wheels reload` CLI command can report success even when the reload didn't take effect ([#3059](https://github.com/wheels-dev/wheels/issues/3059)) — verify with the URL form.

## Secrets handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ The Environment tab label shows the current environment name (Development, Testi

Always shown. Lists the environment, the git branch (when the **web root** — `public/` — contains a `.git` directory; in a standard app layout the `.git` directory lives at the project root, so the row does not appear), the Wheels version, the CFML engine and version, and the host name (when available).

When no reload password is set, the environment name is followed by quick-switch links. These links are dead: any URL-based reload — environment switch or plain restart — requires a configured `reloadPassword` plus a matching `password` parameter, which is exactly the state in which the links do not render, so clicking one serves the page normally without restarting anything. See [#3060](https://github.com/wheels-dev/wheels/issues/3060).
When a `reloadPassword` is configured and `allowEnvironmentSwitchViaUrl` resolves to `true`, the environment name is followed by quick-switch links for the other environments. Clicking one prompts for the reload password — it is never embedded in the page — and then issues the documented `?reload=<environment>&password=<your password>` request. When no `reloadPassword` is set, or switching via the URL is disallowed, the links do not render because the switch cannot work. See [#3060](https://github.com/wheels-dev/wheels/issues/3060).

To actually switch environments at runtime, set a `reloadPassword` in `config/settings.cfm` and request `?reload=<environment>&password=<your password>`. The switch is additionally gated by the `allowEnvironmentSwitchViaUrl` setting: an explicit boolean you set is honored in every environment, and when unset it defaults to `false` in `production`, `testing`, and `maintenance` and `true` everywhere else. Switching to the environment you are already in is a no-op, and after a successful switch the redirect strips the reload parameters from the URL.
You can also switch environments manually: set a `reloadPassword` in `config/settings.cfm` and request `?reload=<environment>&password=<your password>`. The switch is additionally gated by the `allowEnvironmentSwitchViaUrl` setting: an explicit boolean you set is honored in every environment, and when unset it defaults to `false` in `production`, `testing`, and `maintenance` and `true` everywhere else. Switching to the environment you are already in is a no-op, and after a successful switch the redirect strips the reload parameters from the URL.

<Aside type="caution">
When no `reloadPassword` is configured, URL-based reload is disabled entirely — `?reload=true` (the bar's reload link) serves the page normally without restarting, and Wheels logs a `wheels_security` warning on boot telling you to set a password ([#3062](https://github.com/wheels-dev/wheels/issues/3062); before that fix the same state let any visitor restart the application anonymously).
Expand Down
Loading