using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache;
using Umbraco.Core.Cache;
using Umbraco.Web.Composing;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Editors;
using Umbraco.Web.Security.Providers;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Web.Security
{
///
/// A helper class for handling Members
///
public class MembershipHelper
{
private readonly MembersMembershipProvider _membershipProvider;
private readonly RoleProvider _roleProvider;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMemberService _memberService;
private readonly IMemberTypeService _memberTypeService;
private readonly IPublicAccessService _publicAccessService;
private readonly AppCaches _appCaches;
private readonly ILogger _logger;
private readonly IShortStringHelper _shortStringHelper;
private readonly IEntityService _entityService;
#region Constructors
public MembershipHelper
(
IHttpContextAccessor httpContextAccessor,
IPublishedMemberCache memberCache,
MembersMembershipProvider membershipProvider,
RoleProvider roleProvider,
IMemberService memberService,
IMemberTypeService memberTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
ILogger logger,
IShortStringHelper shortStringHelper,
IEntityService entityService
)
{
MemberCache = memberCache;
_httpContextAccessor = httpContextAccessor;
_memberService = memberService;
_memberTypeService = memberTypeService;
_publicAccessService = publicAccessService;
_appCaches = appCaches;
_logger = logger;
_shortStringHelper = shortStringHelper;
_membershipProvider = membershipProvider ?? throw new ArgumentNullException(nameof(membershipProvider));
_roleProvider = roleProvider ?? throw new ArgumentNullException(nameof(roleProvider));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
#endregion
protected IPublishedMemberCache MemberCache { get; }
///
/// 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 _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 _appCaches.RequestCache.GetCacheItem($"{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
///
///
///
///
private bool HasAccess(string path, RoleProvider roleProvider)
{
return _publicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser);
}
///
/// Updates the currently logged in members profile
///
///
///
/// The updated MembershipUser object
///
public virtual Attempt UpdateMemberProfile(ProfileModel model)
{
if (IsLoggedIn() == false)
{
throw new NotSupportedException("No member is currently logged in");
}
//get the current membership user
var provider = _membershipProvider;
var membershipUser = provider.GetCurrentUser();
//NOTE: This should never happen since they are logged in
if (membershipUser == null) throw new InvalidOperationException("Could not find member with username " + _httpContextAccessor.GetRequiredHttpContext().User.Identity.Name);
try
{
//check if the email needs to change
if (model.Email.InvariantEquals(membershipUser.Email) == false)
{
//Use the membership provider to change the email since that is configured to do the checks to check for unique emails if that is configured.
var requiresUpdating = UpdateMember(membershipUser, provider, model.Email);
membershipUser = requiresUpdating.Result;
}
}
catch (Exception ex)
{
//This will occur if an email already exists!
return Attempt.Fail(ex);
}
var member = GetCurrentPersistedMember();
//NOTE: If changing the username is a requirement, than that needs to be done via the IMember directly since MembershipProvider's natively do
// not support changing a username!
if (model.Name != null && member.Name != model.Name)
{
member.Name = model.Name;
}
var memberType = _memberTypeService.Get(member.ContentTypeId);
if (model.MemberProperties != null)
{
foreach (var property in model.MemberProperties
//ensure the property they are posting exists
.Where(p => memberType.PropertyTypeExists(p.Alias))
.Where(property => member.Properties.Contains(property.Alias))
//needs to be editable
.Where(p => memberType.MemberCanEditProperty(p.Alias)))
{
member.Properties[property.Alias].SetValue(property.Value);
}
}
_memberService.Save(member);
//reset the FormsAuth cookie since the username might have changed
FormsAuthentication.SetAuthCookie(member.Username, true);
return Attempt.Succeed(membershipUser);
}
///
/// Registers a new member
///
///
///
///
/// true to log the member in upon successful registration
///
///
public virtual MembershipUser RegisterMember(RegisterModel model, out MembershipCreateStatus status, bool logMemberIn = true)
{
model.Username = (model.UsernameIsEmail || model.Username == null) ? model.Email : model.Username;
MembershipUser membershipUser;
var provider = _membershipProvider;
membershipUser = ((UmbracoMembershipProviderBase)provider).CreateUser(
model.MemberTypeAlias,
model.Username, model.Password, model.Email,
// TODO: Support q/a http://issues.umbraco.org/issue/U4-3213
null, null,
true, null, out status);
if (status != MembershipCreateStatus.Success) return null;
var member = _memberService.GetByUsername(membershipUser.UserName);
member.Name = model.Name;
if (model.MemberProperties != null)
{
foreach (var property in model.MemberProperties.Where(p => p.Value != null)
.Where(property => member.Properties.Contains(property.Alias)))
{
member.Properties[property.Alias].SetValue(property.Value);
}
}
_memberService.Save(member);
if (logMemberIn)
{
//Set member online
provider.GetUser(model.Username, true);
//Log them in
FormsAuthentication.SetAuthCookie(membershipUser.UserName, model.CreatePersistentLoginCookie);
}
return membershipUser;
}
///
/// A helper method to perform the validation and logging in of a member - this is simply wrapping standard membership provider and asp.net forms auth logic.
///
///
///
///
public virtual bool Login(string username, string password)
{
var provider = _membershipProvider;
//Validate credentials
if (provider.ValidateUser(username, password) == false)
{
return false;
}
//Set member online
var member = provider.GetUser(username, true);
if (member == null)
{
//this should not happen
Current.Logger.Warn("The member validated but then no member was returned with the username {Username}", username);
return false;
}
//Log them in
FormsAuthentication.SetAuthCookie(member.UserName, true);
return true;
}
///
/// Logs out the current member
///
public virtual void Logout()
{
FormsAuthentication.SignOut();
}
#region Querying for front-end
public virtual IPublishedContent GetByProviderKey(object key)
{
return MemberCache.GetByProviderKey(key);
}
public virtual IEnumerable GetByProviderKeys(IEnumerable