Activities of "michael.sudnik"

I was able to fix this myself by replacing the OrganizationUnitManagerservice and override the MoveAsync method so that it calledOrganizationUnitRepository.UpdateAsync after making any changes to properties of any OrganizationUnit.

One problem I had, was that I couldn't readily access the OrganizationUnit.Code or OrganizationUnit.ParentId property setters, as they were marked as internal. I had to use reflection to set the properties to combine the existing method functionaliy with the fix.

I'm not sure how it worked for you, as this layer of code is not database specific, and I wouldn't have expected it to work for Entity Framework either.

Check the docs before asking a question: https://docs.abp.io/en/commercial/latest/ Check the samples, to see the basic tasks: https://docs.abp.io/en/commercial/latest/samples/index The exact solution to your question may have been answered before, please use the search on the homepage.

  • ABP Framework version: v4.0.0
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:

The role claims are not being saved as the UpdateClaimsAsync() method within the IdentityRoleAppService class is missing a await RoleRepository.UpdateAsync(role); at the end.

I was able to fix it by replacing the IdentityRoleAppService and appending the missing line in the overriden method.

  • ABP Framework version: v4.0.1
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • Exception message and stack trace:

[15:06:22 ERR] Error occured while adding the module Volo.Identity.Prowith source-code to the solution "2ae0add0-a136-48b3-889f-e331d74b2f66".. System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.ChangeDomainTestReferenceToMongoDB(ModuleWithMastersInfo module, String moduleSolutionFile) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 201 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.RemoveUnnecessaryProjectsAsync(String solutionDirectory, ModuleWithMastersInfo module, String[] projectFiles) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 151 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.AddAsync(String solutionFile, String moduleName, String startupProject, String version, Boolean skipDbMigrations, Boolean withSourceCode, Boolean addSourceCodeToSolutionFile, Boolean newTemplate, Boolean newProTemplate) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 98 at Volo.Abp.Cli.Commands.AddModuleCommand.ExecuteAsync(CommandLineArgs commandLineArgs) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\Commands\AddModuleCommand.cs:line 59 at Volo.Abp.Suite.Controllers.AbpSuiteController.AddModuleAsync(AddModuleInput input) [15:06:38 ERR] Error occured while adding the module Volo.Identity.Prowith source-code to the solution "2ae0add0-a136-48b3-889f-e331d74b2f66".. System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.ChangeDomainTestReferenceToMongoDB(ModuleWithMastersInfo module, String moduleSolutionFile) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 201 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.RemoveUnnecessaryProjectsAsync(String solutionDirectory, ModuleWithMastersInfo module, String[] projectFiles) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 151 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.AddAsync(String solutionFile, String moduleName, String startupProject, String version, Boolean skipDbMigrations, Boolean withSourceCode, Boolean addSourceCodeToSolutionFile, Boolean newTemplate, Boolean newProTemplate) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 98 at Volo.Abp.Cli.Commands.AddModuleCommand.ExecuteAsync(CommandLineArgs commandLineArgs) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\Commands\AddModuleCommand.cs:line 59 at Volo.Abp.Suite.Controllers.AbpSuiteController.AddModuleAsync(AddModuleInput input)
[15:07:27 ERR] Error occured while adding the module Volo.AuditLogging.Uiwith source-code to the solution "2ae0add0-a136-48b3-889f-e331d74b2f66".. System.ArgumentNullException: Value cannot be null. (Parameter 'path') at System.IO.Path.GetFullPath(String path) at System.IO.DirectoryInfo..ctor(String path) at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.RemoveProjectByPostFix(ModuleWithMastersInfo module, String moduleSolutionFile, String targetFolder, String postFix) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 186 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.RemoveUnnecessaryProjectsAsync(String solutionDirectory, ModuleWithMastersInfo module, String[] projectFiles) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 150 at Volo.Abp.Cli.ProjectModification.SolutionModuleAdder.AddAsync(String solutionFile, String moduleName, String startupProject, String version, Boolean skipDbMigrations, Boolean withSourceCode, Boolean addSourceCodeToSolutionFile, Boolean newTemplate, Boolean newProTemplate) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\ProjectModification\SolutionModuleAdder.cs:line 98 at Volo.Abp.Cli.Commands.AddModuleCommand.ExecuteAsync(CommandLineArgs commandLineArgs) in D:\github\abp\framework\src\Volo.Abp.Cli.Core\Volo\Abp\Cli\Commands\AddModuleCommand.cs:line 59 at Volo.Abp.Suite.Controllers.AbpSuiteController.AddModuleAsync(AddModuleInput input)

  • Steps to reproduce the issue:

I am using MongoDB

I tried to add the Identity and Audit Logging modules through the abp suite for an existing solution.

