using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { /// /// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users. /// public class UserService : IUserService { private readonly RepositoryFactory _repositoryFactory; private readonly IDatabaseUnitOfWorkProvider _uowProvider; public UserService(RepositoryFactory repositoryFactory) : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) { } public UserService(IDatabaseUnitOfWorkProvider provider) : this(provider, new RepositoryFactory()) { } public UserService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) { _repositoryFactory = repositoryFactory; _uowProvider = provider; } #region Implementation of IMembershipUserService /// /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll return the first type that is not an admin, otherwise if there's only one /// we will return that one. /// /// public string GetDefaultMemberType() { using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork())) { var types = repository.GetAll().Select(x => x.Alias).ToArray(); if (types.Any() == false) { throw new EntityNotFoundException("No member types could be resolved"); } if (types.InvariantContains("writer")) { return types.First(x => x.InvariantEquals("writer")); } if (types.Length == 1) { return types.First(); } //first that is not admin return types.First(x => x.InvariantEquals("admin") == false); } } public bool Exists(string username) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { return repository.Exists(username); } } public IUser CreateMemberWithIdentity(string username, string email, string password, IUserType userType, bool raiseEvents = true) { if (userType == null) throw new ArgumentNullException("userType"); //TODO: PUT lock here!! var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { var loginExists = uow.Database.ExecuteScalar("SELECT COUNT(id) FROM umbracoUser WHERE userLogin = @Login", new { Login = username }) != 0; if (loginExists) throw new ArgumentException("Login already exists"); var user = new User(userType) { DefaultToLiveEditing = false, Email = email, Language = Configuration.GlobalSettings.DefaultUILanguage, Name = username, Password = password, DefaultPermissions = userType.Permissions, Username = username, StartContentId = -1, StartMediaId = -1, IsLockedOut = false, IsApproved = true }; if (raiseEvents) { if (SavingUser.IsRaisedEventCancelled(new SaveEventArgs(user), this)) return user; } repository.AddOrUpdate(user); uow.Commit(); if (raiseEvents) SavedUser.RaiseEvent(new SaveEventArgs(user, false), this); return user; } } public IUser CreateMemberWithIdentity(string username, string email, string password, string memberTypeAlias, bool raiseEvents = true) { var userType = GetUserTypeByAlias(memberTypeAlias); if (userType == null) { throw new EntityNotFoundException("The user type " + memberTypeAlias + " could not be resolved"); } return CreateMemberWithIdentity(username, email, password, userType); } public IUser GetById(int id) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { var user = repository.Get((int)id); return user; } } public IUser GetByProviderKey(object id) { var asInt = id.TryConvertTo(); if (asInt.Success) { return GetById((int)id); } return null; } public IUser GetByEmail(string email) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Email.Equals(email)); var user = repository.GetByQuery(query).FirstOrDefault(); return user; } } public IUser GetByUsername(string login) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Username.Equals(login)); return repository.GetByQuery(query).FirstOrDefault(); } } /// /// This disables and renames the user, it does not delete them, use the overload to delete them /// /// public void Delete(IUser membershipUser) { //disable membershipUser.IsApproved = false; //can't rename if it's going to take up too many chars if (membershipUser.Username.Length + 9 <= 125) { membershipUser.Username = DateTime.Now.ToString("yyyyMMdd") + "_" + membershipUser.Username; } Save(membershipUser); //clear out the user logins! var uow = _uowProvider.GetUnitOfWork(); uow.Database.Execute("delete from umbracoUserLogins where userID = @id", new {id = membershipUser.Id}); } /// /// To permanently delete the user pass in true, otherwise they will just be disabled /// /// /// public void Delete(IUser user, bool deletePermanently) { if (deletePermanently == false) { Delete(user); } else { if (DeletingUser.IsRaisedEventCancelled(new DeleteEventArgs(user), this)) return; var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { repository.Delete(user); uow.Commit(); } DeletedUser.RaiseEvent(new DeleteEventArgs(user, false), this); } } public void Save(IUser membershipUser, bool raiseEvents = true) { if (raiseEvents) { if (SavingUser.IsRaisedEventCancelled(new SaveEventArgs(membershipUser), this)) return; } var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { repository.AddOrUpdate(membershipUser); uow.Commit(); } if (raiseEvents) SavedUser.RaiseEvent(new SaveEventArgs(membershipUser, false), this); } public void Save(IEnumerable members, bool raiseEvents = true) { if (raiseEvents) { if (SavingUser.IsRaisedEventCancelled(new SaveEventArgs(members), this)) return; } var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { foreach (var member in members) { repository.AddOrUpdate(member); } //commit the whole lot in one go uow.Commit(); } if (raiseEvents) SavedUser.RaiseEvent(new SaveEventArgs(members, false), this); } public IEnumerable FindMembersByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { var query = new Query(); 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("matchType"); } return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Email); } } public IEnumerable FindMembersByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { var query = new Query(); 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("matchType"); } return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Username); } } public int GetMemberCount(MemberCountType countType) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { IQuery query; switch (countType) { case MemberCountType.All: query = new Query(); return repository.Count(query); case MemberCountType.Online: throw new NotImplementedException(); //var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); //query = // Query.Builder.Where( // x => // ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && // ((Member)x).DateTimePropertyValue > fromDate); //return repository.GetCountByQuery(query); case MemberCountType.LockedOut: query = Query.Builder.Where( x => x.IsLockedOut); return repository.GetCountByQuery(query); case MemberCountType.Approved: query = Query.Builder.Where( x => x.IsApproved); return repository.GetCountByQuery(query); default: throw new ArgumentOutOfRangeException("countType"); } } } public IEnumerable GetAllMembers(int pageIndex, int pageSize, out int totalRecords) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Username); } } #endregion #region Implementation of IUserService /// /// Gets an IProfile by User Id. /// /// Id of the User to retrieve /// public IProfile GetProfileById(int id) { var user = GetUserById(id); return user.ProfileData; } public IProfile GetProfileByUserName(string login) { var user = GetByUsername(login); return user.ProfileData; } public IUser GetUserById(int id) { using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork())) { return repository.Get(id); } } /// /// Assigns the same permission set for a single user to any number of entities /// /// /// /// public void AssignUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { repository.AssignUserPermissions(userId, permissions, entityIds); } } public IEnumerable GetAllUserTypes(params int[] ids) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserTypeRepository(uow)) { return repository.GetAll(ids); } } /// /// Gets an IUserType by its Alias /// /// Alias of the UserType to retrieve /// public IUserType GetUserTypeByAlias(string alias) { using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Alias == alias); var contents = repository.GetByQuery(query); return contents.SingleOrDefault(); } } public IUserType GetUserTypeById(int id) { using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork())) { return repository.Get(id); } } /// /// Gets an IUserType by its Name /// /// Name of the UserType to retrieve /// public IUserType GetUserTypeByName(string name) { using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.Name == name); var contents = repository.GetByQuery(query); return contents.SingleOrDefault(); } } public void SaveUserType(IUserType userType, bool raiseEvents = true) { if (raiseEvents) { if (SavingUserType.IsRaisedEventCancelled(new SaveEventArgs(userType), this)) return; } var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserTypeRepository(uow)) { repository.AddOrUpdate(userType); uow.Commit(); } if (raiseEvents) SavedUserType.RaiseEvent(new SaveEventArgs(userType, false), this); } public void DeleteUserType(IUserType userType) { if (DeletingUserType.IsRaisedEventCancelled(new DeleteEventArgs(userType), this)) return; var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserTypeRepository(uow)) { repository.Delete(userType); uow.Commit(); } DeletedUserType.RaiseEvent(new DeleteEventArgs(userType, false), this); } /// /// This is useful for when a section is removed from config /// /// public void DeleteSectionFromAllUsers(string sectionAlias) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { var assignedUsers = repository.GetUsersAssignedToSection(sectionAlias); foreach (var user in assignedUsers) { //now remove the section for each user and commit user.RemoveAllowedSection(sectionAlias); repository.AddOrUpdate(user); } uow.Commit(); } } /// /// Returns permissions for a given user for any number of nodes /// /// /// /// /// /// If no permissions are found for a particular entity then the user's default permissions will be applied /// public IEnumerable GetPermissions(IUser user, params int[] nodeIds) { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateUserRepository(uow)) { var explicitPermissions = repository.GetUserPermissionsForEntities(user.Id, nodeIds); //if no permissions are assigned to a particular node then we will fill in those permissions with the user's defaults var result = new List(explicitPermissions); var missingIds = nodeIds.Except(result.Select(x => x.EntityId)); foreach (var id in missingIds) { result.Add( new EntityPermission( user.Id, id, user.DefaultPermissions.ToArray())); } return result; } } #endregion /// /// Occurs before Save /// public static event TypedEventHandler> SavingUser; /// /// Occurs after Save /// public static event TypedEventHandler> SavedUser; /// /// Occurs before Delete /// public static event TypedEventHandler> DeletingUser; /// /// Occurs after Delete /// public static event TypedEventHandler> DeletedUser; /// /// Occurs before Save /// public static event TypedEventHandler> SavingUserType; /// /// Occurs after Save /// public static event TypedEventHandler> SavedUserType; /// /// Occurs before Delete /// public static event TypedEventHandler> DeletingUserType; /// /// Occurs after Delete /// public static event TypedEventHandler> DeletedUserType; } }