From 83e90969b3d3d92c726dc24b0e5f9605ef05074a Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 29 Mar 2018 22:15:46 +1100 Subject: [PATCH] i guess trying to rename the thing didn't work --- .../IO/FileSystemProviderManager.cs | 366 ----- src/Umbraco.Core/Services/UserService.cs | 1341 ----------------- 2 files changed, 1707 deletions(-) delete mode 100644 src/Umbraco.Core/IO/FileSystemProviderManager.cs delete mode 100644 src/Umbraco.Core/Services/UserService.cs diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs deleted file mode 100644 index 0e9e431b0f..0000000000 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ /dev/null @@ -1,366 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using Umbraco.Core.Configuration; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.IO -{ - public class FileSystemProviderManager - { - private readonly IFileSystemProvidersSection _config; - private readonly ConcurrentSet _wrappers = new ConcurrentSet(); - - private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _filesystems = new ConcurrentDictionary(); - - private ShadowWrapper _macroPartialFileSystem; - private ShadowWrapper _partialViewsFileSystem; - private ShadowWrapper _macroScriptsFileSystem; - private ShadowWrapper _userControlsFileSystem; - private ShadowWrapper _stylesheetsFileSystem; - private ShadowWrapper _scriptsFileSystem; - private ShadowWrapper _xsltFileSystem; - private ShadowWrapper _masterPagesFileSystem; - private ShadowWrapper _mvcViewsFileSystem; - private ShadowWrapper _javaScriptLibraryFileSystem; - - #region Singleton & Constructor - - private static volatile FileSystemProviderManager _instance; - private static readonly object _instanceLocker = new object(); - - public static FileSystemProviderManager Current - { - get - { - if (_instance != null) return _instance; - lock (_instanceLocker) - { - return _instance ?? (_instance = new FileSystemProviderManager()); - } - } - } - - /// - /// For tests only, allows setting the value of the singleton "Current" property - /// - /// - public static void SetCurrent(FileSystemProviderManager instance) - { - lock (_instanceLocker) - { - _instance = instance; - } - } - - internal static void ResetCurrent() - { - lock (_instanceLocker) - { - if (_instance != null) - _instance.Reset(); - } - } - - // for tests only, totally unsafe - private void Reset() - { - _wrappers.Clear(); - _providerLookup.Clear(); - _filesystems.Clear(); - CreateWellKnownFileSystems(); - } - - private IScopeProviderInternal ScopeProvider - { - // this is bad, but enough for now, and we'll refactor - // in v8 when we'll get rid of this class' singleton - // beware: means that we capture the "current" scope provider - take care in tests! - get { return ApplicationContext.Current == null ? null : ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; } - } - - /// - /// Constructor that can be used for tests - /// - /// - public FileSystemProviderManager(IFileSystemProvidersSection configSection) - { - if (configSection == null) throw new ArgumentNullException("configSection"); - _config = configSection; - CreateWellKnownFileSystems(); - } - - /// - /// Default constructor that will read the config from the locally found config section - /// - public FileSystemProviderManager() - { - _config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders"); - CreateWellKnownFileSystems(); - } - - private void CreateWellKnownFileSystems() - { - var macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials); - var partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews); - var macroScriptsFileSystem = new PhysicalFileSystem(SystemDirectories.MacroScripts); - var userControlsFileSystem = new PhysicalFileSystem(SystemDirectories.UserControls); - var stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css); - var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); - var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt); - var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); - var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); - var javaScriptLibraryFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib")); - - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider); - _macroScriptsFileSystem = new ShadowWrapper(macroScriptsFileSystem, "macroScripts", ScopeProvider); - _userControlsFileSystem = new ShadowWrapper(userControlsFileSystem, "usercontrols", ScopeProvider); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", ScopeProvider); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", ScopeProvider); - _xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider); - _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider); - _javaScriptLibraryFileSystem = new ShadowWrapper(javaScriptLibraryFileSystem, "Lib", ScopeProvider); - - // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again - MediaFileSystem = GetFileSystemProvider(); - } - - #endregion - - #region Well-Known FileSystems - - public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } } - public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } } - // Legacy /macroScripts folder - public IFileSystem2 MacroScriptsFileSystem { get { return _macroScriptsFileSystem; } } - // Legacy /usercontrols folder - public IFileSystem2 UserControlsFileSystem { get { return _userControlsFileSystem; } } - public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } } - public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } } - public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } } - public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } } - public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } } - internal IFileSystem2 JavaScriptLibraryFileSystem { get { return _javaScriptLibraryFileSystem; } } - public MediaFileSystem MediaFileSystem { get; private set; } - - #endregion - - #region Providers - - /// - /// used to cache the lookup of how to construct this object so we don't have to reflect each time. - /// - private class ProviderConstructionInfo - { - public object[] Parameters { get; set; } - public ConstructorInfo Constructor { get; set; } - //public string ProviderAlias { get; set; } - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - public IFileSystem GetUnderlyingFileSystemProvider(string alias) - { - return GetUnderlyingFileSystemProvider(alias, null); - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// /// A fallback creator for the filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - private IFileSystem GetUnderlyingFileSystemProvider(string alias, Func fallback) - { - // either get the constructor info from cache or create it and add to cache - var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback)); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func fallback) - { - var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func fallback) - { - // get config - IFileSystemProviderElement providerConfig; - - if (_config.Providers.TryGetValue(alias, out providerConfig) == false) - { - if (fallback != null) return null; - throw new ArgumentException(string.Format("No provider found with alias {0}.", alias)); - } - - // get the filesystem type - var providerType = Type.GetType(providerConfig.Type); - if (providerType == null) - throw new InvalidOperationException(string.Format("Could not find type {0}.", providerConfig.Type)); - - // ensure it implements IFileSystem - if (providerType.IsAssignableFrom(typeof(IFileSystem))) - throw new InvalidOperationException(string.Format("Type {0} does not implement IFileSystem.", providerType.FullName)); - - // find a ctor matching the config parameters - var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0; - var constructor = providerType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name))); - if (constructor == null) - throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount)); - - var parameters = new object[paramCount]; - - if (providerConfig.Parameters != null) - { - var allKeys = providerConfig.Parameters.Keys.ToArray(); - for (var i = 0; i < paramCount; i++) - parameters[i] = providerConfig.Parameters[allKeys[i]]; - } - - return new ProviderConstructionInfo - { - Constructor = constructor, - Parameters = parameters, - //ProviderAlias = s - }; - } - - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider() - where TFileSystem : FileSystemWrapper - { - return GetFileSystemProvider(null); - } - - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A fallback creator for the inner filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// The fallback creator is used only if nothing is configured. - /// Ideally, this should cache the instances, but that would break backward compatibility, so we - /// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller - /// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains - /// its own shadow and having multiple instances would lead to inconsistencies. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider(Func fallback) - where TFileSystem : FileSystemWrapper - { - var alias = GetFileSystemAlias(); - return (TFileSystem) _filesystems.GetOrAdd(alias, _ => - { - // gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return - // so we are double-wrapping here - // could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe - var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback); - var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, ScopeProvider); - var fs = (IFileSystem2) Activator.CreateInstance(typeof (TFileSystem), shadowWrapper); - _wrappers.Add(shadowWrapper); // keeping a reference to the wrapper - return fs; - }); - } - - private string GetFileSystemAlias() - { - var fsType = typeof(TFileSystem); - - // validate the ctor - var constructor = fsType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(x.GetParameters().Single().ParameterType)); - if (constructor == null) - throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + "."); - - // find the attribute and get the alias - var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault(); - if (attr == null) - throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute."); - - return attr.Alias; - } - - #endregion - - #region Shadow - - internal ICompletable Shadow(Guid id) - { - var typed = _wrappers.ToArray(); - var wrappers = new ShadowWrapper[typed.Length + 9]; - var i = 0; - while (i < typed.Length) wrappers[i] = typed[i++]; - wrappers[i++] = _macroPartialFileSystem; - wrappers[i++] = _macroScriptsFileSystem; - wrappers[i++] = _partialViewsFileSystem; - wrappers[i++] = _stylesheetsFileSystem; - wrappers[i++] = _scriptsFileSystem; - wrappers[i++] = _userControlsFileSystem; - wrappers[i++] = _xsltFileSystem; - wrappers[i++] = _masterPagesFileSystem; - wrappers[i] = _mvcViewsFileSystem; - - return new ShadowFileSystems(id, wrappers); - } - - #endregion - - private class ConcurrentSet - where T : class - { - private readonly HashSet _set = new HashSet(); - - public void Add(T item) - { - lock (_set) - { - _set.Add(item); - } - } - - public void Clear() - { - lock (_set) - { - _set.Clear(); - } - } - - public T[] ToArray() - { - lock (_set) - { - return _set.ToArray(); - } - } - } - } -} diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs deleted file mode 100644 index e06f0be799..0000000000 --- a/src/Umbraco.Core/Services/UserService.cs +++ /dev/null @@ -1,1341 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data.Common; -using System.Data.SqlClient; -using System.Data.SqlServerCe; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Security; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users. - /// - public class UserService : ScopeRepositoryService, IUserService - { - //TODO: We need to change the isUpgrading flag to use an app state enum as described here: http://issues.umbraco.org/issue/U4-6816 - // in the meantime, we will use a boolean which we are currently using during upgrades to ensure that a user object is not persisted during this phase, otherwise - // exceptions can occur if the db is not in it's correct state. - internal bool IsUpgrading { get; set; } - - public UserService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - IsUpgrading = false; - } - - #region Implementation of IMembershipUserService - - /// - /// Checks if a User with the username exists - /// - /// Username to check - /// True if the User exists otherwise False - public bool Exists(string username) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.Exists(username); - } - } - - /// - /// Creates a new User - /// - /// The user will be saved in the database and returned with an Id - /// Username of the user to create - /// Email of the user to create - /// - public IUser CreateUserWithIdentity(string username, string email) - { - return CreateUserWithIdentity(username, email, string.Empty); - } - - /// - /// Creates and persists a new - /// - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Not used for users - /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) - { - return CreateUserWithIdentity(username, email, passwordValue); - } - - /// - /// Creates and persists a new - /// - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Alias of the Type - /// Is the member approved - /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) - { - return CreateUserWithIdentity(username, email, passwordValue, isApproved); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Is the user approved - /// - private IUser CreateUserWithIdentity(string username, string email, string passwordValue, bool isApproved = true) - { - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentException("Cannot create user with empty username."); - } - - //TODO: PUT lock here!! - - using (var uow = UowProvider.GetUnitOfWork()) - { - 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 - { - DefaultToLiveEditing = false, - Email = email, - Language = GlobalSettings.DefaultUILanguage, - Name = username, - RawPasswordValue = passwordValue, - Username = username, - IsLockedOut = false, - IsApproved = isApproved - }; - - var saveEventArgs = new SaveEventArgs(user); - if (uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) - { - uow.Commit(); - return user; - } - - repository.AddOrUpdate(user); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedUser, this, saveEventArgs); - - return user; - } - } - - /// - /// Gets a User by its integer id - /// - /// Id - /// - public IUser GetById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - var result = repository.Get(id); - return result; - } - } - - /// - /// Gets an by its provider key - /// - /// Id to use for retrieval - /// - public IUser GetByProviderKey(object id) - { - var asInt = id.TryConvertTo(); - if (asInt.Success) - { - return GetById((int)id); - } - - return null; - } - - /// - /// Get an by email - /// - /// Email to use for retrieval - /// - public IUser GetByEmail(string email) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - var query = Query.Builder.Where(x => x.Email.Equals(email)); - return repository.GetByQuery(query).FirstOrDefault(); - } - } - - /// - /// Get an by username - /// - /// Username to use for retrieval - /// - public IUser GetByUsername(string username) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - - try - { - return repository.GetByUsername(username, includeSecurityData: true); - } - catch (DbException ex) - { - //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. - if (ApplicationContext.Current.IsUpgrading) - { - //NOTE: this will not be cached - return repository.GetByUsername(username, includeSecurityData: false); - } - throw; - } - } - } - - /// - /// Disables an - /// - /// to disable - public void Delete(IUser membershipUser) - { - //disable - membershipUser.IsApproved = false; - - Save(membershipUser); - } - - [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)] - public void SavePassword(IUser user, string password) - { - if (user == null) throw new ArgumentNullException("user"); - - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - - 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); - - //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; - user.UpdateDate = result.UpdateDate; - } - } - - /// - /// Deletes or disables a User - /// - /// to delete - /// True to permanently delete the user, False to disable the user - public void Delete(IUser user, bool deletePermanently) - { - if (deletePermanently == false) - { - Delete(user); - } - else - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(user); - if (uow.Events.DispatchCancelable(DeletingUser, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateUserRepository(uow); - repository.Delete(user); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedUser, this, deleteEventArgs); - } - } - } - - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IUser entity, bool raiseEvents = true) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(entity); - if (raiseEvents && uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - if (string.IsNullOrWhiteSpace(entity.Username)) - { - throw new ArgumentException("Cannot save user with empty username."); - } - - if (string.IsNullOrWhiteSpace(entity.Name)) - { - throw new ArgumentException("Cannot save user with empty name."); - } - - //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. - var explicitUser = entity as User; - if (explicitUser != null && explicitUser.GroupsToSave.Count > 0) - { - var groupRepository = RepositoryFactory.CreateUserGroupRepository(uow); - foreach (var userGroup in explicitUser.GroupsToSave) - { - groupRepository.AddOrUpdate(userGroup); - } - } - - var repository = RepositoryFactory.CreateUserRepository(uow); - repository.AddOrUpdate(entity); - - try - { - // 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 - uow.Commit(); - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedUser, this, saveEventArgs); - } - } - catch (DbException ex) - { - // if we are upgrading and an exception occurs, log and swallow it - if (IsUpgrading == false) throw; - Logger.WarnWithException("An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored", ex); - - // 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) - uow.Scope.Complete(); - } - } - } - - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IEnumerable entities, bool raiseEvents = true) - { - var asArray = entities.ToArray(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(asArray); - if (raiseEvents) - { - if (uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) - { - uow.Commit(); - return; - } - } - var repository = RepositoryFactory.CreateUserRepository(uow); - var groupRepository = RepositoryFactory.CreateUserGroupRepository(uow); - foreach (var user in asArray) - { - if (string.IsNullOrWhiteSpace(user.Username)) - { - throw new ArgumentException("Cannot save user with empty username."); - } - if (string.IsNullOrWhiteSpace(user.Name)) - { - throw new ArgumentException("Cannot save user with empty name."); - } - 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); - } - } - } - //commit the whole lot in one go - uow.Commit(); - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedUser, this, saveEventArgs); - } - } - } - - /// - /// This is just the default user group that the membership provider will use - /// - /// - public string GetDefaultMemberType() - { - return "writer"; - } - - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - 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); - } - } - - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - 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); - } - } - - /// - /// Gets the total number of Users based on the count type - /// - /// - /// 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. - /// - /// to count by - /// with number of Users for passed in type - public int GetCount(MemberCountType countType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - - IQuery query; - int ret; - switch (countType) - { - case MemberCountType.All: - query = new Query(); - ret = repository.Count(query); - break; - 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); - ret = repository.GetCountByQuery(query); - break; - case MemberCountType.Approved: - query = Query.Builder.Where(x => x.IsApproved); - ret = repository.GetCountByQuery(query); - break; - default: - throw new ArgumentOutOfRangeException("countType"); - } - - return ret; - } - } - - public Guid CreateLoginSession(int userId, string requestingIpAddress) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - var session = repository.CreateLoginSession(userId, requestingIpAddress); - uow.Commit(); - return session; - } - } - - public int ClearLoginSessions(int userId) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - var count = repository.ClearLoginSessions(userId); - uow.Commit(); - return count; - } - } - - public void ClearLoginSession(Guid sessionId) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - repository.ClearLoginSession(sessionId); - uow.Commit(); - } - } - - public bool ValidateLoginSession(int userId, Guid sessionId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.ValidateLoginSession(userId, sessionId); - } - } - - public IDictionary GetUserStates() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.GetUserStates(); - } - } - - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, - UserState[] userState = null, - string[] userGroups = null, - string filter = null) - { - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter)); - } - return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, filterQuery); - } - - /// - /// Get paged users - /// - /// - /// - /// - /// - /// - /// - /// - /// A filter to only include user that belong to these user groups - /// - /// - /// A filter to only include users that do not belong to these user groups - /// - /// - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, - UserState[] userState = null, - string[] includeUserGroups = null, - string[] excludeUserGroups = null, - IQuery filter = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - Expression> 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 = RepositoryFactory.CreateUserRepository(uow); - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, orderDirection, includeUserGroups, excludeUserGroups, userState, filter); - } - } - - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Username); - } - } - - internal IEnumerable GetNextUsers(int id, int count) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = (UserRepository)RepositoryFactory.CreateUserRepository(uow); - return repository.GetNextUsers(id, count); - } - } - - /// - /// Gets a list of objects associated with a given group - /// - /// Id of group - /// - public IEnumerable GetAllInGroup(int groupId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) - { - return repository.GetAllInGroup(groupId); - } - } - - /// - /// Gets a list of objects not associated with a given group - /// - /// Id of group - /// - public IEnumerable GetAllNotInGroup(int groupId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateUserRepository(uow)) - { - return repository.GetAllNotInGroup(groupId); - } - } - - #endregion - - #region Implementation of IUserService - - /// - /// Gets an IProfile by User Id. - /// - /// Id of the User to retrieve - /// - public IProfile GetProfileById(int id) - { - //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); - } - - /// - /// Gets a profile by username - /// - /// Username - /// - public IProfile GetProfileByUserName(string username) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.GetProfile(username); - } - } - - /// - /// Gets a user by Id - /// - /// Id of the user to retrieve - /// - public IUser GetUserById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - try - { - var result = repository.Get(id); - return result; - } - catch (DbException ex) - { - //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. - if (ApplicationContext.Current.IsUpgrading) - { - //NOTE: this will not be cached - return repository.Get(id, includeSecurityData: false); - } - throw; - } - } - } - - public IEnumerable GetUsersById(params int[] ids) - { - if (ids.Length <= 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Replaces the same permission set for a single group to any number of entities - /// - /// If no 'entityIds' are specified all permissions will be removed for the specified group. - /// Id of the group - /// Permissions as enumerable list of If nothing is specified all permissions are removed. - /// Specify the nodes to replace permissions for. - public void ReplaceUserGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) - { - if (entityIds.Length == 0) - return; - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - repository.ReplaceGroupPermissions(groupId, permissions, entityIds); - uow.Commit(); - - var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); - uow.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); - } - } - - /// - /// Assigns the same permission set for a single user group to any number of entities - /// - /// Id of the user group - /// - /// Specify the nodes to replace permissions for - public void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds) - { - if (entityIds.Length == 0) - return; - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - repository.AssignGroupPermission(groupId, permission, entityIds); - uow.Commit(); - - var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) }; - uow.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); - } - } - - /// - /// Gets all UserGroups or those specified as parameters - /// - /// Optional Ids of UserGroups to retrieve - /// An enumerable list of - public IEnumerable GetAllUserGroups(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return repository.GetAll(ids).OrderBy(x => x.Name); - } - } - - public IEnumerable GetUserGroupsByAlias(params string[] aliases) - { - if (aliases.Length == 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - var query = Query.Builder.Where(x => aliases.SqlIn(x.Alias)); - var contents = repository.GetByQuery(query); - return contents - .WhereNotNull() - .ToArray(); - } - } - - /// - /// Gets a UserGroup by its Alias - /// - /// Alias of the UserGroup to retrieve - /// - public IUserGroup GetUserGroupByAlias(string alias) - { - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value cannot be null or whitespace.", "alias"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - var query = Query.Builder.Where(x => x.Alias == alias); - var contents = repository.GetByQuery(query); - return contents.FirstOrDefault(); - } - } - - /// - /// Gets a UserGroup by its Id - /// - /// Id of the UserGroup to retrieve - /// - public IUserGroup GetUserGroupById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return repository.Get(id); - } - } - - /// - /// Saves a UserGroup - /// - /// UserGroup to save - /// - /// 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 - /// - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IUserGroup userGroup, int[] userIds = null, bool raiseEvents = true) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - // we need to figure out which users have been added / removed, for audit purposes - var empty = new IUser[0]; - var addedUsers = empty; - var removedUsers = empty; - - if (userIds != null) - { - var urepository = RepositoryFactory.CreateUserRepository(uow); - - var groupUsers = userGroup.HasIdentity ? urepository.GetAllInGroup(userGroup.Id).ToArray() : empty; - var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x); - var groupIds = groupUsers.Select(x => x.Id).ToArray(); - - addedUsers = urepository.GetAll(userIds.Except(groupIds).ToArray()).Where(x => x.Id != 0).ToArray(); - removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); - } - - //raise 2x events - the old and new one for backwards compat reasons - var saveEventArgs = new SaveEventArgs(userGroup); - var saveEventArgs2 = new SaveEventArgs(new UserGroupWithUsers(userGroup, addedUsers, removedUsers)); - if (raiseEvents && - (uow.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs) || uow.Events.DispatchCancelable(SavingUserGroup2, this, saveEventArgs2))) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - repository.AddOrUpdateGroupWithUsers(userGroup, userIds); - - uow.Commit(); - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - - //raise 2x events - the old and new one for backwards compat reasons - uow.Events.Dispatch(SavedUserGroup, this, saveEventArgs); - uow.Events.Dispatch(SavedUserGroup2, this, saveEventArgs2); - } - } - } - - /// - /// Deletes a UserGroup - /// - /// UserGroup to delete - public void DeleteUserGroup(IUserGroup userGroup) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(userGroup); - if (uow.Events.DispatchCancelable(DeletingUserGroup, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - repository.Delete(userGroup); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedUserGroup, this, deleteEventArgs); - } - } - - /// - /// Removes a specific section from all users - /// - /// This is useful when an entire section is removed from config - /// Alias of the section to remove - public void DeleteSectionFromAllUserGroups(string sectionAlias) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - var assignedGroups = repository.GetGroupsAssignedToSection(sectionAlias); - foreach (var group in assignedGroups) - { - //now remove the section for each user and commit - group.RemoveAllowedSection(sectionAlias); - repository.AddOrUpdate(group); - } - - uow.Commit(); - //TODO: Events? - } - } - - /// - /// Get explicitly assigned permissions for a user and optional node ids - /// - /// User to retrieve permissions for - /// Specifiying nothing will return all permissions for all nodes - /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUser user, params int[] nodeIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - - return repository.GetPermissions(user.Groups.ToArray(), true, nodeIds); - } - } - - /// - /// Get explicitly assigned permissions for a group and optional node Ids - /// - /// Groups to retrieve permissions for - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// Specifiying nothing will return all permissions for all nodes - /// An enumerable list of - private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) - { - if (groups == null) throw new ArgumentNullException("group"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return repository.GetPermissions(groups, fallbackToDefaultPermissions, nodeIds); - } - } - - /// - /// Get explicitly assigned permissions for a group and optional node Ids - /// - /// - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// Specifiying nothing will return all permissions for all nodes - /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) - { - if (groups == null) throw new ArgumentNullException("groups"); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateUserGroupRepository(uow); - return repository.GetPermissions(groups.Select(x => x.ToReadOnlyGroup()).ToArray(), fallbackToDefaultPermissions, nodeIds); - } - } - - /// - /// Gets the implicit/inherited permissions for the user for the given path - /// - /// User to check permissions for - /// Path to check permissions for - 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); - } - - /// - /// Gets the permissions for the provided group and path - /// - /// - /// Path to check permissions for - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// String indicating permissions for provided user and path - 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()); - - //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))); - } - - /// - /// This performs the calculations for inherited nodes based on this http://issues.umbraco.org/issue/U4-10075#comment=67-40085 - /// - /// - /// - /// - 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) - { - 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; - } - - 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]); - } - - } - - var permissionSet = new EntityPermissionSet(entityId, resultPermissions); - return permissionSet; - } - - /// - /// Returns the resulting permission set for a group for the path based on all permissions provided for the branch - /// - /// - /// The collective set of permissions provided to calculate the resulting permissions set for the path - /// based on a single group - /// - /// Must be ordered deepest to shallowest (right to left) - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// - internal static EntityPermission GetPermissionsForPathForGroup( - IEnumerable pathPermissions, - int[] pathIds, - bool fallbackToDefaultPermissions = false) - { - //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) - { - 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; - } - } - - //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]]; - } - - /// - /// Checks in a set of permissions associated with a user for those related to a given nodeId - /// - /// The set of permissions - /// The node Id - /// The permissions to return - /// True if permissions for the given path are found - public static bool TryGetAssignedPermissionsForNode(IList 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)) - { - AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); - } - - assignedPermissions = string.Join("", assignedPermissionsArray); - return true; - } - - assignedPermissions = string.Empty; - return false; - } - - private static void AddAdditionalPermissions(List assignedPermissions, string[] additionalPermissions) - { - var permissionsToAdd = additionalPermissions - .Where(x => assignedPermissions.Contains(x) == false); - assignedPermissions.AddRange(permissionsToAdd); - } - - #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 - /// - //TODO: In v8, change this event to be SaveEventArgs and remove SavingUserGroup2 - public static event TypedEventHandler> SavingUserGroup; - - /// - /// Occurs after Save - /// - //TODO: In v8, change this event to be SaveEventArgs and remove SavedUserGroup2 - public static event TypedEventHandler> SavedUserGroup; - - /// - /// Occurs after Save - /// - internal static event TypedEventHandler> SavingUserGroup2; - - /// - /// Occurs after Save - /// - internal static event TypedEventHandler> SavedUserGroup2; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingUserGroup; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> 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> UserGroupPermissionsAssigned; - } -}