Skip to content

Commit 229ca98

Browse files
authored
V9: Allowlisting remote URLs for displaying content on the content dashboard (#11825)
* Fixing ContentDashboardSettings to work when set in the config * Moving file in Models folder and adding ContentDashboardUrlAllowlist setting * Implementing allowlist for content dashboard base url * Cleanup * Error msg vs log msg
1 parent 1b5830a commit 229ca98

File tree

5 files changed

+67
-10
lines changed

5 files changed

+67
-10
lines changed

src/JsonSchema/AppSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public class CmsDefinition
8686
public PackageMigrationSettings PackageMigration { get; set; }
8787

8888
public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; }
89+
90+
public ContentDashboardSettings ContentDashboard { get; set; }
8991
}
9092

9193
/// <summary>

src/Umbraco.Core/Configuration/ContentDashboardSettings.cs renamed to src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-

2-
namespace Umbraco.Cms.Core.Configuration
1+
using System.ComponentModel;
2+
3+
namespace Umbraco.Cms.Core.Configuration.Models
34
{
5+
/// <summary>
6+
/// Typed configuration options for content dashboard settings.
7+
/// </summary>
8+
[UmbracoOptions(Constants.Configuration.ConfigContentDashboard)]
49
public class ContentDashboardSettings
510
{
611
private const string DefaultContentDashboardPath = "cms";
@@ -18,6 +23,13 @@ public class ContentDashboardSettings
1823
/// Gets the path to use when constructing the URL for retrieving data for the content dashboard.
1924
/// </summary>
2025
/// <value>The URL path.</value>
21-
public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath;
26+
[DefaultValue(DefaultContentDashboardPath)]
27+
public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath;
28+
29+
/// <summary>
30+
/// Gets the allowed addresses to retrieve data for the content dashboard.
31+
/// </summary>
32+
/// <value>The URLs.</value>
33+
public string[] ContentDashboardUrlAllowlist { get; set; }
2234
}
2335
}

src/Umbraco.Core/Constants-Configuration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Umbraco.Cms.Core
1+
namespace Umbraco.Cms.Core
22
{
33
public static partial class Constants
44
{
@@ -54,6 +54,7 @@ public static class Configuration
5454
public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword";
5555
public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor";
5656
public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration";
57+
public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard";
5758
}
5859
}
5960
}

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
8282
.AddUmbracoOptions<BasicAuthSettings>()
8383
.AddUmbracoOptions<RuntimeMinificationSettings>()
8484
.AddUmbracoOptions<LegacyPasswordMigrationSettings>()
85-
.AddUmbracoOptions<PackageMigrationSettings>();
85+
.AddUmbracoOptions<PackageMigrationSettings>()
86+
.AddUmbracoOptions<ContentDashboardSettings>();
8687

8788
return builder;
8889
}

src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Net;
45
using System.Net.Http;
56
using System.Text;
67
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Authorization;
89
using Microsoft.AspNetCore.Mvc;
910
using Microsoft.Extensions.Logging;
1011
using Microsoft.Extensions.Options;
12+
using Newtonsoft.Json;
1113
using Newtonsoft.Json.Linq;
1214
using Umbraco.Cms.Core.Cache;
1315
using Umbraco.Cms.Core.Configuration;
16+
using Umbraco.Cms.Core.Configuration.Models;
1417
using Umbraco.Cms.Core.Dashboards;
1518
using Umbraco.Cms.Core.Models.ContentEditing;
1619
using Umbraco.Cms.Core.Security;
@@ -40,7 +43,7 @@ public class DashboardController : UmbracoApiController
4043
private readonly IDashboardService _dashboardService;
4144
private readonly IUmbracoVersion _umbracoVersion;
4245
private readonly IShortStringHelper _shortStringHelper;
43-
private readonly IOptions<ContentDashboardSettings> _dashboardSettings;
46+
private readonly ContentDashboardSettings _dashboardSettings;
4447
/// <summary>
4548
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
4649
/// </summary>
@@ -60,12 +63,13 @@ public DashboardController(
6063
_dashboardService = dashboardService;
6164
_umbracoVersion = umbracoVersion;
6265
_shortStringHelper = shortStringHelper;
63-
_dashboardSettings = dashboardSettings;
66+
_dashboardSettings = dashboardSettings.Value;
6467
}
6568

