diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs index 0b682b2b6e..d159e3ae64 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -72,10 +72,28 @@ namespace Umbraco.Core.Services public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider) { + return publicAccessService.HasAccess(path, member.UserName, roleProvider.GetRolesForUser); + } + + /// + /// Checks if the member with the specified username has access to the path which is also based on the passed in roles for the member + /// + /// + /// + /// + /// A callback to retrieve the roles for this member + /// + public static bool HasAccess(this IPublicAccessService publicAccessService, string path, string username, Func> rolesCallback) + { + if (rolesCallback == null) throw new ArgumentNullException("roles"); + if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); + var entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); if (entry == null) return true; - var roles = roleProvider.GetRolesForUser(member.UserName); + var roles = rolesCallback(username); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue)); } diff --git a/src/Umbraco.Web/Models/LoginStatusModel.cs b/src/Umbraco.Web/Models/LoginStatusModel.cs index e10b42b096..23fb2039e9 100644 --- a/src/Umbraco.Web/Models/LoginStatusModel.cs +++ b/src/Umbraco.Web/Models/LoginStatusModel.cs @@ -23,9 +23,9 @@ namespace Umbraco.Web.Models private LoginStatusModel(bool doLookup) { - if (doLookup && HttpContext.Current != null && ApplicationContext.Current != null) + if (doLookup && UmbracoContext.Current != null) { - var helper = new MembershipHelper(ApplicationContext.Current, new HttpContextWrapper(HttpContext.Current)); + var helper = new MembershipHelper(UmbracoContext.Current); var model = helper.GetCurrentLoginStatus(); if (model != null) { diff --git a/src/Umbraco.Web/Models/ProfileModel.cs b/src/Umbraco.Web/Models/ProfileModel.cs index 118f9a9f7a..39043a4b11 100644 --- a/src/Umbraco.Web/Models/ProfileModel.cs +++ b/src/Umbraco.Web/Models/ProfileModel.cs @@ -32,9 +32,9 @@ namespace Umbraco.Web.Models private ProfileModel(bool doLookup) { MemberProperties = new List(); - if (doLookup) + if (doLookup && UmbracoContext.Current != null) { - var helper = new MembershipHelper(ApplicationContext.Current, new HttpContextWrapper(HttpContext.Current)); + var helper = new MembershipHelper(UmbracoContext.Current); var model = helper.GetCurrentMemberProfileModel(); MemberProperties = model.MemberProperties; } diff --git a/src/Umbraco.Web/Models/RegisterModel.cs b/src/Umbraco.Web/Models/RegisterModel.cs index 237f6d7845..b51f09b631 100644 --- a/src/Umbraco.Web/Models/RegisterModel.cs +++ b/src/Umbraco.Web/Models/RegisterModel.cs @@ -32,9 +32,9 @@ namespace Umbraco.Web.Models MemberProperties = new List(); LoginOnSuccess = true; CreatePersistentLoginCookie = true; - if (doLookup && HttpContext.Current != null && ApplicationContext.Current != null) + if (doLookup && UmbracoContext.Current != null) { - var helper = new MembershipHelper(ApplicationContext.Current, new HttpContextWrapper(HttpContext.Current)); + var helper = new MembershipHelper(UmbracoContext.Current); var model = helper.CreateRegistrationModel(MemberTypeAlias); MemberProperties = model.MemberProperties; } diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 9c21958603..641821ddd6 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Linq; using System.Web; using System.Web.Security; using Umbraco.Core; @@ -63,7 +64,7 @@ namespace Umbraco.Web.Routing Uri = uri; RoutingContext = routingContext; - GetRolesForLogin = getRolesForLogin; + _getRolesForLoginCallback = getRolesForLogin; _engine = new PublishedContentRequestEngine( routingConfig, @@ -446,8 +447,28 @@ namespace Umbraco.Web.Routing /// Gets or sets the current RoutingContext. /// public RoutingContext RoutingContext { get; private set; } + + /// + /// Returns the current members roles if a member is logged in + /// + /// + /// + /// + /// This ensures that the callback is only executed once in case this method is accessed a few times + /// + public IEnumerable GetRolesForLogin(string username) + { + string[] roles; + if (_rolesForLogin.TryGetValue(username, out roles)) + return roles; + + roles = _getRolesForLoginCallback(username).ToArray(); + _rolesForLogin[username] = roles; + return roles; + } - internal Func> GetRolesForLogin { get; private set; } + private readonly IDictionary _rolesForLogin = new Dictionary(); + private readonly Func> _getRolesForLoginCallback; /// /// The "umbraco page" object. diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 605b5137d8..e43adff8b4 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; @@ -14,6 +15,7 @@ using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; using Umbraco.Core.Cache; using Umbraco.Web.Security.Providers; +using Umbraco.Core.Services; using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions; namespace Umbraco.Web.Security @@ -28,13 +30,19 @@ namespace Umbraco.Web.Security private readonly RoleProvider _roleProvider; private readonly ApplicationContext _applicationContext; private readonly HttpContextBase _httpContext; + private readonly UmbracoContext _umbracoContext; #region Constructors + + [Obsolete("Use the constructor specifying an UmbracoContext")] + [EditorBrowsable(EditorBrowsableState.Never)] public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext) : this(applicationContext, httpContext, MPE.GetMembersMembershipProvider(), Roles.Enabled ? Roles.Provider : new MembersRoleProvider(applicationContext.Services.MemberService)) { } + [Obsolete("Use the constructor specifying an UmbracoContext")] + [EditorBrowsable(EditorBrowsableState.Never)] public MembershipHelper(ApplicationContext applicationContext, HttpContextBase httpContext, MembershipProvider membershipProvider, RoleProvider roleProvider) { if (applicationContext == null) throw new ArgumentNullException("applicationContext"); @@ -61,9 +69,58 @@ namespace Umbraco.Web.Security _applicationContext = umbracoContext.Application; _membershipProvider = membershipProvider; _roleProvider = roleProvider; + _umbracoContext = umbracoContext; } #endregion + /// + /// Check if a document object is protected by the "Protect Pages" functionality in umbraco + /// + /// The full path of the document object to check + /// True if the document object is protected + public virtual bool IsProtected(string path) + { + //this is a cached call + return _applicationContext.Services.PublicAccessService.IsProtected(path); + } + + /// + /// Check if the current user has access to a document + /// + /// The full path of the document object to check + /// True if the current user has access or if the current document isn't protected + public virtual bool MemberHasAccess(string path) + { + //cache this in the request cache + return _applicationContext.ApplicationCache.RequestCache.GetCacheItem(string.Format("{0}.{1}-{2}", typeof(MembershipHelper), "MemberHasAccess", path), () => + { + if (IsProtected(path)) + { + return IsLoggedIn() && HasAccess(path, Roles.Provider); + } + return true; + }); + } + + /// + /// This will check if the member has access to this path + /// + /// + /// + /// + /// + /// This is essentially the same as the PublicAccessServiceExtensions.HasAccess however this will use the PCR cache + /// of the already looked up roles for the member so this doesn't need to happen more than once. + /// This does a safety check in case of things like unit tests where there is no PCR and if that is the case it will use + /// lookup the roles directly. + /// + private bool HasAccess(string path, RoleProvider roleProvider) + { + return _umbracoContext.PublishedContentRequest == null + ? _applicationContext.Services.PublicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser) + : _applicationContext.Services.PublicAccessService.HasAccess(path, CurrentUserName, _umbracoContext.PublishedContentRequest.GetRolesForLogin); + } + /// /// Returns true if the current membership provider is the Umbraco built-in one. /// diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index cda9f04fad..b12b6ccde0 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -48,11 +48,11 @@ namespace Umbraco.Web.Security IEnumerable allowGroups = null, IEnumerable allowMembers = null) { - if (HttpContext.Current == null || ApplicationContext.Current == null) + if (UmbracoContext.Current == null) { return false; } - var helper = new MembershipHelper(ApplicationContext.Current, new HttpContextWrapper(HttpContext.Current)); + var helper = new MembershipHelper(UmbracoContext.Current); return helper.IsMemberAuthorized(allowAll, allowTypes, allowGroups, allowMembers); } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 6f3da17254..28960eb07d 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -17,7 +17,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Mvc; -using Umbraco.Core.Cache; +using Umbraco.Core.Cache; namespace Umbraco.Web { @@ -433,7 +433,7 @@ namespace Umbraco.Web /// True if the document object is protected public bool IsProtected(string path) { - return UmbracoContext.Application.Services.PublicAccessService.IsProtected(path); + return MembershipHelper.IsProtected(path); } [EditorBrowsable(EditorBrowsableState.Never)] @@ -450,25 +450,7 @@ namespace Umbraco.Web /// True if the current user has access or if the current document isn't protected public bool MemberHasAccess(string path) { - if (IsProtected(path)) - { - return MembershipHelper.IsLoggedIn() - && UmbracoContext.Application.Services.PublicAccessService.HasAccess(path, GetCurrentMember(), Roles.Provider); - } - return true; - } - - /// - /// Gets (or adds) the current member from the current request cache - /// - private MembershipUser GetCurrentMember() - { - return UmbracoContext.Application.ApplicationCache.RequestCache - .GetCacheItem("UmbracoHelper.GetCurrentMember", () => - { - var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - return provider.GetCurrentUser(); - }); + return MembershipHelper.MemberHasAccess(path); } /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index 91a8677c81..3a0dc0d274 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -615,7 +615,7 @@ namespace umbraco.cms.presentation.user // update when the AD provider is active. if ((BackOfficeProvider is ActiveDirectoryMembershipProvider) == false) { - var membershipHelper = new MembershipHelper(ApplicationContext, new HttpContextWrapper(Context)); + var membershipHelper = new MembershipHelper(UmbracoContext.Current); //set the writable properties that we are editing membershipHelper.UpdateMember(membershipUser, BackOfficeProvider, email.Text.Trim(),