Merge branch '7.0.0' of https://github.com/umbraco/Umbraco-CMS into 7.0.0

This commit is contained in:
Per Ploug
2013-08-09 13:40:18 +02:00
21 changed files with 771 additions and 135 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -25,6 +26,7 @@ using Umbraco.Web.WebApi.Filters;
using umbraco;
using Umbraco.Core.Models;
using Umbraco.Core.Dynamics;
using umbraco.BusinessLogic.Actions;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
@@ -78,12 +80,7 @@ namespace Umbraco.Web.Editors
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(int id)
{
//with the filter applied we need to check if the content has already been looked up
var foundContent = !Request.Properties.ContainsKey("contentItem")
? Services.ContentService.GetById(id)
: (IContent) Request.Properties["contentItem"];
var foundContent = GetEntityFromRequest(() => Services.ContentService.GetById(id));
if (foundContent == null)
{
HandleContentNotFound(id);
@@ -171,20 +168,31 @@ namespace Umbraco.Web.Editors
[ModelBinder(typeof(ContentItemBinder))]
ContentItemSave<IContent> contentItem)
{
//We now need to validate that the user is allowed to be doing what they are doing
if (CheckPermissions(
Request.Properties,
Security.CurrentUser,
Services.UserService,
Services.ContentService,
contentItem.Id,
(int) contentItem.Action >= 2 ? ActionPublish.Instance.Letter : ActionSave.Instance.Letter,
contentItem.PersistedContent) == false)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
//If we've reached here it means:
// * Our model has been bound
// * and validated
// * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object
UpdateName(contentItem);
//TODO: We need to support 'send to publish'
//TODO: We'll need to save the new template, publishat, etc... values here
//TODO: We need to check the user's permissions to see if they are allowed to do this!
MapPropertyValues(contentItem);
//We need to manually check the validation results here because:
@@ -310,6 +318,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <param name="sorted"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("sorted.ParentId", 'S')]
public HttpResponseMessage PostSort(ContentSortOrder sorted)
{
//TODO: We need to check if the user is allowed to sort here!
@@ -325,11 +334,6 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Security.UserHasAppAccess(Constants.Applications.Content, UmbracoUser) == false)
{
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "User has no access to this application");
}
var contentService = Services.ContentService;
var sortedContent = new List<IContent>();
try
@@ -392,5 +396,58 @@ namespace Umbraco.Web.Editors
}
}
/// <summary>
/// Performs a permissions check for the user to check if it has access to the node based on
/// start node and/or permissions for the node
/// </summary>
/// <param name="storage">The storage to add the content item to so it can be reused</param>
/// <param name="user"></param>
/// <param name="userService"></param>
/// <param name="contentService"></param>
/// <param name="nodeId">The content to lookup, if the contentItem is not specified</param>
/// <param name="permissionToCheck"></param>
/// <param name="contentItem">Specifies the already resolved content item to check against, setting this ignores the nodeId</param>
/// <returns></returns>
internal static bool CheckPermissions(
IDictionary<string, object> storage,
IUser user,
IUserService userService,
IContentService contentService,
int nodeId,
char permissionToCheck,
IContent contentItem = null)
{
if (contentItem == null)
{
contentItem = contentService.GetById(nodeId);
}
if (contentItem == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//put the content item into storage so it can be retreived
// in the controller (saves a lookup)
storage[typeof(IContent).ToString()] = contentItem;
var hasPathAccess = user.HasPathAccess(contentItem);
if (hasPathAccess == false)
{
return false;
}
var permission = userService.GetPermissions(user, nodeId).FirstOrDefault();
if (permission == null || permission.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
{
return true;
}
else
{
return false;
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -117,5 +118,24 @@ namespace Umbraco.Web.Editors
}
}
/// <summary>
/// A helper method to attempt to get the instance from the request storage if it can be found there,
/// otherwise gets it from the callback specified
/// </summary>
/// <typeparam name="TPersisted"></typeparam>
/// <param name="getFromService"></param>
/// <returns></returns>
/// <remarks>
/// This is useful for when filters have alraedy looked up a persisted entity and we don't want to have
/// to look it up again.
/// </remarks>
protected TPersisted GetEntityFromRequest<TPersisted>(Func<TPersisted> getFromService)
where TPersisted : IContentBase
{
return Request.Properties.ContainsKey(typeof (TPersisted).ToString()) == false
? getFromService()
: (TPersisted) Request.Properties[typeof (TPersisted).ToString()];
}
}
}

View File