If anyone else is interested in doing this, here is my own implementation to add some simple entity change tracking by overriding the default MongoDbRepository class.

public class MongoDbEntityChangeRepository<TMongoDbContext, TEntity, TKey>
        : MongoDbRepository<TMongoDbContext, TEntity, TKey>
            , IAuditedRepository<TEntity, TKey>
        where TMongoDbContext : IAbpMongoDbContext
        where TEntity : class, IEntity<TKey>
    {
        public MongoDbEntityChangeRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        private readonly object _serviceProviderLock = new object();

        private TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
        {
            if (reference == null)
            {
                lock (_serviceProviderLock)
                {
                    if (reference == null)
                    {
                        reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
                    }
                }
            }

            return reference;
        }

        public EntityChangeLogger EntityChangeLogger => LazyGetRequiredService(typeof(EntityChangeLogger), ref _entityChangeLogger);
        private EntityChangeLogger _entityChangeLogger;


        /// <summary>
        /// Use this method to prevent fetching the entity again, if you already have it
        /// </summary>
        /// <param name="original"></param>
        /// <param name="updateEntity"></param>
        /// <param name="autoSave"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<TEntity> UpdateAsync(
            TEntity original,
            Action<TEntity> updateEntity,
            bool autoSave = false,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            var previous = EntityChangeLogger.CloneEntity<TEntity, TKey>(original);

            updateEntity(original);
            var updated = original;

            updated = await base.UpdateAsync(updated, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityUpdated<TEntity, TKey>(previous, updated);

            return updated;
        }

        public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = new CancellationToken())
        {
            TEntity previous = null;

            bool entityHistoryEnabled = EntityChangeLogger.IsEntityHistoryEnabled<TEntity, TKey>();

            if (entityHistoryEnabled)
            {
                previous = await GetAsync(entity.Id, cancellationToken: cancellationToken);
            }

            var result = await base.UpdateAsync(entity, autoSave, cancellationToken);

            if (entityHistoryEnabled)
            {
                EntityChangeLogger.LogEntityUpdated<TEntity, TKey>(previous, entity);
            }

            return result;
        }

        public override async Task DeleteAsync(
            TEntity entity,
            bool autoSave = false,
            CancellationToken cancellationToken = default)
        {
            await base.DeleteAsync(entity, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityDeleted<TEntity, TKey>(entity);
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = new CancellationToken())
        {
            var result = await base.InsertAsync(entity, autoSave, cancellationToken);

            EntityChangeLogger.LogEntityCreated<TEntity, TKey>(result);

            return result;
        }
    }

