diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0ada375163..9aa7a907dd 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -9,6 +9,7 @@ namespace Umbraco.Core { public const string AdminGroupAlias = "admin"; + public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; @@ -36,4 +37,4 @@ namespace Umbraco.Core } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index a65b307f24..5db36d16ed 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -261,6 +261,16 @@ namespace Umbraco.Core.Models return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); } + /// + /// Determines whether this user has access to view sensitive data + /// + /// + public static bool HasAccessToSensitiveData(this IUser user) + { + if (user == null) throw new ArgumentNullException("user"); + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + } + // calc. start nodes, combining groups' and user's, and excluding what's in the bin public static int[] CalculateContentStartNodeIds(this IUser user, IEntityService entityService) { @@ -413,4 +423,4 @@ namespace Umbraco.Core.Models return lsn.ToArray(); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 45107d3f21..be78638a9e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -219,7 +219,7 @@ namespace Umbraco.Web.Editors Path = "-1," + Constants.System.RecycleBinContent }; - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 0b2044dbe5..e22c83cbeb 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -115,7 +115,7 @@ namespace Umbraco.Web.Editors Path = "-1," + Constants.System.RecycleBinMedia }; - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 841b2df3d5..2440ad0691 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -154,7 +154,7 @@ namespace Umbraco.Web.Editors ParentId = -1 }; - TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); return display; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 9d5975b564..6df9c40f6a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Models.Mapping expression.MapFrom(content => content.ContentType.AllowedTemplates .Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false) .ToDictionary(t => t.Alias, t => t.Name))) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService), new Lazy(() => applicationContext.Services.ContentService)))) .AfterMap((content, display) => AfterMap(content, display, applicationContext.Services.DataTypeService, applicationContext.Services.TextService, @@ -100,6 +100,8 @@ namespace Umbraco.Web.Models.Mapping private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService) { + //TODO: All of this logic should be moved to the TabsAndPropertiesResolver and not in AfterMap + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = content.Parent(); @@ -113,7 +115,7 @@ namespace Umbraco.Web.Models.Mapping if (content.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); + TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 9a0480f771..7069bea2e2 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Alias, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new TabsAndPropertiesResolver(applicationContext.Services.TextService))) .ForMember(display => display.ContentType, expression => expression.ResolveUsing()) .ForMember(display => display.MediaLink, expression => expression.ResolveUsing( content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, applicationContext.ProfilingLogger.Logger)))) @@ -70,6 +70,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); } + //TODO: All of this logic should be moved to the TabsAndPropertiesResolver and not in AfterMap private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, IContentTypeService contentTypeService, ILogger logger) { // Adapted from ContentModelMapper @@ -80,7 +81,7 @@ namespace Umbraco.Web.Models.Mapping if (media.ContentType.IsContainer) { - TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); + TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 7f5bc731b1..147c074747 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -217,7 +217,7 @@ namespace Umbraco.Web.Models.Mapping /// /// This also ensures that the IsSensitive property display value is set based on the configured IMemberType property type /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver + internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver { private readonly ILocalizedTextService _localizedTextService; private readonly IMemberService _memberService; @@ -238,45 +238,23 @@ namespace Umbraco.Web.Models.Mapping _memberService = memberService; _userService = userService; } - + /// - /// Explicitly implement to override behavior - /// - /// - /// - /// - /// This is required to get access to the object which allows us access to the mapping options - /// - ResolutionResult IValueResolver.Resolve(ResolutionResult source) - { - //call the base class logic - var result = base.Resolve(source); - - var member = (IMember)source.Value; - //now we can customize the result and use the ResolutionResult options to get the UmbracoContext - var tabs = (List>) result.Value; - - //now we can customize the result with the current context, we can get the UmbracoContext from the options - CustomizeProperties(source.Context.GetUmbracoContext(), member, tabs); - - return result; - } - - /// - /// Overridden to deal with custom member properties + /// Overridden to deal with custom member properties and permissions /// + /// /// /// - protected override List> ResolveCore(IContentBase content) + protected override List> ResolveCore(UmbracoContext umbracoContext, IMember content) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - + IgnoreProperties = content.PropertyTypes .Where(x => x.HasIdentity == false) .Select(x => x.Alias) .ToArray(); - var result = base.ResolveCore(content); + var result = base.ResolveCore(umbracoContext, content); if (provider.IsUmbracoMembershipProvider() == false) { @@ -303,6 +281,39 @@ namespace Umbraco.Web.Models.Mapping } } + if (umbracoContext != null && umbracoContext.Security.CurrentUser != null + && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", content.ContentTypeId); + + //Replace the doctype property + var docTypeProperty = result.SelectMany(x => x.Properties) + .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new + { + linkText = content.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; + } + + //check if there's an approval field + var legacyProvider = provider as global::umbraco.providers.members.UmbracoMembershipProvider; + if (content.HasIdentity == false && legacyProvider != null) + { + var approvedField = legacyProvider.ApprovedPropertyTypeAlias; + var prop = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); + if (prop != null) + { + prop.Value = 1; + } + } + return result; } @@ -362,63 +373,17 @@ namespace Umbraco.Web.Models.Mapping return genericProperties; } - - /// - /// Performs some customizations for the properties based on the current context - /// - /// - /// - /// The current tab collection - /// - /// If this is a new entity and there is an approved field then we'll set it to true by default. - /// - private void CustomizeProperties(UmbracoContext umbracoContext, IMember member, List> tabs) - { - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (umbracoContext != null && umbracoContext.Security.CurrentUser != null - && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doctype property - var docTypeProperty = tabs.SelectMany(x => x.Properties) - .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProperty.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = memberTypeLink, - target = "_self", - icon = "icon-item-arrangement" - } - }; - docTypeProperty.View = "urllist"; - } - - //check if there's an approval field - var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; - if (member.HasIdentity == false && provider != null) - { - var approvedField = provider.ApprovedPropertyTypeAlias; - var prop = tabs.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); - if (prop != null) - { - prop.Value = 1; - } - } - } - + /// /// Overridden to assign the IsSensitive property values /// + /// /// /// /// - protected override List MapProperties(IContentBase content, List properties) + protected override List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) { - var result = base.MapProperties(content, properties); + var result = base.MapProperties(umbracoContext, content, properties); var member = (IMember)content; var memberType = member.ContentType; var labelPropEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; @@ -427,8 +392,7 @@ namespace Umbraco.Web.Models.Mapping { prop.IsSensitive = memberType.IsSensitiveProperty(prop.Alias); //check permissions for viewing sensitive data - - if (prop.IsSensitive) + if (prop.IsSensitive && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false) { //replace this editor with a label prop.View = labelPropEditor; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 55a72cb704..3792ba39ec 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -16,7 +16,8 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates the tabs collection with properties assigned for display models /// - internal class TabsAndPropertiesResolver : ValueResolver>> + internal class TabsAndPropertiesResolver : IValueResolver + where TSource : IContentBase { private readonly ILocalizedTextService _localizedTextService; protected IEnumerable IgnoreProperties { get; set; } @@ -35,6 +36,24 @@ namespace Umbraco.Web.Models.Mapping IgnoreProperties = ignoreProperties; } + /// + /// Implements the + /// + /// + /// + public ResolutionResult Resolve(ResolutionResult source) + { + if (source.Value != null && (source.Value is TSource) == false) + throw new AutoMapperMappingException(string.Format("Value supplied is of type {0} but expected {1}.\nChange the value resolver source type, or redirect the source value supplied to the value resolver using FromMember.", new object[] + { + source.Value.GetType(), + typeof (TSource) + })); + return source.New( + //perform the mapping with the current umbraco context + ResolveCore(source.Context.GetUmbracoContext(), (TSource)source.Value), typeof(List>)); + } + /// /// Adds the container (listview) tab to the document /// @@ -145,7 +164,13 @@ namespace Umbraco.Web.Models.Mapping display.Tabs = tabs; } - protected override List> ResolveCore(IContentBase content) + /// + /// Create the list of tabs for the + /// + /// + /// Source value + /// Destination + protected virtual List> ResolveCore(UmbracoContext umbracoContext, TSource content) { var tabs = new List>(); @@ -171,7 +196,7 @@ namespace Umbraco.Web.Models.Mapping continue; //map the properties - var mappedProperties = MapProperties(content, properties); + var mappedProperties = MapProperties(umbracoContext, content, properties); // add the tab // we need to pick an identifier... there is no "right" way... @@ -189,7 +214,7 @@ namespace Umbraco.Web.Models.Mapping }); } - MapGenericProperties(content, tabs); + MapGenericProperties(umbracoContext, content, tabs); // activate the first tab tabs[0].IsActive = true; @@ -209,20 +234,21 @@ namespace Umbraco.Web.Models.Mapping /// /// Maps properties on to the generic properties tab /// + /// /// /// /// /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// - protected virtual void MapGenericProperties(IContentBase content, List> tabs) + protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab var noGroupProperties = content.GetNonGroupedProperties() .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored .ToList(); - var genericproperties = MapProperties(content, noGroupProperties); + var genericproperties = MapProperties(umbracoContext, content, noGroupProperties); tabs.Add(new Tab { @@ -268,10 +294,11 @@ namespace Umbraco.Web.Models.Mapping /// /// Maps a list of to a list of /// + /// /// /// /// - protected virtual List MapProperties(IContentBase content, List properties) + protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) { var result = Mapper.Map, IEnumerable>( // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.