This is a migrated thread and some comments may be shown as answers.

[Solved] Telerik - AuthorizeAttribute - Telerik.Web.Mvc.Infrastructure.Implementation.ControllerAuthorization

7 Answers 107 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Matthew
Top achievements
Rank 1
Matthew asked on 20 Jul 2011, 01:14 PM
I have a problem with the Authorize Attribute when Telerik attempts to render a tabstrip (likely similar to the problem described here)

The authorize attribute is created with a custom FilterProvider like following link demonstrates - DI - Filter Injection
At the bottom of the injection the project (its controllers and filters) are feed by Unity.

The attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    /// <summary>
    /// Feed by unity
    /// </summary>
    [Dependency]
    public IAuthorizationService AuthorizationService { get; set;}
 
    /// <summary>
    /// Feed by unity
    /// </summary>
    [Dependency]
    public ISiteInfo SiteInfo { get; set; }
 
    public bool AuthorizeByProduct { get; set; }
 
    //[Dependency]
    //public IUser User { get; set; }
 
    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        if (!this.AuthorizationService.IsLoggedIn)
            return false;
 
        //test if available to current service
        if (SiteInfo.CurrentService != null)
            if (!AuthorizationService.BelongsToService(SiteInfo.CurrentService))
                return false;
 
        //test if available to current product
        if (SiteInfo.CurrentProduct != null)
            if (!AuthorizationService.BelongsToProduct(SiteInfo.CurrentProduct))
                return false;
 
        //test roles
        if (!this.AuthorizationService.HasRole(this.Roles.Split(',')))
            return false;
 
        if(AuthorizeByProduct)
            return this.AuthorizationService.BelongsToProduct(SiteInfo.CurrentProduct);
 
        return true;
    }
}

The problem occurs within the namespace Telerik.Web.Mvc.Infrastructure.Implementation within ControllerAuthorization
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We will not allow if there is any exception.")]
        public bool IsAccessibleToUser(RequestContext requestContext, string controllerName, string actionName, RouteValueDictionary routeValues)
        {
            Guard.IsNotNull(requestContext, "requestContext");
            Guard.IsNotNullOrEmpty(controllerName, "controllerName");
            Guard.IsNotNullOrEmpty(actionName, "actionName");
 
            IEnumerable<AuthorizeAttribute> authorizeAttributes = authorizeAttributeCache.GetAuthorizeAttributes(requestContext, controllerName, actionName, routeValues);
            bool allowed = true;
 
            foreach (AuthorizeAttribute authorizeAttribute in authorizeAttributes)
            {
                if (authorizeAttribute != null)
                {
                    try
                    {
                        Type currentAuthorizationAttributeType = authorizeAttribute.GetType();
                        bool isDefaultAttribute = (currentAuthorizationAttributeType == defaultAuthorizeAttributeType);
 
                        IAuthorizeAttribute subclassedAttribute = isDefaultAttribute ?
                                                                  new InternalAuthorizeAttribute() : // No need to use Reflection.Emit if it is the asp.net mvc built-in attribute
                                                                  authorizeAttribute is IAuthorizeAttribute ?
                                                                  authorizeAttribute as IAuthorizeAttribute :
                                                                  reflectedAuthorizeAttributeCache.GetAttribute(currentAuthorizationAttributeType);
 
                        subclassedAttribute.Order = authorizeAttribute.Order;
                        subclassedAttribute.Roles = authorizeAttribute.Roles;
                        subclassedAttribute.Users = authorizeAttribute.Users;
 
                        if (!isDefaultAttribute)
                        {
                            // Copy the remaining properties (if there is any)
                            objectCopier.Copy(authorizeAttribute, subclassedAttribute, "Order", "Roles", "Users" /* Excluded properties */);
                        }
 
                        allowed = subclassedAttribute.IsAuthorized(requestContext.HttpContext);
                    }
                    catch
                    {
                        // do not allow on exception
                        allowed = false;
                    }
 
                    if (!allowed)
                    {
                        break;
                    }
                }
            }
 
            return allowed;
        }

The types are loaded within ControllerAuthorization correctly (to a extent) loading the CustomAuthorizeAttribute above, however the problem is that the filter provider is no longer being used to create the FilterAttribute leaving the two dependency Properties in CustomAuthorizeAttribute as null.

So the line: reflectedAuthorizeAttributeCache.GetAttribute(currentAuthorizationAttributeType);
returns the correct override, encapsulated as the IAuthorizeAttribute, but doest check if any properties can be resolved, hence the null problem.

Changing the method in my custom attribute so that it loads the correct files when needed is a solution when AuthorizeCore is called when subclassedAttribute.IsAuthorized(requestContext.HttpContext) is run:
if(AuthorizationService == null)
    AuthorizationService = DependencyResolver.Current.GetService<IAuthorizationService>();
if(SiteInfo == null)
    SiteInfo = DependencyResolver.Current.GetService<ISiteInfo>();

Is there any better way for this to play nice?

Kind Regards,
Matthew Green

7 Answers, 1 is accepted

