This commit is contained in:
perploug
2013-11-20 13:34:42 +01:00
28 changed files with 703 additions and 241 deletions

View File

@@ -1,48 +0,0 @@
using System.Runtime.Remoting.Contexts;
using System.Web.Http.Controllers;
namespace Umbraco.Web.WebApi
{
internal static class ControllerContextExtensions
{
/// <summary>
/// Sets the JSON GUID format to not have hyphens
/// </summary>
/// <param name="controllerContext"></param>
internal static void SetOutgoingNoHyphenGuidFormat(this HttpControllerContext controllerContext)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new GuidNoHyphenConverter());
}
/// <summary>
/// Sets the JSON datetime format to be a custom one
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="format"></param>
internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext, string format)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor(format));
}
/// <summary>
/// Sets the JSON datetime format to be our regular iso standard
/// </summary>
internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor("yyyy-MM-dd HH:mm:ss"));
}
/// <summary>
/// Removes the xml formatter so it only outputs json
/// </summary>
/// <param name="controllerContext"></param>
internal static void EnsureJsonOutputOnly(this HttpControllerContext controllerContext)
{
controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter);
}
}
}

View File

@@ -1,138 +1,134 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.WebApi.Filters
{
/// <summary>
/// This inspects the result of the action that returns a collection of content and removes
/// any item that the current user doesn't have access to
/// </summary>
internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute
{
private readonly Type _outgoingType;
private readonly string _propertyName;
public FilterAllowedOutgoingMediaAttribute(Type outgoingType)
{
_outgoingType = outgoingType;
}
public FilterAllowedOutgoingMediaAttribute(Type outgoingType, string propertyName)
: this(outgoingType)
{
_propertyName = propertyName;
}
/// <summary>
/// Returns true so that other filters can execute along with this one
/// </summary>
public override bool AllowMultiple
{
get { return true; }
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var user = UmbracoContext.Current.Security.CurrentUser;
if (user == null)
{
base.OnActionExecuted(actionExecutedContext);
return;
}
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
var collection = GetValueFromResponse(objectContent);
if (collection != null)
{
var items = Enumerable.ToList(collection);
FilterItems(user, items);
//set the return value
SetValueForResponse(objectContent, items);
}
}
base.OnActionExecuted(actionExecutedContext);
}
protected virtual void FilterItems(IUser user, IList items)
{
FilterBasedOnStartNode(items, user);
}
internal void FilterBasedOnStartNode(IList items, IUser user)
{
if (items.Count > 0)
{
var toRemove = new List<dynamic>();
foreach (dynamic item in items)
{
var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, user.StartContentId, Constants.System.RecycleBinContent));
if (!hasPathAccess)
{
toRemove.Add(item);
}
}
foreach (var item in toRemove)
{
items.Remove(item);
}
}
}
private void SetValueForResponse(ObjectContent objectContent, dynamic newVal)
{
if (TypeHelper.IsTypeAssignableFrom(_outgoingType, objectContent.Value.GetType()))
{
objectContent.Value = newVal;
}
else if (_propertyName.IsNullOrWhiteSpace() == false)
{
//try to get the enumerable collection from a property on the result object using reflection
var property = objectContent.Value.GetType().GetProperty(_propertyName);
if (property != null)
{
property.SetValue(objectContent.Value, newVal);
}
}
}
internal dynamic GetValueFromResponse(ObjectContent objectContent)
{
if (TypeHelper.IsTypeAssignableFrom(_outgoingType, objectContent.Value.GetType()))
{
return objectContent.Value;
}
if (_propertyName.IsNullOrWhiteSpace() == false)
{
//try to get the enumerable collection from a property on the result object using reflection
var property = objectContent.Value.GetType().GetProperty(_propertyName);
if (property != null)
{
var result = property.GetValue(objectContent.Value);
if (result != null && TypeHelper.IsTypeAssignableFrom(_outgoingType, result.GetType()))
{
return result;
}
}
}
return null;
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.WebApi.Filters
{
/// <summary>
/// This inspects the result of the action that returns a collection of content and removes
/// any item that the current user doesn't have access to
/// </summary>
internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute
{
private readonly Type _outgoingType;
private readonly string _propertyName;
public FilterAllowedOutgoingMediaAttribute(Type outgoingType)
{
_outgoingType = outgoingType;
}
public FilterAllowedOutgoingMediaAttribute(Type outgoingType, string propertyName)
: this(outgoingType)
{
_propertyName = propertyName;
}
/// <summary>
/// Returns true so that other filters can execute along with this one
/// </summary>
public override bool AllowMultiple
{
get { return true; }
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var user = UmbracoContext.Current.Security.CurrentUser;
if (user == null)
{
base.OnActionExecuted(actionExecutedContext);
return;
}
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
var collection = GetValueFromResponse(objectContent);
if (collection != null)
{
var items = Enumerable.ToList(collection);
FilterItems(user, items);
//set the return value
SetValueForResponse(objectContent, items);
}
}
base.OnActionExecuted(actionExecutedContext);
}
protected virtual void FilterItems(IUser user, IList items)
{
FilterBasedOnStartNode(items, user);
}
internal void FilterBasedOnStartNode(IList items, IUser user)
{
if (items.Count > 0)
{
var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, user.StartContentId, Constants.System.RecycleBinContent));
if (!hasPathAccess)
{
toRemove.Add(item);
}
foreach (var item in toRemove)
{
items.Remove(item);
}
}
}
private void SetValueForResponse(ObjectContent objectContent, dynamic newVal)
{
if (TypeHelper.IsTypeAssignableFrom(_outgoingType, objectContent.Value.GetType()))
{
objectContent.Value = newVal;
}
else if (_propertyName.IsNullOrWhiteSpace() == false)
{
//try to get the enumerable collection from a property on the result object using reflection
var property = objectContent.Value.GetType().GetProperty(_propertyName);
if (property != null)
{
property.SetValue(objectContent.Value, newVal);
}
}
}
internal dynamic GetValueFromResponse(ObjectContent objectContent)
{
if (TypeHelper.IsTypeAssignableFrom(_outgoingType, objectContent.Value.GetType()))
{
return objectContent.Value;
}
if (_propertyName.IsNullOrWhiteSpace() == false)
{
//try to get the enumerable collection from a property on the result object using reflection
var property = objectContent.Value.GetType().GetProperty(_propertyName);
if (property != null)
{
var result = property.GetValue(objectContent.Value);
if (result != null && TypeHelper.IsTypeAssignableFrom(_outgoingType, result.GetType()))
{
return result;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http.Filters;
namespace Umbraco.Web.WebApi.Filters
{
/// <summary>
/// Quickly split filters into different types
/// </summary>
internal class FilterGrouping
{
private readonly List<IActionFilter> _actionFilters = new List<IActionFilter>();
private readonly List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
private readonly List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
public FilterGrouping(IEnumerable<FilterInfo> filters)
{
if (filters == null) throw new ArgumentNullException("filters");
foreach (FilterInfo f in filters)
{
var filter = f.Instance;
Categorize(filter, _actionFilters);
Categorize(filter, _authorizationFilters);
Categorize(filter, _exceptionFilters);
}
}
public IEnumerable<IActionFilter> ActionFilters
{
get { return _actionFilters; }
}
public IEnumerable<IAuthorizationFilter> AuthorizationFilters
{
get { return _authorizationFilters; }
}
public IEnumerable<IExceptionFilter> ExceptionFilters
{
get { return _exceptionFilters; }
}
private static void Categorize<T>(IFilter filter, List<T> list) where T : class
{
T match = filter as T;
if (match != null)
{
list.Add(match);
}
}
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.Remoting.Contexts;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Umbraco.Web.WebApi.Filters;
namespace Umbraco.Web.WebApi
{
internal static class HttpControllerContextExtensions
{
/// <summary>
/// This method will go an execute the authorization filters for the controller action, if any fail
/// it will return their response, otherwise we'll return null.
/// </summary>
/// <param name="controllerContext"></param>
internal static async Task<HttpResponseMessage> InvokeAuthorizationFiltersForRequest(this HttpControllerContext controllerContext)
{
var controllerDescriptor = controllerContext.ControllerDescriptor;
var controllerServices = controllerDescriptor.Configuration.Services;
var actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);
var actionContext = new HttpActionContext(controllerContext, actionDescriptor);
var filters = actionDescriptor.GetFilterPipeline();
var filterGrouping = new FilterGrouping(filters);
var actionFilters = filterGrouping.ActionFilters;
// Because the continuation gets built from the inside out we need to reverse the filter list
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToArray();
if (authorizationFilters.Any())
{
var cancelToken = new CancellationToken();
var filterResult = await FilterContinuation(actionContext, cancelToken, authorizationFilters, 0);
if (filterResult != null)
{
//this means that the authorization filter has returned a result - unauthorized so we cannot continue
return filterResult;
}
}
return null;
}
/// <summary>
/// This method is how you execute a chain of filters, it needs to recursively call in to itself as the continuation for the next filter in the chain
/// </summary>
/// <param name="actionContext"></param>
/// <param name="token"></param>
/// <param name="filters"></param>
/// <param name="index"></param>
/// <returns></returns>
private static async Task<HttpResponseMessage> FilterContinuation(HttpActionContext actionContext, CancellationToken token, IList<IAuthorizationFilter> filters, int index)
{
return await filters[index].ExecuteAuthorizationFilterAsync(actionContext, token, () =>
{
Func<HttpResponseMessage> nullResponse = () => null;
return (index + 1) == filters.Count
? Task.Run(nullResponse)
: FilterContinuation(actionContext, token, filters, ++index);
});
}
/// <summary>
/// Sets the JSON GUID format to not have hyphens
/// </summary>
/// <param name="controllerContext"></param>
internal static void SetOutgoingNoHyphenGuidFormat(this HttpControllerContext controllerContext)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new GuidNoHyphenConverter());
}
/// <summary>
/// Sets the JSON datetime format to be a custom one
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="format"></param>
internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext, string format)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor(format));
}
/// <summary>
/// Sets the JSON datetime format to be our regular iso standard
/// </summary>
internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext)
{
var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor("yyyy-MM-dd HH:mm:ss"));
}
/// <summary>
/// Removes the xml formatter so it only outputs json
/// </summary>
/// <param name="controllerContext"></param>
internal static void EnsureJsonOutputOnly(this HttpControllerContext controllerContext)
{
controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter);
}
}
}