Merge remote-tracking branch 'origin/temp8' into temp8-3527-indexes-via-code
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -22,19 +21,21 @@ namespace Umbraco.Web.Actions
|
||||
|
||||
internal IEnumerable<IAction> GetByLetters(IEnumerable<string> letters)
|
||||
{
|
||||
var all = this.ToArray();
|
||||
return letters.Select(x => all.FirstOrDefault(y => y.Letter.ToString(CultureInfo.InvariantCulture) == x))
|
||||
var actions = this.ToArray(); // no worry: internally, it's already an array
|
||||
return letters
|
||||
.Where(x => x.Length == 1)
|
||||
.Select(x => actions.FirstOrDefault(y => y.Letter == x[0]))
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal IReadOnlyList<IAction> FromEntityPermission(EntityPermission entityPermission)
|
||||
{
|
||||
var actions = this.ToArray(); // no worry: internally, it's already an array
|
||||
return entityPermission.AssignedPermissions
|
||||
.Where(x => x.Length == 1)
|
||||
.Select(x => x.ToCharArray()[0])
|
||||
.SelectMany(c => this.Where(x => x.Letter == c))
|
||||
.Where(action => action != null)
|
||||
.SelectMany(x => actions.Where(y => y.Letter == x[0]))
|
||||
.WhereNotNull()
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
|
||||
namespace Umbraco.Web.Actions
|
||||
{
|
||||
internal class ActionCollectionBuilder : LazyCollectionBuilderBase<ActionCollectionBuilder, ActionCollection, IAction>
|
||||
@@ -19,13 +17,13 @@ namespace Umbraco.Web.Actions
|
||||
protected override IEnumerable<IAction> CreateItems(params object[] args)
|
||||
{
|
||||
var items = base.CreateItems(args).ToList();
|
||||
|
||||
//validate the items, no actions should exist that do not either expose notifications or permissions
|
||||
var invalid = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList();
|
||||
if (invalid.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid actions '{string.Join(", ", invalid.Select(x => x.Alias))}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}");
|
||||
}
|
||||
return items;
|
||||
var invalidItems = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList();
|
||||
if (invalidItems.Count == 0) return items;
|
||||
|
||||
var invalidActions = string.Join(", ", invalidItems.Select(x => "'" + x.Alias + "'"));
|
||||
throw new InvalidOperationException($"Invalid actions {invalidActions}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
//we need to fire up the controller like this to enable loading of remote css directly from this controller
|
||||
[PluginController("UmbracoApi")]
|
||||
[ValidationFilter]
|
||||
[AngularJsonOnlyConfiguration]
|
||||
[IsBackOffice]
|
||||
[WebApi.UmbracoAuthorize]
|
||||
public class DashboardController : UmbracoApiController
|
||||
{
|
||||
//we have just one instance of HttpClient shared for the entire application
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var context = UmbracoContext.Current;
|
||||
if (context == null)
|
||||
throw new HttpResponseException(HttpStatusCode.InternalServerError);
|
||||
|
||||
var user = Security.CurrentUser;
|
||||
var allowedSections = string.Join(",", user.AllowedSections);
|
||||
var language = user.Language;
|
||||
var version = UmbracoVersion.SemanticVersion.ToSemanticString();
|
||||
|
||||
var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version);
|
||||
var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section;
|
||||
|
||||
var content = ApplicationCache.RuntimeCache.GetCacheItem<JObject>(key);
|
||||
var result = new JObject();
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await HttpClient.GetStringAsync(url);
|
||||
content = JObject.Parse(json);
|
||||
result = content;
|
||||
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard content from '{Url}'", url);
|
||||
|
||||
//it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section);
|
||||
var key = "umbraco-dynamic-dashboard-css-" + section;
|
||||
|
||||
var content = ApplicationCache.RuntimeCache.GetCacheItem<string>(key);
|
||||
var result = string.Empty;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch remote css
|
||||
content = await HttpClient.GetStringAsync(url);
|
||||
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
|
||||
//save server content for 30 mins
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard CSS from '{Url}'", url);
|
||||
|
||||
//it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(result, Encoding.UTF8, "text/css")
|
||||
};
|
||||
}
|
||||
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section)
|
||||
{
|
||||
var dashboardHelper = new DashboardHelper(Services.SectionService);
|
||||
return dashboardHelper.GetDashboard(section, Security.CurrentUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
//we need to fire up the controller like this to enable loading of remote css directly from this controller
|
||||
[PluginController("UmbracoApi")]
|
||||
[ValidationFilter]
|
||||
[AngularJsonOnlyConfiguration]
|
||||
[IsBackOffice]
|
||||
[WebApi.UmbracoAuthorize]
|
||||
public class DashboardController : UmbracoApiController
|
||||
{
|
||||
private readonly Dashboards _dashboards;
|
||||
|
||||
public DashboardController(Dashboards dashboards)
|
||||
{
|
||||
_dashboards = dashboards;
|
||||
}
|
||||
|
||||
//we have just one instance of HttpClient shared for the entire application
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var user = Security.CurrentUser;
|
||||
var allowedSections = string.Join(",", user.AllowedSections);
|
||||
var language = user.Language;
|
||||
var version = UmbracoVersion.SemanticVersion.ToSemanticString();
|
||||
|
||||
var url = string.Format(baseUrl + "{0}?section={0}&allowed={1}&lang={2}&version={3}", section, allowedSections, language, version);
|
||||
var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section;
|
||||
|
||||
var content = ApplicationCache.RuntimeCache.GetCacheItem<JObject>(key);
|
||||
var result = new JObject();
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await HttpClient.GetStringAsync(url);
|
||||
content = JObject.Parse(json);
|
||||
result = content;
|
||||
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard content from '{Url}'", url);
|
||||
|
||||
//it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> GetRemoteDashboardCss(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section);
|
||||
var key = "umbraco-dynamic-dashboard-css-" + section;
|
||||
|
||||
var content = ApplicationCache.RuntimeCache.GetCacheItem<string>(key);
|
||||
var result = string.Empty;
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
result = content;
|
||||
}
|
||||
else
|
||||
{
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
//fetch remote css
|
||||
content = await HttpClient.GetStringAsync(url);
|
||||
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
|
||||
//save server content for 30 mins
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Error<DashboardController>(ex.InnerException ?? ex, "Error getting dashboard CSS from '{Url}'", url);
|
||||
|
||||
//it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 5, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(result, Encoding.UTF8, "text/css")
|
||||
};
|
||||
}
|
||||
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section)
|
||||
{
|
||||
return _dashboards.GetDashboards(section, Security.CurrentUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class DashboardHelper
|
||||
{
|
||||
private readonly ISectionService _sectionService;
|
||||
|
||||
public DashboardHelper(ISectionService sectionService)
|
||||
{
|
||||
if (sectionService == null) throw new ArgumentNullException("sectionService");
|
||||
_sectionService = sectionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the dashboard models per section for the current user and it's access
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, IEnumerable<Tab<DashboardControl>>> GetDashboards(IUser currentUser)
|
||||
{
|
||||
var result = new Dictionary<string, IEnumerable<Tab<DashboardControl>>>();
|
||||
foreach (var section in _sectionService.GetSections())
|
||||
{
|
||||
result[section.Alias] = GetDashboard(section.Alias, currentUser);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the dashboard model for the given section based on the current user and it's access
|
||||
/// </summary>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
var i = 1;
|
||||
|
||||
//disable packages section dashboard
|
||||
if (section == "packages") return tabs;
|
||||
|
||||
foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section)))
|
||||
{
|
||||
//we need to validate access to this section
|
||||
if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
//User is authorized
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
//we need to validate access to this tab
|
||||
if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl();
|
||||
var controlPath = control.ControlPath.Trim();
|
||||
dashboardControl.Caption = control.PanelCaption;
|
||||
dashboardControl.Path = IOHelper.FindFile(controlPath);
|
||||
if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant()))
|
||||
dashboardControl.ServerSide = true;
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = i,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
IsActive = i == 1,
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
//In case there are no tabs or a user doesn't have access the empty tabs list is returned
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
@@ -17,105 +18,97 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
public static bool AuthorizeAccess(ISection dashboardSection, IUser user, ISectionService sectionService)
|
||||
{
|
||||
if (user.Id.ToString(CultureInfo.InvariantCulture) == 0.ToInvariantString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var denyTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray();
|
||||
var grantedTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray();
|
||||
var grantedBySectionTypes = dashboardSection.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray();
|
||||
|
||||
return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes);
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardSection.AccessRights.Rules);
|
||||
}
|
||||
|
||||
public static bool AuthorizeAccess(IDashboardTab dashboardTab, IUser user, ISectionService sectionService)
|
||||
{
|
||||
if (user.Id.ToString(CultureInfo.InvariantCulture) == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var denyTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray();
|
||||
var grantedTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray();
|
||||
var grantedBySectionTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray();
|
||||
|
||||
return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes);
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardTab.AccessRights.Rules);
|
||||
}
|
||||
|
||||
public static bool AuthorizeAccess(IDashboardControl dashboardTab, IUser user, ISectionService sectionService)
|
||||
public static bool AuthorizeAccess(IDashboardControl dashboardControl, IUser user, ISectionService sectionService)
|
||||
{
|
||||
if (user.Id.ToString(CultureInfo.InvariantCulture) == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var denyTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Deny).ToArray();
|
||||
var grantedTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.Grant).ToArray();
|
||||
var grantedBySectionTypes = dashboardTab.AccessRights.Rules.Where(x => x.Action == AccessType.GrantBySection).ToArray();
|
||||
|
||||
return CheckUserAccessByRules(user, sectionService, denyTypes, grantedTypes, grantedBySectionTypes);
|
||||
return CheckUserAccessByRules(user, sectionService, dashboardControl.AccessRights.Rules);
|
||||
}
|
||||
|
||||
public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IAccessItem[] denyTypes, IAccessItem[] grantedTypes, IAccessItem[] grantedBySectionTypes)
|
||||
private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable<IAccessRule> rules)
|
||||
{
|
||||
var allowedSoFar = false;
|
||||
IAccessRule[] denyRules = null, grantRules = null, grantBySectionRules = null;
|
||||
|
||||
// if there's no grantBySection or grant rules defined - we allow access so far and skip to checking deny rules
|
||||
if (grantedBySectionTypes.Any() == false && grantedTypes.Any() == false)
|
||||
var groupedRules = rules.GroupBy(x => x.Type);
|
||||
foreach (var group in groupedRules)
|
||||
{
|
||||
allowedSoFar = true;
|
||||
var a = group.ToArray();
|
||||
switch (group.Key)
|
||||
{
|
||||
case AccessRuleType.Deny:
|
||||
denyRules = a;
|
||||
break;
|
||||
case AccessRuleType.Grant:
|
||||
grantRules = a;
|
||||
break;
|
||||
case AccessRuleType.GrantBySection:
|
||||
grantBySectionRules = a;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
}
|
||||
}
|
||||
// else we check the rules and only allow if one matches
|
||||
else
|
||||
|
||||
return (denyRules ?? Array.Empty<IAccessRule>(), grantRules ?? Array.Empty<IAccessRule>(), grantBySectionRules ?? Array.Empty<IAccessRule>());
|
||||
}
|
||||
|
||||
public static bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable<IAccessRule> rules)
|
||||
{
|
||||
if (user.Id == Constants.Security.SuperUserId)
|
||||
return true;
|
||||
|
||||
var (denyRules, grantRules, grantBySectionRules) = GroupRules(rules);
|
||||
|
||||
var hasAccess = true;
|
||||
string[] assignedUserGroups = null;
|
||||
|
||||
// if there are no grant rules, then access is granted by default, unless denied
|
||||
// otherwise, grant rules determine if access can be granted at all
|
||||
if (grantBySectionRules.Length > 0 || grantRules.Length > 0)
|
||||
{
|
||||
hasAccess = false;
|
||||
|
||||
// check if this item has any grant-by-section arguments.
|
||||
// if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far)
|
||||
if (grantedBySectionTypes.Any())
|
||||
if (grantBySectionRules.Length > 0)
|
||||
{
|
||||
var allowedApps = sectionService.GetAllowedSections(Convert.ToInt32(user.Id))
|
||||
.Select(x => x.Alias)
|
||||
.ToArray();
|
||||
var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray();
|
||||
var wantedSections = grantBySectionRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
|
||||
var allApprovedSections = grantedBySectionTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
if (allApprovedSections.Any(allowedApps.Contains))
|
||||
{
|
||||
allowedSoFar = true;
|
||||
}
|
||||
if (wantedSections.Intersect(allowedSections).Any())
|
||||
hasAccess = true;
|
||||
}
|
||||
|
||||
// if not already granted access, check if this item as any grant arguments.
|
||||
// if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far)
|
||||
if (allowedSoFar == false && grantedTypes.Any())
|
||||
if (hasAccess == false && grantRules.Any())
|
||||
{
|
||||
var allApprovedUserTypes = grantedTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
foreach (var userGroup in user.Groups)
|
||||
{
|
||||
if (allApprovedUserTypes.InvariantContains(userGroup.Alias))
|
||||
{
|
||||
allowedSoFar = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray();
|
||||
var wantedUserGroups = grantRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
|
||||
if (wantedUserGroups.Intersect(assignedUserGroups).Any())
|
||||
hasAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAccess || denyRules.Length == 0)
|
||||
return false;
|
||||
|
||||
// check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will
|
||||
// be denied to see it no matter what
|
||||
if (denyTypes.Any())
|
||||
{
|
||||
var allDeniedUserTypes = denyTypes.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
foreach (var userGroup in user.Groups)
|
||||
{
|
||||
if (allDeniedUserTypes.InvariantContains(userGroup.Alias))
|
||||
{
|
||||
allowedSoFar = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray();
|
||||
var deniedUserGroups = denyRules.SelectMany(g => g.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)).ToArray();
|
||||
|
||||
return allowedSoFar;
|
||||
if (deniedUserGroups.Intersect(assignedUserGroups).Any())
|
||||
hasAccess = false;
|
||||
|
||||
return hasAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
159
src/Umbraco.Web/Editors/Dashboards.cs
Normal file
159
src/Umbraco.Web/Editors/Dashboards.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Dashboard;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public class Dashboards
|
||||
{
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly IDashboardSection _dashboardSection;
|
||||
private readonly ManifestParser _manifestParser;
|
||||
|
||||
public Dashboards(ISectionService sectionService, IDashboardSection dashboardSection, ManifestParser manifestParser)
|
||||
{
|
||||
_sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
|
||||
_dashboardSection = dashboardSection;
|
||||
_manifestParser = manifestParser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all dashboards, organized by section, for a user.
|
||||
/// </summary>
|
||||
public IDictionary<string, IEnumerable<Tab<DashboardControl>>> GetDashboards(IUser currentUser)
|
||||
{
|
||||
return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns dashboards for a specific section, for a user.
|
||||
/// </summary>
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboards(string section, IUser currentUser)
|
||||
{
|
||||
var tabId = 1;
|
||||
var configDashboards = GetDashboardsFromConfig(ref tabId, section, currentUser);
|
||||
var pluginDashboards = GetDashboardsFromPlugins(ref tabId, section, currentUser);
|
||||
|
||||
// merge dashboards
|
||||
// both collections contain tab.alias -> controls
|
||||
var dashboards = configDashboards;
|
||||
|
||||
// until now, it was fine to have duplicate tab.aliases in configDashboard
|
||||
// so... the rule should be - just merge whatever we get, don't be clever
|
||||
dashboards.AddRange(pluginDashboards);
|
||||
|
||||
// re-sort by id
|
||||
dashboards.Sort((tab1, tab2) => tab1.Id > tab2.Id ? 1 : 0);
|
||||
|
||||
// re-assign ids (why?)
|
||||
var i = 1;
|
||||
foreach (var tab in dashboards)
|
||||
{
|
||||
tab.Id = i++;
|
||||
tab.IsActive = tab.Id == 1;
|
||||
}
|
||||
|
||||
return configDashboards;
|
||||
}
|
||||
|
||||
// note:
|
||||
// in dashboard.config we have 'sections' which define 'tabs' for 'areas'
|
||||
// and 'areas' are the true UI sections - and each tab can have more than
|
||||
// one control
|
||||
// in a manifest, we directly have 'dashboards' which map to a unique
|
||||
// control in a tab
|
||||
|
||||
// gets all tabs & controls from the config file
|
||||
private List<Tab<DashboardControl>> GetDashboardsFromConfig(ref int tabId, string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
|
||||
// disable packages section dashboard
|
||||
if (section == "packages") return tabs;
|
||||
|
||||
foreach (var dashboardSection in _dashboardSection.Sections.Where(x => x.Areas.InvariantContains(section)))
|
||||
{
|
||||
// validate access to this section
|
||||
if (!DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
// validate access to this tab
|
||||
if (!DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
// validate access to this control
|
||||
if (!DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService))
|
||||
continue;
|
||||
|
||||
// create and add control
|
||||
var dashboardControl = new DashboardControl
|
||||
{
|
||||
Caption = control.PanelCaption,
|
||||
Path = IOHelper.FindFile(control.ControlPath.Trim())
|
||||
};
|
||||
|
||||
if (dashboardControl.Path.InvariantEndsWith(".ascx"))
|
||||
throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
// create and add tab
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = tabId++,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private List<Tab<DashboardControl>> GetDashboardsFromPlugins(ref int tabId, string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
|
||||
foreach (var dashboard in _manifestParser.Manifest.Dashboards.Where(x => x.Sections.InvariantContains(section)).OrderBy(x => x.Weight))
|
||||
{
|
||||
// validate access
|
||||
if (!DashboardSecurity.CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules))
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl
|
||||
{
|
||||
Caption = "",
|
||||
Path = IOHelper.FindFile(dashboard.View.Trim())
|
||||
};
|
||||
|
||||
if (dashboardControl.Path.InvariantEndsWith(".ascx"))
|
||||
throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = tabId++,
|
||||
Alias = dashboard.Alias.ToSafeAlias(),
|
||||
Label = dashboard.Name,
|
||||
Properties = new[] { dashboardControl }
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
@@ -194,7 +195,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
const int level = 0;
|
||||
|
||||
foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems())
|
||||
foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems().OrderBy(ItemSort()))
|
||||
{
|
||||
var item = Mapper.Map<IDictionaryItem, DictionaryOverviewDisplay>(dictionaryItem);
|
||||
item.Level = 0;
|
||||
@@ -220,8 +221,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </param>
|
||||
private void GetChildItemsForList(IDictionaryItem dictionaryItem, int level, List<DictionaryOverviewDisplay> list)
|
||||
{
|
||||
foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(
|
||||
dictionaryItem.Key))
|
||||
foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(ItemSort()))
|
||||
{
|
||||
var item = Mapper.Map<IDictionaryItem, DictionaryOverviewDisplay>(childItem);
|
||||
item.Level = level;
|
||||
@@ -230,5 +230,7 @@ namespace Umbraco.Web.Editors
|
||||
GetChildItemsForList(childItem, level + 1, list);
|
||||
}
|
||||
}
|
||||
|
||||
private Func<IDictionaryItem, string> ItemSort() => item => item.ItemKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Trees;
|
||||
using Section = Umbraco.Web.Models.ContentEditing.Section;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// The API controller used for using the list of sections
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
public IEnumerable<Section> GetSections()
|
||||
{
|
||||
|
||||
var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0));
|
||||
|
||||
var sectionModels = sections.Select(Mapper.Map<Core.Models.Section, Section>).ToArray();
|
||||
|
||||
//Check if there are empty dashboards or dashboards that will end up empty based on the current user's access
|
||||
//and add the meta data about them
|
||||
var dashboardHelper = new DashboardHelper(Services.SectionService);
|
||||
|
||||
// this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that
|
||||
// since tree's by nature are controllers and require request contextual data - and then we have to
|
||||
// remember to inject properties - nasty indeed
|
||||
// fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver
|
||||
var appTreeController = new ApplicationTreeController();
|
||||
Current.Container.InjectProperties(appTreeController);
|
||||
appTreeController.ControllerContext = ControllerContext;
|
||||
|
||||
var dashboards = dashboardHelper.GetDashboards(Security.CurrentUser);
|
||||
//now we can add metadata for each section so that the UI knows if there's actually anything at all to render for
|
||||
//a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree)
|
||||
foreach (var section in sectionModels)
|
||||
{
|
||||
var hasDashboards = false;
|
||||
if (dashboards.TryGetValue(section.Alias, out var dashboardsForSection))
|
||||
{
|
||||
if (dashboardsForSection.Any())
|
||||
hasDashboards = true;
|
||||
}
|
||||
|
||||
if (hasDashboards == false)
|
||||
{
|
||||
//get the first tree in the section and get it's root node route path
|
||||
var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result;
|
||||
section.RoutePath = GetRoutePathForFirstTree(sectionRoot);
|
||||
}
|
||||
}
|
||||
|
||||
return sectionModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first non root/group node's route path
|
||||
/// </summary>
|
||||
/// <param name="rootNode"></param>
|
||||
/// <returns></returns>
|
||||
private string GetRoutePathForFirstTree(TreeRootNode rootNode)
|
||||
{
|
||||
if (!rootNode.IsContainer || !rootNode.ContainsTrees)
|
||||
return rootNode.RoutePath;
|
||||
|
||||
foreach(var node in rootNode.Children)
|
||||
{
|
||||
if (node is TreeRootNode groupRoot)
|
||||
return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group
|
||||
else
|
||||
return node.RoutePath;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the sections that the user has access to
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Section> GetAllSections()
|
||||
{
|
||||
var sections = Services.SectionService.GetSections();
|
||||
var mapped = sections.Select(Mapper.Map<Core.Models.Section, Section>);
|
||||
if (Security.CurrentUser.IsAdmin())
|
||||
return mapped;
|
||||
|
||||
return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Trees;
|
||||
using Section = Umbraco.Web.Models.ContentEditing.Section;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// The API controller used for using the list of sections
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly Dashboards _dashboards;
|
||||
|
||||
public SectionController(Dashboards dashboards)
|
||||
{
|
||||
_dashboards = dashboards;
|
||||
}
|
||||
|
||||
public IEnumerable<Section> GetSections()
|
||||
{
|
||||
var sections = Services.SectionService.GetAllowedSections(Security.GetUserId().ResultOr(0));
|
||||
|
||||
var sectionModels = sections.Select(Mapper.Map<Core.Models.Section, Section>).ToArray();
|
||||
|
||||
// this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that
|
||||
// since tree's by nature are controllers and require request contextual data - and then we have to
|
||||
// remember to inject properties - nasty indeed
|
||||
// fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver
|
||||
var appTreeController = new ApplicationTreeController();
|
||||
Current.Container.InjectProperties(appTreeController);
|
||||
appTreeController.ControllerContext = ControllerContext;
|
||||
|
||||
var dashboards = _dashboards.GetDashboards(Security.CurrentUser);
|
||||
|
||||
//now we can add metadata for each section so that the UI knows if there's actually anything at all to render for
|
||||
//a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree)
|
||||
foreach (var section in sectionModels)
|
||||
{
|
||||
var hasDashboards = dashboards.TryGetValue(section.Alias, out var dashboardsForSection) && dashboardsForSection.Any();
|
||||
if (hasDashboards) continue;
|
||||
|
||||
// get the first tree in the section and get its root node route path
|
||||
var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result;
|
||||
section.RoutePath = GetRoutePathForFirstTree(sectionRoot);
|
||||
}
|
||||
|
||||
return sectionModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first non root/group node's route path
|
||||
/// </summary>
|
||||
/// <param name="rootNode"></param>
|
||||
/// <returns></returns>
|
||||
private string GetRoutePathForFirstTree(TreeRootNode rootNode)
|
||||
{
|
||||
if (!rootNode.IsContainer || !rootNode.ContainsTrees)
|
||||
return rootNode.RoutePath;
|
||||
|
||||
foreach(var node in rootNode.Children)
|
||||
{
|
||||
if (node is TreeRootNode groupRoot)
|
||||
return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group
|
||||
else
|
||||
return node.RoutePath;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the sections that the user has access to
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Section> GetAllSections()
|
||||
{
|
||||
var sections = Services.SectionService.GetSections();
|
||||
var mapped = sections.Select(Mapper.Map<Core.Models.Section, Section>);
|
||||
if (Security.CurrentUser.IsAdmin())
|
||||
return mapped;
|
||||
|
||||
return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,82 @@
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Mvc;
|
||||
using LightInject;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal static class LightInjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers all IControllers using the TypeLoader for scanning and caching found instances for the calling assembly
|
||||
/// Registers Umbraco controllers.
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
/// <param name="typeLoader"></param>
|
||||
/// <param name="assembly"></param>
|
||||
public static void RegisterMvcControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly)
|
||||
public static void RegisterUmbracoControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly umbracoWebAssembly)
|
||||
{
|
||||
//TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again
|
||||
// for all controllers? Seems like we should just do this once and then filter. That said here we are
|
||||
// only scanning our own single assembly. Hrm.
|
||||
// notes
|
||||
//
|
||||
// We scan and auto-registers:
|
||||
// - every IController and IHttpController that *we* have in Umbraco.Web
|
||||
// - PluginController and UmbracoApiController in every assembly
|
||||
//
|
||||
// We do NOT scan:
|
||||
// - any IController or IHttpController (anything not PluginController nor UmbracoApiController), outside of Umbraco.Web
|
||||
// which means that users HAVE to explicitly register their own non-Umbraco controllers
|
||||
//
|
||||
// This is because we try to achieve a balance between "simple" and "fast. Scanning for PluginController or
|
||||
// UmbracoApiController is fast-ish because they both are IDiscoverable. Scanning for IController or IHttpController
|
||||
// is a full, non-cached scan = expensive, we do it only for 1 assembly.
|
||||
//
|
||||
// TODO
|
||||
// find a way to scan for IController *and* IHttpController in one single pass
|
||||
// or, actually register them manually so don't require a full scan for these
|
||||
// 5 are IController but not PluginController
|
||||
// Umbraco.Web.Mvc.RenderMvcController
|
||||
// Umbraco.Web.Install.Controllers.InstallController
|
||||
// Umbraco.Web.Macros.PartialViewMacroController
|
||||
// Umbraco.Web.Editors.PreviewController
|
||||
// Umbraco.Web.Editors.BackOfficeController
|
||||
// 9 are IHttpController but not UmbracoApiController
|
||||
// Umbraco.Web.Controllers.UmbProfileController
|
||||
// Umbraco.Web.Controllers.UmbLoginStatusController
|
||||
// Umbraco.Web.Controllers.UmbRegisterController
|
||||
// Umbraco.Web.Controllers.UmbLoginController
|
||||
// Umbraco.Web.Mvc.RenderMvcController
|
||||
// Umbraco.Web.Install.Controllers.InstallController
|
||||
// Umbraco.Web.Macros.PartialViewMacroController
|
||||
// Umbraco.Web.Editors.PreviewController
|
||||
// Umbraco.Web.Editors.BackOfficeController
|
||||
|
||||
container.RegisterControllers<IController>(typeLoader, assembly);
|
||||
// scan and register every IController in Umbraco.Web
|
||||
var umbracoWebControllers = typeLoader.GetTypes<IController>(specificAssemblies: new[] { umbracoWebAssembly });
|
||||
//foreach (var controller in umbracoWebControllers.Where(x => !typeof(PluginController).IsAssignableFrom(x)))
|
||||
// Current.Logger.Debug(typeof(LightInjectExtensions), "IController NOT PluginController: " + controller.FullName);
|
||||
container.RegisterControllers(umbracoWebControllers);
|
||||
|
||||
// scan and register every PluginController in everything (PluginController is IDiscoverable and IController)
|
||||
var nonUmbracoWebPluginController = typeLoader.GetTypes<PluginController>().Where(x => x.Assembly != umbracoWebAssembly);
|
||||
container.RegisterControllers(nonUmbracoWebPluginController);
|
||||
|
||||
// scan and register every IHttpController in Umbraco.Web
|
||||
var umbracoWebHttpControllers = typeLoader.GetTypes<IHttpController>(specificAssemblies: new[] { umbracoWebAssembly });
|
||||
//foreach (var controller in umbracoWebControllers.Where(x => !typeof(UmbracoApiController).IsAssignableFrom(x)))
|
||||
// Current.Logger.Debug(typeof(LightInjectExtensions), "IHttpController NOT UmbracoApiController: " + controller.FullName);
|
||||
container.RegisterControllers(umbracoWebHttpControllers);
|
||||
|
||||
// scan and register every UmbracoApiController in everything (UmbracoApiController is IDiscoverable and IHttpController)
|
||||
var nonUmbracoWebApiControllers = typeLoader.GetTypes<UmbracoApiController>().Where(x => x.Assembly != umbracoWebAssembly);
|
||||
container.RegisterControllers(nonUmbracoWebApiControllers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers all IHttpController using the TypeLoader for scanning and caching found instances for the calling assembly
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
/// <param name="typeLoader"></param>
|
||||
/// <param name="assembly"></param>
|
||||
public static void RegisterApiControllers(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly)
|
||||
private static void RegisterControllers(this IServiceRegistry container, IEnumerable<Type> controllerTypes)
|
||||
{
|
||||
//TODO: We've already scanned for UmbracoApiControllers and SurfaceControllers - should we scan again
|
||||
// for all controllers? Seems like we should just do this once and then filter. That said here we are
|
||||
// only scanning our own single assembly. Hrm.
|
||||
|
||||
container.RegisterControllers<IHttpController>(typeLoader, assembly);
|
||||
}
|
||||
|
||||
private static void RegisterControllers<TController>(this IServiceRegistry container, TypeLoader typeLoader, Assembly assembly)
|
||||
{
|
||||
var types = typeLoader.GetTypes<TController>(specificAssemblies: new[] { assembly });
|
||||
foreach (var type in types)
|
||||
container.Register(type, new PerRequestLifeTime());
|
||||
foreach (var controllerType in controllerTypes)
|
||||
container.Register(controllerType, new PerRequestLifeTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,6 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataContract(Name = "control", Namespace = "")]
|
||||
public class DashboardControl
|
||||
{
|
||||
[DataMember(Name = "showOnce")]
|
||||
public bool ShowOnce { get; set; }
|
||||
|
||||
[DataMember(Name = "addPanel")]
|
||||
public bool AddPanel { get; set; }
|
||||
|
||||
[DataMember(Name = "serverSide")]
|
||||
public bool ServerSide { get; set; }
|
||||
|
||||
[DataMember(Name = "path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
|
||||
@@ -122,9 +122,10 @@ namespace Umbraco.Web.Runtime
|
||||
composition.Container.EnableMvc(); // does container.EnablePerWebRequestScope()
|
||||
composition.Container.ScopeManagerProvider = smp; // reverts - we will do it last (in WebRuntime)
|
||||
|
||||
composition.Container.RegisterMvcControllers(typeLoader, GetType().Assembly);
|
||||
composition.Container.RegisterSingleton<Dashboards>();
|
||||
|
||||
composition.Container.RegisterUmbracoControllers(typeLoader, GetType().Assembly);
|
||||
composition.Container.EnableWebApi(GlobalConfiguration.Configuration);
|
||||
composition.Container.RegisterApiControllers(typeLoader, GetType().Assembly);
|
||||
|
||||
composition.Container.RegisterCollectionBuilder<SearchableTreeCollectionBuilder>()
|
||||
.Add(() => typeLoader.GetTypes<ISearchableTree>()); // fixme which searchable trees?!
|
||||
@@ -183,7 +184,7 @@ namespace Umbraco.Web.Runtime
|
||||
.Append<ContentFinderByRedirectUrl>();
|
||||
|
||||
composition.Container.RegisterSingleton<ISiteDomainHelper, SiteDomainHelper>();
|
||||
|
||||
|
||||
composition.Container.RegisterSingleton<ICultureDictionaryFactory, DefaultCultureDictionaryFactory>();
|
||||
|
||||
// register *all* checks, except those marked [HideFromTypeFinder] of course
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Actions;
|
||||
|
||||
@@ -52,10 +53,12 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
var nodes = new TreeNodeCollection();
|
||||
|
||||
Func<IDictionaryItem, string> ItemSort() => item => item.ItemKey;
|
||||
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
nodes.AddRange(
|
||||
Services.LocalizationService.GetRootDictionaryItems().Select(
|
||||
Services.LocalizationService.GetRootDictionaryItems().OrderBy(ItemSort()).Select(
|
||||
x => CreateTreeNode(
|
||||
x.Id.ToInvariantString(),
|
||||
id,
|
||||
@@ -71,7 +74,7 @@ namespace Umbraco.Web.Trees
|
||||
if (parentDictionary == null)
|
||||
return nodes;
|
||||
|
||||
nodes.AddRange(Services.LocalizationService.GetDictionaryItemChildren(parentDictionary.Key).ToList().OrderByDescending(item => item.Key).Select(
|
||||
nodes.AddRange(Services.LocalizationService.GetDictionaryItemChildren(parentDictionary.Key).ToList().OrderBy(ItemSort()).Select(
|
||||
x => CreateTreeNode(
|
||||
x.Id.ToInvariantString(),
|
||||
id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
'lib/jquery/jquery.min.js',
|
||||
'lib/jquery-ui/jquery-ui.min.js',
|
||||
'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js',
|
||||
'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js',
|
||||
|
||||
'lib/angular/angular.js',
|
||||
'lib/underscore/underscore-min.js',
|
||||
@@ -23,7 +23,6 @@
|
||||
'lib/ng-file-upload/ng-file-upload.min.js',
|
||||
'lib/angular-local-storage/angular-local-storage.min.js',
|
||||
|
||||
'lib/bootstrap/js/bootstrap.2.3.2.min.js',
|
||||
'lib/umbraco/Extensions.js',
|
||||
|
||||
'lib/umbraco/NamespaceManager.js',
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
<Compile Include="Models\ContentEditing\IContentSave.cs" />
|
||||
<Compile Include="WebApi\TrimModelBinder.cs" />
|
||||
<Compile Include="Editors\CodeFileController.cs" />
|
||||
<Compile Include="Editors\DashboardHelper.cs" />
|
||||
<Compile Include="Editors\Dashboards.cs" />
|
||||
<Compile Include="Editors\DictionaryController.cs" />
|
||||
<Compile Include="Editors\EditorModelEventArgs.cs" />
|
||||
<Compile Include="Editors\EditorValidatorCollection.cs" />
|
||||
|
||||
Reference in New Issue
Block a user