Debug mode: Marks UMB-DEBUG cookie as HttpOnly and Secure (#21032)
* fix: sets profiling cookie to httpOnly and strict in order to run non-secure * fix: adds extra message to explain when you can set a cookie * fix: simplify cookie explanation comment in WebProfilerRepository 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: checks that the profiler is actually enabled and/or disabled and warns the user if that is not the case * Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Repositories;
|
||||
@@ -11,21 +14,35 @@ internal sealed class WebProfilerRepository : IWebProfilerRepository
|
||||
private const string QueryName = "umbDebug";
|
||||
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public WebProfilerRepository(IHttpContextAccessor httpContextAccessor)
|
||||
public WebProfilerRepository(IHttpContextAccessor httpContextAccessor, ICookieManager cookieManager, IOptions<GlobalSettings> globalSettings)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_cookieManager = cookieManager;
|
||||
_globalSettings = globalSettings.Value;
|
||||
}
|
||||
|
||||
public void SetStatus(int userId, bool status)
|
||||
{
|
||||
if (status)
|
||||
{
|
||||
_httpContextAccessor.GetRequiredHttpContext().Response.Cookies.Append(CookieName, "1", new CookieOptions { Expires = DateTime.Now.AddYears(1) });
|
||||
// This cookie enables debug profiling on the front-end without needing query strings or headers.
|
||||
// It uses SameSite=Strict, so it only works when the BackOffice and front-end share the same domain.
|
||||
// It's marked httpOnly to prevent JavaScript access (the server reads it, not client-side code).
|
||||
// No expiration is set, so it's a session cookie and will be deleted when the browser closes.
|
||||
// For cross-site setups, use the query string (?umbDebug=true) or header (X-UMB-DEBUG) instead.
|
||||
_cookieManager.SetCookieValue(
|
||||
CookieName,
|
||||
"1",
|
||||
httpOnly: true,
|
||||
secure: _globalSettings.UseHttps,
|
||||
sameSiteMode: "Strict");
|
||||
}
|
||||
else
|
||||
{
|
||||
_httpContextAccessor.GetRequiredHttpContext().Response.Cookies.Delete(CookieName);
|
||||
_cookieManager.ExpireCookie(CookieName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +60,6 @@ internal sealed class WebProfilerRepository : IWebProfilerRepository
|
||||
return xUmbDebug;
|
||||
}
|
||||
|
||||
return request.Cookies.ContainsKey(CookieName);
|
||||
return _cookieManager.HasCookie(CookieName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2550,13 +2550,19 @@ export default {
|
||||
profiling: {
|
||||
performanceProfiling: 'Performance profiling',
|
||||
performanceProfilingDescription:
|
||||
"<p>Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages.</p><p>If you want to activate the profiler for a specific page rendering, simply add <strong>umbDebug=true</strong> to the querystring when requesting the page.</p><p>If you want the profiler to be activated by default for all page renderings, you can use the toggle below. It will set a cookie in your browser, which then activates the profiler automatically. In other words, the profiler will only be active by default in <em>your</em> browser - not everyone else's.</p>",
|
||||
"<p>Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages.</p><p>If you want to activate the profiler for a specific page rendering, simply add <strong>umbDebug=true</strong> to the querystring when requesting the page.</p><p>If you want the profiler to be activated by default for all page renderings, you can use the toggle below. It will set a cookie in your browser, which then activates the profiler automatically. In other words, the profiler will only be active by default in <em>your</em> browser - not everyone else's.</p><p><strong>Note:</strong> This will only work if the Backoffice is currently located on the same URL as the front-end website.</p>",
|
||||
activateByDefault: 'Activate the profiler by default',
|
||||
reminder: 'Friendly reminder',
|
||||
reminderDescription:
|
||||
'<p>You should never let a production site run in debug mode. Debug mode is turned off by setting <strong>Umbraco:CMS:Hosting:Debug</strong> to <strong>false</strong> in appsettings.json, appsettings.{Environment}.json or via an environment variable.</p>',
|
||||
profilerEnabledDescription:
|
||||
"<p>Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site.</p><p>Debug mode is turned on by setting <strong>Umbraco:CMS:Hosting:Debug</strong> to <strong>true</strong> in appsettings.json, appsettings.{Environment}.json or via an environment variable.</p>",
|
||||
errorEnablingProfilerTitle: 'Error enabling profiler',
|
||||
errorEnablingProfilerDescription:
|
||||
'It was not possible to enable the profiler. Check that you are accessing the Backoffice on the same URL as the front-end website, and try again. If the problem persists, please check the log for more details.',
|
||||
errorDisablingProfilerTitle: 'Error disabling profiler',
|
||||
errorDisablingProfilerDescription:
|
||||
'It was not possible to disable the profiler. Try again, and if the problem persists, please check the log for more details.',
|
||||
},
|
||||
settingsDashboard: {
|
||||
documentationHeader: 'Documentation',
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, state, query, unsafeHTML } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, query, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { ProfilingService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { tryExecute } from '@umbraco-cms/backoffice/resources';
|
||||
import { consumeContext } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
|
||||
|
||||
@customElement('umb-dashboard-performance-profiling')
|
||||
export class UmbDashboardPerformanceProfilingElement extends UmbLitElement {
|
||||
@@ -13,55 +15,107 @@ export class UmbDashboardPerformanceProfilingElement extends UmbLitElement {
|
||||
@state()
|
||||
private _isDebugMode = true;
|
||||
|
||||
@state()
|
||||
private _isLoading = true;
|
||||
|
||||
@query('#toggle')
|
||||
private _toggle!: HTMLInputElement;
|
||||
|
||||
@consumeContext({ context: UMB_NOTIFICATION_CONTEXT })
|
||||
private _notificationContext: typeof UMB_NOTIFICATION_CONTEXT.TYPE | undefined;
|
||||
|
||||
#setToggle(value: boolean) {
|
||||
this._toggle.checked = value;
|
||||
this._profilingStatus = value;
|
||||
this._isLoading = false;
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this._getProfilingStatus();
|
||||
override async firstUpdated() {
|
||||
const status = await this.#getProfilingStatus();
|
||||
this.#setToggle(status);
|
||||
}
|
||||
|
||||
private async _getProfilingStatus() {
|
||||
async #getProfilingStatus() {
|
||||
const { data } = await tryExecute(this, ProfilingService.getProfilingStatus());
|
||||
|
||||
if (!data) return;
|
||||
this._profilingStatus = data.enabled ?? false;
|
||||
return data?.enabled ?? false;
|
||||
}
|
||||
|
||||
private async _changeProfilingStatus() {
|
||||
const { error } = await tryExecute(
|
||||
this,
|
||||
ProfilingService.putProfilingStatus({ body: { enabled: !this._profilingStatus } }),
|
||||
);
|
||||
async #disableProfilingStatus() {
|
||||
this._isLoading = true;
|
||||
const { error } = await tryExecute(this, ProfilingService.putProfilingStatus({ body: { enabled: false } }));
|
||||
|
||||
if (error) {
|
||||
this.#setToggle(this._profilingStatus);
|
||||
} else {
|
||||
this.#setToggle(!this._profilingStatus);
|
||||
this.#setToggle(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Test that it was actually disabled
|
||||
const status = await this.#getProfilingStatus();
|
||||
|
||||
if (status) {
|
||||
this.#setToggle(true);
|
||||
this._notificationContext?.peek('warning', {
|
||||
data: {
|
||||
headline: this.localize.term('profiling_errorDisablingProfilerTitle'),
|
||||
message: this.localize.term('profiling_errorDisablingProfilerDescription'),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.#setToggle(false);
|
||||
}
|
||||
|
||||
async #enableProfilingStatus() {
|
||||
this._isLoading = true;
|
||||
const { error } = await tryExecute(this, ProfilingService.putProfilingStatus({ body: { enabled: true } }));
|
||||
|
||||
if (error) {
|
||||
this.#setToggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Test that it was actually enabled
|
||||
const status = await this.#getProfilingStatus();
|
||||
|
||||
if (!status) {
|
||||
this.#setToggle(false);
|
||||
this._notificationContext?.peek('warning', {
|
||||
data: {
|
||||
headline: this.localize.term('profiling_errorEnablingProfilerTitle'),
|
||||
message: this.localize.term('profiling_errorEnablingProfilerDescription'),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.#setToggle(true);
|
||||
}
|
||||
|
||||
#renderProfilingStatus() {
|
||||
return this._isDebugMode
|
||||
? html`
|
||||
${unsafeHTML(this.localize.term('profiling_performanceProfilingDescription'))}
|
||||
<umb-localize key="profiling_performanceProfilingDescription"></umb-localize>
|
||||
|
||||
<uui-toggle
|
||||
id="toggle"
|
||||
label=${this.localize.term('profiling_activateByDefault')}
|
||||
label-position="left"
|
||||
?checked="${this._profilingStatus}"
|
||||
@change="${this._changeProfilingStatus}"></uui-toggle>
|
||||
?disabled="${this._isLoading}"
|
||||
@change="${() =>
|
||||
this._profilingStatus ? this.#disableProfilingStatus() : this.#enableProfilingStatus()}"></uui-toggle>
|
||||
|
||||
<h4>${this.localize.term('profiling_reminder')}</h4>
|
||||
${when(this._isLoading, () => html`<uui-loader-circle></uui-loader-circle>`)}
|
||||
|
||||
${unsafeHTML(this.localize.term('profiling_reminderDescription'))}
|
||||
<h4>
|
||||
<umb-localize key="profiling_reminder"></umb-localize>
|
||||
</h4>
|
||||
|
||||
<umb-localize key="profiling_reminderDescription"></umb-localize>
|
||||
`
|
||||
: html` ${unsafeHTML(this.localize.term('profiling_profilerEnabledDescription'))} `;
|
||||
: html`<umb-localize key="profiling_profilerEnabledDescription"></umb-localize>`;
|
||||
}
|
||||
|
||||
override render() {
|
||||
|
||||
Reference in New Issue
Block a user