Sort by
0
Georgi Krustev
Telerik team
answered on 20 Jul 2011, 02:34 PM
Hello Matthew,

 
We have never tried such a scenario. If it is possible, send us a simple test project, which replicates the issue. Thus we will investigate it locally and advice you further.

All the best,
Georgi Krustev
the Telerik team

Register for the Q2 2011 What's New Webinar Week. Mark your calendar for the week starting July 18th and book your seat for a walk through of all the exciting stuff we will ship with the new release!

0
Matthew
Top achievements
Rank 1
answered on 20 Jul 2011, 03:52 PM
Sure. I will create one shortly.
0
Matthew
Top achievements
Rank 1
answered on 20 Jul 2011, 11:24 PM
Find the attached project. It demonstrates two bugs which are more apparent after running than my attempt to explain.

First is the mentioned bug when using Action and the filter not building correctly.
Second could be a sign or me going mad, but a tab isn't showing, (which may be caused by the first bug).

If I've missed any references they should be available through nuget.
Telerik MVC version 2011.2.712.340

Thanks,
Matthew
0
Georgi Krustev
Telerik team
answered on 25 Jul 2011, 12:20 PM
Hello Matthew,

 
Thank you for the attached test project.

After further investigation, we found out that this scenario is unsupported. As you noticed in the ControllerAuthorization, internal authorize attribute is created using reflection. Honestly I am not sure how we can support required scenario. I will be really glad if you can provide solution of this limitation.

As a workaround I will suggest you to use custom method which can be called in the AuthorizeCore. Thus you will be able to have more control over the loading of the required properties.

Best regards,
Georgi Krustev
the Telerik team

Register for the Q2 2011 What's New Webinar Week. Mark your calendar for the week starting July 18th and book your seat for a walk through of all the exciting stuff we will ship with the new release!

0
Matthew
Top achievements
Rank 1
answered on 25 Jul 2011, 12:38 PM
Thanks Georgi Krustev

The easiest solution is indeed to check over the injected properties in AuthorizeCore, or call another method to initialize them if not. That is the solution I've gone for now.

I will however attempt to get the DI injection (sounds like a nice challenge) to work without being tampered with inside the Authorize attribute, and learn a little more about the reflection going on inside the telerik core there (probably over the weekend). I hadn't noticed that the the code automatically checks authorization level on 'action' links to determine whether it should be showing them or not. This will make my menu code at least all the better now.


Kind Regards,
Matthew Green
0
Matthew
Top achievements
Rank 1
answered on 30 Jul 2011, 01:17 AM
After some playing about with it the best place to change things turned out to be here:
Telerik.Web.Mvc.Infrastructure.Implementation.AuthorizeAttributeCache

Changing the static method which retrieves the attributes (specifically the Authorize ones) to:
private static IDictionary<string, IEnumerable<AuthorizeAttribute>> GetInternal(
            Type controllerType,
            IEnumerable<KeyValuePair<string, IList<MethodInfo>>> actionMethods)
        {
            //all attributes for the controller
            IDictionary<string, IEnumerable<AuthorizeAttribute>> attributes = new Dictionary<string, IEnumerable<AuthorizeAttribute>>(StringComparer.OrdinalIgnoreCase);
 
            ReflectedControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
 
            HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData =  RouteTable.Routes.GetRouteData(httpContextBase);
            RequestContext requestContext = new RequestContext(httpContextBase, routeData);
            ControllerBuilder controllerFactory = ControllerBuilder.Current;
 
            ControllerBase controller = controllerFactory
                .GetControllerFactory()
                .CreateController(requestContext, routeData.GetRequiredString("controller")) as ControllerBase;
 
            ControllerContext controllerContext = new ControllerContext(requestContext, controller);
             
            FilterAttributeFilterProvider filterProvider = FilterProviders.Providers
                .Where(filter => filter is FilterAttributeFilterProvider)
                .FirstOrDefault() as FilterAttributeFilterProvider;
 
            foreach (KeyValuePair<string, IList<MethodInfo>> pair in actionMethods)
            {
                IList<AuthorizeAttribute> actionAttributes = new List<AuthorizeAttribute>();
 
                foreach (MethodInfo method in pair.Value)
                {
                    ActionDescriptor actionDescritor = controllerDescriptor.FindAction(controllerContext, method.Name);
                    var filtersFromProvider = filterProvider
                        .GetFilters(controllerContext, actionDescritor)
                        .Where(e => e.Instance is AuthorizeAttribute)
                        .Where(e => e.Scope == FilterScope.Action || e.Scope == FilterScope.Controller)
                        .Select(e => e.Instance as AuthorizeAttribute);
 
                    //var telerikAtt = method.GetCustomAttributes(authorizeAttributeType, true).OfType<AuthorizeAttribute>();
                    actionAttributes.AddRange(filtersFromProvider);
                }
 
                attributes.Add(pair.Key, actionAttributes.OrderBy(a => a.Order));
            }
 
            return attributes;
        }

The who class now looks like:
// (c) Copyright 2002-2010 Telerik
// This source is subject to the GNU General Public License, version 2
// All other rights reserved.
 
