2013-05-27 06:50:14 -02:00
using System ;
2013-08-09 13:23:27 +10:00
using System.Collections.Generic ;
2017-08-25 17:55:26 +02:00
using System.ComponentModel ;
2015-07-27 15:48:14 +02:00
using System.Data.Common ;
2017-08-25 17:55:26 +02:00
using System.Data.SqlClient ;
using System.Data.SqlServerCe ;
using System.Globalization ;
2013-05-27 06:50:14 -02:00
using System.Linq ;
2017-08-25 17:55:26 +02:00
using System.Linq.Expressions ;
2017-05-12 14:49:44 +02:00
using Umbraco.Core.Configuration ;
2013-07-03 19:01:37 +10:00
using Umbraco.Core.Events ;
2017-05-12 14:49:44 +02:00
using Umbraco.Core.Exceptions ;
2015-01-19 18:37:48 +11:00
using Umbraco.Core.Logging ;
2012-11-05 14:42:21 -01:00
using Umbraco.Core.Models.Membership ;
2017-08-25 17:55:26 +02:00
using Umbraco.Core.Persistence.DatabaseModelDefinitions ;
2013-05-27 06:50:14 -02:00
using Umbraco.Core.Persistence.Querying ;
2016-04-28 08:48:59 +02:00
using Umbraco.Core.Persistence.Repositories ;
2012-11-06 10:47:14 -01:00
using Umbraco.Core.Persistence.UnitOfWork ;
2014-03-18 18:41:18 +11:00
using Umbraco.Core.Security ;
2012-11-05 14:42:21 -01:00
2012-11-12 07:40:11 -01:00
namespace Umbraco.Core.Services
2012-11-05 14:42:21 -01:00
{
/// <summary>
2013-05-23 13:25:14 -02:00
/// Represents the UserService, which is an easy access to operations involving <see cref="IProfile"/>, <see cref="IMembershipUser"/> and eventually Backoffice Users.
2012-11-05 14:42:21 -01:00
/// </summary>
2017-05-12 14:49:44 +02:00
public class UserService : ScopeRepositoryService , IUserService
2012-11-05 14:42:21 -01:00
{
2017-09-13 14:40:10 +02:00
private readonly bool _isUpgrading ;
2015-07-27 15:48:14 +02:00
2017-09-13 14:40:10 +02:00
public UserService ( IScopeUnitOfWorkProvider provider , ILogger logger , IEventMessagesFactory eventMessagesFactory , IRuntimeState runtimeState )
2016-05-02 12:17:30 +02:00
: base ( provider , logger , eventMessagesFactory )
2012-11-05 14:42:21 -01:00
{
2017-09-13 14:40:10 +02:00
_isUpgrading = runtimeState . Level = = RuntimeLevel . Install | | runtimeState . Level = = RuntimeLevel . Upgrade ;
2012-11-05 14:42:21 -01:00
}
2014-01-06 11:04:26 +11:00
#region Implementation of IMembershipUserService
2012-11-05 14:42:21 -01:00
2014-08-15 14:41:25 +02:00
/// <summary>
/// Checks if a User with the username exists
/// </summary>
/// <param name="username">Username to check</param>
/// <returns><c>True</c> if the User exists otherwise <c>False</c></returns>
2014-01-06 11:04:26 +11:00
public bool Exists ( string username )
2013-07-03 16:52:00 +10:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 11:56:53 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-05-12 14:49:44 +02:00
return repository . Exists ( username ) ;
2014-01-06 11:56:53 +11:00
}
2013-07-03 16:52:00 +10:00
}
2014-08-15 11:13:00 +02:00
/// <summary>
/// Creates a new User
/// </summary>
/// <remarks>The user will be saved in the database and returned with an Id</remarks>
/// <param name="username">Username of the user to create</param>
/// <param name="email">Email of the user to create</param>
/// <returns><see cref="IUser"/></returns>
2017-08-25 17:55:26 +02:00
public IUser CreateUserWithIdentity ( string username , string email )
2014-03-18 18:41:18 +11:00
{
2017-08-25 17:55:26 +02:00
return CreateUserWithIdentity ( username , email , string . Empty ) ;
2014-03-18 18:41:18 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Creates and persists a new <see cref="IUser"/>
/// </summary>
/// <param name="username">Username of the <see cref="IUser"/> to create</param>
/// <param name="email">Email of the <see cref="IUser"/> 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>
2017-08-25 17:55:26 +02:00
/// <param name="memberTypeAlias">Not used for users</param>
2014-08-15 14:41:25 +02:00
/// <returns><see cref="IUser"/></returns>
IUser IMembershipMemberService < IUser > . CreateWithIdentity ( string username , string email , string passwordValue , string memberTypeAlias )
2014-03-18 18:41:18 +11:00
{
2017-08-25 17:55:26 +02:00
return CreateUserWithIdentity ( username , email , passwordValue ) ;
2017-09-08 19:39:13 +02:00
}
2014-03-18 18:41:18 +11:00
2017-08-25 17:55:26 +02:00
/// <summary>
/// Creates and persists a new <see cref="IUser"/>
/// </summary>
/// <param name="username">Username of the <see cref="IUser"/> to create</param>
/// <param name="email">Email of the <see cref="IUser"/> 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="IUser"/></returns>
IUser IMembershipMemberService < IUser > . CreateWithIdentity ( string username , string email , string passwordValue , string memberTypeAlias , bool isApproved )
{
return CreateUserWithIdentity ( username , email , passwordValue , isApproved ) ;
2014-03-18 18:41:18 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Creates and persists a Member
/// </summary>
2017-05-12 14:49:44 +02:00
/// <remarks>Using this method will persist the Member object before its returned
2014-08-15 14:41:25 +02:00
/// 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="passwordValue">This value should be the encoded/encrypted/hashed value for the password that will be stored in the database</param>
2017-08-25 17:55:26 +02:00
/// <param name="isApproved">Is the user approved</param>
2014-08-15 14:41:25 +02:00
/// <returns><see cref="IUser"/></returns>
2017-08-25 17:55:26 +02:00
private IUser CreateUserWithIdentity ( string username , string email , string passwordValue , bool isApproved = true )
2013-07-03 16:52:00 +10:00
{
2017-05-12 14:49:44 +02:00
if ( string . IsNullOrWhiteSpace ( username ) ) throw new ArgumentNullOrEmptyException ( nameof ( username ) ) ;
2014-01-06 11:04:26 +11:00
2014-01-23 10:41:57 +11:00
//TODO: PUT lock here!!
2016-05-02 12:12:21 +02:00
User user ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-07-03 16:52:00 +10:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2014-01-06 11:04:26 +11:00
var loginExists = uow . Database . ExecuteScalar < int > ( "SELECT COUNT(id) FROM umbracoUser WHERE userLogin = @Login" , new { Login = username } ) ! = 0 ;
if ( loginExists )
2016-05-18 10:55:19 +02:00
throw new ArgumentException ( "Login already exists" ) ; // causes rollback
2014-01-06 11:04:26 +11:00
2017-08-25 17:55:26 +02:00
user = new User
2014-01-06 11:04:26 +11:00
{
DefaultToLiveEditing = false ,
Email = email ,
2017-05-12 14:49:44 +02:00
Language = GlobalSettings . DefaultUILanguage ,
2014-01-06 11:04:26 +11:00
Name = username ,
2017-05-12 14:49:44 +02:00
RawPasswordValue = passwordValue ,
2014-01-06 11:04:26 +11:00
Username = username ,
IsLockedOut = false ,
2017-08-25 17:55:26 +02:00
IsApproved = isApproved
2014-01-06 11:04:26 +11:00
} ;
2017-05-12 14:49:44 +02:00
2017-09-18 15:33:13 +02:00
var saveEventArgs = new SaveEventArgs < IUser > ( user ) ;
if ( uow . Events . DispatchCancelable ( SavingUser , this , saveEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
2014-03-06 18:07:02 +11:00
return user ;
2017-05-12 14:49:44 +02:00
}
2014-01-23 10:41:57 +11:00
2014-01-06 11:04:26 +11:00
repository . AddOrUpdate ( user ) ;
2017-05-12 14:49:44 +02:00
2017-09-18 15:33:13 +02:00
saveEventArgs . CanCancel = false ;
uow . Events . Dispatch ( SavedUser , this , saveEventArgs ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2014-01-06 11:04:26 +11:00
}
2016-05-02 12:12:21 +02:00
return user ;
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Gets a User by its integer id
/// </summary>
/// <param name="id"><see cref="System.int"/> Id</param>
/// <returns><see cref="IUser"/></returns>
2014-01-23 18:44:41 +11:00
public IUser GetById ( int id )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 11:56:53 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-05-12 14:49:44 +02:00
return repository . Get ( id ) ;
2014-01-06 11:56:53 +11:00
}
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Gets an <see cref="IUser"/> by its provider key
/// </summary>
/// <param name="id">Id to use for retrieval</param>
/// <returns><see cref="IUser"/></returns>
2014-02-17 14:14:17 +11:00
public IUser GetByProviderKey ( object id )
{
var asInt = id . TryConvertTo < int > ( ) ;
2017-05-12 14:49:44 +02:00
return asInt . Success ? GetById ( asInt . Result ) : null ;
2014-02-17 14:14:17 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Get an <see cref="IUser"/> by email
/// </summary>
/// <param name="email">Email to use for retrieval</param>
/// <returns><see cref="IUser"/></returns>
2014-01-06 11:04:26 +11:00
public IUser GetByEmail ( string email )
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 11:56:53 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-07-11 16:29:44 +02:00
var query = uow . Query < IUser > ( ) . Where ( x = > x . Email . Equals ( email ) ) ;
2017-05-12 14:49:44 +02:00
return repository . GetByQuery ( query ) . FirstOrDefault ( ) ;
2014-01-06 11:56:53 +11:00
}
2014-01-06 11:04:26 +11:00
}
2017-05-12 14:49:44 +02:00
2014-08-15 14:41:25 +02:00
/// <summary>
/// Get an <see cref="IUser"/> by username
/// </summary>
/// <param name="username">Username to use for retrieval</param>
/// <returns><see cref="IUser"/></returns>
2014-08-15 11:13:00 +02:00
public IUser GetByUsername ( string username )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 11:04:26 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-08-25 17:55:26 +02:00
try
{
return repository . GetByUsername ( username , includeSecurityData : true ) ;
}
catch ( Exception ex )
{
if ( ex is SqlException | | ex is SqlCeException )
{
// fixme kill in v8
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
//that method would not be cached.
2017-09-13 14:40:10 +02:00
if ( _isUpgrading )
2017-08-25 17:55:26 +02:00
{
//NOTE: this will not be cached
return repository . GetByUsername ( username , includeSecurityData : false ) ;
}
}
throw ;
}
2013-07-03 16:52:00 +10:00
}
}
2014-01-23 18:44:41 +11:00
/// <summary>
2014-08-15 14:41:25 +02:00
/// Deletes an <see cref="IUser"/>
2014-01-23 18:44:41 +11:00
/// </summary>
2014-08-15 14:41:25 +02:00
/// <param name="membershipUser"><see cref="IUser"/> to Delete</param>
2014-01-06 11:04:26 +11:00
public void Delete ( IUser membershipUser )
2017-05-12 14:49:44 +02:00
{
2014-01-23 18:44:41 +11:00
//disable
2017-05-12 14:49:44 +02:00
membershipUser . IsApproved = false ;
2017-08-25 17:55:26 +02:00
2014-01-23 18:44:41 +11:00
Save ( membershipUser ) ;
}
2017-08-25 17:55:26 +02:00
[Obsolete("ASP.NET Identity APIs like the BackOfficeUserManager should be used to manage passwords, this will not work with correct security practices because you would need the existing password")]
[EditorBrowsable(EditorBrowsableState.Never)]
2014-03-18 18:41:18 +11:00
public void SavePassword ( IUser user , string password )
{
2017-05-12 14:49:44 +02:00
if ( user = = null ) throw new ArgumentNullException ( nameof ( user ) ) ;
2014-03-18 18:41:18 +11:00
var provider = MembershipProviderExtensions . GetUsersMembershipProvider ( ) ;
2014-05-27 11:43:24 +02:00
if ( provider . IsUmbracoMembershipProvider ( ) = = false )
throw new NotSupportedException ( "When using a non-Umbraco membership provider you must change the user password by using the MembershipProvider.ChangePassword method" ) ;
provider . ChangePassword ( user . Username , "" , password ) ;
2014-03-18 18:41:18 +11:00
//go re-fetch the member and update the properties that may have changed
var result = GetByUsername ( user . Username ) ;
if ( result ! = null )
{
//should never be null but it could have been deleted by another thread.
user . RawPasswordValue = result . RawPasswordValue ;
user . LastPasswordChangeDate = result . LastPasswordChangeDate ;
2016-01-05 23:22:16 +01:00
user . UpdateDate = result . UpdateDate ;
2014-03-18 18:41:18 +11:00
}
}
2014-01-23 18:44:41 +11:00
/// <summary>
2014-08-15 11:13:00 +02:00
/// Deletes or disables a User
2014-01-23 18:44:41 +11:00
/// </summary>
2014-08-15 11:13:00 +02:00
/// <param name="user"><see cref="IUser"/> to delete</param>
/// <param name="deletePermanently"><c>True</c> to permanently delete the user, <c>False</c> to disable the user</param>
2014-01-23 18:44:41 +11:00
public void Delete ( IUser user , bool deletePermanently )
{
if ( deletePermanently = = false )
2014-01-06 11:56:53 +11:00
{
2014-01-23 18:44:41 +11:00
Delete ( user ) ;
2014-01-06 11:56:53 +11:00
}
2014-01-23 18:44:41 +11:00
else
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-01-23 18:44:41 +11:00
{
2017-09-18 15:33:13 +02:00
var deleteEventArgs = new DeleteEventArgs < IUser > ( user ) ;
if ( uow . Events . DispatchCancelable ( DeletingUser , this , deleteEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
return ;
}
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2014-01-23 18:44:41 +11:00
repository . Delete ( user ) ;
2017-05-12 14:49:44 +02:00
2017-09-18 15:33:13 +02:00
deleteEventArgs . CanCancel = false ;
uow . Events . Dispatch ( DeletedUser , this , deleteEventArgs ) ;
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2014-01-23 18:44:41 +11:00
}
}
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Saves an <see cref="IUser"/>
/// </summary>
/// <param name="entity"><see cref="IUser"/> to Save</param>
2017-05-12 14:49:44 +02:00
/// <param name="raiseEvents">Optional parameter to raise events.
2014-08-15 14:41:25 +02:00
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
2014-02-21 16:03:32 +11:00
public void Save ( IUser entity , bool raiseEvents = true )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-01-06 11:56:53 +11:00
{
2017-09-18 15:33:13 +02:00
var saveEventArgs = new SaveEventArgs < IUser > ( entity ) ;
if ( raiseEvents & & uow . Events . DispatchCancelable ( SavingUser , this , saveEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
2014-01-06 11:56:53 +11:00
return ;
2017-05-12 14:49:44 +02:00
}
if ( string . IsNullOrWhiteSpace ( entity . Username ) )
throw new ArgumentException ( "Empty username." , nameof ( entity ) ) ;
if ( string . IsNullOrWhiteSpace ( entity . Name ) )
throw new ArgumentException ( "Empty name." , nameof ( entity ) ) ;
2014-01-06 11:56:53 +11:00
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-08-25 17:55:26 +02:00
2017-09-18 15:33:13 +02:00
//Now we have to check for backwards compat hacks, we'll need to process any groups
//to save first before we update the user since these groups might be new groups.
2017-08-25 17:55:26 +02:00
var explicitUser = entity as User ;
if ( explicitUser ! = null & & explicitUser . GroupsToSave . Count > 0 )
{
var groupRepository = uow . CreateRepository < IUserGroupRepository > ( ) ;
foreach ( var userGroup in explicitUser . GroupsToSave )
{
groupRepository . AddOrUpdate ( userGroup ) ;
}
}
2015-07-27 15:48:14 +02:00
try
{
2016-05-18 10:55:19 +02:00
repository . AddOrUpdate ( entity ) ;
2017-05-12 14:49:44 +02:00
if ( raiseEvents )
2017-09-18 15:33:13 +02:00
{
saveEventArgs . CanCancel = false ;
uow . Events . Dispatch ( SavedUser , this , saveEventArgs ) ;
}
2017-05-12 14:49:44 +02:00
// try to flush the unit of work
// ie executes the SQL but does not commit the trx yet
// so we are *not* catching commit exceptions
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2015-07-27 15:48:14 +02:00
}
catch ( DbException ex )
{
2017-05-12 14:49:44 +02:00
// if we are upgrading and an exception occurs, log and swallow it
2017-09-13 14:40:10 +02:00
if ( _isUpgrading = = false ) throw ;
2015-07-27 15:48:14 +02:00
2016-09-11 19:57:33 +02:00
Logger . Warn < UserService > ( ex , "An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored" ) ;
2017-05-12 14:49:44 +02:00
// we don't want the uow to rollback its scope! and yet
// we cannot try and uow.Commit() again as it would fail again,
// we have to bypass the uow entirely and complete its inner scope.
// (when the uow disposes, it won't complete the scope again, just dispose it)
// fixme it would be better to be able to uow.Clear() and then complete again
uow . Scope . Complete ( ) ;
2015-07-27 15:48:14 +02:00
}
2014-01-06 11:56:53 +11:00
}
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Saves a list of <see cref="IUser"/> objects
/// </summary>
/// <param name="entities"><see cref="IEnumerable{IUser}"/> to save</param>
2017-05-12 14:49:44 +02:00
/// <param name="raiseEvents">Optional parameter to raise events.
2014-08-15 14:41:25 +02:00
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
2014-02-21 16:03:32 +11:00
public void Save ( IEnumerable < IUser > entities , bool raiseEvents = true )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
var entitiesA = entities . ToArray ( ) ;
2014-01-06 11:56:53 +11:00
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2017-09-18 15:33:13 +02:00
{
var saveEventArgs = new SaveEventArgs < IUser > ( entitiesA ) ;
if ( raiseEvents & & uow . Events . DispatchCancelable ( SavingUser , this , saveEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
return ;
}
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-08-25 17:55:26 +02:00
var groupRepository = uow . CreateRepository < IUserGroupRepository > ( ) ;
foreach ( var user in entitiesA )
2014-01-06 11:56:53 +11:00
{
2017-08-25 17:55:26 +02:00
if ( string . IsNullOrWhiteSpace ( user . Username ) )
2017-05-12 14:49:44 +02:00
throw new ArgumentException ( "Empty username." , nameof ( entities ) ) ;
2017-08-25 17:55:26 +02:00
if ( string . IsNullOrWhiteSpace ( user . Name ) )
2017-05-12 14:49:44 +02:00
throw new ArgumentException ( "Empty name." , nameof ( entities ) ) ;
2017-08-25 17:55:26 +02:00
repository . AddOrUpdate ( user ) ;
//Now we have to check for backwards compat hacks
var explicitUser = user as User ;
if ( explicitUser ! = null & & explicitUser . GroupsToSave . Count > 0 )
{
foreach ( var userGroup in explicitUser . GroupsToSave )
{
groupRepository . AddOrUpdate ( userGroup ) ;
}
}
2014-01-06 11:56:53 +11:00
}
2017-05-12 14:49:44 +02:00
if ( raiseEvents )
2017-09-18 15:33:13 +02:00
{
saveEventArgs . CanCancel = false ;
uow . Events . Dispatch ( SavedUser , this , saveEventArgs ) ;
}
2017-05-12 14:49:44 +02:00
2014-01-06 11:56:53 +11:00
//commit the whole lot in one go
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2014-01-06 11:56:53 +11:00
}
2014-01-06 11:04:26 +11:00
}
2017-08-25 17:55:26 +02:00
/// <summary>
/// This is just the default user group that the membership provider will use
/// </summary>
/// <returns></returns>
public string GetDefaultMemberType ( )
{
return "writer" ;
}
2014-01-06 11:04:26 +11:00
2014-08-15 14:41:25 +02:00
/// <summary>
/// Finds a list of <see cref="IUser"/> 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{IUser}"/></returns>
2016-05-18 16:39:54 +02:00
public IEnumerable < IUser > FindByEmail ( string emailStringToMatch , long pageIndex , int pageSize , out long totalRecords , StringPropertyMatchType matchType = StringPropertyMatchType . StartsWith )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 17:30:08 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-07-11 16:29:44 +02:00
var query = uow . Query < IUser > ( ) ;
2014-01-06 17:30:08 +11:00
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 :
2017-05-12 14:49:44 +02:00
throw new ArgumentOutOfRangeException ( nameof ( matchType ) ) ;
2014-01-06 17:30:08 +11:00
}
2017-05-12 14:49:44 +02:00
return repository . GetPagedResultsByQuery ( query , pageIndex , pageSize , out totalRecords , dto = > dto . Email ) ;
2014-01-06 17:30:08 +11:00
}
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Finds a list of <see cref="IUser"/> 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{IUser}"/></returns>
2016-05-18 16:39:54 +02:00
public IEnumerable < IUser > FindByUsername ( string login , long pageIndex , int pageSize , out long totalRecords , StringPropertyMatchType matchType = StringPropertyMatchType . StartsWith )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 17:30:08 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-07-11 16:29:44 +02:00
var query = uow . Query < IUser > ( ) ;
2014-01-06 17:30:08 +11:00
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 :
2017-05-12 14:49:44 +02:00
throw new ArgumentOutOfRangeException ( nameof ( matchType ) ) ;
2014-01-06 17:30:08 +11:00
}
2017-05-12 14:49:44 +02:00
return repository . GetPagedResultsByQuery ( query , pageIndex , pageSize , out totalRecords , dto = > dto . Username ) ;
2014-01-06 17:30:08 +11:00
}
2014-01-06 11:04:26 +11:00
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Gets the total number of Users 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 Users for passed in type</returns>
2014-02-21 16:03:32 +11:00
public int GetCount ( MemberCountType countType )
2013-08-26 11:26:58 +10:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2013-08-26 11:26:58 +10:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2014-01-06 12:25:14 +11:00
IQuery < IUser > query ;
switch ( countType )
{
case MemberCountType . All :
2017-07-11 16:29:44 +02:00
query = uow . Query < IUser > ( ) ;
2016-05-18 10:55:19 +02:00
break ;
2014-01-06 12:25:14 +11:00
case MemberCountType . Online :
throw new NotImplementedException ( ) ;
//var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow);
//query =
// Query<IMember>.Builder.Where(
// x =>
// ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate &&
// ((Member)x).DateTimePropertyValue > fromDate);
//return repository.GetCountByQuery(query);
case MemberCountType . LockedOut :
2017-07-11 16:29:44 +02:00
query = uow . Query < IUser > ( ) . Where ( x = > x . IsLockedOut ) ;
2016-05-18 10:55:19 +02:00
break ;
2014-01-06 12:25:14 +11:00
case MemberCountType . Approved :
2017-07-11 16:29:44 +02:00
query = uow . Query < IUser > ( ) . Where ( x = > x . IsApproved ) ;
2016-05-18 10:55:19 +02:00
break ;
2014-01-06 12:25:14 +11:00
default :
2017-05-12 14:49:44 +02:00
throw new ArgumentOutOfRangeException ( nameof ( countType ) ) ;
2014-01-06 12:25:14 +11:00
}
2016-05-18 10:55:19 +02:00
2017-05-12 14:49:44 +02:00
return repository . GetCountByQuery ( query ) ;
2014-01-06 12:25:14 +11:00
}
2014-01-06 11:04:26 +11:00
}
2017-08-25 17:55:26 +02:00
public IDictionary < UserState , int > GetUserStates ( )
{
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
var repository = uow . CreateRepository < IUserRepository > ( ) ;
return repository . GetUserStates ( ) ;
}
}
2017-09-18 15:33:13 +02:00
public IEnumerable < IUser > GetAll ( long pageIndex , int pageSize , out long totalRecords ,
string orderBy , Direction orderDirection ,
UserState [ ] userState = null ,
string [ ] userGroups = null ,
string filter = null )
{
IQuery < IUser > filterQuery = null ;
if ( filter . IsNullOrWhiteSpace ( ) = = false )
{
filterQuery = UowProvider . DatabaseContext . Query < IUser > ( ) . Where ( x = > x . Name . Contains ( filter ) | | x . Username . Contains ( filter ) ) ;
}
return GetAll ( pageIndex , pageSize , out totalRecords , orderBy , orderDirection , userState , userGroups , null , filterQuery ) ;
}
public IEnumerable < IUser > GetAll ( long pageIndex , int pageSize , out long totalRecords ,
string orderBy , Direction orderDirection ,
UserState [ ] userState = null , string [ ] includeUserGroups = null , string [ ] excludeUserGroups = null ,
IQuery < IUser > filter = null )
2017-08-25 17:55:26 +02:00
{
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
Expression < Func < IUser , object > > sort ;
switch ( orderBy . ToUpperInvariant ( ) )
{
case "USERNAME" :
sort = member = > member . Username ;
break ;
case "LANGUAGE" :
sort = member = > member . Language ;
break ;
case "NAME" :
sort = member = > member . Name ;
break ;
case "EMAIL" :
sort = member = > member . Email ;
break ;
case "ID" :
sort = member = > member . Id ;
break ;
case "CREATEDATE" :
sort = member = > member . CreateDate ;
break ;
case "UPDATEDATE" :
sort = member = > member . UpdateDate ;
break ;
case "ISAPPROVED" :
sort = member = > member . IsApproved ;
break ;
case "ISLOCKEDOUT" :
sort = member = > member . IsLockedOut ;
break ;
case "LASTLOGINDATE" :
sort = member = > member . LastLoginDate ;
break ;
default :
throw new IndexOutOfRangeException ( "The orderBy parameter " + orderBy + " is not valid" ) ;
}
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-09-18 15:33:13 +02:00
return repository . GetPagedResultsByQuery ( null , pageIndex , pageSize , out totalRecords , sort , orderDirection , includeUserGroups , excludeUserGroups , userState , filter ) ;
2017-08-25 17:55:26 +02:00
}
}
2014-08-15 14:41:25 +02:00
/// <summary>
/// Gets a list of paged <see cref="IUser"/> 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>
2016-05-18 16:39:54 +02:00
public IEnumerable < IUser > GetAll ( long pageIndex , int pageSize , out long totalRecords )
2014-01-06 11:04:26 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-06 17:30:08 +11:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-05-12 14:49:44 +02:00
return repository . GetPagedResultsByQuery ( null , pageIndex , pageSize , out totalRecords , member = > member . Username ) ;
2013-08-26 11:26:58 +10:00
}
}
2016-09-01 12:13:58 +02:00
internal IEnumerable < IUser > GetNextUsers ( int id , int count )
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2016-09-01 12:13:58 +02:00
{
2016-11-03 10:31:44 +01:00
var repository = ( UserRepository ) uow . CreateRepository < IUserRepository > ( ) ;
2017-05-12 14:49:44 +02:00
return repository . GetNextUsers ( id , count ) ;
2016-09-01 12:13:58 +02:00
}
}
2017-08-25 17:55:26 +02:00
/// <summary>
/// Gets a list of <see cref="IUser"/> objects associated with a given group
/// </summary>
/// <param name="groupId">Id of group</param>
/// <returns><see cref="IEnumerable{IUser}"/></returns>
public IEnumerable < IUser > GetAllInGroup ( int groupId )
{
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
var repository = uow . CreateRepository < IUserRepository > ( ) ;
return repository . GetAllInGroup ( groupId ) ;
}
}
/// <summary>
/// Gets a list of <see cref="IUser"/> objects not associated with a given group
/// </summary>
/// <param name="groupId">Id of group</param>
/// <returns><see cref="IEnumerable{IUser}"/></returns>
public IEnumerable < IUser > GetAllNotInGroup ( int groupId )
{
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
{
var repository = uow . CreateRepository < IUserRepository > ( ) ;
return repository . GetAllNotInGroup ( groupId ) ;
}
}
2014-01-06 11:04:26 +11:00
#endregion
2012-11-05 14:42:21 -01:00
#region Implementation of IUserService
/// <summary>
2013-05-23 13:25:14 -02:00
/// Gets an IProfile by User Id.
2012-11-05 14:42:21 -01:00
/// </summary>
2013-05-23 13:25:14 -02:00
/// <param name="id">Id of the User to retrieve</param>
/// <returns><see cref="IProfile"/></returns>
public IProfile GetProfileById ( int id )
2012-11-05 14:42:21 -01:00
{
2017-09-18 15:33:13 +02:00
//This is called a TON. Go get the full user from cache which should already be IProfile
var fullUser = GetUserById ( id ) ;
var asProfile = fullUser as IProfile ;
return asProfile ? ? new UserProfile ( fullUser . Id , fullUser . Name ) ;
2012-11-05 14:42:21 -01:00
}
2014-08-15 11:13:00 +02:00
/// <summary>
/// Gets a profile by username
/// </summary>
/// <param name="username">Username</param>
/// <returns><see cref="IProfile"/></returns>
public IProfile GetProfileByUserName ( string username )
2013-07-03 16:52:00 +10:00
{
2017-09-13 14:40:10 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2017-08-25 17:55:26 +02:00
{
var repository = uow . CreateRepository < IUserRepository > ( ) ;
return repository . GetProfile ( username ) ;
}
2013-07-03 16:52:00 +10:00
}
2014-08-15 11:13:00 +02:00
/// <summary>
/// Gets a user by Id
/// </summary>
/// <param name="id">Id of the user to retrieve</param>
/// <returns><see cref="IUser"/></returns>
2013-07-03 16:52:00 +10:00
public IUser GetUserById ( int id )
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2013-07-03 16:52:00 +10:00
{
2016-04-28 08:48:59 +02:00
var repository = uow . CreateRepository < IUserRepository > ( ) ;
2017-08-25 17:55:26 +02:00
try
{
return repository . Get ( id ) ;
}
catch ( Exception ex )
{
if ( ex is SqlException | | ex is SqlCeException )
{
// fixme kill in v8
//we need to handle this one specific case which is when we are upgrading to 7.7 since the user group
//tables don't exist yet. This is the 'easiest' way to deal with this without having to create special
//version checks in the BackOfficeSignInManager and calling into other special overloads that we'd need
//like "GetUserById(int id, bool includeSecurityData)" which may cause confusion because the result of
//that method would not be cached.
2017-09-13 14:40:10 +02:00
if ( _isUpgrading )
2017-08-25 17:55:26 +02:00
{
//NOTE: this will not be cached
return repository . Get ( id , includeSecurityData : false ) ;
}
}
throw ;
}
}
}
public IEnumerable < IUser > GetUsersById ( params int [ ] ids )
{
if ( ids . Length < = 0 ) return Enumerable . Empty < IUser > ( ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
var repository = uow . CreateRepository < IUserRepository > ( ) ;
return repository . GetAll ( ids ) ;
2013-07-03 16:52:00 +10:00
}
}
2014-08-15 11:13:00 +02:00
2014-02-17 17:45:59 +11:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Replaces the same permission set for a single group to any number of entities
2014-02-17 17:45:59 +11:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <remarks>If no 'entityIds' are specified all permissions will be removed for the specified group.</remarks>
/// <param name="groupId">Id of the group</param>
/// <param name="permissions">Permissions as enumerable list of <see cref="char"/> If nothing is specified all permissions are removed.</param>
/// <param name="entityIds">Specify the nodes to replace permissions for. </param>
public void ReplaceUserGroupPermissions ( int groupId , IEnumerable < char > permissions , params int [ ] entityIds )
2014-02-17 17:45:59 +11:00
{
2017-08-25 17:55:26 +02:00
if ( entityIds . Length = = 0 )
return ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-02-17 17:45:59 +11:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
repository . ReplaceGroupPermissions ( groupId , permissions , entityIds ) ;
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2017-08-25 17:55:26 +02:00
uow . Events . Dispatch ( UserGroupPermissionsAssigned , this , new SaveEventArgs < EntityPermission > (
entityIds . Select (
x = > new EntityPermission (
groupId ,
x ,
permissions . Select ( p = > p . ToString ( CultureInfo . InvariantCulture ) ) . ToArray ( ) ) )
. ToArray ( ) , false ) ) ;
2014-02-17 17:45:59 +11:00
}
}
2015-05-18 19:34:01 +10:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Assigns the same permission set for a single user group to any number of entities
2015-05-18 19:34:01 +10:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="groupId">Id of the user group</param>
2015-05-18 19:34:01 +10:00
/// <param name="permission"></param>
/// <param name="entityIds">Specify the nodes to replace permissions for</param>
2017-08-25 17:55:26 +02:00
public void AssignUserGroupPermission ( int groupId , char permission , params int [ ] entityIds )
2015-05-18 19:34:01 +10:00
{
2017-08-25 17:55:26 +02:00
if ( entityIds . Length = = 0 )
return ;
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2015-05-18 19:34:01 +10:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
repository . AssignGroupPermission ( groupId , permission , entityIds ) ;
2016-05-18 10:55:19 +02:00
uow . Complete ( ) ;
2017-08-25 17:55:26 +02:00
uow . Events . Dispatch ( UserGroupPermissionsAssigned , this , new SaveEventArgs < EntityPermission > (
entityIds . Select (
x = > new EntityPermission (
groupId ,
x ,
new [ ] { permission . ToString ( CultureInfo . InvariantCulture ) } ) )
. ToArray ( ) , false ) ) ;
2015-05-18 19:34:01 +10:00
}
}
2014-08-15 11:13:00 +02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Gets all UserGroups or those specified as parameters
2014-08-15 11:13:00 +02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="ids">Optional Ids of UserGroups to retrieve</param>
/// <returns>An enumerable list of <see cref="IUserGroup"/></returns>
public IEnumerable < IUserGroup > GetAllUserGroups ( params int [ ] ids )
2014-01-23 17:11:58 +11:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-23 17:11:58 +11:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
return repository . GetAll ( ids ) . OrderBy ( x = > x . Name ) ;
2014-01-23 17:11:58 +11:00
}
}
2017-08-25 17:55:26 +02:00
public IEnumerable < IUserGroup > GetUserGroupsByAlias ( params string [ ] aliases )
2013-05-27 06:50:14 -02:00
{
2017-08-25 17:55:26 +02:00
if ( aliases . Length = = 0 ) return Enumerable . Empty < IUserGroup > ( ) ;
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2013-05-27 06:50:14 -02:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
var query = uow . Query < IUserGroup > ( ) . Where ( x = > aliases . SqlIn ( x . Alias ) ) ;
var contents = repository . GetByQuery ( query ) ;
2017-09-18 15:33:13 +02:00
return contents . WhereNotNull ( ) . ToArray ( ) ;
2013-05-27 06:50:14 -02:00
}
}
2014-08-15 11:13:00 +02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Gets a UserGroup by its Alias
2014-08-15 11:13:00 +02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="alias">Alias of the UserGroup to retrieve</param>
/// <returns><see cref="IUserGroup"/></returns>
public IUserGroup GetUserGroupByAlias ( string alias )
2014-01-23 17:11:58 +11:00
{
2017-08-25 17:55:26 +02:00
if ( string . IsNullOrWhiteSpace ( alias ) ) throw new ArgumentException ( "Value cannot be null or whitespace." , "alias" ) ;
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2014-01-23 17:11:58 +11:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
var query = uow . Query < IUserGroup > ( ) . Where ( x = > x . Alias = = alias ) ;
var contents = repository . GetByQuery ( query ) ;
return contents . FirstOrDefault ( ) ;
2014-01-23 17:11:58 +11:00
}
}
2013-05-27 06:50:14 -02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Gets a UserGroup by its Id
2013-05-27 06:50:14 -02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="id">Id of the UserGroup to retrieve</param>
/// <returns><see cref="IUserGroup"/></returns>
public IUserGroup GetUserGroupById ( int id )
2012-11-12 08:39:29 -01:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2013-05-27 06:50:14 -02:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
return repository . Get ( id ) ;
2013-05-27 06:50:14 -02:00
}
2012-11-12 08:39:29 -01:00
}
2014-08-15 11:13:00 +02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Saves a UserGroup
2014-08-15 11:13:00 +02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="userGroup">UserGroup to save</param>
/// <param name="userIds">
/// If null than no changes are made to the users who are assigned to this group, however if a value is passed in
/// than all users will be removed from this group and only these users will be added
/// </param>
2017-05-12 14:49:44 +02:00
/// <param name="raiseEvents">Optional parameter to raise events.
2014-08-15 11:13:00 +02:00
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
2017-08-25 17:55:26 +02:00
public void Save ( IUserGroup userGroup , int [ ] userIds = null , bool raiseEvents = true )
2013-07-03 19:01:37 +10:00
{
2017-05-12 14:49:44 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2014-01-06 11:56:53 +11:00
{
2017-09-18 15:33:13 +02:00
var saveEventArgs = new SaveEventArgs < IUserGroup > ( userGroup ) ;
if ( raiseEvents & & uow . Events . DispatchCancelable ( SavingUserGroup , this , saveEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
2014-01-06 11:56:53 +11:00
return ;
2017-05-12 14:49:44 +02:00
}
2014-01-06 11:56:53 +11:00
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
repository . AddOrUpdateGroupWithUsers ( userGroup , userIds ) ;
2017-05-12 14:49:44 +02:00
if ( raiseEvents )
2017-09-18 15:33:13 +02:00
{
saveEventArgs . CanCancel = false ;
uow . Events . Dispatch ( SavedUserGroup , this , saveEventArgs ) ;
}
2017-05-12 14:49:44 +02:00
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2014-01-06 11:56:53 +11:00
}
}
2014-08-15 11:13:00 +02:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Deletes a UserGroup
2014-08-15 11:13:00 +02:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="userGroup">UserGroup to delete</param>
public void DeleteUserGroup ( IUserGroup userGroup )
2013-07-03 19:01:37 +10:00
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-07-03 19:01:37 +10:00
{
2017-09-18 15:33:13 +02:00
var deleteEventArgs = new DeleteEventArgs < IUserGroup > ( userGroup ) ;
if ( uow . Events . DispatchCancelable ( DeletingUserGroup , this , deleteEventArgs ) )
2017-05-12 14:49:44 +02:00
{
uow . Complete ( ) ;
return ;
}
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
repository . Delete ( userGroup ) ;
2017-05-12 14:49:44 +02:00
2017-09-18 15:33:13 +02:00
deleteEventArgs . CanCancel = false ;
uow . Events . Dispatch ( DeletedUserGroup , this , deleteEventArgs ) ;
2017-05-12 14:49:44 +02:00
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2013-07-03 19:01:37 +10:00
}
}
/// <summary>
2014-08-15 11:13:00 +02:00
/// Removes a specific section from all users
2013-07-03 19:01:37 +10:00
/// </summary>
2014-08-15 11:13:00 +02:00
/// <remarks>This is useful when an entire section is removed from config</remarks>
/// <param name="sectionAlias">Alias of the section to remove</param>
2017-08-25 17:55:26 +02:00
public void DeleteSectionFromAllUserGroups ( string sectionAlias )
2013-07-03 19:01:37 +10:00
{
2016-05-02 12:24:13 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( ) )
2013-07-03 19:01:37 +10:00
{
2017-08-25 17:55:26 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
var assignedGroups = repository . GetGroupsAssignedToSection ( sectionAlias ) ;
foreach ( var group in assignedGroups )
2013-07-03 19:01:37 +10:00
{
//now remove the section for each user and commit
2017-08-25 17:55:26 +02:00
group . RemoveAllowedSection ( sectionAlias ) ;
repository . AddOrUpdate ( group ) ;
2013-07-03 19:01:37 +10:00
}
2017-08-25 17:55:26 +02:00
2016-05-02 12:12:21 +02:00
uow . Complete ( ) ;
2013-07-03 19:01:37 +10:00
}
}
2017-05-12 14:49:44 +02:00
2015-01-05 18:55:31 +00:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Get explicitly assigned permissions for a user and optional node ids
2015-01-05 18:55:31 +00:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="user">User to retrieve permissions for</param>
/// <param name="nodeIds">Specifiying nothing will return all permissions for all nodes</param>
/// <returns>An enumerable list of <see cref="EntityPermission"/></returns>
public EntityPermissionCollection GetPermissions ( IUser user , params int [ ] nodeIds )
2015-01-05 18:55:31 +00:00
{
2017-08-25 17:55:26 +02:00
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
return repository . GetPermissions ( user . Groups . ToArray ( ) , true , nodeIds ) ;
}
}
/// <summary>
/// Get explicitly assigned permissions for a group and optional node Ids
/// </summary>
/// <param name="groups">Groups to retrieve permissions for</param>
/// <param name="fallbackToDefaultPermissions">
/// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
/// </param>
/// <param name="nodeIds">Specifiying nothing will return all permissions for all nodes</param>
/// <returns>An enumerable list of <see cref="EntityPermission"/></returns>
private IEnumerable < EntityPermission > GetPermissions ( IReadOnlyUserGroup [ ] groups , bool fallbackToDefaultPermissions , params int [ ] nodeIds )
{
if ( groups = = null ) throw new ArgumentNullException ( nameof ( groups ) ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
{
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
return repository . GetPermissions ( groups , fallbackToDefaultPermissions , nodeIds ) ;
}
}
/// <summary>
/// Get explicitly assigned permissions for a group and optional node Ids
/// </summary>
/// <param name="groups"></param>
/// <param name="fallbackToDefaultPermissions">
/// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
/// </param>
/// <param name="nodeIds">Specifiying nothing will return all permissions for all nodes</param>
/// <returns>An enumerable list of <see cref="EntityPermission"/></returns>
public EntityPermissionCollection GetPermissions ( IUserGroup [ ] groups , bool fallbackToDefaultPermissions , params int [ ] nodeIds )
{
if ( groups = = null ) throw new ArgumentNullException ( nameof ( groups ) ) ;
using ( var uow = UowProvider . CreateUnitOfWork ( readOnly : true ) )
2015-01-05 18:55:31 +00:00
{
2017-09-13 14:40:10 +02:00
var repository = uow . CreateRepository < IUserGroupRepository > ( ) ;
2017-08-25 17:55:26 +02:00
return repository . GetPermissions ( groups . Select ( x = > x . ToReadOnlyGroup ( ) ) . ToArray ( ) , fallbackToDefaultPermissions , nodeIds ) ;
}
}
/// <summary>
/// Gets the implicit/inherited permissions for the user for the given path
/// </summary>
/// <param name="user">User to check permissions for</param>
/// <param name="path">Path to check permissions for</param>
public EntityPermissionSet GetPermissionsForPath ( IUser user , string path )
{
var nodeIds = path . GetIdsFromPathReversed ( ) ;
if ( nodeIds . Length = = 0 )
return EntityPermissionSet . Empty ( ) ;
//collect all permissions structures for all nodes for all groups belonging to the user
var groupPermissions = GetPermissionsForPath ( user . Groups . ToArray ( ) , nodeIds , fallbackToDefaultPermissions : true ) . ToArray ( ) ;
return CalculatePermissionsForPathForUser ( groupPermissions , nodeIds ) ;
}
/// <summary>
/// Gets the permissions for the provided group and path
/// </summary>
/// <param name="groups"></param>
/// <param name="path">Path to check permissions for</param>
/// <param name="fallbackToDefaultPermissions">
/// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
/// </param>
/// <returns>String indicating permissions for provided user and path</returns>
public EntityPermissionSet GetPermissionsForPath ( IUserGroup [ ] groups , string path , bool fallbackToDefaultPermissions = false )
{
var nodeIds = path . GetIdsFromPathReversed ( ) ;
if ( nodeIds . Length = = 0 )
return EntityPermissionSet . Empty ( ) ;
//collect all permissions structures for all nodes for all groups
var groupPermissions = GetPermissionsForPath ( groups . Select ( x = > x . ToReadOnlyGroup ( ) ) . ToArray ( ) , nodeIds , fallbackToDefaultPermissions : true ) . ToArray ( ) ;
return CalculatePermissionsForPathForUser ( groupPermissions , nodeIds ) ;
}
private EntityPermissionCollection GetPermissionsForPath ( IReadOnlyUserGroup [ ] groups , int [ ] pathIds , bool fallbackToDefaultPermissions = false )
{
if ( pathIds . Length = = 0 )
return new EntityPermissionCollection ( Enumerable . Empty < EntityPermission > ( ) ) ;
//get permissions for all nodes in the path by group
var permissions = GetPermissions ( groups , fallbackToDefaultPermissions , pathIds )
. GroupBy ( x = > x . UserGroupId ) ;
return new EntityPermissionCollection (
permissions . Select ( x = > GetPermissionsForPathForGroup ( x , pathIds , fallbackToDefaultPermissions ) ) ) ;
}
/// <summary>
/// This performs the calculations for inherited nodes based on this http://issues.umbraco.org/issue/U4-10075#comment=67-40085
/// </summary>
/// <param name="groupPermissions"></param>
/// <param name="pathIds"></param>
/// <returns></returns>
internal static EntityPermissionSet CalculatePermissionsForPathForUser (
EntityPermission [ ] groupPermissions ,
int [ ] pathIds )
{
// not sure this will ever happen, it shouldn't since this should return defaults, but maybe those are empty?
if ( groupPermissions . Length = = 0 | | pathIds . Length = = 0 )
return EntityPermissionSet . Empty ( ) ;
//The actual entity id being looked at (deepest part of the path)
var entityId = pathIds [ 0 ] ;
var resultPermissions = new EntityPermissionCollection ( ) ;
//create a grouped by dictionary of another grouped by dictionary
var permissionsByGroup = groupPermissions
. GroupBy ( x = > x . UserGroupId )
. ToDictionary (
x = > x . Key ,
x = > x . GroupBy ( a = > a . EntityId ) . ToDictionary ( a = > a . Key , a = > a . ToArray ( ) ) ) ;
//iterate through each group
foreach ( var byGroup in permissionsByGroup )
{
var added = false ;
//iterate deepest to shallowest
foreach ( var pathId in pathIds )
2015-01-05 18:55:31 +00:00
{
2017-08-25 17:55:26 +02:00
EntityPermission [ ] permissionsForNodeAndGroup ;
if ( byGroup . Value . TryGetValue ( pathId , out permissionsForNodeAndGroup ) = = false )
continue ;
//In theory there will only be one EntityPermission in this group
// but there's nothing stopping the logic of this method
// from having more so we deal with it here
foreach ( var entityPermission in permissionsForNodeAndGroup )
{
if ( entityPermission . IsDefaultPermissions = = false )
{
//explicit permision found so we'll append it and move on, the collection is a hashset anyways
//so only supports adding one element per groupid/contentid
resultPermissions . Add ( entityPermission ) ;
added = true ;
break ;
}
}
//if the permission has been added for this group and this branch then we can exit this loop
if ( added )
break ;
2015-01-05 18:55:31 +00:00
}
2017-08-25 17:55:26 +02:00
if ( added = = false & & byGroup . Value . Count > 0 )
{
//if there was no explicit permissions assigned in this branch for this group, then we will
//add the group's default permissions
resultPermissions . Add ( byGroup . Value [ entityId ] [ 0 ] ) ;
}
2015-01-05 18:55:31 +00:00
}
2017-08-25 17:55:26 +02:00
var permissionSet = new EntityPermissionSet ( entityId , resultPermissions ) ;
return permissionSet ;
2017-05-12 14:49:44 +02:00
}
2013-07-03 19:01:37 +10:00
2014-01-08 15:42:49 +11:00
/// <summary>
2017-08-25 17:55:26 +02:00
/// Returns the resulting permission set for a group for the path based on all permissions provided for the branch
2014-01-08 15:42:49 +11:00
/// </summary>
2017-08-25 17:55:26 +02:00
/// <param name="pathPermissions">
/// The collective set of permissions provided to calculate the resulting permissions set for the path
/// based on a single group
/// </param>
/// <param name="pathIds">Must be ordered deepest to shallowest (right to left)</param>
/// <param name="fallbackToDefaultPermissions">
/// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
/// </param>
/// <returns></returns>
internal static EntityPermission GetPermissionsForPathForGroup (
IEnumerable < EntityPermission > pathPermissions ,
int [ ] pathIds ,
bool fallbackToDefaultPermissions = false )
2014-01-08 15:42:49 +11:00
{
2017-08-25 17:55:26 +02:00
//get permissions for all nodes in the path
var permissionsByEntityId = pathPermissions . ToDictionary ( x = > x . EntityId , x = > x ) ;
//then the permissions assigned to the path will be the 'deepest' node found that has permissions
foreach ( var id in pathIds )
2014-01-08 15:42:49 +11:00
{
2017-08-25 17:55:26 +02:00
EntityPermission permission ;
if ( permissionsByEntityId . TryGetValue ( id , out permission ) )
{
//don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found)
if ( permission . IsDefaultPermissions = = false )
return permission ;
}
}
2014-01-08 15:42:49 +11:00
2017-08-25 17:55:26 +02:00
//if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified
if ( fallbackToDefaultPermissions = = false )
return null ;
return permissionsByEntityId [ pathIds [ 0 ] ] ;
}
/// <summary>
/// Checks in a set of permissions associated with a user for those related to a given nodeId
/// </summary>
/// <param name="permissions">The set of permissions</param>
/// <param name="nodeId">The node Id</param>
/// <param name="assignedPermissions">The permissions to return</param>
/// <returns>True if permissions for the given path are found</returns>
public static bool TryGetAssignedPermissionsForNode ( IList < EntityPermission > permissions ,
int nodeId ,
out string assignedPermissions )
{
if ( permissions . Any ( x = > x . EntityId = = nodeId ) )
{
var found = permissions . First ( x = > x . EntityId = = nodeId ) ;
var assignedPermissionsArray = found . AssignedPermissions . ToList ( ) ;
// Working with permissions assigned directly to a user AND to their groups, so maybe several per node
// and we need to get the most permissive set
foreach ( var permission in permissions . Where ( x = > x . EntityId = = nodeId ) . Skip ( 1 ) )
2014-01-08 15:42:49 +11:00
{
2017-08-25 17:55:26 +02:00
AddAdditionalPermissions ( assignedPermissionsArray , permission . AssignedPermissions ) ;
2014-01-08 15:42:49 +11:00
}
2017-08-25 17:55:26 +02:00
assignedPermissions = string . Join ( "" , assignedPermissionsArray ) ;
return true ;
2014-01-08 15:42:49 +11:00
}
2017-08-25 17:55:26 +02:00
assignedPermissions = string . Empty ;
return false ;
}
private static void AddAdditionalPermissions ( List < string > assignedPermissions , string [ ] additionalPermissions )
{
var permissionsToAdd = additionalPermissions
. Where ( x = > assignedPermissions . Contains ( x ) = = false ) ;
assignedPermissions . AddRange ( permissionsToAdd ) ;
2014-01-08 15:42:49 +11:00
}
2012-11-05 14:42:21 -01:00
#endregion
2017-05-12 14:49:44 +02:00
2013-08-09 13:23:27 +10:00
/// <summary>
2013-07-03 19:01:37 +10:00
/// Occurs before Save
2013-05-27 06:50:14 -02:00
/// </summary>
2014-01-23 10:41:57 +11:00
public static event TypedEventHandler < IUserService , SaveEventArgs < IUser > > SavingUser ;
2013-05-23 13:25:14 -02:00
2013-07-03 19:01:37 +10:00
/// <summary>
/// Occurs after Save
/// </summary>
2014-01-23 10:41:57 +11:00
public static event TypedEventHandler < IUserService , SaveEventArgs < IUser > > SavedUser ;
2013-05-23 13:25:14 -02:00
2014-01-06 11:56:53 +11:00
/// <summary>
/// Occurs before Delete
/// </summary>
2014-01-23 10:41:57 +11:00
public static event TypedEventHandler < IUserService , DeleteEventArgs < IUser > > DeletingUser ;
2012-11-05 14:42:21 -01:00
2014-01-06 11:56:53 +11:00
/// <summary>
/// Occurs after Delete
/// </summary>
2014-01-23 10:41:57 +11:00
public static event TypedEventHandler < IUserService , DeleteEventArgs < IUser > > DeletedUser ;
2013-07-03 19:01:37 +10:00
/// <summary>
/// Occurs before Save
/// </summary>
2017-08-25 17:55:26 +02:00
public static event TypedEventHandler < IUserService , SaveEventArgs < IUserGroup > > SavingUserGroup ;
2013-07-03 19:01:37 +10:00
/// <summary>
/// Occurs after Save
/// </summary>
2017-08-25 17:55:26 +02:00
public static event TypedEventHandler < IUserService , SaveEventArgs < IUserGroup > > SavedUserGroup ;
2014-01-06 11:56:53 +11:00
/// <summary>
/// Occurs before Delete
/// </summary>
2017-08-25 17:55:26 +02:00
public static event TypedEventHandler < IUserService , DeleteEventArgs < IUserGroup > > DeletingUserGroup ;
2014-01-06 11:56:53 +11:00
/// <summary>
/// Occurs after Delete
/// </summary>
2017-08-25 17:55:26 +02:00
public static event TypedEventHandler < IUserService , DeleteEventArgs < IUserGroup > > DeletedUserGroup ;
//TODO: still don't know if we need this yet unless we start caching permissions, but that also means we'll need another
// event on the ContentService since there's a method there to modify node permissions too, or we can proxy events if needed.
internal static event TypedEventHandler < IUserService , SaveEventArgs < EntityPermission > > UserGroupPermissionsAssigned ;
2012-11-05 14:42:21 -01:00
}
2015-01-05 18:55:31 +00:00
}