public class EntityChangeLogger
    {
        private readonly IAuditingManager _auditingManager;
        private readonly IClock _clock;
        private readonly IAuditingHelper _auditingHelper;

        public EntityChangeLogger(IAuditingManager auditingManager, IClock clock, IAuditingHelper auditingHelper)
        {
            _auditingManager = auditingManager;
            _clock = clock;
            _auditingHelper = auditingHelper;
        }

        public void LogEntityCreated<T, TKey>(T current) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!_auditingHelper.IsEntityHistoryEnabled(entityType))
                return;

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Created,
                EntityId = GetEntityId(current),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(null, current, true)
            });
        }

        public void LogEntityDeleted<T, TKey>(T previous) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!_auditingHelper.IsEntityHistoryEnabled(entityType))
                return;

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Deleted,
                EntityId = GetEntityId(previous),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(previous, null, true)
            });
        }

        public bool IsEntityHistoryEnabled<T, TKey>() where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            return _auditingHelper.IsEntityHistoryEnabled(entityType);
        }

        public void LogEntityUpdated<T, TKey>(T previous, T current) where T : class, IEntity<TKey>
        {
            var entityType = typeof(T);

            if (!IsEntityHistoryEnabled<T, TKey>())
                return;

            var notNullEntity = GetNotNull(previous, current);

            _auditingManager.Current.Log.EntityChanges.Add(new EntityChangeInfo()
            {
                ChangeTime = _clock.Now,
                ChangeType = EntityChangeType.Updated,
                EntityId = GetEntityId(notNullEntity),
                EntityTenantId = null,
                EntityTypeFullName = entityType.FullName,
                PropertyChanges = GetPropertyChanges<T, TKey>(previous, current, false)
            });
        }

        private static ConcurrentDictionary<Type, PropertyInfos> _propertyInfoCache = new ConcurrentDictionary<Type, PropertyInfos>();

        private class PropertyInfos
        {
            public PropertyInfos(PropertyInfo id, PropertyInfo extraProperties)
            {
                Id = id;
                ExtraProperties = extraProperties;
            }

            public PropertyInfo Id { get; private set; }
            public PropertyInfo ExtraProperties { get; private set; }
        }

        public T CloneEntity<T, TKey>(T entity) where T : class, IEntity<TKey>
        {
            // create a clone, without going back to the database again
            var doc = JsonSerializer.Serialize(entity);

            var clone = JsonSerializer.Deserialize<T>(doc);

            // set protected properties
            // - Id
            // - Extra Properties

            var entityType = typeof(T);

            var propertyInfo = _propertyInfoCache.GetOrAdd(entityType, CreatePropertyInfos<TKey>);

            propertyInfo.Id.SetValue(clone, entity.Id);

            if (entityType.IsAssignableTo<IHasExtraProperties>())
            {
                IHasExtraProperties e = (IHasExtraProperties)entity;

                propertyInfo.ExtraProperties.SetValue(clone, SimpleClone(e.ExtraProperties));
            }

            return clone;
        }

        private PropertyInfos CreatePropertyInfos<TKey>(Type entityType)
        {
            return new PropertyInfos(
                entityType.GetProperty(nameof(IEntity<TKey>.Id)),
                    entityType.GetProperty(nameof(IHasExtraProperties.ExtraProperties)));
        }

        private T SimpleClone<T>(T toClone)
        {
            var doc = JsonSerializer.Serialize(toClone);

            return JsonSerializer.Deserialize<T>(doc);
        }

        private List<EntityPropertyChangeInfo> GetPropertyChanges<T, TKey>(T previous, T current, bool logIfNoChange) where T : class, IEntity<TKey>
        {
            List<EntityPropertyChangeInfo> result = new List<EntityPropertyChangeInfo>();

            foreach (var propertyInfo in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if (ShouldSavePropertyHistory(previous, current, propertyInfo, logIfNoChange, out string previousValue, out string currentValue))
                {
                    result.Add(new EntityPropertyChangeInfo
                    {
                        NewValue = currentValue,
                        OriginalValue = previousValue,
                        PropertyName = propertyInfo.Name,
                        PropertyTypeFullName = propertyInfo.PropertyType.FullName
                    });
                }
            }

            return result;
        }

        private bool ShouldSavePropertyHistory<T>(T previous, T current, PropertyInfo propertyInfo, bool logIfNoChange, out string previousValue, out string currentValue)
        {
            previousValue = null;
            currentValue = null;

            if (propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
            {
                return false;
            }

            var entityType = typeof(T);

            if (entityType.IsDefined(typeof(DisableAuditingAttribute), true))
            {
                if (!propertyInfo.IsDefined(typeof(AuditedAttribute), true))
                {
                    return false;
                }
            }

            if (IsBaseAuditProperty(propertyInfo, entityType))
            {
                return false;
            }

            previousValue = previous != null ? JsonSerializer.Serialize(propertyInfo.GetValue(previous)).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength) : null;
            currentValue = current != null ? JsonSerializer.Serialize(propertyInfo.GetValue(current)).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength) : null;

            var isModified = !(previousValue?.Equals(currentValue) ?? currentValue == null);
            if (isModified)
            {
                return true;
            }

            return logIfNoChange;
        }

        private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType)
        {
            if (entityType.IsAssignableTo<IHasCreationTime>()
                && propertyInfo.Name == nameof(IHasCreationTime.CreationTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IMayHaveCreator>()
                && propertyInfo.Name == nameof(IMayHaveCreator.CreatorId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IMustHaveCreator>()
                && propertyInfo.Name == nameof(IMustHaveCreator.CreatorId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IHasModificationTime>()
                && propertyInfo.Name == nameof(IHasModificationTime.LastModificationTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IModificationAuditedObject>()
                && propertyInfo.Name == nameof(IModificationAuditedObject.LastModifierId))
            {
                return true;
            }

            if (entityType.IsAssignableTo<ISoftDelete>()
                && propertyInfo.Name == nameof(ISoftDelete.IsDeleted))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IHasDeletionTime>()
                && propertyInfo.Name == nameof(IHasDeletionTime.DeletionTime))
            {
                return true;
            }

            if (entityType.IsAssignableTo<IDeletionAuditedObject>()
                && propertyInfo.Name == nameof(IDeletionAuditedObject.DeleterId))
            {
                return true;
            }

            return false;
        }

        private object GetNotNull(object obj1, object obj2)
        {
            if (obj1 != null)
                return obj1;
            if (obj2 != null)
                return obj2;

            throw new NotSupportedException();
        }

        private string GetEntityId(object entityAsObj)
        {
            if (!(entityAsObj is IEntity entity))
            {
                throw new AbpException($"Entities should implement the {typeof(IEntity).AssemblyQualifiedName} interface! Given entity does not implement it: {entityAsObj.GetType().AssemblyQualifiedName}");
            }

            var keys = entity.GetKeys();
            if (keys.All(k => k == null))
            {
                return null;
            }

            return keys.JoinAsString(",");
        }

        protected virtual Guid? GetTenantId(object entity)
        {
            if (!(entity is IMultiTenant multiTenantEntity))
            {
                return null;
            }

            return multiTenantEntity.TenantId;
        }
    }
  • ABP Framework version: v4.0.0
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes / no
  • Exception message and stack trace:
  • Steps to reproduce the issue:
  • **Database: MongoDB

Hi,

I am implementing my own entity change tracking and am using MongoDB.

There is a bug, which means that the sorting is only being applied to the current page of entity change results. https://github.com/abpframework/abp/blob/f4c7def47a97c77e4fa2813f52e8ebe61598f095/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs#L179

Paging should be performed after the sort, same as this line: https://github.com/abpframework/abp/blob/f4c7def47a97c77e4fa2813f52e8ebe61598f095/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs#L58

Best regards,

Mike

  • ABP Framework version: v4.0.0
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:

Hi,

I have been trying to get the entity auditing working, but then I found out through the documentation that it is not supported for MongoDB. https://docs.abp.io/en/abp/latest/Audit-Logging

Is there a reason that this has not yet been implemented for MongoDB? Can you let me know if this is planned to be supported? Can you let me know how I might go about implementing this myself?

Many thanks,

Mike

  • ABP Framework version: c4.0.0
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • MongoDB
  • Exception message and stack trace:
  • Steps to reproduce the issue:

Hi,

The existing OU functionality allows us to define a hierarchy of OUs and then specify the users which are within the OUs. We are then able to define the roles for an OU, which results in those roles being applied to the users within the OU.

However, we have a requirement where we need to able to assign a role to a user, which should only give them the role permissions for the specific organizational units defined by the role.

The idea would be the following.

  1. Be able to create a role and be able to indicate that it is a "Restricted Access" role (it would just be an extension of the existing role entity) and specify which organization units the role functionality is applicable to.
  • it does not grant its permissions globally, but only for data within the specified organization units.
  • Be able to choose which organization unit(s) the the role applies to
  • The permissions enabled by the role will be limited to those that support restricted access (as it is up to the services / repositories to ensure that they can filter by OU).
  • A user does not need to be in the orgnization unit to be assigned the role
  1. Assign the role to user/organisation units/api clients in the usual way
  • As it is a normal role (with some additional properties), this will mean that it will work for the usual role functionality and security checks within razor pages and services
  • Within the services the data can be filtered and actions restricted so that they are ou specific

I have looked at all the existing documentation regarding organization units and looked at many existing software solutions to discover how it works from a users perspective and I believe the above design would work well.

I am wondering if you are planning to extend the OU functionality to include this feature in the future? Or anything similar? Is there already a request of backlog item for it?

If not, are you able to make any recommendations about how we should go about implementing this functionality?

My current thinking is

  • Extend the permissions system to be able to define permissions as "Restricted Access"
    • This is needed because not all permissions will support filtering by OU
  • Extend the role creation / editor
    • to be able to indicate that a role is "Restricted Access"
    • to be able to select the "Restricted Access" permissions which are enabled for the role
  • Extend relative entities so that they can be included in an OU.
    • We might want to support entities existing in more thatn on OU
  • Extend my services / repositories to do the required filtering
  • Make the design flexible enough that we can override and extend existing modules to make the support the "Restricted Access" concept

I settled on the "Restricted Access" rather than "OU Specific" as I think "OU Specific" does not make it clear what its purpose is and can be confused with the existing ability to add roles to an OU. I also chose to extend permissions to be "Restricted Access" and to make a distinction between normal roles vs "Restricted Access" roles to make it clear what a user with the role will be able to do and there would be no confusion about if the user will have access to all data or only data associated with particular OUs.

Your help is greatly appreciated,

Mike

i have not yet tried this yet, but my instinct is that maybe this is a problem with the mongodb specific version. I have not made any modifications to the main application and only created custom modules, so I would have expected this to work. Which db did you use for your test? I will create a new solution and do some more investigating when i get a chance.

  • ABP Framework version: v4.0.0 RC5
  • UI type: MVC
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue:

When I try to move an organization unit to a different parent (e.g. move Production under Central)... I can see in the preview that I am requesting to move it where I want to

and the confirmation dialog is correct. However, after completion, it has not moved. This appears to be the case when trying to move organization units to any different level, including to the root level.

Mike

This appears to now be fixed in RC5

Showing 31 to 40 of 56 entries
Made with ❤️ on ABP v9.1.0-preview. Updated on November 01, 2024, 05:35