1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Linq ;
4
+ using System . Net ;
4
5
using System . Net . Http ;
5
6
using System . Text ;
6
7
using System . Threading . Tasks ;
7
8
using Microsoft . AspNetCore . Authorization ;
8
9
using Microsoft . AspNetCore . Mvc ;
9
10
using Microsoft . Extensions . Logging ;
10
11
using Microsoft . Extensions . Options ;
12
+ using Newtonsoft . Json ;
11
13
using Newtonsoft . Json . Linq ;
12
14
using Umbraco . Cms . Core . Cache ;
13
15
using Umbraco . Cms . Core . Configuration ;
16
+ using Umbraco . Cms . Core . Configuration . Models ;
14
17
using Umbraco . Cms . Core . Dashboards ;
15
18
using Umbraco . Cms . Core . Models . ContentEditing ;
16
19
using Umbraco . Cms . Core . Security ;
@@ -40,7 +43,7 @@ public class DashboardController : UmbracoApiController
40
43
private readonly IDashboardService _dashboardService ;
41
44
private readonly IUmbracoVersion _umbracoVersion ;
42
45
private readonly IShortStringHelper _shortStringHelper ;
43
- private readonly IOptions < ContentDashboardSettings > _dashboardSettings ;
46
+ private readonly ContentDashboardSettings _dashboardSettings ;
44
47
/// <summary>
45
48
/// Initializes a new instance of the <see cref="DashboardController"/> with all its dependencies.
46
49
/// </summary>
@@ -60,12 +63,13 @@ public DashboardController(
60
63
_dashboardService = dashboardService ;
61
64
_umbracoVersion = umbracoVersion ;
62
65
_shortStringHelper = shortStringHelper ;
63
- _dashboardSettings = dashboardSettings ;
66
+ _dashboardSettings = dashboardSettings . Value ;
64
67
}
65
68
66
69
//we have just one instance of HttpClient shared for the entire application
67
70
private static readonly HttpClient HttpClient = new HttpClient ( ) ;
68
71
72
+ // TODO(V10) : change return type to Task<ActionResult<JObject>> and consider removing baseUrl as parameter
69
73
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
70
74
[ ValidateAngularAntiForgeryToken ]
71
75
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
76
80
var version = _umbracoVersion . SemanticVersion . ToSemanticStringWithoutBuild ( ) ;
77
81
var isAdmin = user . IsAdmin ( ) ;
78
82
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
+
79
93
var url = string . Format ( "{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}" ,
80
94
baseUrl ,
81
- _dashboardSettings . Value . ContentDashboardPath ,
95
+ _dashboardSettings . ContentDashboardPath ,
82
96
section ,
83
97
allowedSections ,
84
98
language ,
@@ -116,8 +130,15 @@ public async Task<JObject> GetRemoteDashboardContent(string section, string base
116
130
return result ;
117
131
}
118
132
133
+ // TODO(V10) : consider removing baseUrl as parameter
119
134
public async Task < IActionResult > GetRemoteDashboardCss ( string section , string baseUrl = "https://dashboard.umbraco.org/" )
120
135
{
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
+
121
142
var url = string . Format ( baseUrl + "css/dashboard.css?section={0}" , section ) ;
122
143
var key = "umbraco-dynamic-dashboard-css-" + section ;
123
144
@@ -152,12 +173,18 @@ public async Task<IActionResult> GetRemoteDashboardCss(string section, string ba
152
173
}
153
174
154
175
155
- return Content ( result , "text/css" , Encoding . UTF8 ) ;
176
+ return Content ( result , "text/css" , Encoding . UTF8 ) ;
156
177
157
178
}
158
179
159
180
public async Task < IActionResult > GetRemoteXml ( string site , string url )
160
181
{
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
+
161
188
// This is used in place of the old feedproxy.config
162
189
// Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv
163
190
// for certain dashboards or the help drawer
@@ -214,7 +241,7 @@ public async Task<IActionResult> GetRemoteXml(string site, string url)
214
241
}
215
242
}
216
243
217
- return Content ( result , "text/xml" , Encoding . UTF8 ) ;
244
+ return Content ( result , "text/xml" , Encoding . UTF8 ) ;
218
245
219
246
}
220
247
@@ -240,5 +267,19 @@ public IEnumerable<Tab<IDashboardSlim>> GetDashboard(string section)
240
267
} )
241
268
} ) . ToList ( ) ;
242
269
}
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
+ }
243
284
}
244
285
}
0 commit comments