@@ -9,6 +9,8 @@ using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
using Umbraco.Web.Mvc;
@@ -17,6 +19,7 @@ using System.Linq;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using umbraco;
using umbraco.BusinessLogic.Actions;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Editors
@@ -70,7 +73,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("id")]
[EnsureUserPermissionForMedia("id")]
public MediaItemDisplay GetById(int id)
{
var foundContent = Services.MediaService.GetById(id);
@@ -86,6 +89,8 @@ namespace Umbraco.Web.Editors
/// </summary>
public IEnumerable<ContentItemBasic<ContentPropertyBasic, IMedia>> GetRootMedia()
{
//TODO: Add permissions check!
return Services.MediaService.GetRootMedia()
.Select(Mapper.Map<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>);
}
@@ -95,6 +100,9 @@ namespace Umbraco.Web.Editors
/// </summary>
public IEnumerable<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(int parentId)
{
//TODO: Change this to be like content with paged params
//TODO: filter results based on permissions!
return Services.MediaService.GetChildren(parentId)
.Select(Mapper.Map<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>);
}
@@ -108,6 +116,17 @@ namespace Umbraco.Web.Editors
[ModelBinder(typeof(MediaItemBinder))]
ContentItemSave<IMedia> contentItem)
{
//We now need to validate that the user is allowed to be doing what they are doing
if (CheckPermissions(
Request.Properties,
Security.CurrentUser,
Services.MediaService,
contentItem.Id,
contentItem.PersistedContent) == false)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
//If we've reached here it means:
// * Our model has been bound
// * and validated
@@ -163,6 +182,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <param name="sorted"></param>
/// <returns></returns>
[EnsureUserPermissionForContent("sorted.ParentId", 'S')]
public HttpResponseMessage PostSort(ContentSortOrder sorted)
{
if (sorted == null)
@@ -183,7 +203,7 @@ namespace Umbraco.Web.Editors
sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById));
// Save Media with new sort order and update content xml in db accordingly
if (!mediaService.Sort(sortedMedia))
if (mediaService.Sort(sortedMedia) == false)
{
LogHelper.Warn<MediaController>("Media sorting failed, this was probably caused by an event being cancelled");
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Media sorting failed, this was probably caused by an event being cancelled");
@@ -197,5 +217,31 @@ namespace Umbraco.Web.Editors
}
}
/// <summary>
/// Performs a permissions check for the user to check if it has access to the node based on
/// start node and/or permissions for the node
/// </summary>
/// <param name="storage">The storage to add the content item to so it can be reused</param>
/// <param name="user"></param>
/// <param name="mediaService"></param>
/// <param name="nodeId">The content to lookup, if the contentItem is not specified</param>
/// <param name="media">Specifies the already resolved content item to check against, setting this ignores the nodeId</param>
/// <returns></returns>
internal static bool CheckPermissions(IDictionary<string, object> storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null)
{
var contentItem = mediaService.GetById(nodeId);
if (contentItem == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//put the content item into storage so it can be retreived
// in the controller (saves a lookup)
storage.Add(typeof(IMedia).ToString(), contentItem);
var hasPathAccess = user.HasPathAccess(contentItem);
return hasPathAccess;
}
}
}

View File

@@ -8,21 +8,21 @@
/// <summary>
/// Saves the content item, no publish
/// </summary>
Save,
/// <summary>
/// Saves and publishes the content item
/// </summary>
Publish,
Save = 0,
/// <summary>
/// Saves a new content item
/// </summary>
SaveNew,
SaveNew = 1,
/// <summary>
/// Saves and publishes the content item
/// </summary>
Publish = 2,
/// <summary>
/// Saves an publishes a new content item
/// </summary>
PublishNew
PublishNew = 3
}
}

View File

