using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security { public class MemberManager : UmbracoUserManager, IMemberManager { private readonly IMemberUserStore _store; private readonly IPublicAccessService _publicAccessService; private readonly IHttpContextAccessor _httpContextAccessor; private MemberIdentityUser _currentMember; public MemberManager( IIpResolver ipResolver, IMemberUserStore store, IOptions optionsAccessor, IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger, IOptions passwordConfiguration, IPublicAccessService publicAccessService, IHttpContextAccessor httpContextAccessor) : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration) { _store = store; _publicAccessService = publicAccessService; _httpContextAccessor = httpContextAccessor; } /// public override bool SupportsUserTwoFactor => true; /// public async Task IsMemberAuthorizedAsync(IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { if (allowTypes == null) { allowTypes = Enumerable.Empty(); } if (allowGroups == null) { allowGroups = Enumerable.Empty(); } if (allowMembers == null) { allowMembers = Enumerable.Empty(); } // Allow by default var allowAction = true; if (IsLoggedIn() == false) { // If not logged on, not allowed allowAction = false; } else { MemberIdentityUser currentMember = await GetCurrentMemberAsync(); // If a member could not be resolved from the provider, we are clearly not authorized and can break right here if (currentMember == null) { return false; } int memberId = int.Parse(currentMember.Id, CultureInfo.InvariantCulture); // If types defined, check member is of one of those types IList allowTypesList = allowTypes as IList ?? allowTypes.ToList(); if (allowTypesList.Any(allowType => allowType != string.Empty)) { // Allow only if member's type is in list allowAction = allowTypesList.Select(x => x.ToLowerInvariant()).Contains(currentMember.MemberTypeAlias.ToLowerInvariant()); } // If specific members defined, check member is of one of those var allowMembersList = allowMembers.ToList(); if (allowAction && allowMembersList.Any()) { // Allow only if member's Id is in the list allowAction = allowMembersList.Contains(memberId); } // If groups defined, check member is of one of those groups IList allowGroupsList = allowGroups as IList ?? allowGroups.ToList(); if (allowAction && allowGroupsList.Any(allowGroup => allowGroup != string.Empty)) { // Allow only if member is assigned to a group in the list IList groups = await GetRolesAsync(currentMember); allowAction = allowGroupsList.Select(s => s.ToLowerInvariant()).Intersect(groups.Select(myGroup => myGroup.ToLowerInvariant())).Any(); } } return allowAction; } /// public bool IsLoggedIn() { HttpContext httpContext = _httpContextAccessor.HttpContext; return httpContext?.User.Identity?.IsAuthenticated ?? false; } /// public async Task MemberHasAccessAsync(string path) { if (await IsProtectedAsync(path)) { return await HasAccessAsync(path); } return true; } /// public async Task> MemberHasAccessAsync(IEnumerable paths) { IReadOnlyDictionary protectedPaths = await IsProtectedAsync(paths); IEnumerable pathsWithProtection = protectedPaths.Where(x => x.Value).Select(x => x.Key); IReadOnlyDictionary pathsWithAccess = await HasAccessAsync(pathsWithProtection); var result = new Dictionary(); foreach (var path in paths) { pathsWithAccess.TryGetValue(path, out var hasAccess); // if it's not found it's false anyways result[path] = !pathsWithProtection.Contains(path) || hasAccess; } return result; } /// /// /// this is a cached call /// public Task IsProtectedAsync(string path) => Task.FromResult(_publicAccessService.IsProtected(path).Success); /// public Task> IsProtectedAsync(IEnumerable paths) { var result = new Dictionary(); foreach (var path in paths) { //this is a cached call result[path] = _publicAccessService.IsProtected(path); } return Task.FromResult((IReadOnlyDictionary)result); } /// public async Task GetCurrentMemberAsync() { if (_currentMember == null) { if (!IsLoggedIn()) { return null; } _currentMember = await GetUserAsync(_httpContextAccessor.HttpContext.User); } return _currentMember; } /// /// This will check if the member has access to this path /// /// /// /// private async Task HasAccessAsync(string path) { MemberIdentityUser currentMember = await GetCurrentMemberAsync(); if (currentMember == null || !currentMember.IsApproved || currentMember.IsLockedOut) { return false; } return await _publicAccessService.HasAccessAsync( path, currentMember.UserName, async () => await GetRolesAsync(currentMember)); } private async Task> HasAccessAsync(IEnumerable paths) { var result = new Dictionary(); MemberIdentityUser currentMember = await GetCurrentMemberAsync(); if (currentMember == null || !currentMember.IsApproved || currentMember.IsLockedOut) { return result; } // ensure we only lookup user roles once IList userRoles = null; async Task> getUserRolesAsync() { if (userRoles != null) { return userRoles; } userRoles = await GetRolesAsync(currentMember); return userRoles; } foreach (var path in paths) { result[path] = await _publicAccessService.HasAccessAsync( path, currentMember.UserName, async () => await getUserRolesAsync()); } return result; } public IPublishedContent AsPublishedMember(MemberIdentityUser user) => _store.GetPublishedMember(user); } }