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 ;
2021-02-09 10:22:42 +01:00
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 ;
2021-02-15 11:41:12 +01:00
using Umbraco.Cms.Core.Scoping ;
2021-04-09 13:43:39 +02:00
using Umbraco.Cms.Core.Services.Notifications ;
2021-02-12 13:36:50 +01:00
using Umbraco.Cms.Infrastructure.Persistence.Querying ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2017-12-28 09:18:09 +01:00
2021-02-23 12:24:51 +01:00
namespace Umbraco.Cms.Core.Services.Implement
2017-12-28 09:18:09 +01:00
{
/// <summary>
/// Represents the MemberService.
/// </summary>
2021-03-07 09:53:25 +01:00
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 :
2020-05-20 11:33:10 +10:00
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 :
2020-05-20 11:33:10 +10:00
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>
2020-12-05 23:44:50 +00:00
/// <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 )
{
2020-12-05 23:44:50 +00:00
IMemberType memberType = GetMemberType ( memberTypeAlias ) ;
2017-12-28 09:18:09 +01:00
if ( memberType = = null )
2020-12-05 23:44:50 +00:00
{
2017-12-28 09:18:09 +01:00
throw new ArgumentException ( "No member type with that alias." , nameof ( memberTypeAlias ) ) ;
2020-12-05 23:44:50 +00:00
}
2017-12-28 09:18:09 +01:00
2021-02-01 16:07:42 +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 ) ) ;
2021-02-01 16:07:42 +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 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
2021-02-01 16:07:42 +01:00
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
2021-02-01 16:07:42 +01:00
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 ) ;
2021-02-01 16:07:42 +01:00
return member ;
2020-12-05 23:44:50 +00:00
}
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 ) ;
2018-09-18 11:53:33 +02:00
return _memberRepository . GetPage ( null , pageIndex , pageSize , out totalRecords , null , Ordering . By ( "LoginName" ) ) ;
2017-12-28 09:18:09 +01:00
}
}
2018-11-01 01:01:50 +11: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 ) ;
}
2018-11-01 01:01:50 +11:00
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 ) ) ;
2018-09-18 11:53:33 +02:00
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 ) ;
2020-07-31 00:19:36 +10:00
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
}
2018-09-18 11:53:33 +02:00
return _memberRepository . GetPage ( query , pageIndex , pageSize , out totalRecords , null , Ordering . By ( "Name" ) ) ;
2017-12-28 09:18:09 +01:00
}
}
2020-01-07 13:50:38 +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 ) ) ;
}
2018-09-18 11:53:33 +02:00
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 ) ) ;
}
2018-09-18 11:53:33 +02:00
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
}
2019-01-27 01:17:32 -05:00
// 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 )
{
2021-01-29 11:41:58 +00:00
// 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 ( ) ;
2021-03-09 07:52:32 +01:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2021-01-29 11:41:58 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( ) )
2017-12-28 09:18:09 +01:00
{
2021-03-16 09:21:27 +01:00
var savingNotification = new MemberSavingNotification ( member , evtMsgs ) ;
2021-03-09 07:52:32 +01:00
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 )
{
2021-03-16 09:21:27 +01:00
scope . Notifications . Publish ( new MemberSavedNotification ( member , evtMsgs ) . WithStateFrom ( savingNotification ) ) ;
2017-12-28 09:18:09 +01:00
}
2021-01-29 11:41:58 +00:00
2018-10-18 22:47:12 +11: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 ( ) ;
2021-03-09 07:52:32 +01:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2017-12-28 09:18:09 +01:00
using ( var scope = ScopeProvider . CreateScope ( ) )
{
2021-03-16 09:21:27 +01:00
var savingNotification = new MemberSavingNotification ( membersA , evtMsgs ) ;
2021-03-09 07:52:32 +01:00
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 )
{
2021-03-16 09:21:27 +01:00
scope . Notifications . Publish ( new MemberSavedNotification ( membersA , evtMsgs ) . WithStateFrom ( savingNotification ) ) ;
2017-12-28 09:18:09 +01:00
}
2018-10-18 22:47:12 +11: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 )
{
2021-03-09 07:52:32 +01:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2017-12-28 09:18:09 +01:00
using ( var scope = ScopeProvider . CreateScope ( ) )
{
2021-03-16 09:21:27 +01:00
var deletingNotification = new MemberDeletingNotification ( member , evtMsgs ) ;
2021-03-09 07:52:32 +01:00
if ( scope . Notifications . PublishCancelable ( deletingNotification ) )
2017-12-28 09:18:09 +01:00
{
scope . Complete ( ) ;
return ;
}
scope . WriteLock ( Constants . Locks . MemberTree ) ;
2021-03-09 07:52:32 +01:00
DeleteLocked ( scope , member , evtMsgs , deletingNotification . State ) ;
2017-12-28 09:18:09 +01:00
2018-10-18 22:47:12 +11:00
Audit ( AuditType . Delete , 0 , member . Id ) ;
2017-12-28 09:18:09 +01:00
scope . Complete ( ) ;
}
}
2021-03-09 07:52:32 +01:00
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 ) ;
2021-03-16 09:21:27 +01:00
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 ( ) ;
}
}
2021-01-11 17:01:44 +00:00
2020-12-08 17:18:22 +00:00
/// <summary>
2021-01-11 17:01:44 +00:00
/// Returns a list of all member roles
2020-12-08 17:18:22 +00:00
/// </summary>
2021-01-11 17:01:44 +00:00
/// <returns>A list of member roles</returns>
2020-12-08 17:18:22 +00:00
2021-01-11 17:01:44 +00:00
public IEnumerable < IMemberGroup > GetAllRoles ( )
2020-12-08 17:18:22 +00:00
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( autoComplete : true ) )
2020-12-08 17:18:22 +00:00
{
scope . ReadLock ( Constants . Locks . MemberTree ) ;
2021-02-12 17:03:53 +00:00
return _memberGroupRepository . GetMany ( ) . Distinct ( ) ;
2020-12-08 17:18:22 +00:00
}
}
2021-01-11 17:01:44 +00:00
/// <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 )
{
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( autoComplete : true ) )
2017-12-28 09:18:09 +01:00
{
scope . ReadLock ( Constants . Locks . MemberTree ) ;
2021-01-11 17:01:44 +00:00
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 ( )
{
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( autoComplete : true ) )
2020-03-24 14:15:52 +00:00
{
scope . ReadLock ( Constants . Locks . MemberTree ) ;
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( autoComplete : true ) )
2020-03-24 14:15:52 +00:00
{
scope . ReadLock ( Constants . Locks . MemberTree ) ;
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( ) )
2017-12-28 09:18:09 +01:00
{
scope . WriteLock ( Constants . Locks . MemberTree ) ;
if ( throwIfBeingUsed )
{
// get members in role
2021-01-11 17:01:44 +00:00
IEnumerable < IMember > membersInRole = _memberRepository . GetByMemberGroup ( roleName ) ;
2017-12-28 09:18:09 +01:00
if ( membersInRole . Any ( ) )
2021-01-11 17:01:44 +00:00
{
2017-12-28 09:18:09 +01:00
throw new InvalidOperationException ( "The role " + roleName + " is currently assigned to members" ) ;
2021-01-11 17:01:44 +00:00
}
2017-12-28 09:18:09 +01:00
}
2021-01-11 17:01:44 +00: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
2021-01-11 17:01:44 +00:00
foreach ( IMemberGroup memberGroup in found )
{
2017-12-28 09:18:09 +01:00
_memberGroupService . Delete ( memberGroup ) ;
2021-01-11 17:01:44 +00:00
}
2017-12-28 09:18:09 +01:00
scope . Complete ( ) ;
return found . Length > 0 ;
}
}
2021-01-11 17:01:44 +00:00
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 )
{
2021-01-11 17:01:44 +00:00
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 ) ;
2021-03-09 07:52:32 +01:00
scope . Notifications . Publish ( new AssignedMemberRolesNotification ( ids , roleNames ) ) ;
2021-03-17 12:47:16 +01:00
scope . Complete ( ) ;
2017-12-28 09:18:09 +01:00
}
}
2021-01-11 17:01:44 +00: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 )
{
2021-01-11 17:01:44 +00:00
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 ) ;
2021-03-09 07:52:32 +01:00
scope . Notifications . Publish ( new RemovedMemberRolesNotification ( ids , roleNames ) ) ;
2021-03-17 12:47:16 +01:00
scope . Complete ( ) ;
2017-12-28 09:18:09 +01:00
}
}
2021-01-11 17:01:44 +00: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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( ) )
2017-12-28 09:18:09 +01:00
{
scope . WriteLock ( Constants . Locks . MemberTree ) ;
_memberGroupRepository . AssignRoles ( memberIds , roleNames ) ;
2021-03-09 07:52:32 +01:00
scope . Notifications . Publish ( new AssignedMemberRolesNotification ( memberIds , roleNames ) ) ;
2021-03-17 12:47:16 +01:00
scope . Complete ( ) ;
2017-12-28 09:18:09 +01:00
}
}
2021-01-11 17:01:44 +00: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 )
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( ) )
2017-12-28 09:18:09 +01:00
{
scope . WriteLock ( Constants . Locks . MemberTree ) ;
_memberGroupRepository . DissociateRoles ( memberIds , roleNames ) ;
2021-03-09 07:52:32 +01:00
scope . Notifications . Publish ( new RemovedMemberRolesNotification ( memberIds , roleNames ) ) ;
2021-03-17 12:47:16 +01:00
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
2021-01-11 17:01:44 +00:00
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
{
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( autoComplete : true ) )
2018-03-22 17:41:13 +01:00
{
2021-01-11 17:01:44 +00: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
2021-01-11 17:01:44 +00: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 ) )
} ;
2021-03-09 07:52:32 +01:00
scope . Notifications . Publish ( new ExportedMemberNotification ( member , model ) ) ;
2018-03-22 17:41:13 +01:00
return model ;
}
}
private static IEnumerable < MemberExportProperty > GetPropertyExportItems ( IMember member )
{
2021-01-11 17:01:44 +00:00
if ( member = = null )
{
throw new ArgumentNullException ( nameof ( member ) ) ;
}
2018-03-22 17:41:13 +01:00
var exportProperties = new List < MemberExportProperty > ( ) ;
2021-01-11 17:01:44 +00:00
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 ,
2019-01-26 09:42:14 -05:00
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 )
{
2021-03-09 07:52:32 +01:00
var evtMsgs = EventMessagesFactory . Get ( ) ;
2017-12-28 09:18:09 +01:00
// note: no tree to manage here
2021-01-11 17:01:44 +00:00
using ( IScope scope = ScopeProvider . CreateScope ( ) )
2017-12-28 09:18:09 +01:00
{
scope . WriteLock ( Constants . Locks . MemberTree ) ;
2019-01-27 01:17:32 -05:00
// TODO: What about content that has the contenttype as part of its composition?
2021-01-11 17:01:44 +00:00
IQuery < IMember > query = Query < IMember > ( ) . Where ( x = > x . ContentTypeId = = memberTypeId ) ;
2017-12-28 09:18:09 +01:00
2021-01-11 17:01:44 +00:00
IMember [ ] members = _memberRepository . Get ( query ) . ToArray ( ) ;
2017-12-28 09:18:09 +01:00
2021-03-16 09:21:27 +01:00
if ( scope . Notifications . PublishCancelable ( new MemberDeletingNotification ( members , evtMsgs ) ) )
2017-12-28 09:18:09 +01:00
{
scope . Complete ( ) ;
return ;
}
2021-01-11 17:01:44 +00:00
foreach ( IMember member in members )
2017-12-28 09:18:09 +01:00
{
// delete media
// triggers the deleted event (and handles the files)
2021-03-09 07:52:32 +01:00
DeleteLocked ( scope , member , evtMsgs ) ;
2017-12-28 09:18:09 +01:00
}
2021-01-11 17:01:44 +00:00
2017-12-28 09:18:09 +01:00
scope . Complete ( ) ;
}
}
private IMemberType GetMemberType ( IScope scope , string memberTypeAlias )
{
2021-01-11 17:01:44 +00:00
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 ) ;
2021-01-11 17:01:44 +00:00
IMemberType memberType = _memberTypeRepository . Get ( memberTypeAlias ) ;
2017-12-28 09:18:09 +01:00
if ( memberType = = null )
2021-01-11 17:01:44 +00:00
{
2017-12-28 09:18:09 +01:00
throw new Exception ( $"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found" ) ; // causes rollback
2021-01-11 17:01:44 +00:00
}
2017-12-28 09:18:09 +01:00
return memberType ;
}
private IMemberType GetMemberType ( string memberTypeAlias )
{
2021-01-11 17:01:44 +00:00
if ( memberTypeAlias = = null )
{
throw new ArgumentNullException ( nameof ( memberTypeAlias ) ) ;
}
2017-12-28 09:18:09 +01:00
2021-01-11 17:01:44 +00: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
}
}