@@ -498,6 +498,7 @@
<Compile Include="WebApi\Binders\MediaItemBinder.cs" />
<Compile Include="WebApi\Filters\EnsureUserPermissionForContentAttribute.cs" />
<Compile Include="WebApi\Filters\ContentItemValidationHelper.cs" />
<Compile Include="WebApi\Filters\EnsureUserPermissionForMediaAttribute.cs" />
<Compile Include="WebApi\Filters\FileUploadCleanupFilterAttribute.cs" />
<Compile Include="WebApi\Filters\FilterAllowedOutgoingContentAttribute.cs" />
<Compile Include="WebApi\Filters\HttpQueryStringFilterAttribute.cs" />

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Http;
@@ -9,6 +10,8 @@ using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;
using umbraco.BusinessLogic.Actions;
namespace Umbraco.Web.WebApi.Filters
@@ -19,50 +22,16 @@ namespace Umbraco.Web.WebApi.Filters
/// <remarks>
///
/// This first checks if the user can access this based on their start node, and then checks node permissions
/// TODO: Implement start node check!!!
///
/// By default the permission that is checked is browse but this can be specified in the ctor.
/// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params.
/// </remarks>
internal sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute
{
private int? _nodeId;
private readonly IUser _user;
private readonly IUserService _userService;
private readonly IContentService _contentService;
private IContentService ContentService
{
get { return _contentService ?? ApplicationContext.Current.Services.ContentService; }
}
private IUserService UserService
{
get { return _userService ?? ApplicationContext.Current.Services.UserService; }
}
private IUser User
{
get { return _user ?? UmbracoContext.Current.Security.CurrentUser; }
}
private readonly int? _nodeId;
private readonly string _paramName;
private readonly char _permissionToCheck;
/// <summary>
/// used for unit testing
/// </summary>
/// <param name="user"></param>
/// <param name="userService"></param>
/// <param name="contentService"></param>
/// <param name="nodeId"></param>
/// <param name="permissionToCheck"></param>
internal EnsureUserPermissionForContentAttribute(IUser user, IUserService userService, IContentService contentService, int nodeId, char permissionToCheck)
{
_user = user;
_userService = userService;
_contentService = contentService;
_nodeId = nodeId;
_permissionToCheck = permissionToCheck;
}
/// <summary>
/// This constructor will only be able to test the start node access
/// </summary>
@@ -73,12 +42,13 @@ namespace Umbraco.Web.WebApi.Filters
public EnsureUserPermissionForContentAttribute(string paramName)
{
Mandate.ParameterNotNullOrEmpty(paramName, "paramName");
_paramName = paramName;
_permissionToCheck = ActionBrowse.Instance.Letter;
}
public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck)
: this(paramName)
{
_paramName = paramName;
_permissionToCheck = permissionToCheck;
}
@@ -89,36 +59,47 @@ namespace Umbraco.Web.WebApi.Filters
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (User == null)
if (UmbracoContext.Current.Security.CurrentUser == null)
{
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
int nodeId;
if (_nodeId.HasValue == false)
{
if (actionContext.ActionArguments[_paramName] == null)
var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
if (actionContext.ActionArguments[parts[0]] == null)
{
throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName);
}
_nodeId = (int)actionContext.ActionArguments[_paramName];
if (parts.Length == 1)
{
nodeId = (int)actionContext.ActionArguments[parts[0]];
}
else
{
//now we need to see if we can get the property of whatever object it is
var pType = actionContext.ActionArguments[parts[0]].GetType();
var prop = pType.GetProperty(parts[1]);
if (prop == null)
{
throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName);
}
nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]);
}
}
var contentItem = ContentService.GetById(_nodeId.Value);
if (contentItem == null)
else
{
throw new HttpResponseException(System.Net.HttpStatusCode.NotFound);
nodeId = _nodeId.Value;
}
var hasPathAccess = User.HasPathAccess(contentItem);
if (hasPathAccess == false)
{
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
var permission = UserService.GetPermissions(User, _nodeId.Value).FirstOrDefault();
if (permission == null || permission.AssignedPermissions.Contains(_permissionToCheck.ToString(CultureInfo.InvariantCulture)))
if (ContentController.CheckPermissions(
actionContext.Request.Properties,
UmbracoContext.Current.Security.CurrentUser,
ApplicationContext.Current.Services.UserService,
ApplicationContext.Current.Services.ContentService, nodeId, _permissionToCheck))
{
base.OnActionExecuting(actionContext);
}
@@ -126,7 +107,10 @@ namespace Umbraco.Web.WebApi.Filters
{
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Editors;
using umbraco.BusinessLogic.Actions;
namespace Umbraco.Web.WebApi.Filters
{
/// <summary>
/// Auth filter to check if the current user has access to the content item
/// </summary>
/// <remarks>
/// Since media doesn't have permissions, this simply checks start node access
/// </remarks>
internal sealed class EnsureUserPermissionForMediaAttribute : ActionFilterAttribute
{
private readonly int? _nodeId;
private readonly string _paramName;
/// <summary>
/// This constructor will only be able to test the start node access
/// </summary>
public EnsureUserPermissionForMediaAttribute(int nodeId)
{
_nodeId = nodeId;
}
public EnsureUserPermissionForMediaAttribute(string paramName)
{
Mandate.ParameterNotNullOrEmpty(paramName, "paramName");
_paramName = paramName;
}
public override bool AllowMultiple
{
get { return true; }
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (UmbracoContext.Current.Security.CurrentUser == null)
{
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
int nodeId;
if (_nodeId.HasValue == false)
{
var parts = _paramName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
if (actionContext.ActionArguments[parts[0]] == null)
{
throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName);
}
if (parts.Length == 1)
{
nodeId = (int)actionContext.ActionArguments[parts[0]];
}
else
{
//now we need to see if we can get the property of whatever object it is
var pType = actionContext.ActionArguments[parts[0]].GetType();
var prop = pType.GetProperty(parts[1]);
if (prop == null)
{
throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName);
}
nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]);
}
}
else
{
nodeId = _nodeId.Value;
}
if (MediaController.CheckPermissions(
actionContext.Request.Properties,
UmbracoContext.Current.Security.CurrentUser,
ApplicationContext.Current.Services.MediaService, nodeId))
{
base.OnActionExecuting(actionContext);
}
else
{
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}