6669
//we have just one instance of HttpClient shared for the entire application
6770
private static readonly HttpClient HttpClient = new HttpClient();
6871

72+
// TODO(V10) : change return type to Task<ActionResult<JObject>> and consider removing baseUrl as parameter
6973
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
7074
[ValidateAngularAntiForgeryToken]
7175
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.com/")
@@ -76,9 +80,19 @@ public async Task<JObject> GetRemoteDashboardContent(string section, string base
7680
var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
7781
var isAdmin = user.IsAdmin();
7882

83+
if (!IsAllowedUrl(baseUrl))
84+
{
85+
_logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}");
86+
HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
87+
88+
// Hacking the response - can't set the HttpContext.Response.Body, so instead returning the error as JSON
89+
var errorJson = JsonConvert.SerializeObject(new { Error = "Dashboard source not permitted" });
90+
return JObject.Parse(errorJson);
91+
}
92+
7993
var url = string.Format("{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}",
8094
baseUrl,
81-
_dashboardSettings.Value.ContentDashboardPath,
95+
_dashboardSettings.ContentDashboardPath,
8296
section,
8397
allowedSections,
8498
language,
@@ -116,8 +130,15 @@ public async Task<JObject> GetRemoteDashboardContent(string section, string base
116130
return result;
117131
}
118132

133+
// TODO(V10) : consider removing baseUrl as parameter
119134
public async Task<IActionResult> GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/")
120135
{
136+
if (!IsAllowedUrl(baseUrl))
137+
{
138+
_logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}");
139+
return BadRequest("Dashboard source not permitted");
140+
}
141+
121142
var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section);
122143
var key = "umbraco-dynamic-dashboard-css-" + section;
123144

@@ -152,12 +173,18 @@ public async Task<IActionResult> GetRemoteDashboardCss(string section, string ba
152173
}
153174

154175

155-
return Content(result,"text/css", Encoding.UTF8);
176+
return Content(result, "text/css", Encoding.UTF8);
156177

157178
}
158179

159180
public async Task<IActionResult> GetRemoteXml(string site, string url)
160181
{
182+
if (!IsAllowedUrl(url))
183+
{
184+
_logger.LogError($"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {url}");
185+
return BadRequest("Dashboard source not permitted");
186+
}
187+
161188
// This is used in place of the old feedproxy.config
162189
// Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv
163190
// for certain dashboards or the help drawer
@@ -214,7 +241,7 @@ public async Task<IActionResult> GetRemoteXml(string site, string url)
214241
}
215242
}
216243

217-
return Content(result,"text/xml", Encoding.UTF8);
244+
return Content(result, "text/xml", Encoding.UTF8);
218245

219246
}
220247

@@ -240,5 +267,19 @@ public IEnumerable<Tab<IDashboardSlim>> GetDashboard(string section)
240267
})
241268
}).ToList();
242269
}
270+
271+
// Checks if the passed URL is part of the configured allowlist of addresses
272+
private bool IsAllowedUrl(string url)
273+
{
274+
// No addresses specified indicates that any URL is allowed
275+
if (_dashboardSettings.ContentDashboardUrlAllowlist is null || _dashboardSettings.ContentDashboardUrlAllowlist.Contains(url, StringComparer.OrdinalIgnoreCase))
276+
{
277+
return true;
278+
}
279+
else
280+
{
281+
return false;
282+
}
283+
}
243284
}
244285
}

0 commit comments

Comments
 (0)