Files
Umbraco-CMS/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs

1247 lines
56 KiB
C#
Raw Normal View History

Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
using System;
2017-12-28 09:18:09 +01:00
using System.Collections.Generic;
using System.Linq;
2020-09-18 15:27:38 +02:00
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Notifications;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Extensions;
2017-12-28 09:18:09 +01:00
namespace Umbraco.Cms.Core.Services.Implement
2017-12-28 09:18:09 +01:00
{
/// <summary>
/// Represents the MemberService.
/// </summary>
public class MemberService : RepositoryService, IMemberService
2017-12-28 09:18:09 +01:00
{
private readonly IMemberRepository _memberRepository;
private readonly IMemberTypeRepository _memberTypeRepository;
private readonly IMemberGroupRepository _memberGroupRepository;
private readonly IAuditRepository _auditRepository;
private readonly IMemberGroupService _memberGroupService;
#region Constructor
2020-09-18 15:27:38 +02:00
public MemberService(IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService,
2017-12-28 09:18:09 +01:00
IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository)
2020-09-18 15:27:38 +02:00
: base(provider, loggerFactory, eventMessagesFactory)
2017-12-28 09:18:09 +01:00
{
_memberRepository = memberRepository;
_memberTypeRepository = memberTypeRepository;
_memberGroupRepository = memberGroupRepository;
_auditRepository = auditRepository;
_memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
}
#endregion
#region Count
/// <summary>
/// Gets the total number of Members based on the count type
/// </summary>
/// <remarks>
/// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
/// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
/// but that is how MS have made theirs so we'll follow that principal.
/// </remarks>
/// <param name="countType"><see cref="MemberCountType"/> to count by</param>
/// <returns><see cref="System.int"/> with number of Members for passed in type</returns>
public int GetCount(MemberCountType countType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
IQuery<IMember> query;
switch (countType)
{
case MemberCountType.All:
query = Query<IMember>();
2019-12-18 10:32:22 +01:00
break;
2017-12-28 09:18:09 +01:00
case MemberCountType.LockedOut:
query = Query<IMember>().Where(x => x.PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && ((Member) x).BoolPropertyValue);
2017-12-28 09:18:09 +01:00
break;
case MemberCountType.Approved:
query = Query<IMember>().Where(x => x.PropertyTypeAlias == Constants.Conventions.Member.IsApproved && ((Member) x).BoolPropertyValue);
2017-12-28 09:18:09 +01:00
break;
default:
throw new ArgumentOutOfRangeException(nameof(countType));
}
return _memberRepository.GetCountByQuery(query);
}
}
/// <summary>
/// Gets the count of Members by an optional MemberType alias
/// </summary>
/// <remarks>If no alias is supplied then the count for all Member will be returned</remarks>
/// <param name="memberTypeAlias">Optional alias for the MemberType when counting number of Members</param>
/// <returns><see cref="System.int"/> with number of Members</returns>
public int Count(string memberTypeAlias = null)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.Count(memberTypeAlias);
}
}
#endregion
#region Create
/// <summary>
/// Creates an <see cref="IMember"/> object without persisting it
/// </summary>
/// <remarks>This method is convenient for when you need to add properties to a new Member
/// before persisting it in order to limit the amount of times its saved.
/// Also note that the returned <see cref="IMember"/> will not have an Id until its saved.</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="name">Name of the Member to create</param>
/// <param name="memberTypeAlias">Alias of the MemberType the Member should be based on</param>
/// <exception cref="ArgumentException">Thrown when a member type for the given alias isn't found</exception>
2017-12-28 09:18:09 +01:00
/// <returns><see cref="IMember"/></returns>
public IMember CreateMember(string username, string email, string name, string memberTypeAlias)
{
IMemberType memberType = GetMemberType(memberTypeAlias);
2017-12-28 09:18:09 +01:00
if (memberType == null)
{
2017-12-28 09:18:09 +01:00
throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias));
}
2017-12-28 09:18:09 +01:00
var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
2017-12-28 09:18:09 +01:00
return member;
}
/// <summary>
/// Creates an <see cref="IMember"/> object without persisting it
/// </summary>
/// <remarks>This method is convenient for when you need to add properties to a new Member
/// before persisting it in order to limit the amount of times its saved.
/// Also note that the returned <see cref="IMember"/> will not have an Id until its saved.</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="name">Name of the Member to create</param>
/// <param name="memberType">MemberType the Member should be based on</param>
/// <returns><see cref="IMember"/></returns>
public IMember CreateMember(string username, string email, string name, IMemberType memberType)
{
if (memberType == null) throw new ArgumentNullException(nameof(memberType));
var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
2017-12-28 09:18:09 +01:00
return member;
}
/// <summary>
/// Creates and persists a new <see cref="IMember"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
/// <param name="isApproved">Is the member approved</param>
/// <returns><see cref="IMember"/></returns>
IMember IMembershipMemberService<IMember>.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias)
{
return CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias);
}
/// <summary>
/// Creates and persists a new <see cref="IMember"/>
/// </summary>
/// <remarks>An <see cref="IMembershipUser"/> can be of type <see cref="IMember"/> or <see cref="IUser"/></remarks>
/// <param name="username">Username of the <see cref="IMembershipUser"/> to create</param>
/// <param name="email">Email of the <see cref="IMembershipUser"/> to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberTypeAlias">Alias of the Type</param>
2018-05-03 14:58:34 +02:00
/// <param name="isApproved"></param>
2017-12-28 09:18:09 +01:00
/// <returns><see cref="IMember"/></returns>
2018-05-03 14:58:34 +02:00
IMember IMembershipMemberService<IMember>.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved)
2017-12-28 09:18:09 +01:00
{
return CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved);
}
public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias)
{
return CreateMemberWithIdentity(username, email, username, "", memberTypeAlias);
}
public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved)
{
return CreateMemberWithIdentity(username, email, username, "", memberTypeAlias, isApproved);
}
public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias)
{
return CreateMemberWithIdentity(username, email, name, "", memberTypeAlias);
}
public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved)
{
return CreateMemberWithIdentity(username, email, name, "", memberTypeAlias, isApproved);
}
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="name">Name of the Member to create</param>
/// <param name="memberTypeAlias">Alias of the MemberType the Member should be based on</param>
/// <param name="isApproved">Optional IsApproved of the Member to create</param>
/// <returns><see cref="IMember"/></returns>
public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true)
{
using (var scope = ScopeProvider.CreateScope())
{
// locking the member tree secures member types too
scope.WriteLock(Constants.Locks.MemberTree);
var memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks
if (memberType == null)
throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback // causes rollback
var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
2021-02-25 08:08:02 +01:00
Save(member);
2017-12-28 09:18:09 +01:00
return member;
}
}
public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, username, "", memberType);
}
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="memberType">MemberType the Member should be based on</param>
/// <returns><see cref="IMember"/></returns>
public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved)
{
return CreateMemberWithIdentity(username, email, username, "", memberType, isApproved);
}
public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, name, "", memberType);
}
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="name">Name of the Member to create</param>
/// <param name="memberType">MemberType the Member should be based on</param>
/// <returns><see cref="IMember"/></returns>
public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved)
{
return CreateMemberWithIdentity(username, email, name, "", memberType, isApproved);
}
/// <summary>
/// Creates and persists a Member
/// </summary>
/// <remarks>Using this method will persist the Member object before its returned
/// meaning that it will have an Id available (unlike the CreateMember method)</remarks>
/// <param name="username">Username of the Member to create</param>
/// <param name="email">Email of the Member to create</param>
/// <param name="name">Name of the Member to create</param>
/// <param name="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
/// <param name="memberType">MemberType the Member should be based on</param>
/// <returns><see cref="IMember"/></returns>
private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true)
{
if (memberType == null) throw new ArgumentNullException(nameof(memberType));
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
// ensure it all still make sense
// ensure it all still make sense
var vrfy = GetMemberType(scope, memberType.Alias); // + locks
if (vrfy == null || vrfy.Id != memberType.Id)
throw new ArgumentException($"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback
var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
2017-12-28 09:18:09 +01:00
2021-02-25 08:08:02 +01:00
Save(member);
return member;
}
2017-12-28 09:18:09 +01:00
}
#endregion
#region Get, Has, Is, Exists...
/// <summary>
/// Gets a Member by its integer id
/// </summary>
/// <param name="id"><see cref="System.int"/> Id</param>
/// <returns><see cref="IMember"/></returns>
public IMember GetById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.Get(id);
}
}
/// <summary>
/// Gets a Member by the unique key
/// </summary>
/// <remarks>The guid key corresponds to the unique id in the database
/// and the user id in the membership provider.</remarks>
/// <param name="id"><see cref="Guid"/> Id</param>
/// <returns><see cref="IMember"/></returns>
public IMember GetByKey(Guid id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>().Where(x => x.Key == id);
return _memberRepository.Get(query).FirstOrDefault();
}
}
/// <summary>
/// Gets a list of paged <see cref="IMember"/> objects
/// </summary>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetAll(long pageIndex, int pageSize, out long totalRecords)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName"));
2017-12-28 09:18:09 +01:00
}
}
public IEnumerable<IMember> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "")
2017-12-28 09:18:09 +01:00
{
return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter);
}
public IEnumerable<IMember> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter)
2017-12-28 09:18:09 +01:00
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query1 = memberTypeAlias == null ? null : Query<IMember>().Where(x => x.ContentTypeAlias == memberTypeAlias);
2018-03-22 17:41:13 +01:00
var query2 = filter == null ? null : Query<IMember>().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter) || x.Email.Contains(filter));
return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField));
2017-12-28 09:18:09 +01:00
}
}
/// <summary>
/// Gets an <see cref="IMember"/> by its provider key
/// </summary>
/// <param name="id">Id to use for retrieval</param>
/// <returns><see cref="IMember"/></returns>
public IMember GetByProviderKey(object id)
{
var asGuid = id.TryConvertTo<Guid>();
if (asGuid.Success)
return GetByKey(asGuid.Result);
var asInt = id.TryConvertTo<int>();
if (asInt.Success)
return GetById(asInt.Result);
return null;
}
/// <summary>
/// Get an <see cref="IMember"/> by email
/// </summary>
/// <param name="email">Email to use for retrieval</param>
/// <returns><see cref="IMember"/></returns>
public IMember GetByEmail(string email)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>().Where(x => x.Email.Equals(email));
return _memberRepository.Get(query).FirstOrDefault();
}
}
/// <summary>
/// Get an <see cref="IMember"/> by username
/// </summary>
/// <param name="username">Username to use for retrieval</param>
/// <returns><see cref="IMember"/></returns>
public IMember GetByUsername(string username)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
2021-02-25 08:08:02 +01:00
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.GetByUsername(username);
2017-12-28 09:18:09 +01:00
}
}
/// <summary>
/// Gets all Members for the specified MemberType alias
/// </summary>
/// <param name="memberTypeAlias">Alias of the MemberType</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByMemberType(string memberTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>().Where(x => x.ContentTypeAlias == memberTypeAlias);
return _memberRepository.Get(query);
}
}
/// <summary>
/// Gets all Members for the MemberType id
/// </summary>
/// <param name="memberTypeId">Id of the MemberType</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByMemberType(int memberTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
return _memberRepository.Get(query);
}
}
/// <summary>
/// Gets all Members within the specified MemberGroup name
/// </summary>
/// <param name="memberGroupName">Name of the MemberGroup</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByGroup(string memberGroupName)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.GetByMemberGroup(memberGroupName);
}
}
/// <summary>
/// Gets all Members with the ids specified
/// </summary>
/// <remarks>If no Ids are specified all Members will be retrieved</remarks>
/// <param name="ids">Optional list of Member Ids</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetAllMembers(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.GetMany(ids);
}
}
/// <summary>
/// Finds Members based on their display name
/// </summary>
/// <param name="displayNameToMatch">Display name to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>();
switch (matchType)
{
case StringPropertyMatchType.Exact:
query.Where(member => member.Name.Equals(displayNameToMatch));
break;
case StringPropertyMatchType.Contains:
query.Where(member => member.Name.Contains(displayNameToMatch));
break;
case StringPropertyMatchType.StartsWith:
query.Where(member => member.Name.StartsWith(displayNameToMatch));
break;
case StringPropertyMatchType.EndsWith:
query.Where(member => member.Name.EndsWith(displayNameToMatch));
break;
case StringPropertyMatchType.Wildcard:
query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar));
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
}
return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name"));
2017-12-28 09:18:09 +01:00
}
}
2017-12-28 09:18:09 +01:00
/// <summary>
/// Finds a list of <see cref="IMember"/> objects by a partial email string
/// </summary>
/// <param name="emailStringToMatch">Partial email string to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>();
switch (matchType)
{
case StringPropertyMatchType.Exact:
query.Where(member => member.Email.Equals(emailStringToMatch));
break;
case StringPropertyMatchType.Contains:
query.Where(member => member.Email.Contains(emailStringToMatch));
break;
case StringPropertyMatchType.StartsWith:
query.Where(member => member.Email.StartsWith(emailStringToMatch));
break;
case StringPropertyMatchType.EndsWith:
query.Where(member => member.Email.EndsWith(emailStringToMatch));
break;
case StringPropertyMatchType.Wildcard:
query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar));
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType));
}
return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email"));
2017-12-28 09:18:09 +01:00
}
}
/// <summary>
/// Finds a list of <see cref="IMember"/> objects by a partial username
/// </summary>
/// <param name="login">Partial username to match</param>
/// <param name="pageIndex">Current page index</param>
/// <param name="pageSize">Size of the page</param>
/// <param name="totalRecords">Total number of records found (out)</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.StartsWith"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>();
switch (matchType)
{
case StringPropertyMatchType.Exact:
query.Where(member => member.Username.Equals(login));
break;
case StringPropertyMatchType.Contains:
query.Where(member => member.Username.Contains(login));
break;
case StringPropertyMatchType.StartsWith:
query.Where(member => member.Username.StartsWith(login));
break;
case StringPropertyMatchType.EndsWith:
query.Where(member => member.Username.EndsWith(login));
break;
case StringPropertyMatchType.Wildcard:
query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar));
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType));
}
return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName"));
2017-12-28 09:18:09 +01:00
}
}
/// <summary>
/// Gets a list of Members based on a property search
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType to search for</param>
/// <param name="value"><see cref="System.string"/> Value to match</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.Exact"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
IQuery<IMember> query;
switch (matchType)
{
case StringPropertyMatchType.Exact:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar)));
break;
case StringPropertyMatchType.Contains:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar)));
break;
case StringPropertyMatchType.StartsWith:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar)));
break;
case StringPropertyMatchType.EndsWith:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar)));
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType));
}
return _memberRepository.Get(query);
}
}
/// <summary>
/// Gets a list of Members based on a property search
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType to search for</param>
/// <param name="value"><see cref="System.int"/> Value to match</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.Exact"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
IQuery<IMember> query;
switch (matchType)
{
case ValuePropertyMatchType.Exact:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue == value);
break;
case ValuePropertyMatchType.GreaterThan:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue > value);
break;
case ValuePropertyMatchType.LessThan:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue < value);
break;
case ValuePropertyMatchType.GreaterThanOrEqualTo:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue >= value);
break;
case ValuePropertyMatchType.LessThanOrEqualTo:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue <= value);
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType));
}
return _memberRepository.Get(query);
}
}
/// <summary>
/// Gets a list of Members based on a property search
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType to search for</param>
/// <param name="value"><see cref="System.bool"/> Value to match</param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByPropertyValue(string propertyTypeAlias, bool value)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
var query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).BoolPropertyValue == value);
return _memberRepository.Get(query);
}
}
/// <summary>
/// Gets a list of Members based on a property search
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType to search for</param>
/// <param name="value"><see cref="System.DateTime"/> Value to match</param>
/// <param name="matchType">The type of match to make as <see cref="StringPropertyMatchType"/>. Default is <see cref="StringPropertyMatchType.Exact"/></param>
/// <returns><see cref="IEnumerable{IMember}"/></returns>
public IEnumerable<IMember> GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
IQuery<IMember> query;
switch (matchType)
{
case ValuePropertyMatchType.Exact:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue == value);
break;
case ValuePropertyMatchType.GreaterThan:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue > value);
break;
case ValuePropertyMatchType.LessThan:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue < value);
break;
case ValuePropertyMatchType.GreaterThanOrEqualTo:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue >= value);
break;
case ValuePropertyMatchType.LessThanOrEqualTo:
query = Query<IMember>().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue <= value);
break;
default:
throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
}
// TODO: Since this is by property value, we need a GetByPropertyQuery on the repo!
2017-12-28 09:18:09 +01:00
return _memberRepository.Get(query);
}
}
/// <summary>
/// Checks if a Member with the id exists
/// </summary>
/// <param name="id">Id of the Member</param>
/// <returns><c>True</c> if the Member exists otherwise <c>False</c></returns>
public bool Exists(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.Exists(id);
}
}
/// <summary>
/// Checks if a Member with the username exists
/// </summary>
/// <param name="username">Username to check</param>
/// <returns><c>True</c> if the Member exists otherwise <c>False</c></returns>
public bool Exists(string username)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.Exists(username);
}
}
#endregion
#region Save
2020-07-30 22:56:31 +10:00
/// <inheritdoc />
public void SetLastLogin(string username, DateTime date)
{
using (var scope = ScopeProvider.CreateScope())
2021-02-25 08:08:02 +01:00
{
2020-07-30 22:56:31 +10:00
_memberRepository.SetLastLogin(username, date);
scope.Complete();
}
}
/// <inheritdoc />
2017-12-28 09:18:09 +01:00
public void Save(IMember member, bool raiseEvents = true)
{
// trimming username and email to make sure we have no trailing space
2018-03-22 17:41:13 +01:00
member.Username = member.Username.Trim();
2019-12-18 10:32:22 +01:00
member.Email = member.Email.Trim();
var evtMsgs = EventMessagesFactory.Get();
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
var savingNotification = new MemberSavingNotification(member, evtMsgs);
if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification))
2017-12-28 09:18:09 +01:00
{
scope.Complete();
return;
}
if (string.IsNullOrWhiteSpace(member.Name))
{
throw new ArgumentException("Cannot save member with empty name.");
}
scope.WriteLock(Constants.Locks.MemberTree);
_memberRepository.Save(member);
if (raiseEvents)
{
scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification));
2017-12-28 09:18:09 +01:00
}
Audit(AuditType.Save, 0, member.Id);
2017-12-28 09:18:09 +01:00
scope.Complete();
}
}
2020-07-30 22:56:31 +10:00
/// <inheritdoc />
2017-12-28 09:18:09 +01:00
public void Save(IEnumerable<IMember> members, bool raiseEvents = true)
{
var membersA = members.ToArray();
var evtMsgs = EventMessagesFactory.Get();
2017-12-28 09:18:09 +01:00
using (var scope = ScopeProvider.CreateScope())
{
var savingNotification = new MemberSavingNotification(membersA, evtMsgs);
if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification))
2017-12-28 09:18:09 +01:00
{
scope.Complete();
return;
}
scope.WriteLock(Constants.Locks.MemberTree);
foreach (var member in membersA)
2018-03-22 17:41:13 +01:00
{
//trimming username and email to make sure we have no trailing space
member.Username = member.Username.Trim();
member.Email = member.Email.Trim();
2017-12-28 09:18:09 +01:00
_memberRepository.Save(member);
2018-03-22 17:41:13 +01:00
}
2017-12-28 09:18:09 +01:00
if (raiseEvents)
{
scope.Notifications.Publish(new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification));
2017-12-28 09:18:09 +01:00
}
Audit(AuditType.Save, 0, -1, "Save multiple Members");
2017-12-28 09:18:09 +01:00
scope.Complete();
}
}
#endregion
#region Delete
/// <summary>
/// Deletes an <see cref="IMember"/>
/// </summary>
/// <param name="member"><see cref="IMember"/> to Delete</param>
public void Delete(IMember member)
{
var evtMsgs = EventMessagesFactory.Get();
2017-12-28 09:18:09 +01:00
using (var scope = ScopeProvider.CreateScope())
{
var deletingNotification = new MemberDeletingNotification(member, evtMsgs);
if (scope.Notifications.PublishCancelable(deletingNotification))
2017-12-28 09:18:09 +01:00
{
scope.Complete();
return;
}
scope.WriteLock(Constants.Locks.MemberTree);
DeleteLocked(scope, member, evtMsgs, deletingNotification.State);
2017-12-28 09:18:09 +01:00
Audit(AuditType.Delete, 0, member.Id);
2017-12-28 09:18:09 +01:00
scope.Complete();
}
}
private void DeleteLocked(IScope scope, IMember member, EventMessages evtMsgs, IDictionary<string, object> notificationState = null)
2017-12-28 09:18:09 +01:00
{
// a member has no descendants
_memberRepository.Delete(member);
scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState));
2017-12-28 09:18:09 +01:00
2018-10-26 15:06:53 +02:00
// media files deleted by QueuingEventDispatcher
2017-12-28 09:18:09 +01:00
}
#endregion
#region Roles
public void AddRole(string roleName)
{
using (var scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.CreateIfNotExists(roleName);
scope.Complete();
}
}
/// <summary>
/// Returns a list of all member roles
/// </summary>
/// <returns>A list of member roles</returns>
public IEnumerable<IMemberGroup> GetAllRoles()
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.MemberTree);
2021-02-12 17:03:53 +00:00
return _memberGroupRepository.GetMany().Distinct();
}
}
/// <summary>
/// Returns a list of all member roles for a given member ID
/// </summary>
/// <param name="memberId"></param>
/// <returns>A list of member roles</returns>
2017-12-28 09:18:09 +01:00
public IEnumerable<string> GetAllRoles(int memberId)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2017-12-28 09:18:09 +01:00
{
scope.ReadLock(Constants.Locks.MemberTree);
var result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
return result.Select(x => x.Name).Distinct();
}
}
public IEnumerable<string> GetAllRoles(string username)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2017-12-28 09:18:09 +01:00
{
scope.ReadLock(Constants.Locks.MemberTree);
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(username);
2017-12-28 09:18:09 +01:00
return result.Select(x => x.Name).Distinct();
}
}
2020-03-24 14:15:52 +00:00
public IEnumerable<int> GetAllRolesIds()
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2020-03-24 14:15:52 +00:00
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct();
}
}
public IEnumerable<int> GetAllRolesIds(int memberId)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2020-03-24 14:15:52 +00:00
{
scope.ReadLock(Constants.Locks.MemberTree);
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
2020-03-24 14:15:52 +00:00
return result.Select(x => x.Id).Distinct();
}
}
public IEnumerable<int> GetAllRolesIds(string username)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2020-03-24 14:15:52 +00:00
{
scope.ReadLock(Constants.Locks.MemberTree);
IEnumerable<IMemberGroup> result = _memberGroupRepository.GetMemberGroupsForMember(username);
2020-03-24 14:15:52 +00:00
return result.Select(x => x.Id).Distinct();
}
}
2020-09-18 15:27:38 +02:00
2017-12-28 09:18:09 +01:00
public IEnumerable<IMember> GetMembersInRole(string roleName)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2017-12-28 09:18:09 +01:00
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.GetByMemberGroup(roleName);
}
}
public IEnumerable<IMember> FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2017-12-28 09:18:09 +01:00
{
scope.ReadLock(Constants.Locks.MemberTree);
return _memberRepository.FindMembersInRole(roleName, usernameToMatch, matchType);
}
}
public bool DeleteRole(string roleName, bool throwIfBeingUsed)
{
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
if (throwIfBeingUsed)
{
// get members in role
IEnumerable<IMember> membersInRole = _memberRepository.GetByMemberGroup(roleName);
2017-12-28 09:18:09 +01:00
if (membersInRole.Any())
{
2017-12-28 09:18:09 +01:00
throw new InvalidOperationException("The role " + roleName + " is currently assigned to members");
}
2017-12-28 09:18:09 +01:00
}
IQuery<IMemberGroup> query = Query<IMemberGroup>().Where(g => g.Name == roleName);
IMemberGroup[] found = _memberGroupRepository.Get(query).ToArray();
2017-12-28 09:18:09 +01:00
foreach (IMemberGroup memberGroup in found)
{
2017-12-28 09:18:09 +01:00
_memberGroupService.Delete(memberGroup);
}
2017-12-28 09:18:09 +01:00
scope.Complete();
return found.Length > 0;
}
}
public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName });
2017-12-28 09:18:09 +01:00
public void AssignRoles(string[] usernames, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
int[] ids = _memberRepository.GetMemberIds(usernames);
2018-03-22 17:41:13 +01:00
_memberGroupRepository.AssignRoles(ids, roleNames);
scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
scope.Complete();
2017-12-28 09:18:09 +01:00
}
}
public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName });
2017-12-28 09:18:09 +01:00
public void DissociateRoles(string[] usernames, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
int[] ids = _memberRepository.GetMemberIds(usernames);
2018-03-22 17:41:13 +01:00
_memberGroupRepository.DissociateRoles(ids, roleNames);
scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames));
scope.Complete();
2017-12-28 09:18:09 +01:00
}
}
public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName });
2017-12-28 09:18:09 +01:00
public void AssignRoles(int[] memberIds, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.AssignRoles(memberIds, roleNames);
scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
scope.Complete();
2017-12-28 09:18:09 +01:00
}
}
public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName });
2017-12-28 09:18:09 +01:00
public void DissociateRoles(int[] memberIds, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.DissociateRoles(memberIds, roleNames);
scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames));
scope.Complete();
2017-12-28 09:18:09 +01:00
}
}
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
public void ReplaceRoles(string[] usernames, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
int[] ids = _memberRepository.GetMemberIds(usernames);
_memberGroupRepository.ReplaceRoles(ids, roleNames);
scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
scope.Complete();
}
}
public void ReplaceRoles(int[] memberIds, string[] roleNames)
{
using (IScope scope = ScopeProvider.CreateScope())
{
scope.WriteLock(Constants.Locks.MemberTree);
_memberGroupRepository.ReplaceRoles(memberIds, roleNames);
scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
scope.Complete();
}
}
2017-12-28 09:18:09 +01:00
#endregion
#region Private Methods
private void Audit(AuditType type, int userId, int objectId, string message = null) => _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message));
2017-12-28 09:18:09 +01:00
#endregion
#region Membership
2018-03-22 17:41:13 +01:00
/// <summary>
/// Exports a member.
/// </summary>
/// <remarks>
2019-11-05 13:45:42 +01:00
/// This is internal for now and is used to export a member in the member editor,
2018-03-22 17:41:13 +01:00
/// it will raise an event so that auditing logs can be created.
/// </remarks>
2019-12-18 10:32:22 +01:00
public MemberExportModel ExportMember(Guid key)
2018-03-22 17:41:13 +01:00
{
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2018-03-22 17:41:13 +01:00
{
IQuery<IMember> query = Query<IMember>().Where(x => x.Key == key);
IMember member = _memberRepository.Get(query).FirstOrDefault();
2018-03-22 17:41:13 +01:00
if (member == null)
{
return null;
}
2018-03-22 17:41:13 +01:00
var model = new MemberExportModel
{
Id = member.Id,
Key = member.Key,
Name = member.Name,
Username = member.Username,
Email = member.Email,
Groups = GetAllRoles(member.Id).ToList(),
ContentTypeAlias = member.ContentTypeAlias,
CreateDate = member.CreateDate,
UpdateDate = member.UpdateDate,
Properties = new List<MemberExportProperty>(GetPropertyExportItems(member))
};
scope.Notifications.Publish(new ExportedMemberNotification(member, model));
2018-03-22 17:41:13 +01:00
return model;
}
}
private static IEnumerable<MemberExportProperty> GetPropertyExportItems(IMember member)
{
if (member == null)
{
throw new ArgumentNullException(nameof(member));
}
2018-03-22 17:41:13 +01:00
var exportProperties = new List<MemberExportProperty>();
foreach (IProperty property in member.Properties)
2018-03-22 17:41:13 +01:00
{
var propertyExportModel = new MemberExportProperty
{
Id = property.Id,
Alias = property.Alias,
Name = property.PropertyType.Name,
Value = property.GetValue(), // TODO: ignoring variants
2018-03-22 17:41:13 +01:00
CreateDate = property.CreateDate,
UpdateDate = property.UpdateDate
};
exportProperties.Add(propertyExportModel);
}
return exportProperties;
}
2017-12-28 09:18:09 +01:00
#endregion
#region Content Types
/// <summary>
/// Delete Members of the specified MemberType id
/// </summary>
/// <param name="memberTypeId">Id of the MemberType</param>
public void DeleteMembersOfType(int memberTypeId)
{
var evtMsgs = EventMessagesFactory.Get();
2017-12-28 09:18:09 +01:00
// note: no tree to manage here
using (IScope scope = ScopeProvider.CreateScope())
2017-12-28 09:18:09 +01:00
{
scope.WriteLock(Constants.Locks.MemberTree);
// TODO: What about content that has the contenttype as part of its composition?
IQuery<IMember> query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
2017-12-28 09:18:09 +01:00
IMember[] members = _memberRepository.Get(query).ToArray();
2017-12-28 09:18:09 +01:00
if (scope.Notifications.PublishCancelable(new MemberDeletingNotification(members, evtMsgs)))
2017-12-28 09:18:09 +01:00
{
scope.Complete();
return;
}
foreach (IMember member in members)
2017-12-28 09:18:09 +01:00
{
// delete media
// triggers the deleted event (and handles the files)
DeleteLocked(scope, member, evtMsgs);
2017-12-28 09:18:09 +01:00
}
2017-12-28 09:18:09 +01:00
scope.Complete();
}
}
private IMemberType GetMemberType(IScope scope, string memberTypeAlias)
{
if (memberTypeAlias == null)
{
throw new ArgumentNullException(nameof(memberTypeAlias));
}
if (string.IsNullOrWhiteSpace(memberTypeAlias))
{
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
}
2017-12-28 09:18:09 +01:00
scope.ReadLock(Constants.Locks.MemberTypes);
IMemberType memberType = _memberTypeRepository.Get(memberTypeAlias);
2017-12-28 09:18:09 +01:00
if (memberType == null)
{
2017-12-28 09:18:09 +01:00
throw new Exception($"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback
}
2017-12-28 09:18:09 +01:00
return memberType;
}
private IMemberType GetMemberType(string memberTypeAlias)
{
if (memberTypeAlias == null)
{
throw new ArgumentNullException(nameof(memberTypeAlias));
}
2017-12-28 09:18:09 +01:00
if (string.IsNullOrWhiteSpace(memberTypeAlias))
{
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
}
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
2017-12-28 09:18:09 +01:00
{
return GetMemberType(scope, memberTypeAlias);
}
}
#endregion
}
}