using Nop.Core; using Nop.Core.Caching; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Customers; using Nop.Core.Domain.Security; using Nop.Data; using Nop.Services.Customers; namespace Nop.Services.Security; /// /// ACL service /// public partial class AclService : IAclService { #region Fields protected readonly CatalogSettings _catalogSettings; protected readonly ICustomerService _customerService; protected readonly INopDataProvider _dataProvider; protected readonly IRepository _aclRecordRepository; protected readonly IStaticCacheManager _staticCacheManager; protected readonly Lazy _workContext; #endregion #region Ctor public AclService(CatalogSettings catalogSettings, ICustomerService customerService, INopDataProvider dataProvider, IRepository aclRecordRepository, IStaticCacheManager staticCacheManager, Lazy workContext) { _catalogSettings = catalogSettings; _customerService = customerService; _dataProvider = dataProvider; _aclRecordRepository = aclRecordRepository; _staticCacheManager = staticCacheManager; _workContext = workContext; } #endregion #region Utilities /// /// Inserts an ACL record /// /// ACL record /// A task that represents the asynchronous operation protected virtual async Task InsertAclRecordAsync(AclRecord aclRecord) { await _aclRecordRepository.InsertAsync(aclRecord); } /// /// Get a value indicating whether any ACL records exist for entity type are related to customer roles /// /// Type of entity that supports the ACL /// /// A task that represents the asynchronous operation /// The task result contains true if exist; otherwise false /// protected virtual async Task IsEntityAclMappingExistAsync() where TEntity : BaseEntity, IAclSupported { var entityName = typeof(TEntity).Name; var key = _staticCacheManager.PrepareKeyForDefaultCache(NopSecurityDefaults.EntityAclRecordExistsCacheKey, entityName); var query = from acl in _aclRecordRepository.Table where acl.EntityName == entityName select acl; return await _staticCacheManager.GetAsync(key, query.Any); } #endregion #region Methods /// /// Apply ACL to the passed query /// /// Type of entity that supports the ACL /// Query to filter /// Customer /// /// A task that represents the asynchronous operation /// The task result contains the filtered query /// public virtual async Task> ApplyAcl(IQueryable query, Customer customer) where TEntity : BaseEntity, IAclSupported { ArgumentNullException.ThrowIfNull(query); ArgumentNullException.ThrowIfNull(customer); var customerRoleIds = await _customerService.GetCustomerRoleIdsAsync(customer); return await ApplyAcl(query, customerRoleIds); } /// /// Apply ACL to the passed query /// /// Type of entity that supports the ACL /// Query to filter /// Identifiers of customer's roles /// /// A task that represents the asynchronous operation /// The task result contains the filtered query /// public virtual async Task> ApplyAcl(IQueryable query, int[] customerRoleIds) where TEntity : BaseEntity, IAclSupported { ArgumentNullException.ThrowIfNull(query); ArgumentNullException.ThrowIfNull(customerRoleIds); if (!customerRoleIds.Any() || _catalogSettings.IgnoreAcl || !await IsEntityAclMappingExistAsync()) return query; return from entity in query where !entity.SubjectToAcl || _aclRecordRepository.Table.Any(acl => acl.EntityName == typeof(TEntity).Name && acl.EntityId == entity.Id && customerRoleIds.Contains(acl.CustomerRoleId)) select entity; } /// /// Deletes an ACL record /// /// ACL record /// A task that represents the asynchronous operation public virtual async Task DeleteAclRecordAsync(AclRecord aclRecord) { await _aclRecordRepository.DeleteAsync(aclRecord); } /// /// Gets ACL records /// /// Type of entity that supports the ACL /// Entity /// /// A task that represents the asynchronous operation /// The task result contains the ACL records /// public virtual async Task> GetAclRecordsAsync(TEntity entity) where TEntity : BaseEntity, IAclSupported { ArgumentNullException.ThrowIfNull(entity); var entityId = entity.Id; var entityName = entity.GetType().Name; var query = from ur in _aclRecordRepository.Table where ur.EntityId == entityId && ur.EntityName == entityName select ur; var aclRecords = await query.ToListAsync(); return aclRecords; } /// /// Inserts an ACL record /// /// Type of entity that supports the ACL /// Entity /// Customer role id /// A task that represents the asynchronous operation public virtual async Task InsertAclRecordAsync(TEntity entity, int customerRoleId) where TEntity : BaseEntity, IAclSupported { ArgumentNullException.ThrowIfNull(entity); if (customerRoleId == 0) throw new ArgumentOutOfRangeException(nameof(customerRoleId)); var entityId = entity.Id; var entityName = entity.GetType().Name; var aclRecord = new AclRecord { EntityId = entityId, EntityName = entityName, CustomerRoleId = customerRoleId }; await InsertAclRecordAsync(aclRecord); } /// /// Find customer role identifiers with granted access /// /// Entity ID /// Entity name /// /// A task that represents the asynchronous operation /// The task result contains the customer role identifiers /// public virtual async Task GetCustomerRoleIdsWithAccessAsync(int entityId, string entityName) { if (entityId == 0) return []; var key = _staticCacheManager.PrepareKeyForDefaultCache(NopSecurityDefaults.AclRecordCacheKey, entityId, entityName); var query = from ur in _aclRecordRepository.Table where ur.EntityId == entityId && ur.EntityName == entityName select ur.CustomerRoleId; return await _staticCacheManager.GetAsync(key, () => query.ToArrayAsync()); } /// /// Authorize ACL permission /// /// Type of entity that supports the ACL /// Entity /// /// A task that represents the asynchronous operation /// The task result contains true - authorized; otherwise, false /// public virtual async Task AuthorizeAsync(TEntity entity) where TEntity : BaseEntity, IAclSupported { return await AuthorizeAsync(entity, await _workContext.Value.GetCurrentCustomerAsync()); } /// /// Authorize ACL permission /// /// Type of entity that supports the ACL /// Entity /// Customer /// /// A task that represents the asynchronous operation /// The task result contains true - authorized; otherwise, false /// public virtual async Task AuthorizeAsync(TEntity entity, Customer customer) where TEntity : BaseEntity, IAclSupported { if (entity == null) return false; if (!entity.SubjectToAcl) return true; return await AuthorizeAsync(entity.GetType().Name, entity.Id, customer); } /// /// Authorize ACL permission /// /// Type name of entity that supports the ACL /// Entity ID /// Customer /// /// A task that represents the asynchronous operation /// The task result contains true - authorized; otherwise, false /// public virtual async Task AuthorizeAsync(string entityTypeName, int entityId, Customer customer) { if (string.IsNullOrEmpty(entityTypeName)) return false; if (entityId <= 0) return false; if (customer == null) return false; if (_catalogSettings.IgnoreAcl) return true; foreach (var role1 in await _customerService.GetCustomerRolesAsync(customer)) foreach (var role2Id in await GetCustomerRoleIdsWithAccessAsync(entityId, entityTypeName)) if (role1.Id == role2Id) //yes, we have such permission return true; //no permission found return false; } /// /// Authorize ACL permission /// /// Customer /// List of allowed customer role IDs /// /// A task that represents the asynchronous operation /// The task result contains true - authorized; otherwise, false /// public virtual async Task AuthorizeAsync(Customer customer, IList allowedCustomerRoleIds) { return _catalogSettings.IgnoreAcl || allowedCustomerRoleIds.Intersect(await _customerService.GetCustomerRoleIdsAsync(customer)).Any(); } /// /// Save ACL mapping /// /// Type of entity /// Entity /// Customer roles for mapping /// A task that represents the asynchronous operation public virtual async Task SaveAclAsync(TEntity entity, IList selectedCustomerRoleIds) where TEntity : BaseEntity, IAclSupported { if (entity == null) return; if (entity.SubjectToAcl != selectedCustomerRoleIds.Any()) { entity.SubjectToAcl = selectedCustomerRoleIds.Any(); await _dataProvider.UpdateEntityAsync(entity); } var existingAclRecords = await GetAclRecordsAsync(entity); var allCustomerRoles = await _customerService.GetAllCustomerRolesAsync(true); foreach (var customerRole in allCustomerRoles) if (selectedCustomerRoleIds.Contains(customerRole.Id)) { //new role if (existingAclRecords.All(acl => acl.CustomerRoleId != customerRole.Id)) await InsertAclRecordAsync(entity, customerRole.Id); } else { //remove role var aclRecordToDelete = existingAclRecords.FirstOrDefault(acl => acl.CustomerRoleId == customerRole.Id); if (aclRecordToDelete != null) await DeleteAclRecordAsync(aclRecordToDelete); } } #endregion }