Revert "Temp8 tinymce"
This commit is contained in:
@@ -39,9 +39,9 @@ namespace Umbraco.Web.WebApi
|
||||
|
||||
var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers);
|
||||
|
||||
using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding,
|
||||
using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding,
|
||||
//we are only writing a few chars so we don't need to allocate a large buffer
|
||||
128,
|
||||
128,
|
||||
//this is important! We don't want to close the stream, the base class is in charge of stream management, we just want to write to it.
|
||||
leaveOpen:true))
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Web.WebApi
|
||||
public class AngularJsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration
|
||||
{
|
||||
public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
{
|
||||
//remove all json/xml formatters then add our custom one
|
||||
var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList();
|
||||
foreach (var r in toRemove)
|
||||
@@ -21,4 +21,4 @@ namespace Umbraco.Web.WebApi
|
||||
controllerSettings.Formatters.Add(new AngularJsonMediaTypeFormatter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs
Normal file
28
src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
internal class BlueprintItemBinder : ContentItemBinder
|
||||
{
|
||||
public BlueprintItemBinder(ApplicationContext applicationContext)
|
||||
: base(applicationContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public BlueprintItemBinder()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IContent GetExisting(ContentItemSave model)
|
||||
{
|
||||
return ApplicationContext.Services.ContentService.GetBlueprintById(Convert.ToInt32(model.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs
Normal file
205
src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding.Binders;
|
||||
using System.Web.Http.Validation;
|
||||
using System.Web.ModelBinding;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using IModelBinder = System.Web.Http.ModelBinding.IModelBinder;
|
||||
using ModelBindingContext = System.Web.Http.ModelBinding.ModelBindingContext;
|
||||
using ModelMetadata = System.Web.Http.Metadata.ModelMetadata;
|
||||
using ModelMetadataProvider = System.Web.Http.Metadata.ModelMetadataProvider;
|
||||
using MutableObjectModelBinder = System.Web.Http.ModelBinding.Binders.MutableObjectModelBinder;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds the content model to the controller action for the posted multi-part Post
|
||||
/// </summary>
|
||||
internal abstract class ContentItemBaseBinder<TPersisted, TModelSave> : IModelBinder
|
||||
where TPersisted : class, IContentBase
|
||||
where TModelSave : ContentBaseItemSave<TPersisted>
|
||||
{
|
||||
protected ApplicationContext ApplicationContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="applicationContext"></param>
|
||||
internal ContentItemBaseBinder(ApplicationContext applicationContext)
|
||||
{
|
||||
ApplicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
|
||||
{
|
||||
//NOTE: Validation is done in the filter
|
||||
if (actionContext.Request.Content.IsMimeMultipartContent() == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
||||
}
|
||||
|
||||
var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads");
|
||||
//ensure it exists
|
||||
Directory.CreateDirectory(root);
|
||||
var provider = new MultipartFormDataStreamProvider(root);
|
||||
|
||||
var task = Task.Run(() => GetModelAsync(actionContext, bindingContext, provider))
|
||||
.ContinueWith(x =>
|
||||
{
|
||||
if (x.IsFaulted && x.Exception != null)
|
||||
{
|
||||
throw x.Exception;
|
||||
}
|
||||
|
||||
//now that everything is binded, validate the properties
|
||||
var contentItemValidator = GetValidationHelper();
|
||||
contentItemValidator.ValidateItem(actionContext, x.Result);
|
||||
|
||||
bindingContext.Model = x.Result;
|
||||
});
|
||||
|
||||
task.Wait();
|
||||
|
||||
return bindingContext.Model != null;
|
||||
}
|
||||
|
||||
protected virtual ContentItemValidationHelper<TPersisted, TModelSave> GetValidationHelper()
|
||||
{
|
||||
return new ContentItemValidationHelper<TPersisted, TModelSave>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the model from the request contents
|
||||
/// </summary>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <param name="bindingContext"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<TModelSave> GetModelAsync(HttpActionContext actionContext, ModelBindingContext bindingContext, MultipartFormDataStreamProvider provider)
|
||||
{
|
||||
var request = actionContext.Request;
|
||||
|
||||
//IMPORTANT!!! We need to ensure the umbraco context here because this is running in an async thread
|
||||
var httpContext = (HttpContextBase) request.Properties["MS_HttpContext"];
|
||||
UmbracoContext.EnsureContext(
|
||||
httpContext,
|
||||
ApplicationContext.Current,
|
||||
new WebSecurity(httpContext, ApplicationContext.Current));
|
||||
|
||||
var content = request.Content;
|
||||
|
||||
var result = await content.ReadAsMultipartAsync(provider);
|
||||
|
||||
if (result.FormData["contentItem"] == null)
|
||||
{
|
||||
var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
|
||||
response.ReasonPhrase = "The request was not formatted correctly and is missing the 'contentItem' parameter";
|
||||
throw new HttpResponseException(response);
|
||||
}
|
||||
|
||||
//get the string json from the request
|
||||
var contentItem = result.FormData["contentItem"];
|
||||
|
||||
//deserialize into our model
|
||||
var model = JsonConvert.DeserializeObject<TModelSave>(contentItem);
|
||||
|
||||
//get the default body validator and validate the object
|
||||
var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator();
|
||||
var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
|
||||
//all validation errors will not contain a prefix
|
||||
bodyValidator.Validate(model, typeof(TModelSave), metadataProvider, actionContext, "");
|
||||
|
||||
//get the files
|
||||
foreach (var file in result.FileData)
|
||||
{
|
||||
//The name that has been assigned in JS has 2 parts and the second part indicates the property id
|
||||
// for which the file belongs.
|
||||
var parts = file.Headers.ContentDisposition.Name.Trim(new char[] { '\"' }).Split('_');
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
|
||||
response.ReasonPhrase = "The request was not formatted correctly the file name's must be underscore delimited";
|
||||
throw new HttpResponseException(response);
|
||||
}
|
||||
var propAlias = parts[1];
|
||||
|
||||
var fileName = file.Headers.ContentDisposition.FileName.Trim(new char[] {'\"'});
|
||||
|
||||
model.UploadedFiles.Add(new ContentItemFile
|
||||
{
|
||||
TempFilePath = file.LocalFileName,
|
||||
PropertyAlias = propAlias,
|
||||
FileName = fileName
|
||||
});
|
||||
}
|
||||
|
||||
if (ContentControllerBase.IsCreatingAction(model.Action))
|
||||
{
|
||||
//we are creating new content
|
||||
model.PersistedContent = CreateNew(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
//finally, let's lookup the real content item and create the DTO item
|
||||
model.PersistedContent = GetExisting(model);
|
||||
}
|
||||
|
||||
//create the dto from the persisted model
|
||||
if (model.PersistedContent != null)
|
||||
{
|
||||
model.ContentDto = MapFromPersisted(model);
|
||||
}
|
||||
if (model.ContentDto != null)
|
||||
{
|
||||
//now map all of the saved values to the dto
|
||||
MapPropertyValuesFromSaved(model, model.ContentDto);
|
||||
}
|
||||
|
||||
model.Name = model.Name.Trim();
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// we will now assign all of the values in the 'save' model to the DTO object
|
||||
/// </summary>
|
||||
/// <param name="saveModel"></param>
|
||||
/// <param name="dto"></param>
|
||||
private static void MapPropertyValuesFromSaved(TModelSave saveModel, ContentItemDto<TPersisted> dto)
|
||||
{
|
||||
//NOTE: Don't convert this to linq, this is much quicker
|
||||
foreach (var p in saveModel.Properties)
|
||||
{
|
||||
foreach (var propertyDto in dto.Properties)
|
||||
{
|
||||
if (propertyDto.Alias != p.Alias) continue;
|
||||
propertyDto.Value = p.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract TPersisted GetExisting(TModelSave model);
|
||||
protected abstract TPersisted CreateNew(TModelSave model);
|
||||
protected abstract ContentItemDto<TPersisted> MapFromPersisted(TModelSave model);
|
||||
}
|
||||
}
|
||||
45
src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
Normal file
45
src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
internal class ContentItemBinder : ContentItemBaseBinder<IContent, ContentItemSave>
|
||||
{
|
||||
|
||||
public ContentItemBinder(ApplicationContext applicationContext)
|
||||
: base(applicationContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ContentItemBinder()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IContent GetExisting(ContentItemSave model)
|
||||
{
|
||||
return ApplicationContext.Services.ContentService.GetById(Convert.ToInt32(model.Id));
|
||||
}
|
||||
|
||||
protected override IContent CreateNew(ContentItemSave model)
|
||||
{
|
||||
var contentType = ApplicationContext.Services.ContentTypeService.GetContentType(model.ContentTypeAlias);
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new InvalidOperationException("No content type found with alias " + model.ContentTypeAlias);
|
||||
}
|
||||
return new Content(model.Name, model.ParentId, contentType);
|
||||
}
|
||||
|
||||
protected override ContentItemDto<IContent> MapFromPersisted(ContentItemSave model)
|
||||
{
|
||||
return Mapper.Map<IContent, ContentItemDto<IContent>>(model.PersistedContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs
Normal file
44
src/Umbraco.Web/WebApi/Binders/MediaItemBinder.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
internal class MediaItemBinder : ContentItemBaseBinder<IMedia, MediaItemSave>
|
||||
{
|
||||
public MediaItemBinder(ApplicationContext applicationContext)
|
||||
: base(applicationContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public MediaItemBinder()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMedia GetExisting(MediaItemSave model)
|
||||
{
|
||||
return ApplicationContext.Services.MediaService.GetById(Convert.ToInt32(model.Id));
|
||||
}
|
||||
|
||||
protected override IMedia CreateNew(MediaItemSave model)
|
||||
{
|
||||
var mediaType = ApplicationContext.Services.ContentTypeService.GetMediaType(model.ContentTypeAlias);
|
||||
if (mediaType == null)
|
||||
{
|
||||
throw new InvalidOperationException("No media type found with alias " + model.ContentTypeAlias);
|
||||
}
|
||||
return new Core.Models.Media(model.Name, model.ParentId, mediaType);
|
||||
}
|
||||
|
||||
protected override ContentItemDto<IMedia> MapFromPersisted(MediaItemSave model)
|
||||
{
|
||||
return Mapper.Map<IMedia, ContentItemDto<IMedia>>(model.PersistedContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
377
src/Umbraco.Web/WebApi/Binders/MemberBinder.cs
Normal file
377
src/Umbraco.Web/WebApi/Binders/MemberBinder.cs
Normal file
@@ -0,0 +1,377 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Binders
|
||||
{
|
||||
internal class MemberBinder : ContentItemBaseBinder<IMember, MemberSave>
|
||||
{
|
||||
public MemberBinder(ApplicationContext applicationContext)
|
||||
: base(applicationContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public MemberBinder()
|
||||
: this(ApplicationContext.Current)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ContentItemValidationHelper<IMember, MemberSave> GetValidationHelper()
|
||||
{
|
||||
return new MemberValidationHelper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IMember instance used to bind values to and save (depending on the membership scenario)
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
protected override IMember GetExisting(MemberSave model)
|
||||
{
|
||||
var scenario = ApplicationContext.Services.MemberService.GetMembershipScenario();
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
switch (scenario)
|
||||
{
|
||||
case MembershipScenario.NativeUmbraco:
|
||||
return GetExisting(model.Key);
|
||||
case MembershipScenario.CustomProviderWithUmbracoLink:
|
||||
case MembershipScenario.StandaloneCustomProvider:
|
||||
default:
|
||||
var membershipUser = provider.GetUser(model.Key, false);
|
||||
if (membershipUser == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find member with key " + model.Key);
|
||||
}
|
||||
|
||||
//TODO: Support this scenario!
|
||||
//if (scenario == MembershipScenario.CustomProviderWithUmbracoLink)
|
||||
//{
|
||||
// //if there's a 'Member' type then we should be able to just go get it from the db since it was created with a link
|
||||
// // to our data.
|
||||
// var memberType = ApplicationContext.Services.MemberTypeService.GetMemberType(Constants.Conventions.MemberTypes.Member);
|
||||
// if (memberType != null)
|
||||
// {
|
||||
// var existing = GetExisting(model.Key);
|
||||
// FilterContentTypeProperties(existing.ContentType, existing.ContentType.PropertyTypes.Select(x => x.Alias).ToArray());
|
||||
// }
|
||||
//}
|
||||
|
||||
//generate a member for a generic membership provider
|
||||
//NOTE: We don't care about the password here, so just generate something
|
||||
//var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N"));
|
||||
|
||||
//var convertResult = membershipUser.ProviderUserKey.TryConvertTo<Guid>();
|
||||
//if (convertResult.Success == false)
|
||||
//{
|
||||
// throw new InvalidOperationException("Only membership providers that store a GUID as their ProviderUserKey are supported" + model.Key);
|
||||
//}
|
||||
//member.Key = convertResult.Result;
|
||||
|
||||
var member = Mapper.Map<MembershipUser, IMember>(membershipUser);
|
||||
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
private IMember GetExisting(Guid key)
|
||||
{
|
||||
var member = ApplicationContext.Services.MemberService.GetByKey(key);
|
||||
if (member == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find member with key " + key);
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of IMember used when creating a member
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Depending on whether a custom membership provider is configured this will return different results.
|
||||
/// </remarks>
|
||||
protected override IMember CreateNew(MemberSave model)
|
||||
{
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
if (provider.IsUmbracoMembershipProvider())
|
||||
{
|
||||
var contentType = ApplicationContext.Services.MemberTypeService.Get(model.ContentTypeAlias);
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias);
|
||||
}
|
||||
|
||||
//remove all membership properties, these values are set with the membership provider.
|
||||
FilterMembershipProviderProperties(contentType);
|
||||
|
||||
//return the new member with the details filled in
|
||||
return new Member(model.Name, model.Email, model.Username, model.Password.NewPassword, contentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
//A custom membership provider is configured
|
||||
|
||||
//NOTE: Below we are assigning the password to just a new GUID because we are not actually storing the password, however that
|
||||
// field is mandatory in the database so we need to put something there.
|
||||
|
||||
//If the default Member type exists, we'll use that to create the IMember - that way we can associate the custom membership
|
||||
// provider to our data - eventually we can support editing custom properties with a custom provider.
|
||||
var memberType = ApplicationContext.Services.MemberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias);
|
||||
if (memberType != null)
|
||||
{
|
||||
FilterContentTypeProperties(memberType, memberType.PropertyTypes.Select(x => x.Alias).ToArray());
|
||||
return new Member(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N"), memberType);
|
||||
}
|
||||
|
||||
//generate a member for a generic membership provider
|
||||
var member = MemberService.CreateGenericMembershipProviderMember(model.Name, model.Email, model.Username, Guid.NewGuid().ToString("N"));
|
||||
//we'll just remove all properties here otherwise we'll end up with validation errors, we don't want to persist any property data anyways
|
||||
// in this case.
|
||||
FilterContentTypeProperties(member.ContentType, member.ContentType.PropertyTypes.Select(x => x.Alias).ToArray());
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will remove all of the special membership provider properties which were required to display the property editors
|
||||
/// for editing - but the values have been mapped back ot the MemberSave object directly - we don't want to keep these properties
|
||||
/// on the IMember because they will attempt to be persisted which we don't want since they might not even exist.
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
private void FilterMembershipProviderProperties(IContentTypeBase contentType)
|
||||
{
|
||||
var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
|
||||
//remove all membership properties, these values are set with the membership provider.
|
||||
var exclude = defaultProps.Select(x => x.Value.Alias).ToArray();
|
||||
FilterContentTypeProperties(contentType, exclude);
|
||||
}
|
||||
|
||||
private void FilterContentTypeProperties(IContentTypeBase contentType, IEnumerable<string> exclude)
|
||||
{
|
||||
//remove all properties based on the exclusion list
|
||||
foreach (var remove in exclude)
|
||||
{
|
||||
if (contentType.PropertyTypeExists(remove))
|
||||
{
|
||||
contentType.RemovePropertyType(remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override ContentItemDto<IMember> MapFromPersisted(MemberSave model)
|
||||
{
|
||||
return Mapper.Map<IMember, ContentItemDto<IMember>>(model.PersistedContent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom validation helper so that we can exclude the Member.StandardPropertyTypeStubs from being validating for existence
|
||||
/// </summary>
|
||||
internal class MemberValidationHelper : ContentItemValidationHelper<IMember, MemberSave>
|
||||
{
|
||||
/// <summary>
|
||||
/// We need to manually validate a few things here like email and login to make sure they are valid and aren't duplicates
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ValidatePropertyData(ContentItemBasic<ContentPropertyBasic, IMember> postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
var memberSave = (MemberSave)postedItem;
|
||||
|
||||
if (memberSave.Username.IsNullOrWhiteSpace())
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid user name", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
if (memberSave.Email.IsNullOrWhiteSpace() || new EmailAddressAttribute().IsValid(memberSave.Email) == false)
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(
|
||||
new ValidationResult("Invalid email", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
//default provider!
|
||||
var membershipProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
var validEmail = ValidateUniqueEmail(memberSave, membershipProvider, actionContext);
|
||||
if (validEmail == false)
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(
|
||||
new ValidationResult("Email address is already in use", new[] { "value" }),
|
||||
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
var validLogin = ValidateUniqueLogin(memberSave, membershipProvider, actionContext);
|
||||
if (validLogin == false)
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(
|
||||
new ValidationResult("Username is already in use", new[] { "value" }),
|
||||
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
return base.ValidatePropertyData(postedItem, actionContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This ensures that the internal membership property types are removed from validation before processing the validation
|
||||
/// since those properties are actually mapped to real properties of the IMember.
|
||||
/// This also validates any posted data for fields that are sensitive.
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, IMember> postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
var propertiesToValidate = postedItem.Properties.ToList();
|
||||
var defaultProps = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
|
||||
var exclude = defaultProps.Select(x => x.Value.Alias).ToArray();
|
||||
foreach (var remove in exclude)
|
||||
{
|
||||
propertiesToValidate.RemoveAll(property => property.Alias == remove);
|
||||
}
|
||||
|
||||
var httpCtx = actionContext.Request.TryGetHttpContext();
|
||||
if (httpCtx.Success == false)
|
||||
{
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "No http context");
|
||||
return false;
|
||||
}
|
||||
var umbCtx = httpCtx.Result.GetUmbracoContext();
|
||||
|
||||
//if the user doesn't have access to sensitive values, then we need to validate the incoming properties to check
|
||||
//if a sensitive value is being submitted.
|
||||
if (umbCtx.Security.CurrentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
var sensitiveProperties = postedItem.PersistedContent.ContentType
|
||||
.PropertyTypes.Where(x => postedItem.PersistedContent.ContentType.IsSensitiveProperty(x.Alias))
|
||||
.ToList();
|
||||
|
||||
foreach (var sensitiveProperty in sensitiveProperties)
|
||||
{
|
||||
var prop = propertiesToValidate.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias);
|
||||
|
||||
if (prop != null)
|
||||
{
|
||||
//this should not happen, this means that there was data posted for a sensitive property that
|
||||
//the user doesn't have access to, which means that someone is trying to hack the values.
|
||||
|
||||
var message = string.Format("property with alias: {0} cannot be posted", prop.Alias);
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new InvalidOperationException(message));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ValidateProperties(propertiesToValidate, postedItem.PersistedContent.Properties.ToList(), actionContext);
|
||||
}
|
||||
|
||||
internal bool ValidateUniqueLogin(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
|
||||
{
|
||||
if (contentItem == null) throw new ArgumentNullException("contentItem");
|
||||
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
|
||||
|
||||
int totalRecs;
|
||||
var existingByName = membershipProvider.FindUsersByName(contentItem.Username.Trim(), 0, int.MaxValue, out totalRecs);
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
|
||||
//ok, we're updating the member, we need to check if they are changing their login and if so, does it exist already ?
|
||||
if (contentItem.PersistedContent.Username.InvariantEquals(contentItem.Username.Trim()) == false)
|
||||
{
|
||||
//they are changing their login name
|
||||
if (existingByName.Cast<MembershipUser>().Select(x => x.UserName)
|
||||
.Any(x => x == contentItem.Username.Trim()))
|
||||
{
|
||||
//the user cannot use this login
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//check if the user's login already exists
|
||||
if (existingByName.Cast<MembershipUser>().Select(x => x.UserName)
|
||||
.Any(x => x == contentItem.Username.Trim()))
|
||||
{
|
||||
//the user cannot use this login
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//we don't support this for members
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool ValidateUniqueEmail(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
|
||||
{
|
||||
if (contentItem == null) throw new ArgumentNullException("contentItem");
|
||||
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
|
||||
|
||||
if (membershipProvider.RequiresUniqueEmail == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int totalRecs;
|
||||
var existingByEmail = membershipProvider.FindUsersByEmail(contentItem.Email.Trim(), 0, int.MaxValue, out totalRecs);
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
//ok, we're updating the member, we need to check if they are changing their email and if so, does it exist already ?
|
||||
if (contentItem.PersistedContent.Email.InvariantEquals(contentItem.Email.Trim()) == false)
|
||||
{
|
||||
//they are changing their email
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//check if the user's email already exists
|
||||
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
|
||||
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
|
||||
{
|
||||
//the user cannot use this email
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//we don't support this for members
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs
Normal file
27
src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to convert the format of a DateTime object when serializing
|
||||
/// </summary>
|
||||
internal class CustomDateTimeConvertor : IsoDateTimeConverter
|
||||
{
|
||||
private readonly string _dateTimeFormat;
|
||||
|
||||
public CustomDateTimeConvertor(string dateTimeFormat)
|
||||
{
|
||||
Mandate.ParameterNotNullOrEmpty(dateTimeFormat, "dateTimeFormat");
|
||||
_dateTimeFormat = dateTimeFormat;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(((DateTime)value).ToString(_dateTimeFormat, CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,4 +14,4 @@ namespace Umbraco.Web.WebApi
|
||||
actionContext.ControllerContext.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using System.Net.Http.Headers;
|
||||
using System.Web.Helpers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
@@ -35,7 +34,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <param name="cookieToken"></param>
|
||||
/// <param name="headerToken"></param>
|
||||
/// <remarks>
|
||||
/// .Net provides us a way to validate one token with another for added security. With the way angular works, this
|
||||
/// .Net provides us a way to validate one token with another for added security. With the way angular works, this
|
||||
/// means that we need to set 2 cookies since angular uses one cookie value to create the header value, then we want to validate
|
||||
/// this header value against our original cookie value.
|
||||
/// </remarks>
|
||||
@@ -59,7 +58,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Current.Logger.Error(typeof(AngularAntiForgeryHelper), ex, "Could not validate XSRF token");
|
||||
LogHelper.Error(typeof(AngularAntiForgeryHelper), "Could not validate XSRF token", ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -115,6 +114,6 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
cookieToken == null ? null : cookieToken,
|
||||
out failedReason);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.UI;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Automatically checks if any request is a non-GET and if the
|
||||
/// Automatically checks if any request is a non-GET and if the
|
||||
/// resulting message is INotificationModel in which case it will append any Event Messages
|
||||
/// currently in the request.
|
||||
/// </summary>
|
||||
@@ -27,7 +26,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var notifications = obj.Value as INotificationModel;
|
||||
if (notifications == null) return;
|
||||
|
||||
var msgs = Current.EventMessages;
|
||||
var msgs = UmbracoContext.Current.GetCurrentEventMessages();
|
||||
if (msgs == null) return;
|
||||
|
||||
foreach (var eventMessage in msgs.GetAll())
|
||||
@@ -63,4 +62,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -50,7 +50,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var actionContext = actionExecutedContext.ActionContext;
|
||||
if (actionContext.ActionArguments[_userIdParameter] == null)
|
||||
{
|
||||
throw new InvalidOperationException("No argument found for the current action with the name: " + _userIdParameter);
|
||||
throw new InvalidOperationException("No argument found for the current action with the name: " + _userIdParameter);
|
||||
}
|
||||
|
||||
var user = UmbracoContext.Current.Security.CurrentUser;
|
||||
@@ -73,4 +73,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
throw new InvalidOperationException("The id type: " + parameterValue.GetType() + " is not a supported id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,8 +6,6 @@ using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
@@ -37,7 +35,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
|
||||
//we need new tokens and append the custom header if changes have been made
|
||||
if (actionExecutedContext.ActionContext.Request.Properties.ContainsKey(typeof(CheckIfUserTicketDataIsStaleAttribute).Name))
|
||||
{
|
||||
{
|
||||
var tokenFilter = new SetAngularAntiForgeryTokensAttribute();
|
||||
tokenFilter.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
@@ -67,35 +65,35 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var userId = identity.Id.TryConvertTo<int>();
|
||||
if (userId == false) return;
|
||||
|
||||
var user = Current.Services.UserService.GetUserById(userId.Result);
|
||||
var user = ApplicationContext.Current.Services.UserService.GetUserById(userId.Result);
|
||||
if (user == null) return;
|
||||
|
||||
|
||||
//a list of checks to execute, if any of them pass then we resync
|
||||
var checks = new Func<bool>[]
|
||||
{
|
||||
() => user.Username != identity.Username,
|
||||
() =>
|
||||
{
|
||||
var culture = UserExtensions.GetUserCulture(user, Current.Services.TextService, UmbracoConfig.For.GlobalSettings());
|
||||
var culture = UserExtensions.GetUserCulture(user, ApplicationContext.Current.Services.TextService);
|
||||
return culture != null && culture.ToString() != identity.Culture;
|
||||
},
|
||||
},
|
||||
() => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false,
|
||||
() => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false,
|
||||
() =>
|
||||
{
|
||||
var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, Current.Services.EntityService);
|
||||
var startContentIds = UserExtensions.CalculateContentStartNodeIds(user, ApplicationContext.Current.Services.EntityService);
|
||||
return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, Current.Services.EntityService);
|
||||
var startMediaIds = UserExtensions.CalculateMediaStartNodeIds(user, ApplicationContext.Current.Services.EntityService);
|
||||
return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false;
|
||||
}
|
||||
};
|
||||
|
||||
if (checks.Any(check => check()))
|
||||
{
|
||||
await ReSync(user, actionContext);
|
||||
await ReSync(user, actionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +117,8 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
actionContext.Request.SetPrincipalForRequest(owinCtx.Result.Request.User);
|
||||
|
||||
//flag that we've made changes
|
||||
actionContext.Request.Properties[typeof(CheckIfUserTicketDataIsStaleAttribute).Name] = true;
|
||||
actionContext.Request.Properties[typeof(CheckIfUserTicketDataIsStaleAttribute).Name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
context.Response.Headers.AddCookies(new[] { angularCookie, validationCookie });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
187
src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs
Normal file
187
src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A validation helper class used with ContentItemValidationFilterAttribute to be shared between content, media, etc...
|
||||
/// </summary>
|
||||
/// <typeparam name="TPersisted"></typeparam>
|
||||
/// <typeparam name="TModelSave"></typeparam>
|
||||
/// <remarks>
|
||||
/// If any severe errors occur then the response gets set to an error and execution will not continue. Property validation
|
||||
/// errors will just be added to the ModelState.
|
||||
/// </remarks>
|
||||
internal class ContentItemValidationHelper<TPersisted, TModelSave>
|
||||
where TPersisted : class, IContentBase
|
||||
where TModelSave : ContentBaseItemSave<TPersisted>
|
||||
{
|
||||
|
||||
public void ValidateItem(HttpActionContext actionContext, string argumentName)
|
||||
{
|
||||
var contentItem = actionContext.ActionArguments[argumentName] as TModelSave;
|
||||
if (contentItem == null)
|
||||
{
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(TModelSave) + " found in request");
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateItem(actionContext, contentItem);
|
||||
|
||||
}
|
||||
|
||||
public void ValidateItem(HttpActionContext actionContext, TModelSave contentItem)
|
||||
{
|
||||
//now do each validation step
|
||||
if (ValidateExistingContent(contentItem, actionContext) == false) return;
|
||||
if (ValidateProperties(contentItem, actionContext) == false) return;
|
||||
if (ValidatePropertyData(contentItem, actionContext) == false) return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the content exists
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ValidateExistingContent(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
if (postedItem.PersistedContent == null)
|
||||
{
|
||||
var message = string.Format("content with id: {0} was not found", postedItem.Id);
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure all of the ids in the post are valid
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ValidateProperties(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
return ValidateProperties(postedItem.Properties.ToList(), postedItem.PersistedContent.Properties.ToList(), actionContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This validates that all of the posted properties exist on the persisted entity
|
||||
/// </summary>
|
||||
/// <param name="postedProperties"></param>
|
||||
/// <param name="persistedProperties"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
protected bool ValidateProperties(List<ContentPropertyBasic> postedProperties , List<Property> persistedProperties, HttpActionContext actionContext)
|
||||
{
|
||||
foreach (var p in postedProperties)
|
||||
{
|
||||
if (persistedProperties.Any(property => property.Alias == p.Alias) == false)
|
||||
{
|
||||
//TODO: Do we return errors here ? If someone deletes a property whilst their editing then should we just
|
||||
//save the property data that remains? Or inform them they need to reload... not sure. This problem exists currently too i think.
|
||||
|
||||
var message = string.Format("property with alias: {0} was not found", p.Alias);
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new InvalidOperationException(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the data for each property
|
||||
/// </summary>
|
||||
/// <param name="postedItem"></param>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// All property data validation goes into the modelstate with a prefix of "Properties"
|
||||
/// </remarks>
|
||||
protected virtual bool ValidatePropertyData(ContentItemBasic<ContentPropertyBasic, TPersisted> postedItem, HttpActionContext actionContext)
|
||||
{
|
||||
foreach (var p in postedItem.ContentDto.Properties)
|
||||
{
|
||||
var editor = p.PropertyEditor;
|
||||
if (editor == null)
|
||||
{
|
||||
var message = string.Format("The property editor with alias: {0} was not found for property with id {1}", p.DataType.PropertyEditorAlias, p.Id);
|
||||
LogHelper.Warn<ContentItemValidationHelper<TPersisted, TModelSave>>(message);
|
||||
//actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
|
||||
//return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//get the posted value for this property, this may be null in cases where the property was marked as readonly which means
|
||||
//the angular app will not post that value.
|
||||
var postedProp = postedItem.Properties.FirstOrDefault(x => x.Alias == p.Alias);
|
||||
if (postedProp == null) continue;
|
||||
|
||||
var postedValue = postedProp.Value;
|
||||
|
||||
//get the pre-values for this property
|
||||
var preValues = p.PreValues;
|
||||
|
||||
//TODO: when we figure out how to 'override' certain pre-value properties we'll either need to:
|
||||
// * Combine the preValues with the overridden values stored with the document type property (but how to combine?)
|
||||
// * Or, pass in the overridden values stored with the doc type property separately
|
||||
|
||||
foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, preValues, editor)))
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
|
||||
//Now we need to validate the property based on the PropertyType validation (i.e. regex and required)
|
||||
// NOTE: These will become legacy once we have pre-value overrides.
|
||||
if (p.IsRequired)
|
||||
{
|
||||
foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, "", preValues, editor))
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
if (p.ValidationRegExp.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
|
||||
//We only want to execute the regex statement if:
|
||||
// * the value is null or empty AND it is required OR
|
||||
// * the value is not null or empty
|
||||
//See: http://issues.umbraco.org/issue/U4-4669
|
||||
|
||||
var asString = postedValue as string;
|
||||
|
||||
if (
|
||||
//Value is not null or empty
|
||||
(postedValue != null && asString.IsNullOrWhiteSpace() == false)
|
||||
//It's required
|
||||
|| (p.IsRequired))
|
||||
{
|
||||
foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor))
|
||||
{
|
||||
actionContext.ModelState.AddPropertyError(result, p.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return actionContext.ModelState.IsValid;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
|
||||
public sealed class EnableOverrideAuthorizationAttribute : Attribute
|
||||
{
|
||||
//fixme we should remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead
|
||||
//TODO: Can we remove this and use the System.Web.Http.OverrideAuthorizationAttribute which uses IOverrideFilter instead?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +1,137 @@
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Editors;
|
||||
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Core.Security;
|
||||
using System.Net;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Auth filter to check if the current user has access to the content item (by id).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// This first checks if the user can access this based on their start node, and then checks node permissions
|
||||
///
|
||||
/// 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>
|
||||
public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly int? _nodeId;
|
||||
private readonly string _paramName;
|
||||
private readonly char? _permissionToCheck;
|
||||
|
||||
/// <summary>
|
||||
/// This constructor will only be able to test the start node access
|
||||
/// </summary>
|
||||
public EnsureUserPermissionForContentAttribute(int nodeId)
|
||||
{
|
||||
_nodeId = nodeId;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
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.Web.Models.ContentEditing;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Auth filter to check if the current user has access to the content item (by id).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// This first checks if the user can access this based on their start node, and then checks node permissions
|
||||
///
|
||||
/// 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>
|
||||
public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly int? _nodeId;
|
||||
private readonly string _paramName;
|
||||
private readonly char? _permissionToCheck;
|
||||
|
||||
/// <summary>
|
||||
/// This constructor will only be able to test the start node access
|
||||
/// </summary>
|
||||
public EnsureUserPermissionForContentAttribute(int nodeId)
|
||||
{
|
||||
_nodeId = nodeId;
|
||||
}
|
||||
|
||||
public EnsureUserPermissionForContentAttribute(string paramName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName");
|
||||
|
||||
_paramName = paramName;
|
||||
_permissionToCheck = ActionBrowse.Instance.Letter;
|
||||
}
|
||||
|
||||
public EnsureUserPermissionForContentAttribute(string paramName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName));
|
||||
_paramName = paramName;
|
||||
_permissionToCheck = ActionBrowse.ActionLetter;
|
||||
}
|
||||
|
||||
public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck)
|
||||
: this(paramName)
|
||||
{
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public override bool AllowMultiple => true;
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
if (UmbracoContext.Current.Security.CurrentUser == null)
|
||||
{
|
||||
//not logged in
|
||||
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
int nodeId;
|
||||
if (_nodeId.HasValue == false)
|
||||
{
|
||||
var parts = _paramName.Split(new[] {'.'}, 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)
|
||||
{
|
||||
var argument = actionContext.ActionArguments[parts[0]].ToString();
|
||||
// if the argument is an int, it will parse and can be assigned to nodeId
|
||||
// if might be a udi, so check that next
|
||||
// otherwise treat it as a guid - unlikely we ever get here
|
||||
if (int.TryParse(argument, out int parsedId))
|
||||
{
|
||||
nodeId = parsedId;
|
||||
}
|
||||
else if (Udi.TryParse(argument, true, out Udi udi))
|
||||
{
|
||||
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
nodeId = Current.Services.EntityService.GetId(udi).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Guid.TryParse(argument, out Guid key);
|
||||
//fixme: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services
|
||||
nodeId = Current.Services.EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId,
|
||||
Current.UmbracoContext.Security.CurrentUser,
|
||||
Current.Services.UserService,
|
||||
Current.Services.ContentService,
|
||||
Current.Services.EntityService,
|
||||
out var contentItem,
|
||||
_permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null);
|
||||
|
||||
if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied)
|
||||
throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse());
|
||||
|
||||
if (contentItem != null)
|
||||
{
|
||||
//store the content item in request cache so it can be resolved in the controller without re-looking it up
|
||||
actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem;
|
||||
}
|
||||
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck)
|
||||
: this(paramName)
|
||||
{
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public override bool AllowMultiple
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
if (UmbracoContext.Current.Security.CurrentUser == null)
|
||||
{
|
||||
//not logged in
|
||||
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)
|
||||
{
|
||||
var argument = actionContext.ActionArguments[parts[0]].ToString();
|
||||
// if the argument is an int, it will parse and can be assigned to nodeId
|
||||
// if might be a udi, so check that next
|
||||
// otherwise treat it as a guid - unlikely we ever get here
|
||||
if (int.TryParse(argument, out int parsedId))
|
||||
{
|
||||
nodeId = parsedId;
|
||||
}
|
||||
else if (Udi.TryParse(argument, true, out Udi udi))
|
||||
{
|
||||
nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
Guid.TryParse(argument, out Guid key);
|
||||
nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result;
|
||||
}
|
||||
}
|
||||
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 (ContentController.CheckPermissions(
|
||||
actionContext.Request.Properties,
|
||||
UmbracoContext.Current.Security.CurrentUser,
|
||||
ApplicationContext.Current.Services.UserService,
|
||||
ApplicationContext.Current.Services.ContentService,
|
||||
ApplicationContext.Current.Services.EntityService,
|
||||
nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null))
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +1,142 @@
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Editors;
|
||||
|
||||
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;
|
||||
|
||||
public enum DictionarySource
|
||||
{
|
||||
ActionArguments,
|
||||
RequestForm,
|
||||
RequestQueryString
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This constructor will only be able to test the start node access
|
||||
/// </summary>
|
||||
public EnsureUserPermissionForMediaAttribute(int nodeId)
|
||||
{
|
||||
_nodeId = nodeId;
|
||||
}
|
||||
|
||||
public EnsureUserPermissionForMediaAttribute(string paramName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName));
|
||||
_paramName = paramName;
|
||||
}
|
||||
|
||||
// fixme v8 guess this is not used anymore, source is ignored?!
|
||||
public EnsureUserPermissionForMediaAttribute(string paramName, DictionarySource source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(paramName)) throw new ArgumentNullOrEmptyException(nameof(paramName));
|
||||
_paramName = paramName;
|
||||
}
|
||||
|
||||
public override bool AllowMultiple => true;
|
||||
|
||||
private int GetNodeIdFromParameter(object parameterValue)
|
||||
{
|
||||
if (parameterValue is int)
|
||||
{
|
||||
return (int) parameterValue;
|
||||
}
|
||||
|
||||
var guidId = Guid.Empty;
|
||||
if (parameterValue is Guid)
|
||||
{
|
||||
guidId = (Guid)parameterValue;
|
||||
}
|
||||
else if (parameterValue is GuidUdi)
|
||||
{
|
||||
guidId = ((GuidUdi) parameterValue).Guid;
|
||||
}
|
||||
|
||||
if (guidId != Guid.Empty)
|
||||
{
|
||||
var found = Current.Services.EntityService.GetId(guidId, UmbracoObjectTypes.Media);
|
||||
if (found)
|
||||
return found.Result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The id type: " + parameterValue.GetType() + " is not a supported id");
|
||||
}
|
||||
|
||||
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 [] { '.' }, 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 = GetNodeIdFromParameter(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 = GetNodeIdFromParameter(prop.GetValue(actionContext.ActionArguments[parts[0]]));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeId = _nodeId.Value;
|
||||
}
|
||||
|
||||
if (MediaController.CheckPermissions(
|
||||
actionContext.Request.Properties,
|
||||
UmbracoContext.Current.Security.CurrentUser,
|
||||
Current.Services.MediaService,
|
||||
Current.Services.EntityService,
|
||||
nodeId))
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
public enum DictionarySource
|
||||
{
|
||||
ActionArguments,
|
||||
RequestForm,
|
||||
RequestQueryString
|
||||
}
|
||||
|
||||
/// <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 EnsureUserPermissionForMediaAttribute(string paramName, DictionarySource source)
|
||||
{
|
||||
Mandate.ParameterNotNullOrEmpty(paramName, "paramName");
|
||||
_paramName = paramName;
|
||||
}
|
||||
|
||||
public override bool AllowMultiple
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
private int GetNodeIdFromParameter(object parameterValue)
|
||||
{
|
||||
if (parameterValue is int)
|
||||
{
|
||||
return (int) parameterValue;
|
||||
}
|
||||
|
||||
var guidId = Guid.Empty;
|
||||
if (parameterValue is Guid)
|
||||
{
|
||||
guidId = (Guid)parameterValue;
|
||||
}
|
||||
else if (parameterValue is GuidUdi)
|
||||
{
|
||||
guidId = ((GuidUdi) parameterValue).Guid;
|
||||
}
|
||||
|
||||
if (guidId != Guid.Empty)
|
||||
{
|
||||
var found = ApplicationContext.Current.Services.EntityService.GetIdForKey(guidId, UmbracoObjectTypes.Media);
|
||||
if (found)
|
||||
return found.Result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The id type: " + parameterValue.GetType() + " is not a supported id");
|
||||
}
|
||||
|
||||
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 [] { '.' }, 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 = GetNodeIdFromParameter(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 = GetNodeIdFromParameter(prop.GetValue(actionContext.ActionArguments[parts[0]]));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeId = _nodeId.Value;
|
||||
}
|
||||
|
||||
if (MediaController.CheckPermissions(
|
||||
actionContext.Request.Properties,
|
||||
UmbracoContext.Current.Security.CurrentUser,
|
||||
ApplicationContext.Current.Services.MediaService,
|
||||
ApplicationContext.Current.Services.EntityService,
|
||||
nodeId))
|
||||
{
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Web.Features;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
@@ -14,16 +12,12 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
// if no features resolver has been set then return true, this will occur in unit
|
||||
// tests and we don't want users to have to set a resolver
|
||||
//if no features resolver has been set then return true, this will occur in unit tests and we don't want users to have to set a resolver
|
||||
//just so their unit tests work.
|
||||
|
||||
// fixme inject?
|
||||
var features = Current.Container?.TryGetInstance<UmbracoFeatures>();
|
||||
if (features == null) return true;
|
||||
if (FeaturesResolver.HasCurrent == false) return true;
|
||||
|
||||
var controllerType = actionContext.ControllerContext.ControllerDescriptor.ControllerType;
|
||||
return features.IsControllerEnabled(controllerType);
|
||||
return FeaturesResolver.Current.Features.IsControllerEnabled(controllerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,183 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the parameter is ContentItemSave and then deletes any temporary saved files from file uploads associated with the request
|
||||
/// </summary>
|
||||
internal sealed class FileUploadCleanupFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly bool _incomingModel;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor specifies if the filter should analyze the incoming or outgoing model
|
||||
/// </summary>
|
||||
/// <param name="incomingModel"></param>
|
||||
public FileUploadCleanupFilterAttribute(bool incomingModel = true)
|
||||
{
|
||||
_incomingModel = incomingModel;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
var tempFolders = new List<string>();
|
||||
|
||||
if (_incomingModel)
|
||||
{
|
||||
if (actionExecutedContext.ActionContext.ActionArguments.Any())
|
||||
{
|
||||
var contentItem = actionExecutedContext.ActionContext.ActionArguments.First().Value as IHaveUploadedFiles;
|
||||
if (contentItem != null)
|
||||
{
|
||||
//cleanup any files associated
|
||||
foreach (var f in contentItem.UploadedFiles)
|
||||
{
|
||||
//track all temp folders so we can remove old files afterwords
|
||||
var dir = Path.GetDirectoryName(f.TempFilePath);
|
||||
if (tempFolders.Contains(dir) == false)
|
||||
{
|
||||
tempFolders.Add(dir);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(f.TempFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Current.Logger.Error<FileUploadCleanupFilterAttribute>(ex, "Could not delete temp file {FileName}", f.TempFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actionExecutedContext == null)
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext is null!!??");
|
||||
return;
|
||||
}
|
||||
if (actionExecutedContext.Request == null)
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request is null!!??");
|
||||
return;
|
||||
}
|
||||
if (actionExecutedContext.Request.Content == null)
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content is null!!??");
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectContent objectContent;
|
||||
|
||||
try
|
||||
{
|
||||
objectContent = actionExecutedContext.Response.Content as ObjectContent;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Current.Logger.Error<FileUploadCleanupFilterAttribute>(ex, "Could not acquire actionExecutedContext.Response.Content");
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectContent != null)
|
||||
{
|
||||
var uploadedFiles = objectContent.Value as IHaveUploadedFiles;
|
||||
if (uploadedFiles != null)
|
||||
{
|
||||
if (uploadedFiles.UploadedFiles != null)
|
||||
{
|
||||
//cleanup any files associated
|
||||
foreach (var f in uploadedFiles.UploadedFiles)
|
||||
{
|
||||
if (f.TempFilePath.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//track all temp folders so we can remove old files afterwords
|
||||
var dir = Path.GetDirectoryName(f.TempFilePath);
|
||||
if (tempFolders.Contains(dir) == false)
|
||||
{
|
||||
tempFolders.Add(dir);
|
||||
}
|
||||
|
||||
Current.Logger.Debug<FileUploadCleanupFilterAttribute>("Removing temp file {FileName}", f.TempFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(f.TempFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Current.Logger.Error<FileUploadCleanupFilterAttribute>(ex, "Could not delete temp file {FileName}", f.TempFilePath);
|
||||
}
|
||||
|
||||
//clear out the temp path so it's not returned in the response
|
||||
f.TempFilePath = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The f.TempFilePath is null or whitespace!!??");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The uploadedFiles.UploadedFiles is null!!??");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content.Value is not IHaveUploadedFiles, it is {ObjectType}", objectContent.Value.GetType());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Logger.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content is not ObjectContent, it is {RequestObjectType}", actionExecutedContext.Request.Content.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
//Now remove all old files so that the temp folder(s) never grow
|
||||
foreach (var tempFolder in tempFolders.Distinct())
|
||||
{
|
||||
var files = Directory.GetFiles(tempFolder);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (DateTime.UtcNow - File.GetLastWriteTimeUtc(file) > TimeSpan.FromDays(1))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Current.Logger.Error<FileUploadCleanupFilterAttribute>(ex, "Could not delete temp file {FileName}", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the parameter is ContentItemSave and then deletes any temporary saved files from file uploads associated with the request
|
||||
/// </summary>
|
||||
internal sealed class FileUploadCleanupFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly bool _incomingModel;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor specifies if the filter should analyze the incoming or outgoing model
|
||||
/// </summary>
|
||||
/// <param name="incomingModel"></param>
|
||||
public FileUploadCleanupFilterAttribute(bool incomingModel = true)
|
||||
{
|
||||
_incomingModel = incomingModel;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
var tempFolders = new List<string>();
|
||||
|
||||
if (_incomingModel)
|
||||
{
|
||||
if (actionExecutedContext.ActionContext.ActionArguments.Any())
|
||||
{
|
||||
var contentItem = actionExecutedContext.ActionContext.ActionArguments.First().Value as IHaveUploadedFiles;
|
||||
if (contentItem != null)
|
||||
{
|
||||
//cleanup any files associated
|
||||
foreach (var f in contentItem.UploadedFiles)
|
||||
{
|
||||
//track all temp folders so we can remove old files afterwords
|
||||
var dir = Path.GetDirectoryName(f.TempFilePath);
|
||||
if (tempFolders.Contains(dir) == false)
|
||||
{
|
||||
tempFolders.Add(dir);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(f.TempFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
LogHelper.Error<FileUploadCleanupFilterAttribute>("Could not delete temp file " + f.TempFilePath, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actionExecutedContext == null)
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext is null!!??");
|
||||
return;
|
||||
}
|
||||
if (actionExecutedContext.Request == null)
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request is null!!??");
|
||||
return;
|
||||
}
|
||||
if (actionExecutedContext.Request.Content == null)
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content is null!!??");
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectContent objectContent;
|
||||
|
||||
try
|
||||
{
|
||||
objectContent = actionExecutedContext.Response.Content as ObjectContent;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
LogHelper.Error<FileUploadCleanupFilterAttribute>("Could not acquire actionExecutedContext.Response.Content", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectContent != null)
|
||||
{
|
||||
var uploadedFiles = objectContent.Value as IHaveUploadedFiles;
|
||||
if (uploadedFiles != null)
|
||||
{
|
||||
if (uploadedFiles.UploadedFiles != null)
|
||||
{
|
||||
//cleanup any files associated
|
||||
foreach (var f in uploadedFiles.UploadedFiles)
|
||||
{
|
||||
if (f.TempFilePath.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//track all temp folders so we can remove old files afterwords
|
||||
var dir = Path.GetDirectoryName(f.TempFilePath);
|
||||
if (tempFolders.Contains(dir) == false)
|
||||
{
|
||||
tempFolders.Add(dir);
|
||||
}
|
||||
|
||||
LogHelper.Debug<FileUploadCleanupFilterAttribute>("Removing temp file " + f.TempFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(f.TempFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
LogHelper.Error<FileUploadCleanupFilterAttribute>("Could not delete temp file " + f.TempFilePath, ex);
|
||||
}
|
||||
|
||||
//clear out the temp path so it's not returned in the response
|
||||
f.TempFilePath = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The f.TempFilePath is null or whitespace!!??");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The uploadedFiles.UploadedFiles is null!!??");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content.Value is not IHaveUploadedFiles, it is " + objectContent.Value.GetType());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<FileUploadCleanupFilterAttribute>("The actionExecutedContext.Request.Content is not ObjectContent, it is " + actionExecutedContext.Request.Content.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
//Now remove all old files so that the temp folder(s) never grow
|
||||
foreach (var tempFolder in tempFolders.Distinct())
|
||||
{
|
||||
var files = Directory.GetFiles(tempFolder);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (DateTime.UtcNow - File.GetLastWriteTimeUtc(file) > TimeSpan.FromDays(1))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
LogHelper.Error<FileUploadCleanupFilterAttribute>("Could not delete temp file " + file, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +1,126 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Actions;
|
||||
|
||||
|
||||
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 sealed class FilterAllowedOutgoingContentAttribute : FilterAllowedOutgoingMediaAttribute
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly char _permissionToCheck;
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType)
|
||||
: this(outgoingType, Current.Services.UserService, Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = ActionBrowse.ActionLetter;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
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 sealed class FilterAllowedOutgoingContentAttribute : FilterAllowedOutgoingMediaAttribute
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly char _permissionToCheck;
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType)
|
||||
: this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = ActionBrowse.Instance.Letter;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck)
|
||||
: this(outgoingType, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName)
|
||||
: this(outgoingType, propertyName, ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = ActionBrowse.Instance.Letter;
|
||||
}
|
||||
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType)
|
||||
{
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (entityService == null) throw new ArgumentNullException("entityService");
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_permissionToCheck = ActionBrowse.Instance.Letter;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType)
|
||||
{
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (entityService == null) throw new ArgumentNullException("entityService");
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType, propertyName)
|
||||
{
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (entityService == null) throw new ArgumentNullException("entityService");
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_permissionToCheck = ActionBrowse.Instance.Letter;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck)
|
||||
: this(outgoingType, Current.Services.UserService, Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName)
|
||||
: this(outgoingType, propertyName, Current.Services.UserService, Current.Services.EntityService)
|
||||
{
|
||||
_permissionToCheck = ActionBrowse.ActionLetter;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType)
|
||||
{
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
_permissionToCheck = ActionBrowse.ActionLetter;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, char permissionToCheck, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType)
|
||||
{
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_permissionToCheck = permissionToCheck;
|
||||
}
|
||||
|
||||
public FilterAllowedOutgoingContentAttribute(Type outgoingType, string propertyName, IUserService userService, IEntityService entityService)
|
||||
: base(outgoingType, propertyName)
|
||||
{
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_permissionToCheck = ActionBrowse.ActionLetter;
|
||||
}
|
||||
|
||||
protected override void FilterItems(IUser user, IList items)
|
||||
{
|
||||
base.FilterItems(user, items);
|
||||
|
||||
FilterBasedOnPermissions(items, user);
|
||||
}
|
||||
|
||||
protected override int[] GetUserStartNodes(IUser user)
|
||||
{
|
||||
return user.CalculateContentStartNodeIds(_entityService);
|
||||
}
|
||||
|
||||
protected override int RecycleBinId
|
||||
{
|
||||
get { return Constants.System.RecycleBinContent; }
|
||||
}
|
||||
|
||||
internal void FilterBasedOnPermissions(IList items, IUser user)
|
||||
{
|
||||
var length = items.Count;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
var ids = new List<int>();
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
ids.Add(((dynamic)items[i]).Id);
|
||||
}
|
||||
//get all the permissions for these nodes in one call
|
||||
var permissions = _userService.GetPermissions(user, ids.ToArray());
|
||||
var toRemove = new List<dynamic>();
|
||||
foreach (dynamic item in items)
|
||||
|
||||
|
||||
protected override void FilterItems(IUser user, IList items)
|
||||
{
|
||||
base.FilterItems(user, items);
|
||||
|
||||
FilterBasedOnPermissions(items, user);
|
||||
}
|
||||
|
||||
protected override int[] GetUserStartNodes(IUser user)
|
||||
{
|
||||
return user.CalculateContentStartNodeIds(_entityService);
|
||||
}
|
||||
|
||||
protected override int RecycleBinId
|
||||
{
|
||||
get { return Constants.System.RecycleBinContent; }
|
||||
}
|
||||
|
||||
internal void FilterBasedOnPermissions(IList items, IUser user)
|
||||
{
|
||||
var length = items.Count;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
var ids = new List<int>();
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
ids.Add(((dynamic)items[i]).Id);
|
||||
}
|
||||
//get all the permissions for these nodes in one call
|
||||
var permissions = _userService.GetPermissions(user, ids.ToArray());
|
||||
var toRemove = new List<dynamic>();
|
||||
foreach (dynamic item in items)
|
||||
{
|
||||
//get the combined permission set across all user groups for this node
|
||||
//we're in the world of dynamics here so we need to cast
|
||||
//we're in the world of dynamics here so we need to cast
|
||||
var nodePermission = ((IEnumerable<string>)permissions.GetAllPermissions(item.Id)).ToArray();
|
||||
|
||||
//if the permission being checked doesn't exist then remove the item
|
||||
if (nodePermission.Contains(_permissionToCheck.ToString(CultureInfo.InvariantCulture)) == false)
|
||||
{
|
||||
toRemove.Add(item);
|
||||
}
|
||||
}
|
||||
foreach (var item in toRemove)
|
||||
{
|
||||
items.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
//if the permission being checked doesn't exist then remove the item
|
||||
if (nodePermission.Contains(_permissionToCheck.ToString(CultureInfo.InvariantCulture)) == false)
|
||||
{
|
||||
toRemove.Add(item);
|
||||
}
|
||||
}
|
||||
foreach (var item in toRemove)
|
||||
{
|
||||
items.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,12 @@ using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Security;
|
||||
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
|
||||
/// 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
|
||||
@@ -35,14 +34,20 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <summary>
|
||||
/// Returns true so that other filters can execute along with this one
|
||||
/// </summary>
|
||||
public override bool AllowMultiple => true;
|
||||
public override bool AllowMultiple
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
protected virtual int[] GetUserStartNodes(IUser user)
|
||||
{
|
||||
return user.CalculateMediaStartNodeIds(Current.Services.EntityService);
|
||||
return user.CalculateMediaStartNodeIds(ApplicationContext.Current.Services.EntityService);
|
||||
}
|
||||
|
||||
protected virtual int RecycleBinId => Constants.System.RecycleBinMedia;
|
||||
protected virtual int RecycleBinId
|
||||
{
|
||||
get { return Constants.System.RecycleBinMedia; }
|
||||
}
|
||||
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
@@ -80,7 +85,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var toRemove = new List<dynamic>();
|
||||
foreach (dynamic item in items)
|
||||
{
|
||||
var hasPathAccess = (item != null && ContentPermissionsHelper.HasPathAccess(item.Path, GetUserStartNodes(user), RecycleBinId));
|
||||
var hasPathAccess = (item != null && UserExtensions.HasPathAccess(item.Path, GetUserStartNodes(user), RecycleBinId));
|
||||
if (hasPathAccess == false)
|
||||
{
|
||||
toRemove.Add(item);
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Web.Http.Filters;
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Quickly split filters into different types
|
||||
/// Quickly split filters into different types
|
||||
/// </summary>
|
||||
internal class FilterGrouping
|
||||
{
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an Action to execute with an arbitrary number of QueryStrings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it
|
||||
/// </remarks>
|
||||
public sealed class HttpQueryStringFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public string ParameterName { get; private set; }
|
||||
|
||||
public HttpQueryStringFilterAttribute(string parameterName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameterName))
|
||||
throw new ArgumentException("ParameterName is required.");
|
||||
ParameterName = parameterName;
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
//get the query strings from the request properties
|
||||
if (actionContext.Request.Properties.ContainsKey("MS_QueryNameValuePairs"))
|
||||
{
|
||||
var queryStrings = actionContext.Request.Properties["MS_QueryNameValuePairs"] as IEnumerable<KeyValuePair<string, string>>;
|
||||
if (queryStrings == null) return;
|
||||
|
||||
var formData = new FormDataCollection(queryStrings);
|
||||
|
||||
actionContext.ActionArguments[ParameterName] = formData;
|
||||
}
|
||||
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an Action to execute with an arbitrary number of QueryStrings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it
|
||||
/// </remarks>
|
||||
public sealed class HttpQueryStringFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public string ParameterName { get; private set; }
|
||||
|
||||
public HttpQueryStringFilterAttribute(string parameterName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameterName))
|
||||
throw new ArgumentException("ParameterName is required.");
|
||||
ParameterName = parameterName;
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
//get the query strings from the request properties
|
||||
if (actionContext.Request.Properties.ContainsKey("MS_QueryNameValuePairs"))
|
||||
{
|
||||
var queryStrings = actionContext.Request.Properties["MS_QueryNameValuePairs"] as IEnumerable<KeyValuePair<string, string>>;
|
||||
if (queryStrings == null) return;
|
||||
|
||||
var formData = new FormDataCollection(queryStrings);
|
||||
|
||||
actionContext.ActionArguments[ParameterName] = formData;
|
||||
}
|
||||
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
@@ -15,7 +14,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
var treeRequest = httpContext.Result.Request.QueryString["treeType"];
|
||||
if (treeRequest.IsNullOrWhiteSpace()) return false;
|
||||
|
||||
var tree = Current.Services.ApplicationTreeService.GetByAlias(treeRequest);
|
||||
var tree = ApplicationContext.Current.Services.ApplicationTreeService.GetByAlias(treeRequest);
|
||||
if (tree == null) return false;
|
||||
|
||||
return UmbracoContext.Current.Security.CurrentUser != null
|
||||
@@ -23,7 +22,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,44 @@
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the json outgoing/serialized datetime format
|
||||
/// </summary>
|
||||
internal sealed class JsonDateTimeFormatAttributeAttribute : Attribute, IControllerConfiguration
|
||||
{
|
||||
private readonly string _format = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom format
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
public JsonDateTimeFormatAttributeAttribute(string format)
|
||||
{
|
||||
if (string.IsNullOrEmpty(format)) throw new ArgumentNullOrEmptyException(nameof(format));
|
||||
_format = format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will use the standard ISO format
|
||||
/// </summary>
|
||||
public JsonDateTimeFormatAttributeAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
var jsonFormatter = controllerSettings.Formatters.OfType<JsonMediaTypeFormatter>();
|
||||
foreach (var r in jsonFormatter)
|
||||
{
|
||||
r.SerializerSettings.Converters.Add(
|
||||
new IsoDateTimeConverter
|
||||
{
|
||||
DateTimeFormat = _format
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the json outgoing/serialized datetime format
|
||||
/// </summary>
|
||||
internal sealed class OutgoingDateTimeFormatAttribute : Attribute, IControllerConfiguration
|
||||
{
|
||||
private readonly string _format = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom format
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
public OutgoingDateTimeFormatAttribute(string format)
|
||||
{
|
||||
Mandate.ParameterNotNullOrEmpty(format, "format");
|
||||
_format = format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will use the standard ISO format
|
||||
/// </summary>
|
||||
public OutgoingDateTimeFormatAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
var jsonFormatter = controllerSettings.Formatters.OfType<JsonMediaTypeFormatter>();
|
||||
foreach (var r in jsonFormatter)
|
||||
{
|
||||
r.SerializerSettings.Converters.Add(new CustomDateTimeConvertor(_format));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
@@ -33,6 +33,6 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Web.Http.Controllers;
|
||||
@@ -21,4 +21,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
//fixme remove this since we don't need it, see notes in EnableOverrideAuthorizationAttribute
|
||||
|
||||
/// <summary>
|
||||
/// Abstract auth filter class that can be used to enable overriding class auth filters at the action level
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To enable a class auth filter to be overridden by an action auth filter the EnableOverrideAuthorizationAttribute can be applied
|
||||
/// To enable a class auth filter to be overridden by an action auth filter the EnableOverrideAuthorizationAttribute can be applied
|
||||
/// to the class.
|
||||
/// </remarks>
|
||||
public abstract class OverridableAuthorizationAttribute : AuthorizeAttribute
|
||||
@@ -49,4 +47,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
base.OnAuthorization(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
//if they are not valid for some strange reason - we need to continue setting valid ones
|
||||
string failedReason;
|
||||
if (AngularAntiForgeryHelper.ValidateHeaders(context.Request.Headers, out failedReason))
|
||||
{
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -43,17 +43,17 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
Path = "/",
|
||||
//must be js readable
|
||||
HttpOnly = false,
|
||||
Secure = UmbracoConfig.For.GlobalSettings().UseHttps
|
||||
Secure = GlobalSettings.UseSSL
|
||||
};
|
||||
|
||||
var validationCookie = new CookieHeaderValue(AngularAntiForgeryHelper.CsrfValidationCookieName, cookieToken)
|
||||
{
|
||||
Path = "/",
|
||||
HttpOnly = true,
|
||||
Secure = UmbracoConfig.For.GlobalSettings().UseHttps
|
||||
Secure = GlobalSettings.UseSSL
|
||||
};
|
||||
|
||||
context.Response.Headers.AddCookies(new[] { angularCookie, validationCookie });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,44 @@
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Web.Composing;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the current user has access to the specified application
|
||||
/// </summary>
|
||||
public sealed class UmbracoApplicationAuthorizeAttribute : OverridableAuthorizationAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used by unit tests to enable/disable this filter
|
||||
/// </summary>
|
||||
internal static bool Enable = true;
|
||||
|
||||
private readonly string[] _appNames;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to set any number of applications that the user needs access to to be authorized
|
||||
/// </summary>
|
||||
/// <param name="appName">
|
||||
/// If the user has access to any of the specified apps, they will be authorized.
|
||||
/// </param>
|
||||
public UmbracoApplicationAuthorizeAttribute(params string[] appName)
|
||||
{
|
||||
_appNames = appName;
|
||||
}
|
||||
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
if (Enable == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the current user has access to the specified application
|
||||
/// </summary>
|
||||
public sealed class UmbracoApplicationAuthorizeAttribute : OverridableAuthorizationAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used by unit tests to enable/disable this filter
|
||||
/// </summary>
|
||||
internal static bool Enable = true;
|
||||
|
||||
private readonly string[] _appNames;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to set any number of applications that the user needs access to to be authorized
|
||||
/// </summary>
|
||||
/// <param name="appName">
|
||||
/// If the user has access to any of the specified apps, they will be authorized.
|
||||
/// </param>
|
||||
public UmbracoApplicationAuthorizeAttribute(params string[] appName)
|
||||
{
|
||||
_appNames = appName;
|
||||
}
|
||||
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
if (Enable == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var authorized = Current.UmbracoContext.Security.CurrentUser != null
|
||||
&& _appNames.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess(
|
||||
app, Current.UmbracoContext.Security.CurrentUser));
|
||||
|
||||
return authorized;
|
||||
}
|
||||
}
|
||||
}
|
||||
var authorized = UmbracoContext.Current.Security.CurrentUser != null
|
||||
&& _appNames.Any(app => UmbracoContext.Current.Security.UserHasSectionAccess(
|
||||
app, UmbracoContext.Current.Security.CurrentUser));
|
||||
|
||||
return authorized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future, use OWIN IAuthenticationManager.SignOut instead", true)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class UmbracoBackOfficeLogoutAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext context)
|
||||
{
|
||||
throw new NotSupportedException("This method is not supported and should not be used, it has been removed in Umbraco 7.4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
@@ -40,16 +39,16 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
return true;
|
||||
}
|
||||
|
||||
var apps = _treeAliases.Select(x => Current.Services.ApplicationTreeService
|
||||
var apps = _treeAliases.Select(x => ApplicationContext.Current.Services.ApplicationTreeService
|
||||
.GetByAlias(x))
|
||||
.WhereNotNull()
|
||||
.Select(x => x.ApplicationAlias)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
return Current.UmbracoContext.Security.CurrentUser != null
|
||||
&& apps.Any(app => Current.UmbracoContext.Security.UserHasSectionAccess(
|
||||
app, Current.UmbracoContext.Security.CurrentUser));
|
||||
return UmbracoContext.Current.Security.CurrentUser != null
|
||||
&& apps.Any(app => UmbracoContext.Current.Security.UserHasSectionAccess(
|
||||
app, UmbracoContext.Current.Security.CurrentUser));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs
Normal file
17
src/Umbraco.Web/WebApi/Filters/UmbracoUseHttps.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
using System.Web.Mvc;
|
||||
using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the filter Umbraco.Web.Mvc.UmbracoRequireHttpsAttribute instead, this one is in the wrong namespace")]
|
||||
public class UmbracoUseHttps : Umbraco.Web.Mvc.UmbracoRequireHttpsAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,33 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// This will check if the request is authenticated and if there's an auth ticket present we will
|
||||
/// add a custom header to the response indicating how many seconds are remaining for the current
|
||||
/// user's session. This allows us to keep track of a user's session effectively in the back office.
|
||||
/// </summary>
|
||||
public sealed class UmbracoUserTimeoutFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
//this can occur if an error has already occurred.
|
||||
if (actionExecutedContext.Response == null) return;
|
||||
|
||||
var httpContextAttempt = actionExecutedContext.Request.TryGetHttpContext();
|
||||
if (httpContextAttempt.Success)
|
||||
{
|
||||
|
||||
var ticket = httpContextAttempt.Result.GetUmbracoAuthTicket();
|
||||
if (ticket?.Properties.ExpiresUtc != null && ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow)
|
||||
{
|
||||
var remainingSeconds = httpContextAttempt.Result.GetRemainingAuthSeconds();
|
||||
actionExecutedContext.Response.Headers.Add("X-Umb-User-Seconds", remainingSeconds.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Globalization;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// This will check if the request is authenticated and if there's an auth ticket present we will
|
||||
/// add a custom header to the response indicating how many seconds are remaining for the current
|
||||
/// user's session. This allows us to keep track of a user's session effectively in the back office.
|
||||
/// </summary>
|
||||
public sealed class UmbracoUserTimeoutFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
|
||||
{
|
||||
base.OnActionExecuted(actionExecutedContext);
|
||||
|
||||
//this can occur if an error has already occurred.
|
||||
if (actionExecutedContext.Response == null) return;
|
||||
|
||||
var httpContextAttempt = actionExecutedContext.Request.TryGetHttpContext();
|
||||
if (httpContextAttempt.Success)
|
||||
{
|
||||
var ticket = httpContextAttempt.Result.GetUmbracoAuthTicket();
|
||||
if (ticket != null && ticket.Expired == false)
|
||||
{
|
||||
var remainingSeconds = httpContextAttempt.Result.GetRemainingAuthSeconds();
|
||||
actionExecutedContext.Response.Headers.Add("X-Umb-User-Seconds", remainingSeconds.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will only redirect Head/Get requests, otherwise will respond with text
|
||||
///
|
||||
/// References:
|
||||
///
|
||||
/// References:
|
||||
/// http://issues.umbraco.org/issue/U4-8542
|
||||
/// https://blogs.msdn.microsoft.com/carlosfigueira/2012/03/09/implementing-requirehttps-with-asp-net-web-api/
|
||||
/// </remarks>
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
public override void OnAuthorization(HttpActionContext actionContext)
|
||||
{
|
||||
var request = actionContext.Request;
|
||||
if (UmbracoConfig.For.GlobalSettings().UseHttps && request.RequestUri.Scheme != Uri.UriSchemeHttps)
|
||||
if (GlobalSettings.UseSSL && request.RequestUri.Scheme != Uri.UriSchemeHttps)
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
var uri = new UriBuilder(request.RequestUri)
|
||||
@@ -52,4 +52,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/
|
||||
///
|
||||
///
|
||||
/// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled
|
||||
/// </remarks>
|
||||
public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute
|
||||
@@ -40,4 +40,4 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
base.OnActionExecuting(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,26 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// An action filter used to do basic validation against the model and return a result
|
||||
/// straight away if it fails.
|
||||
/// </summary>
|
||||
internal sealed class ValidationFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var modelState = actionContext.ModelState;
|
||||
|
||||
if (!modelState.IsValid)
|
||||
{
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// An action filter used to do basic validation against the model and return a result
|
||||
/// straight away if it fails.
|
||||
/// </summary>
|
||||
internal sealed class ValidationFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var modelState = actionContext.ModelState;
|
||||
|
||||
if (!modelState.IsValid)
|
||||
{
|
||||
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,4 +37,4 @@ namespace Umbraco.Web.WebApi
|
||||
return typeof(Guid) == objectType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
internal static class HttpActionContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper method to get a model from a multipart request and ensure that the model is validated
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="requestKey"></param>
|
||||
/// <param name="validationKeyPrefix"></param>
|
||||
/// <returns></returns>
|
||||
public static T GetModelFromMultipartRequest<T>(this HttpActionContext actionContext, MultipartFormDataStreamProvider result, string requestKey, string validationKeyPrefix = "")
|
||||
{
|
||||
if (result.FormData[requestKey/*"contentItem"*/] == null)
|
||||
{
|
||||
var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
|
||||
response.ReasonPhrase = $"The request was not formatted correctly and is missing the '{requestKey}' parameter";
|
||||
throw new HttpResponseException(response);
|
||||
}
|
||||
|
||||
//get the string json from the request
|
||||
var contentItem = result.FormData[requestKey];
|
||||
|
||||
//deserialize into our model
|
||||
var model = JsonConvert.DeserializeObject<T>(contentItem);
|
||||
|
||||
//get the default body validator and validate the object
|
||||
var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator();
|
||||
var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
|
||||
//by default all validation errors will not contain a prefix (empty string) unless specified
|
||||
bodyValidator.Validate(model, typeof(T), metadataProvider, actionContext, validationKeyPrefix);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to get the <see cref="MultipartFormDataStreamProvider"/> from the request in a non-async manner
|
||||
/// </summary>
|
||||
/// <param name="actionContext"></param>
|
||||
/// <param name="rootVirtualPath"></param>
|
||||
/// <returns></returns>
|
||||
public static MultipartFormDataStreamProvider ReadAsMultipart(this HttpActionContext actionContext, string rootVirtualPath)
|
||||
{
|
||||
if (actionContext.Request.Content.IsMimeMultipartContent() == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
|
||||
}
|
||||
|
||||
var root = IOHelper.MapPath(rootVirtualPath);
|
||||
//ensure it exists
|
||||
Directory.CreateDirectory(root);
|
||||
var provider = new MultipartFormDataStreamProvider(root);
|
||||
|
||||
var request = actionContext.Request;
|
||||
var content = request.Content;
|
||||
|
||||
// Note: YES this is super strange, ugly, and weird.
|
||||
// One would think that you could just do:
|
||||
//
|
||||
//var result = content.ReadAsMultipartAsync(provider).Result;
|
||||
//
|
||||
// But it deadlocks. See https://stackoverflow.com/questions/15201255 for details, which
|
||||
// points to https://msdn.microsoft.com/en-us/magazine/jj991977.aspx which contains more
|
||||
// details under "Async All the Way" - see also https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/
|
||||
// which contains a simplified explaination: ReadAsMultipartAsync is meant to be awaited,
|
||||
// not used in the non-async .Result way, and there is nothing we can do about it.
|
||||
//
|
||||
// Alas, model binders cannot be async "all the way", so we have to wrap in a task, to
|
||||
// force proper threading, and then it works.
|
||||
|
||||
MultipartFormDataStreamProvider result = null;
|
||||
var task = Task.Run(() => content.ReadAsMultipartAsync(provider))
|
||||
.ContinueWith(x =>
|
||||
{
|
||||
if (x.IsFaulted && x.Exception != null)
|
||||
{
|
||||
throw x.Exception;
|
||||
}
|
||||
result = x.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
});
|
||||
task.Wait();
|
||||
|
||||
if (result == null)
|
||||
throw new InvalidOperationException("Could not read multi-part message");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,13 @@ namespace Umbraco.Web.WebApi
|
||||
|
||||
if (authorizationFilters.Any())
|
||||
{
|
||||
var cancelToken = new CancellationToken();
|
||||
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;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace Umbraco.Web.WebApi
|
||||
? Task.FromResult<HttpResponseMessage>(null)
|
||||
: FilterContinuation(actionContext, token, filters, ++index));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,33 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
|
||||
public static class HttpRequestMessageExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed from the latest Microsoft.AspNet.WebApi.Owin package which we cannot use because of a later webapi dependency
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<IOwinContext> TryGetOwinContext(this HttpRequestMessage request)
|
||||
{
|
||||
// occurs in unit tests?
|
||||
if (request.Properties.TryGetValue("MS_OwinContext", out var o) && o is IOwinContext owinContext)
|
||||
return Attempt.Succeed(owinContext);
|
||||
|
||||
var httpContext = request.TryGetHttpContext();
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Http.Results;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
|
||||
public static class HttpRequestMessageExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed from the latest Microsoft.AspNet.WebApi.Owin package which we cannot use because of a later webapi dependency
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<IOwinContext> TryGetOwinContext(this HttpRequestMessage request)
|
||||
{
|
||||
var httpContext = request.TryGetHttpContext();
|
||||
try
|
||||
{
|
||||
return httpContext
|
||||
@@ -33,131 +36,131 @@ namespace Umbraco.Web.WebApi
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//this will occur if there is no OWIN environment which generally would only be in things like unit tests
|
||||
return Attempt<IOwinContext>.Fail();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Attempt<HttpContextBase> TryGetHttpContext(this HttpRequestMessage request)
|
||||
{
|
||||
object context;
|
||||
if (request.Properties.TryGetValue("MS_HttpContext", out context))
|
||||
{
|
||||
var httpContext = context as HttpContextBase;
|
||||
if (httpContext != null)
|
||||
{
|
||||
return Attempt.Succeed(httpContext);
|
||||
}
|
||||
}
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
return Attempt<HttpContextBase>.Succeed(new HttpContextWrapper(HttpContext.Current));
|
||||
}
|
||||
|
||||
return Attempt<HttpContextBase>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 403 (Forbidden) response indicating that hte current user doesn't have access to the resource
|
||||
/// requested or the action it needs to take.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is different from a 401 which indicates that the user is not logged in.
|
||||
/// </remarks>
|
||||
public static HttpResponseMessage CreateUserNoAccessResponse(this HttpRequestMessage request)
|
||||
{
|
||||
return request.CreateResponse(HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse<T>(this HttpRequestMessage request, T value)
|
||||
{
|
||||
var msg = request.CreateResponse(HttpStatusCode.BadRequest, value);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request)
|
||||
{
|
||||
var msg = request.CreateResponse(HttpStatusCode.BadRequest);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, string errorMessage)
|
||||
{
|
||||
var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessage);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an error response with notifications in the result to be displayed in the UI
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage)
|
||||
{
|
||||
var notificationModel = new SimpleNotificationModel
|
||||
{
|
||||
Message = errorMessage
|
||||
};
|
||||
notificationModel.AddErrorNotification(errorMessage, string.Empty);
|
||||
return request.CreateValidationErrorResponse(notificationModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a succressful response with notifications in the result to be displayed in the UI
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="successMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateNotificationSuccessResponse(this HttpRequestMessage request, string successMessage)
|
||||
{
|
||||
var notificationModel = new SimpleNotificationModel
|
||||
{
|
||||
Message = successMessage
|
||||
};
|
||||
notificationModel.AddSuccessNotification(successMessage, string.Empty);
|
||||
return request.CreateResponse(HttpStatusCode.OK, notificationModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="modelState"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, ModelStateDictionary modelState)
|
||||
{
|
||||
var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//this will occur if there is no OWIN environment which generally would only be in things like unit tests
|
||||
return Attempt<IOwinContext>.Fail();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Attempt<HttpContextBase> TryGetHttpContext(this HttpRequestMessage request)
|
||||
{
|
||||
object context;
|
||||
if (request.Properties.TryGetValue("MS_HttpContext", out context))
|
||||
{
|
||||
var httpContext = context as HttpContextBase;
|
||||
if (httpContext != null)
|
||||
{
|
||||
return Attempt.Succeed(httpContext);
|
||||
}
|
||||
}
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
return Attempt<HttpContextBase>.Succeed(new HttpContextWrapper(HttpContext.Current));
|
||||
}
|
||||
|
||||
return Attempt<HttpContextBase>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 403 (Forbidden) response indicating that hte current user doesn't have access to the resource
|
||||
/// requested or the action it needs to take.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is different from a 401 which indicates that the user is not logged in.
|
||||
/// </remarks>
|
||||
public static HttpResponseMessage CreateUserNoAccessResponse(this HttpRequestMessage request)
|
||||
{
|
||||
return request.CreateResponse(HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse<T>(this HttpRequestMessage request, T value)
|
||||
{
|
||||
var msg = request.CreateResponse(HttpStatusCode.BadRequest, value);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request)
|
||||
{
|
||||
var msg = request.CreateResponse(HttpStatusCode.BadRequest);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, string errorMessage)
|
||||
{
|
||||
var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, errorMessage);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an error response with notifications in the result to be displayed in the UI
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateNotificationValidationErrorResponse(this HttpRequestMessage request, string errorMessage)
|
||||
{
|
||||
var notificationModel = new SimpleNotificationModel
|
||||
{
|
||||
Message = errorMessage
|
||||
};
|
||||
notificationModel.AddErrorNotification(errorMessage, string.Empty);
|
||||
return request.CreateValidationErrorResponse(notificationModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a succressful response with notifications in the result to be displayed in the UI
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="successMessage"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateNotificationSuccessResponse(this HttpRequestMessage request, string successMessage)
|
||||
{
|
||||
var notificationModel = new SimpleNotificationModel
|
||||
{
|
||||
Message = successMessage
|
||||
};
|
||||
notificationModel.AddSuccessNotification(successMessage, string.Empty);
|
||||
return request.CreateResponse(HttpStatusCode.OK, notificationModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a 400 response message indicating that a validation error occurred
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="modelState"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpResponseMessage CreateValidationErrorResponse(this HttpRequestMessage request, ModelStateDictionary modelState)
|
||||
{
|
||||
var msg = request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
|
||||
msg.Headers.Add("X-Status-Reason", "Validation failed");
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,4 +10,4 @@ namespace Umbraco.Web.WebApi
|
||||
public sealed class IsBackOfficeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,6 @@ namespace Umbraco.Web.WebApi
|
||||
}
|
||||
};
|
||||
controllerSettings.Formatters.Add(jsonFormatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for attributing controller actions to restrict them
|
||||
/// to just authenticated members, and optionally of a particular type and/or group
|
||||
/// </summary>
|
||||
public sealed class MemberAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
return _umbracoContext ?? UmbracoContext.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
public MemberAuthorizeAttribute(UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
_umbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public MemberAuthorizeAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag for whether to allow all site visitors or just authenticated members
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the same as applying the [AllowAnonymous] attribute
|
||||
/// </remarks>
|
||||
[Obsolete("Use [AllowAnonymous] instead")]
|
||||
public bool AllowAll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed member types
|
||||
/// </summary>
|
||||
public string AllowType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed member groups
|
||||
/// </summary>
|
||||
public string AllowGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed members
|
||||
/// </summary>
|
||||
public string AllowMembers { get; set; }
|
||||
|
||||
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
|
||||
{
|
||||
if (AllowMembers.IsNullOrWhiteSpace())
|
||||
AllowMembers = "";
|
||||
if (AllowGroup.IsNullOrWhiteSpace())
|
||||
AllowGroup = "";
|
||||
if (AllowType.IsNullOrWhiteSpace())
|
||||
AllowType = "";
|
||||
|
||||
var members = new List<int>();
|
||||
foreach (var s in AllowMembers.Split(','))
|
||||
{
|
||||
int id;
|
||||
if (int.TryParse(s, out id))
|
||||
{
|
||||
members.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return GetUmbracoContext().Security.IsMemberAuthorized(AllowAll,
|
||||
AllowType.Split(','),
|
||||
AllowGroup.Split(','),
|
||||
members);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Web.Security;
|
||||
using umbraco.cms.businesslogic.member;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for attributing controller actions to restrict them
|
||||
/// to just authenticated members, and optionally of a particular type and/or group
|
||||
/// </summary>
|
||||
public sealed class MemberAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
return _umbracoContext ?? UmbracoContext.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
public MemberAuthorizeAttribute(UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
_umbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public MemberAuthorizeAttribute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag for whether to allow all site visitors or just authenticated members
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the same as applying the [AllowAnonymous] attribute
|
||||
/// </remarks>
|
||||
[Obsolete("Use [AllowAnonymous] instead")]
|
||||
public bool AllowAll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed member types
|
||||
/// </summary>
|
||||
public string AllowType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed member groups
|
||||
/// </summary>
|
||||
public string AllowGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited list of allowed members
|
||||
/// </summary>
|
||||
public string AllowMembers { get; set; }
|
||||
|
||||
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
|
||||
{
|
||||
if (AllowMembers.IsNullOrWhiteSpace())
|
||||
AllowMembers = "";
|
||||
if (AllowGroup.IsNullOrWhiteSpace())
|
||||
AllowGroup = "";
|
||||
if (AllowType.IsNullOrWhiteSpace())
|
||||
AllowType = "";
|
||||
|
||||
var members = new List<int>();
|
||||
foreach (var s in AllowMembers.Split(','))
|
||||
{
|
||||
int id;
|
||||
if (int.TryParse(s, out id))
|
||||
{
|
||||
members.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return GetUmbracoContext().Security.IsMemberAuthorized(AllowAll,
|
||||
AllowType.Split(','),
|
||||
AllowGroup.Split(','),
|
||||
members);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Umbraco.Web.WebApi
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
internal class WebApiVersionCheck
|
||||
{
|
||||
@@ -7,4 +7,4 @@
|
||||
get { return typeof(System.Web.Http.ApiController).Assembly.GetName().Version; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,27 +25,27 @@ namespace Umbraco.Web.WebApi
|
||||
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
|
||||
{
|
||||
var routeData = request.GetRouteData();
|
||||
if (routeData == null
|
||||
if (routeData == null
|
||||
|| routeData.Route == null
|
||||
|| routeData.Route.DataTokens == null
|
||||
|| routeData.Route.DataTokens.ContainsKey("Namespaces") == false
|
||||
|| routeData.Route.DataTokens["Namespaces"] == null)
|
||||
return base.SelectController(request);
|
||||
|
||||
|
||||
// Look up controller in route data
|
||||
object controllerName;
|
||||
routeData.Values.TryGetValue(ControllerKey, out controllerName);
|
||||
var controllerNameAsString = controllerName as string;
|
||||
if (controllerNameAsString == null)
|
||||
if (controllerNameAsString == null)
|
||||
return base.SelectController(request);
|
||||
|
||||
|
||||
//get the currently cached default controllers - this will not contain duplicate controllers found so if
|
||||
// this controller is found in the underlying cache we don't need to do anything
|
||||
var map = base.GetControllerMapping();
|
||||
if (map.ContainsKey(controllerNameAsString))
|
||||
if (map.ContainsKey(controllerNameAsString))
|
||||
return base.SelectController(request);
|
||||
|
||||
//the cache does not contain this controller because it's most likely a duplicate,
|
||||
|
||||
//the cache does not contain this controller because it's most likely a duplicate,
|
||||
// so we need to sort this out ourselves and we can only do that if the namespace token
|
||||
// is formatted correctly.
|
||||
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
|
||||
@@ -95,4 +95,4 @@ namespace Umbraco.Web.WebApi
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Web;
|
||||
using System.Web.Http.Controllers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to auto-select specific actions on controllers that would otherwise be ambiguous based on a single parameter type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since
|
||||
/// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id")
|
||||
///
|
||||
/// This supports POST values too however only for JSON values
|
||||
/// </remarks>
|
||||
internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector
|
||||
{
|
||||
private readonly ParameterSwapInfo[] _actions;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor accepting a list of action name + parameter name
|
||||
/// </summary>
|
||||
/// <param name="actions"></param>
|
||||
public ParameterSwapControllerActionSelector(params ParameterSwapInfo[] actions)
|
||||
{
|
||||
_actions = actions;
|
||||
}
|
||||
|
||||
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
|
||||
{
|
||||
var found = _actions.FirstOrDefault(x => controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith(x.ActionName));
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
HttpActionDescriptor method;
|
||||
if (TryBindFromUri(controllerContext, found, out method))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
//if it's a post we can try to read from the body and bind from the json value
|
||||
if (controllerContext.Request.Method == HttpMethod.Post)
|
||||
{
|
||||
var requestContent = new HttpMessageContent(controllerContext.Request);
|
||||
var strJson = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result;
|
||||
var json = JsonConvert.DeserializeObject<JObject>(strJson);
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
return base.SelectAction(controllerContext);
|
||||
}
|
||||
|
||||
var requestParam = json[found.ParamName];
|
||||
|
||||
if (requestParam != null)
|
||||
{
|
||||
var paramTypes = found.SupportedTypes;
|
||||
|
||||
foreach (var paramType in paramTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var converted = requestParam.ToObject(paramType);
|
||||
if (converted != null)
|
||||
{
|
||||
method = MatchByType(paramType, controllerContext, found);
|
||||
if (method != null)
|
||||
return method;
|
||||
}
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
//can't convert
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
//can't convert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.SelectAction(controllerContext);
|
||||
}
|
||||
|
||||
private bool TryBindFromUri(HttpControllerContext controllerContext, ParameterSwapInfo found, out HttpActionDescriptor method)
|
||||
{
|
||||
var requestParam = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName);
|
||||
|
||||
requestParam = (requestParam == null) ? null : requestParam.Trim();
|
||||
var paramTypes = found.SupportedTypes;
|
||||
|
||||
if (requestParam == string.Empty && paramTypes.Length > 0)
|
||||
{
|
||||
//if it's empty then in theory we can select any of the actions since they'll all need to deal with empty or null parameters
|
||||
//so we'll try to use the first one available
|
||||
method = MatchByType(paramTypes[0], controllerContext, found);
|
||||
if (method != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (requestParam != null)
|
||||
{
|
||||
foreach (var paramType in paramTypes)
|
||||
{
|
||||
//check if this is IEnumerable and if so this will get it's type
|
||||
//we need to know this since the requestParam will always just be a string
|
||||
var enumType = paramType.GetEnumeratedType();
|
||||
|
||||
var converted = requestParam.TryConvertTo(enumType ?? paramType);
|
||||
if (converted)
|
||||
{
|
||||
method = MatchByType(paramType, controllerContext, found);
|
||||
if (method != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
method = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ReflectedHttpActionDescriptor MatchByType(Type idType, HttpControllerContext controllerContext, ParameterSwapInfo found)
|
||||
{
|
||||
var controllerType = controllerContext.Controller.GetType();
|
||||
var methods = controllerType.GetMethods().Where(info => info.Name == found.ActionName).ToArray();
|
||||
if (methods.Length > 1)
|
||||
{
|
||||
//choose the one that has the parameter with the T type
|
||||
var method = methods.FirstOrDefault(x => x.GetParameters().FirstOrDefault(p => p.Name == found.ParamName && p.ParameterType == idType) != null);
|
||||
|
||||
return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal class ParameterSwapInfo
|
||||
{
|
||||
public string ActionName { get; private set; }
|
||||
public string ParamName { get; private set; }
|
||||
public Type[] SupportedTypes { get; private set; }
|
||||
|
||||
public ParameterSwapInfo(string actionName, string paramName, params Type[] supportedTypes)
|
||||
{
|
||||
ActionName = actionName;
|
||||
ParamName = paramName;
|
||||
SupportedTypes = supportedTypes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,4 @@ namespace Umbraco.Web.WebApi
|
||||
return _innerValidator.Validate(model, type, metadataProvider, actionContext, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ namespace Umbraco.Web.WebApi
|
||||
public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
|
||||
{
|
||||
//replace the normal validator with our custom one for this controller
|
||||
controllerSettings.Services.Replace(typeof(IBodyModelValidator),
|
||||
new PrefixlessBodyModelValidator(controllerSettings.Services.GetBodyModelValidator()));
|
||||
controllerSettings.Services.Replace(typeof(IBodyModelValidator),
|
||||
new PrefixlessBodyModelValidator(controllerSettings.Services.GetBodyModelValidator()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ using System.Web.SessionState;
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom WebApi route handler that enables session on the HttpContext - use with caution!
|
||||
/// A custom WebApi route handler that enables session on the HttpContext - use with caution!
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// WebApi controllers (and REST in general) shouldn't have session state enabled since it's stateless,
|
||||
/// enabling session state puts additional locks on requests so only use this when absolutley needed
|
||||
/// enabling session state puts additional locks on requests so only use this when absolutley needed
|
||||
/// </remarks>
|
||||
internal class SessionHttpControllerRouteHandler : HttpControllerRouteHandler
|
||||
{
|
||||
@@ -21,11 +21,11 @@ namespace Umbraco.Web.WebApi
|
||||
|
||||
/// <summary>
|
||||
/// A custom WebApi handler that enables session on the HttpContext
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
private class SessionHttpControllerHandler : HttpControllerHandler, IRequiresSessionState
|
||||
{
|
||||
public SessionHttpControllerHandler(RouteData routeData) : base(routeData)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// A model binder to trim the string
|
||||
/// </summary>
|
||||
internal class TrimModelBinder : IModelBinder
|
||||
{
|
||||
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
|
||||
{
|
||||
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueResult?.AttemptedValue == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bindingContext.Model = (string.IsNullOrWhiteSpace(valueResult.AttemptedValue) ? valueResult.AttemptedValue : valueResult.AttemptedValue.Trim());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,29 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for auto-routed Umbraco API controllers.
|
||||
/// </summary>
|
||||
public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
|
||||
{ }
|
||||
}
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using umbraco.interfaces;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class for auto-routed API controllers for Umbraco
|
||||
/// </summary>
|
||||
public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
|
||||
{
|
||||
protected UmbracoApiController()
|
||||
{
|
||||
}
|
||||
|
||||
protected UmbracoApiController(UmbracoContext umbracoContext) : base(umbracoContext)
|
||||
{
|
||||
}
|
||||
|
||||
protected UmbracoApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using LightInject;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
@@ -15,107 +11,125 @@ using Umbraco.Web.WebApi.Filters;
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for Umbraco API controllers.
|
||||
/// The base class for API controllers that expose Umbraco services - THESE ARE NOT AUTO ROUTED
|
||||
/// </summary>
|
||||
/// <remarks>These controllers are NOT auto-routed.</remarks>
|
||||
[FeatureAuthorize]
|
||||
public abstract class UmbracoApiControllerBase : ApiController
|
||||
{
|
||||
private UmbracoHelper _umbracoHelper;
|
||||
protected UmbracoApiControllerBase()
|
||||
: this(UmbracoContext.Current)
|
||||
{
|
||||
|
||||
// for debugging purposes
|
||||
internal Guid InstanceId { get; } = Guid.NewGuid();
|
||||
}
|
||||
|
||||
// note
|
||||
// properties marked as [Inject] below will be property-injected (vs constructor-injected) in
|
||||
// order to keep the constuctor as light as possible, so that ppl implementing eg a SurfaceController
|
||||
// don't need to implement complex constructors + need to refactor them each time we change ours.
|
||||
// this means that these properties have a setter.
|
||||
// what can go wrong?
|
||||
protected UmbracoApiControllerBase(UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
UmbracoContext = umbracoContext;
|
||||
InstanceId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
protected UmbracoApiControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
if (umbracoHelper == null) throw new ArgumentNullException("umbracoHelper");
|
||||
UmbracoContext = umbracoContext;
|
||||
InstanceId = Guid.NewGuid();
|
||||
_umbraco = umbracoHelper;
|
||||
}
|
||||
|
||||
private UmbracoHelper _umbraco;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Umbraco context.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public virtual IGlobalSettings GlobalSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Umbraco context.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public virtual UmbracoContext UmbracoContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sql context.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public ISqlContext SqlContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the services context.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public ServiceContext Services { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application cache.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public CacheHelper ApplicationCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profiling logger.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public ProfilingLogger ProfilingLogger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the runtime state.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
internal IRuntimeState RuntimeState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application url.
|
||||
/// </summary>
|
||||
protected Uri ApplicationUrl => RuntimeState.ApplicationUrl;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the membership helper.
|
||||
/// </summary>
|
||||
public MembershipHelper Members => Umbraco.MembershipHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Umbraco helper.
|
||||
/// </summary>
|
||||
public UmbracoHelper Umbraco => _umbracoHelper
|
||||
?? (_umbracoHelper = new UmbracoHelper(UmbracoContext, Services, ApplicationCache));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the web security helper.
|
||||
/// </summary>
|
||||
public WebSecurity Security => UmbracoContext.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the current HttpContext.
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected Attempt<HttpContextBase> TryGetHttpContext()
|
||||
{
|
||||
return Request.TryGetHttpContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the current OWIN context.
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected Attempt<IOwinContext> TryGetOwinContext()
|
||||
{
|
||||
return Request.TryGetOwinContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ILogger
|
||||
/// </summary>
|
||||
public ILogger Logger
|
||||
{
|
||||
get { return ProfilingLogger.Logger; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ProfilingLogger
|
||||
/// </summary>
|
||||
public virtual ProfilingLogger ProfilingLogger
|
||||
{
|
||||
get { return UmbracoContext.Application.ProfilingLogger; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current ApplicationContext
|
||||
/// </summary>
|
||||
public virtual ApplicationContext ApplicationContext
|
||||
{
|
||||
get { return UmbracoContext.Application; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ServiceContext
|
||||
/// </summary>
|
||||
public ServiceContext Services
|
||||
{
|
||||
get { return ApplicationContext.Services; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a DatabaseContext
|
||||
/// </summary>
|
||||
public DatabaseContext DatabaseContext
|
||||
{
|
||||
get { return ApplicationContext.DatabaseContext; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an UmbracoHelper object
|
||||
/// </summary>
|
||||
public virtual UmbracoHelper Umbraco
|
||||
{
|
||||
get { return _umbraco ?? (_umbraco = new UmbracoHelper(UmbracoContext)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current UmbracoContext
|
||||
/// </summary>
|
||||
public virtual UmbracoContext UmbracoContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the WebSecurity instance
|
||||
/// </summary>
|
||||
public WebSecurity Security
|
||||
{
|
||||
get { return UmbracoContext.Security; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the MemberHelper instance
|
||||
/// </summary>
|
||||
public MembershipHelper Members
|
||||
{
|
||||
get { return Umbraco.MembershipHelper; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Useful for debugging
|
||||
/// </summary>
|
||||
internal Guid InstanceId { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Umbraco.Web/WebApi/UmbracoApiControllerResolver.cs
Normal file
25
src/Umbraco.Web/WebApi/UmbracoApiControllerResolver.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
internal sealed class UmbracoApiControllerResolver : ManyObjectsResolverBase<UmbracoApiControllerResolver, UmbracoApiController>
|
||||
{
|
||||
public UmbracoApiControllerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable<Type> apiControllers)
|
||||
: base(serviceProvider, logger, apiControllers)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all of the umbraco api controller types
|
||||
/// </summary>
|
||||
public IEnumerable<Type> RegisteredUmbracoApiControllers
|
||||
{
|
||||
get { return InstanceTypes; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
// unless we want to modify the content of the collection
|
||||
// which we are not doing at the moment
|
||||
// we can inherit from BuilderCollectionBase and just be enumerable
|
||||
|
||||
internal class UmbracoApiControllerTypeCollection : BuilderCollectionBase<Type>
|
||||
{
|
||||
public UmbracoApiControllerTypeCollection(IEnumerable<Type> items)
|
||||
: base(items)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,81 @@
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Ensures authorization is successful for a back office user.
|
||||
/// </summary>
|
||||
public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
private readonly bool _requireApproval;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by unit tests to enable/disable this filter
|
||||
/// </summary>
|
||||
internal static bool Enable = true;
|
||||
|
||||
// fixme - inject!
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
private IRuntimeState RuntimeState => _runtimeState ?? Current.RuntimeState;
|
||||
|
||||
private UmbracoContext UmbracoContext => _umbracoContext ?? Current.UmbracoContext;
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext, IRuntimeState runtimeState)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
_umbracoContext = umbracoContext;
|
||||
_runtimeState = runtimeState;
|
||||
}
|
||||
|
||||
public UmbracoAuthorizeAttribute() : this(true)
|
||||
{ }
|
||||
|
||||
public UmbracoAuthorizeAttribute(bool requireApproval)
|
||||
{
|
||||
_requireApproval = requireApproval;
|
||||
}
|
||||
|
||||
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
|
||||
{
|
||||
if (Enable == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// if not configured (install or upgrade) then we can continue
|
||||
// otherwise we need to ensure that a user is logged in
|
||||
return RuntimeState.Level == RuntimeLevel.Install
|
||||
|| RuntimeState.Level == RuntimeLevel.Upgrade
|
||||
|| UmbracoContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Ensures authorization is successful for a back office user
|
||||
/// </summary>
|
||||
public sealed class UmbracoAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
private readonly bool _requireApproval;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by unit tests to enable/disable this filter
|
||||
/// </summary>
|
||||
internal static bool Enable = true;
|
||||
|
||||
private readonly ApplicationContext _applicationContext;
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
|
||||
private ApplicationContext GetApplicationContext()
|
||||
{
|
||||
return _applicationContext ?? ApplicationContext.Current;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
return _umbracoContext ?? UmbracoContext.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="umbracoContext"></param>
|
||||
public UmbracoAuthorizeAttribute(UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
_umbracoContext = umbracoContext;
|
||||
_applicationContext = _umbracoContext.Application;
|
||||
}
|
||||
|
||||
public UmbracoAuthorizeAttribute() : this(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public UmbracoAuthorizeAttribute(bool requireApproval)
|
||||
{
|
||||
_requireApproval = requireApproval;
|
||||
}
|
||||
|
||||
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
|
||||
{
|
||||
if (Enable == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var appContext = GetApplicationContext();
|
||||
var umbContext = GetUmbracoContext();
|
||||
|
||||
//we need to that the app is configured and that a user is logged in
|
||||
if (appContext.IsConfigured == false)
|
||||
return false;
|
||||
|
||||
var isLoggedIn = umbContext.Security.ValidateCurrentUser(false, _requireApproval) == ValidateRequestAttempt.Success;
|
||||
|
||||
return isLoggedIn;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,77 @@
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for autorized auto-routed Umbraco API controllers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This controller will also append a custom header to the response if the user is logged in using forms authentication
|
||||
/// which indicates the seconds remaining before their timeout expires.
|
||||
/// </remarks>
|
||||
[IsBackOffice]
|
||||
[UmbracoUserTimeoutFilter]
|
||||
[UmbracoAuthorize]
|
||||
[DisableBrowserCache]
|
||||
[UmbracoWebApiRequireHttps]
|
||||
[CheckIfUserTicketDataIsStale]
|
||||
[UnhandedExceptionLoggerConfiguration]
|
||||
[EnableDetailedErrors]
|
||||
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
|
||||
{
|
||||
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
|
||||
|
||||
protected BackOfficeUserManager<BackOfficeIdentityUser> UserManager
|
||||
=> _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager());
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco.BusinessLogic;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// A base controller that ensures all requests are authorized - the user is logged in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This controller will also append a custom header to the response if the user is logged in using forms authentication
|
||||
/// which indicates the seconds remaining before their timeout expires.
|
||||
/// </remarks>
|
||||
[IsBackOffice]
|
||||
[UmbracoUserTimeoutFilter]
|
||||
[UmbracoAuthorize]
|
||||
[DisableBrowserCache]
|
||||
[UmbracoWebApiRequireHttps]
|
||||
[CheckIfUserTicketDataIsStale]
|
||||
[UnhandedExceptionLoggerConfiguration]
|
||||
[EnableDetailedErrors]
|
||||
public abstract class UmbracoAuthorizedApiController : UmbracoApiController
|
||||
{
|
||||
|
||||
|
||||
protected UmbracoAuthorizedApiController()
|
||||
{
|
||||
}
|
||||
|
||||
protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext) : base(umbracoContext)
|
||||
{
|
||||
}
|
||||
|
||||
protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper)
|
||||
{
|
||||
}
|
||||
|
||||
protected UmbracoAuthorizedApiController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper, BackOfficeUserManager<BackOfficeIdentityUser> backOfficeUserManager) : base(umbracoContext, umbracoHelper)
|
||||
{
|
||||
_userManager = backOfficeUserManager;
|
||||
}
|
||||
|
||||
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
|
||||
protected BackOfficeUserManager<BackOfficeIdentityUser> UserManager
|
||||
{
|
||||
get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); }
|
||||
}
|
||||
|
||||
private bool _userisValidated = false;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently logged in Umbraco User
|
||||
/// </summary>
|
||||
[Obsolete("This should no longer be used since it returns the legacy user object, use The Security.CurrentUser instead to return the proper user object, or Security.GetUserId() if you want to just get the user id")]
|
||||
protected User UmbracoUser
|
||||
{
|
||||
get
|
||||
{
|
||||
//throw exceptions if not valid (true)
|
||||
if (!_userisValidated)
|
||||
{
|
||||
Security.ValidateCurrentUser(true);
|
||||
_userisValidated = true;
|
||||
}
|
||||
|
||||
return new User(Security.CurrentUser);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Web.WebApi
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Important to note that the <see cref="UnhandledExceptionLogger"/> will only be called if the controller has an ExceptionFilter applied
|
||||
/// to it, so to kill two birds with one stone, this class inherits from ExceptionFilterAttribute purely to force webapi to use the
|
||||
/// to it, so to kill two birds with one stone, this class inherits from ExceptionFilterAttribute purely to force webapi to use the
|
||||
/// IExceptionLogger (strange)
|
||||
/// </remarks>
|
||||
public class UnhandedExceptionLoggerConfigurationAttribute : ExceptionFilterAttribute, IControllerConfiguration
|
||||
@@ -24,6 +24,6 @@ namespace Umbraco.Web.WebApi
|
||||
{
|
||||
controllerSettings.Services.Add(typeof(IExceptionLogger), new UnhandledExceptionLogger());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Web.Http.ExceptionHandling;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
@@ -13,7 +12,7 @@ namespace Umbraco.Web.WebApi
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UnhandledExceptionLogger()
|
||||
: this(Current.Logger)
|
||||
: this(ApplicationContext.Current.ProfilingLogger.Logger)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -24,14 +23,14 @@ namespace Umbraco.Web.WebApi
|
||||
|
||||
public override void Log(ExceptionLoggerContext context)
|
||||
{
|
||||
if (context != null && context.Exception != null)
|
||||
if (context != null && context.ExceptionContext != null
|
||||
&& context.ExceptionContext.ActionContext != null && context.ExceptionContext.ActionContext.ControllerContext != null
|
||||
&& context.ExceptionContext.ActionContext.ControllerContext.Controller != null
|
||||
&& context.Exception != null)
|
||||
{
|
||||
var requestUrl = context.ExceptionContext?.ControllerContext?.Request?.RequestUri?.AbsoluteUri;
|
||||
var controllerType = context.ExceptionContext?.ActionContext?.ControllerContext?.Controller?.GetType();
|
||||
|
||||
_logger.Error(controllerType, context.Exception, "Unhandled controller exception occurred for request '{RequestUrl}'", requestUrl);
|
||||
}
|
||||
_logger.Error(context.ExceptionContext.ActionContext.ControllerContext.Controller.GetType(), "Unhandled controller exception occurred", context.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Umbraco.Web/WebApi/WebApiHelper.cs
Normal file
31
src/Umbraco.Web/WebApi/WebApiHelper.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Hosting;
|
||||
using System.Web.Http.Routing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
internal static class WebApiHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper method to create a WebAPI HttpControllerContext which can be used to execute a controller manually
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns></returns>
|
||||
internal static HttpControllerContext CreateContext(HttpMethod method, Uri uri, HttpContextBase httpContext)
|
||||
{
|
||||
var config = new HttpConfiguration(GlobalConfiguration.Configuration.Routes);
|
||||
IHttpRouteData route = new HttpRouteData(new HttpRoute());
|
||||
var req = new HttpRequestMessage(method, uri);
|
||||
req.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
|
||||
req.Properties[HttpPropertyKeys.HttpRouteDataKey] = route;
|
||||
req.Properties["MS_HttpContext"] = httpContext;
|
||||
return new HttpControllerContext(config, route, req);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user