Merge pull request #2119 from umbraco/temp-U4-10274

U4-10274 Umbraco.MemberHasAccess isn't cached
This commit is contained in:
Sebastiaan Janssen
2017-09-01 15:45:55 +02:00
committed by GitHub
10 changed files with 120 additions and 56 deletions

View File

@@ -184,30 +184,16 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable<IMemberGroup> GetMemberGroupsForMember(string username)
{
//find the member by username
var memberSql = new Sql();
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
var sql = new Sql()
.Select("un.*")
.From("umbracoNode AS un")
.InnerJoin("cmsMember2MemberGroup")
.On("un.id = cmsMember2MemberGroup.MemberGroup")
.LeftJoin("(SELECT umbracoNode.id, cmsMember.LoginName FROM umbracoNode INNER JOIN cmsMember ON umbracoNode.id = cmsMember.nodeId) AS member")
.On("member.id = cmsMember2MemberGroup.Member")
.Where("un.nodeObjectType=@objectType", new {objectType = NodeObjectTypeId })
.Where("member.LoginName=@loginName", new {loginName = username});
memberSql.Select("umbracoNode.id")
.From<NodeDto>()
.InnerJoin<MemberDto>()
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
.Where<MemberDto>(x => x.LoginName == username);
var memberIdUsername = Database.Fetch<int?>(memberSql).FirstOrDefault();
if (memberIdUsername.HasValue == false)
{
return Enumerable.Empty<IMemberGroup>();
}
var sql = new Sql();
sql.Select("umbracoNode.*")
.From<NodeDto>()
.InnerJoin<Member2MemberGroupDto>()
.On<NodeDto, Member2MemberGroupDto>(dto => dto.NodeId, dto => dto.MemberGroup)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<Member2MemberGroupDto>(x => x.Member == memberIdUsername.Value);
return Database.Fetch<NodeDto>(sql)
.DistinctBy(dto => dto.NodeId)
.Select(x => _modelFactory.BuildEntity(x));

View File

@@ -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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="publicAccessService"></param>
/// <param name="path"></param>
/// <param name="username"></param>
/// <param name="rolesCallback">A callback to retrieve the roles for this member</param>
/// <returns></returns>
public static bool HasAccess(this IPublicAccessService publicAccessService, string path, string username, Func<string, IEnumerable<string>> 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));
}

View File

@@ -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)
{

View File

@@ -32,9 +32,9 @@ namespace Umbraco.Web.Models
private ProfileModel(bool doLookup)
{
MemberProperties = new List<UmbracoProperty>();
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;
}

View File

@@ -32,9 +32,9 @@ namespace Umbraco.Web.Models
MemberProperties = new List<UmbracoProperty>();
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;
}

View File

@@ -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.
/// </summary>
public RoutingContext RoutingContext { get; private set; }
/// <summary>
/// Returns the current members roles if a member is logged in
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
/// <remarks>
/// This ensures that the callback is only executed once in case this method is accessed a few times
/// </remarks>
public IEnumerable<string> GetRolesForLogin(string username)
{
string[] roles;
if (_rolesForLogin.TryGetValue(username, out roles))
return roles;
roles = _getRolesForLoginCallback(username).ToArray();
_rolesForLogin[username] = roles;
return roles;
}
internal Func<string, IEnumerable<string>> GetRolesForLogin { get; private set; }
private readonly IDictionary<string, string[]> _rolesForLogin = new Dictionary<string, string[]>();
private readonly Func<string, IEnumerable<string>> _getRolesForLoginCallback;
/// <summary>
/// The "umbraco page" object.

View File

@@ -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
/// <summary>
/// Check if a document object is protected by the "Protect Pages" functionality in umbraco
/// </summary>
/// <param name="path">The full path of the document object to check</param>
/// <returns>True if the document object is protected</returns>
public virtual bool IsProtected(string path)
{
//this is a cached call
return _applicationContext.Services.PublicAccessService.IsProtected(path);
}
/// <summary>
/// Check if the current user has access to a document
/// </summary>
/// <param name="path">The full path of the document object to check</param>
/// <returns>True if the current user has access or if the current document isn't protected</returns>
public virtual bool MemberHasAccess(string path)
{
//cache this in the request cache
return _applicationContext.ApplicationCache.RequestCache.GetCacheItem<bool>(string.Format("{0}.{1}-{2}", typeof(MembershipHelper), "MemberHasAccess", path), () =>
{
if (IsProtected(path))
{
return IsLoggedIn() && HasAccess(path, Roles.Provider);
}
return true;
});
}
/// <summary>
/// This will check if the member has access to this path
/// </summary>
/// <param name="path"></param>
/// <param name="roleProvider"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
/// <summary>
/// Returns true if the current membership provider is the Umbraco built-in one.
/// </summary>

View File

@@ -48,11 +48,11 @@ namespace Umbraco.Web.Security
IEnumerable<string> allowGroups = null,
IEnumerable<int> 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);
}

View File

@@ -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
/// <returns>True if the document object is protected</returns>
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
/// <returns>True if the current user has access or if the current document isn't protected</returns>
public bool MemberHasAccess(string path)
{
if (IsProtected(path))
{
return MembershipHelper.IsLoggedIn()
&& UmbracoContext.Application.Services.PublicAccessService.HasAccess(path, GetCurrentMember(), Roles.Provider);
}
return true;
}
/// <summary>
/// Gets (or adds) the current member from the current request cache
/// </summary>
private MembershipUser GetCurrentMember()
{
return UmbracoContext.Application.ApplicationCache.RequestCache
.GetCacheItem<MembershipUser>("UmbracoHelper.GetCurrentMember", () =>
{
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
return provider.GetCurrentUser();
});
return MembershipHelper.MemberHasAccess(path);
}
/// <summary>

View File

@@ -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(),