namespace Telerik.Web.Mvc.Infrastructure.Implementation
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Web.Mvc;
    using System.Web.Routing;
 
    using Extensions;
    using System.Web;
 
    internal  class AuthorizeAttributeCache : IAuthorizeAttributeCache
    {
        private static readonly Type authorizeAttributeType = typeof(AuthorizeAttribute);
 
        private readonly IControllerTypeCache controllerTypeCache;
        private readonly IActionMethodCache actionMethodCache;
        private readonly ICache cache;
 
        public AuthorizeAttributeCache(ICache cache, IControllerTypeCache controllerTypeCache, IActionMethodCache actionMethodCache)
        {
            this.cache = cache;
            this.controllerTypeCache = controllerTypeCache;
            this.actionMethodCache = actionMethodCache;
        }
 
        public IEnumerable<AuthorizeAttribute> GetAuthorizeAttributes(RequestContext requestContext, string controllerName, string actionName, RouteValueDictionary routeValues)
        {
            IEnumerable<AuthorizeAttribute> attributes = null;
             
            IList<Type> controllerTypes = controllerTypeCache.GetControllerTypes(requestContext, controllerName) ?? new List<Type>();
 
            Type controllerType = GetControllerByArea(controllerTypes, routeValues);
             
            if (controllerType != null)
            {
                var map = cache.Get(controllerType.AssemblyQualifiedName,() => GetInternal(controllerType, actionMethodCache.GetAllActionMethods(controllerType)));
 
                map.TryGetValue(actionName, out attributes);
            }
             
            return attributes ?? new List<AuthorizeAttribute>();
        }
 
        private Type GetControllerByArea(IList<Type> controllerTypes, RouteValueDictionary routeValues)
        {
            object area;
 
            if (routeValues != null && routeValues.TryGetValue("Area", out area))
            {
                return controllerTypes.Where(t => t.FullName.Contains(area.ToString())).FirstOrDefault();
            }
 
            return controllerTypes.FirstOrDefault();
        }
 
        private static IDictionary<string, IEnumerable<AuthorizeAttribute>> GetInternal(
            Type controllerType,
            IEnumerable<KeyValuePair<string, IList<MethodInfo>>> actionMethods)
        {
            //all attributes for the controller
            IDictionary<string, IEnumerable<AuthorizeAttribute>> attributes = new Dictionary<string, IEnumerable<AuthorizeAttribute>>(StringComparer.OrdinalIgnoreCase);
 
            ReflectedControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
 
            HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData =  RouteTable.Routes.GetRouteData(httpContextBase);
            RequestContext requestContext = new RequestContext(httpContextBase, routeData);
            ControllerBuilder controllerFactory = ControllerBuilder.Current;
 
            ControllerBase controller = controllerFactory
                .GetControllerFactory()
                .CreateController(requestContext, routeData.GetRequiredString("controller")) as ControllerBase;
 
            ControllerContext controllerContext = new ControllerContext(requestContext, controller);
             
            FilterAttributeFilterProvider filterProvider = FilterProviders.Providers
                .Where(filter => filter is FilterAttributeFilterProvider)
                .FirstOrDefault() as FilterAttributeFilterProvider;
 
            foreach (KeyValuePair<string, IList<MethodInfo>> pair in actionMethods)
            {
                IList<AuthorizeAttribute> actionAttributes = new List<AuthorizeAttribute>();
 
                foreach (MethodInfo method in pair.Value)
                {
                    ActionDescriptor actionDescritor = controllerDescriptor.FindAction(controllerContext, method.Name);
                    var filtersFromProvider = filterProvider
                        .GetFilters(controllerContext, actionDescritor)
                        .Where(e => e.Instance is AuthorizeAttribute)
                        .Where(e => e.Scope == FilterScope.Action || e.Scope == FilterScope.Controller)
                        .Select(e => e.Instance as AuthorizeAttribute);
 
                    //var telerikAtt = method.GetCustomAttributes(authorizeAttributeType, true).OfType<AuthorizeAttribute>();
                    actionAttributes.AddRange(filtersFromProvider);
                }
 
                attributes.Add(pair.Key, actionAttributes.OrderBy(a => a.Order));
            }
 
            return attributes;
        }
    }
}

Im not sure what implications the above may or may not have on performance. There is certainly room for improvement on initiating some of the types and contexts... but has certainly allowed the custom properties to be successfully implemented by the Unity dependency injection.

This solution works as expected. The test project has been updated to demonstrate the same effect on the menu also. Both controls are working happily.

If anymore information is needed then im happy to help out.

Kind Regards,
Matthew
0
Georgi Krustev
Telerik team
answered on 05 Aug 2011, 08:37 AM
Hello Matthew,

 
Thank you for the proposed solution.

We will further investigate it and will decide whether we will be able to include it (or modified version of it) in the Telerik Extensions for ASP.NET MVC.

As a gratitude for your involvement I have updated your Telerik points.

Kind regards,
Georgi Krustev
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items

Tags
General Discussions
Asked by
Matthew
Top achievements
Rank 1
Answers by
Georgi Krustev
Telerik team
Matthew
Top achievements
Rank 1
Share this question
or