Base solution for your next web application
Open Closed

Authorized attribute on properties #4444


User avatar
0
zokho created

Simply, can we apply the authorized and permission attributes at the property level like calss or methods? Basicallt, I want to filter some peoperties based on the caller permissions. Thanks :ugeek:


10 Answer(s)
  • User Avatar
    0
    aaron created
    Support Team

    Do you mean:

  • User Avatar
    0
    zokho created

    Hi Champs! Well at first I was looking for the second one but now that you have raised it, both! With the second one however, I have found it a bit hard to implement it with the current project structure. I assume that I need to define the ShouldSerializeManager by where the Dto classes are (Application.Shared). But the Application.Shared does not have a reference to the UserManager so I could check the caller's permission and roles to decide whether or not serialize the property. I have also tried the second approach by defining the ShouldSerializeContractResolver in the Application project by one of the AppServices implementation but it does work! In the below code I was expecting that the GetJobLocationList() in the JobLocationAppService not returning the CreatoUserId for the callers who have nnot been granted the required permission but it does!

    public class JobLocationAppService : AgbizCareersDemoAppServiceBase, IJobLocationAppService
        {
    public async Task<ListResultDto<JobLocationDto>> GetJobLocationList(GetJobLocationListInput input)
            {
                var jobLocations = await _jobLocationRepository.GetAll().Where(jobLocation => jobLocation.IsActive).ToListAsync();
                return new ListResultDto<JobLocationDto>(ObjectMapper.Map<List<JobLocationDto>>(jobLocations));
            }
    }
    
    public class ShouldSerializeContractResolver : DefaultContractResolver
        {
            public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {
                JsonProperty property = base.CreateProperty(member, memberSerialization);
                UserManager userManager = new UserManager();
    
                if (property.DeclaringType == typeof(JobLocationDto) && property.PropertyName == "CreatorUserId")
                {
                    property.ShouldSerialize =
                        instance =>
                        {
                            JobLocationDto e = (JobLocationDto)instance;
                            if (userManager.UserManager.IsGrantedAsync(userManager.UserManager.AbpSession.UserId.Value, AppPermissions.Pages_Administration_Jobs_Locations).Result) {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        };
                }
                return property;
            }
            protected class UserManager : AgbizCareersDemoAppServiceBase { }
        }
    

    Thanks for your input in advance :)

  • User Avatar
    0
    aaron created
    Support Team
    1. Did you put a breakpoint to see if that code is executed at all?
    2. Don't create UserManager wrapper like that, and don't new it:
    UserManager userManager = new UserManager();
    ...
    protected class UserManager : AgbizCareersDemoAppServiceBase { }
    
    • Perhaps you were figuring out how to use a ContractResolver: <a class="postlink" href="https://github.com/aspnet/Mvc/issues/4842">https://github.com/aspnet/Mvc/issues/4842</a>
    • You can use IocManager.Instance and ResolveAsDisposable to resolve a proper reference to UserManager.
  • User Avatar
    0
    zokho created

    Hey Aaron,

    1. No, the breakpoint does not get hit! I have change the code and removed the UserManager for now:
    public class ShouldSerializeContractResolver : DefaultContractResolver
        {
            public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {
                JsonProperty property = base.CreateProperty(member, memberSerialization);
    
                if (property.DeclaringType == typeof(JobLocationDto) && property.PropertyName == "CreatorUserId")
                {
                    property.ShouldSerialize =
                        instance =>
                        {
                            JobLocationDto e = (JobLocationDto)instance;
                            if (true) {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        };
                }
                return property;
            }
        }
    

    and this is the AppService API method:

    public async Task<ListResultDto<JobLocationDto>> GetJobLocationList(GetJobLocationListInput input)
            {
                var jobLocations = await _jobLocationRepository.GetAll().Where(jobLocation => jobLocation.IsActive).ToListAsync();
                
                return new ListResultDto<JobLocationDto>(ObjectMapper.Map<List<JobLocationDto>>(jobLocations));
            }
    

    As you can see I am not using the JsonConvert.SerializeObject to serialize it as the method is supposed to return Task<ListResultDto<JobLocationDto>> not a JSON result. I am pretty sure I am missing something in my code! but what?!

  • User Avatar
    0
    aaron created
    Support Team
    • Perhaps you were figuring out how to use a ContractResolver: <a class="postlink" href="https://github.com/aspnet/Mvc/issues/4842">https://github.com/aspnet/Mvc/issues/4842</a>
  • User Avatar
    0
    zokho created

    I managed to hit the breakpoint and its is ignoring the property that I want. The problem however is that it returns a JSON in string format but I need to have my API return the DTO class so I could easily use it in the Angular project!

    Any thought?

  • User Avatar
    0
    zokho created

    Any comment on this? :roll:

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @zokho,

    Can you share your related codes to this issue ? I mean the app service and usage of ShouldSerializeContractResolver ?

  • User Avatar
    0
    zokho created

    Hi, I have already included the code in my last post. By here it is again:

    public class ShouldSerializeContractResolver : DefaultContractResolver
        {
            public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
            {
                JsonProperty property = base.CreateProperty(member, memberSerialization);
    
                if (property.PropertyName == "CreatorUserId")
                {
                    property.ShouldSerialize =
                        instance =>
                        {
                            JobLocationDto e = (JobLocationDto)instance;
                            if (e.CreatorUserId.Value != GetCurrentUser().Id) 
                            {
                                return false;
                            }
                            return true;
                        };
                }
                return property;
            }
        }
    

    Please note that I have not injected the UserManager to the ShouldSerializeContractResolver class yet to use the GetCurrentUser, but the whole idea is dropping the Audited fields from the the response if the caller is not Admin or the owner of the entity

    public async Task<ListResultDto<JobLocationDto>> GetJobLocationList(GetJobLocationListInput input)
            {
                if (AbpSession.UserId != null)
                {
                    if (await UserManager.IsGrantedAsync(GetCurrentUser().Id, AppPermissions.Pages_Administration_Jobs_Locations))
                    {
                        var jobLocationsManage = await _jobLocationRepository.GetAll().Where(jobLocation => input.State == Common.VisibilityState.All ? (jobLocation.IsActive || !jobLocation.IsActive) :
                                                                                                      input.State == Common.VisibilityState.Active ? jobLocation.IsActive : !jobLocation.IsActive).ToListAsync();
                        return new ListResultDto<JobLocationDto>(ObjectMapper.Map<List<JobLocationDto>>(jobLocationsManage));
                    }
                }
                var jobLocations = await _jobLocationRepository.GetAll().Where(jobLocation => jobLocation.IsActive).ToListAsync();
                foreach (var jobLocation in jobLocations)
                {
                    //this jb would be one jobLocation in string format, and the method returns Task<ListResultDto<JobLocationDto>>!!!!
                    var jb = JsonConvert.SerializeObject(ObjectMapper.Map<JobLocationDto>(jobLocation), new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() });
                }
                return new ListResultDto<JobLocationDto>(ObjectMapper.Map<List<JobLocationDto>>(jobLocations));
            }
    

    I have also tried to conditionally ignore the property in the CustomeDtoMapper class like below:

    configuration.CreateMap<JobLocation, JobLocationDto>()
                   .ForMember(jl => jl.CreationTime, option => option.Condition(jl => jl.CreatorUserId == ADMIN_USER_ID));
    

    but there are 2 problems with this approach:

    1. The option.Condition does not drop the property altogether and it just simply not setting its value if the condition met, which would not be a big deal!
    2. I can't use the UserManager in there to check the caller's userId against the entity's userId. Please kindly guide me on how I can if it is possible to use UserManager within the CustomDtoMapper class.

    Today is my licence expires and I assume I no longer can post any comment on this thread. So I really appreciate a response ASAP, so I could reply back if is needed. I wish I could afford to renew my licence :cry:

    Thank you very much guys ;)

  • User Avatar
    0
    aaron created
    Support Team
    //this jb would be one jobLocation in string format, and the method returns Task<ListResultDto<JobLocationDto>>!!!!
    var jb = JsonConvert.SerializeObject(ObjectMapper.Map<JobLocationDto>(jobLocation),
                                        new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() });
    

    Why would you do that, instead of following the approach mentioned in #p25789 and quoted in #p25912?

    services
        .AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new ShouldSerializeContractResolver());