Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported over the fixes in #11858 "Check media Parent for permissions when setting correct MediaType" to target v8 #12233

Merged
merged 4 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@
<key alias="orClickHereToUpload">or click here to choose files</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload</key>
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
<key alias="disallowedMediaType">Cannot upload this file, the media type with alias '%0%' is not allowed here</key>
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
<key alias="maxFileSize">Max file size is</key>
<key alias="mediaRoot">Media root</key>
Expand Down Expand Up @@ -1448,6 +1449,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="invalidUserPermissionsText">Insufficient user permissions, could not complete the operation</key>
<key alias="operationCancelledHeader">Cancelled</key>
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
<key alias="contentTypePropertyTypeCreated">Property type created</key>
Expand Down
6 changes: 5 additions & 1 deletion src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,9 @@
<area alias="media">
<key alias="clickToUpload">Click to upload</key>
<key alias="orClickHereToUpload">or click here to choose files</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload.</key>
<key alias="dragFilesHereToUpload">You can drag files here to upload.</key>
<key alias="disallowedFileType">Cannot upload this file, it does not have an approved file type</key>
<key alias="disallowedMediaType">Cannot upload this file, the media type with alias '%0%' is not allowed here</key>
<key alias="invalidFileName">Cannot upload this file, it does not have a valid file name</key>
<key alias="maxFileSize">Max file size is</key>
<key alias="mediaRoot">Media root</key>
Expand Down Expand Up @@ -1460,6 +1461,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="invalidUserPermissionsText">Insufficient user permissions, could not complete the operation</key>
<key alias="operationCancelledHeader">Cancelled</key>
<key alias="operationCancelledText">Operation was cancelled by a 3rd party add-in</key>
<key alias="folderUploadNotAllowed">This file is being uploaded as part of a folder, but creating a new folder is not allowed here</key>
<key alias="folderCreationNotAllowed">Creating a new folder is not allowed here</key>
<key alias="contentPublishedFailedByEvent">Publishing was cancelled by a 3rd party add-in</key>
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
<key alias="contentTypePropertyTypeCreated">Property type created</key>
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Name: %0% <br /> DataType: %1%]]></key>
Expand Down
192 changes: 140 additions & 52 deletions src/Umbraco.Web/Editors/MediaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,14 @@ public MediaItemDisplay PostAddFolder(PostedFolder folder)
{
var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true);

var isFolderAllowed = IsFolderCreationAllowedHere(intParentId);
if (isFolderAllowed == false)
{
var notificationModel = new SimpleNotificationModel();
notificationModel.AddErrorNotification(Services.TextService.Localize("speechBubbles", "folderCreationNotAllowed"), "");
throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can just use the CreateNotificationValidationErrorResponse() that we already have and pass in the error msg. It is doing exactly what you are trying to do here. I will fix up

}

var mediaService = Services.MediaService;

var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder);
Expand Down Expand Up @@ -644,6 +652,11 @@ public async Task<HttpResponseMessage> PostAddFile()
//in case we pass a path with a folder in it, we will create it and upload media to it.
if (result.FormData.ContainsKey("path"))
{
if (!IsFolderCreationAllowedHere(parentId))
{
AddCancelMessage(tempFiles, Services.TextService.Localize("speechBubbles", "folderUploadNotAllowed"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying the message param is needed here because otherwise, the AddCancelMessage() will interpret the passed text as the header and the message displayed will end up being just the default operationCancelledText.

Also, the syntax for v8 is a little different, here we can just pass "speechBubbles/folderUploadNotAllowed" as the method is already doing the localization

Will fix up!

return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}

var folders = result.FormData["path"].Split(Constants.CharArrays.ForwardSlash);

Expand All @@ -653,7 +666,7 @@ public async Task<HttpResponseMessage> PostAddFile()
IMedia folderMediaItem;

//if uploading directly to media root and not a subfolder
if (parentId == -1)
if (parentId == Constants.System.Root)
{
//look for matching folder
folderMediaItem =
Expand Down Expand Up @@ -691,88 +704,140 @@ public async Task<HttpResponseMessage> PostAddFile()
}
}

var mediaTypeAlias = string.Empty;
var allMediaTypes = Services.MediaTypeService.GetAll().ToList();
var allowedContentTypes = new HashSet<IMediaType>();

if (parentId != Constants.System.Root)
{
var mediaFolderItem = mediaService.GetById(parentId);
var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == mediaFolderItem.ContentType.Alias);

if (mediaFolderType != null)
{
IMediaType mediaTypeItem = null;

foreach (ContentTypeSort allowedContentType in mediaFolderType.AllowedContentTypes)
{
IMediaType checkMediaTypeItem = allMediaTypes.FirstOrDefault(x => x.Id == allowedContentType.Id.Value);
allowedContentTypes.Add(checkMediaTypeItem);

var fileProperty = checkMediaTypeItem?.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File);
if (fileProperty != null)
{
mediaTypeItem = checkMediaTypeItem;
}
}

//Only set the permission-based mediaType if we only allow 1 specific file under this parent.
if (allowedContentTypes.Count == 1 && mediaTypeItem != null)
{
mediaTypeAlias = mediaTypeItem.Alias;
}
}
}
else
{
var typesAllowedAtRoot = allMediaTypes.Where(x => x.AllowedAsRoot).ToList();
allowedContentTypes.UnionWith(typesAllowedAtRoot);
}

//get the files
foreach (var file in result.FileData)
{
var fileName = file.Headers.ContentDisposition.FileName.Trim(Constants.CharArrays.DoubleQuote).TrimEnd();
var safeFileName = fileName.ToSafeFileName();
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();

if (Current.Configs.Settings().Content.IsFileAllowedForUpload(ext))
if (!Current.Configs.Settings().Content.IsFileAllowedForUpload(ext))
{
tempFiles.Notifications.Add(new Notification(
Services.TextService.Localize("speechBubbles", "operationFailedHeader"),
Services.TextService.Localize("media", "disallowedFileType"),
NotificationStyle.Warning));
continue;
}

if (string.IsNullOrEmpty(mediaTypeAlias))
{
var mediaType = Constants.Conventions.MediaTypes.File;
mediaTypeAlias = Constants.Conventions.MediaTypes.File;

if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect)
{
var mediaTypes = Services.MediaTypeService.GetAll();
// Look up MediaTypes
foreach (var mediaTypeItem in mediaTypes)
foreach (var mediaTypeItem in allMediaTypes)
{
var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == "umbracoFile");
if (fileProperty != null) {
var dataTypeKey = fileProperty.DataTypeKey;
var dataType = Services.DataTypeService.GetDataType(dataTypeKey);

if (dataType != null && dataType.Configuration is IFileExtensionsConfig fileExtensionsConfig) {
var fileExtensions = fileExtensionsConfig.FileExtensions;
if (fileExtensions != null)
{
if (fileExtensions.Where(x => x.Value == ext).Count() != 0)
{
mediaType = mediaTypeItem.Alias;
break;
}
}
}
var fileProperty = mediaTypeItem.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == Constants.Conventions.Media.File);
if (fileProperty == null)
{
continue;
}

var dataTypeKey = fileProperty.DataTypeKey;
var dataType = Services.DataTypeService.GetDataType(dataTypeKey);


if (dataType == null || dataType.Configuration is not IFileExtensionsConfig fileExtensionsConfig)
{
continue;
}

var fileExtensions = fileExtensionsConfig.FileExtensions;
if (fileExtensions == null || fileExtensions.All(x => x.Value != ext))
{
continue;
}

mediaTypeAlias = mediaTypeItem.Alias;
break;
}

// If media type is still File then let's check if it's an image.
if (mediaType == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext))
if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && Current.Configs.Settings().Content.ImageFileTypes.Contains(ext))
{
mediaType = Constants.Conventions.MediaTypes.Image;
mediaTypeAlias = Constants.Conventions.MediaTypes.Image;
}
}
else
{
mediaType = result.FormData["contentTypeAlias"];
mediaTypeAlias = result.FormData["contentTypeAlias"];
}
}

var mediaItemName = fileName.ToFriendlyName();
if (allowedContentTypes.Any(x => x.Alias == mediaTypeAlias) == false)
{
tempFiles.Notifications.Add(new Notification(
Services.TextService.Localize("speechBubbles", "operationFailedHeader"),
Services.TextService.Localize("media", "disallowedMediaType", new[] { mediaTypeAlias }),
NotificationStyle.Warning));
continue;
}

var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id);
var mediaItemName = fileName.ToFriendlyName();

var fileInfo = new FileInfo(file.LocalFileName);
var fs = fileInfo.OpenReadWithRetry();
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
using (fs)
{
f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File,fileName, fs);
}
var f = mediaService.CreateMedia(mediaItemName, parentId, mediaTypeAlias, Security.CurrentUser.Id);

var saveResult = mediaService.Save(f, Security.CurrentUser.Id);
if (saveResult == false)
{
AddCancelMessage(tempFiles,
message: Services.TextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName);
}
else
{
tempFiles.UploadedFiles.Add(new ContentPropertyFile
{
FileName = fileName,
PropertyAlias = Constants.Conventions.Media.File,
TempFilePath = file.LocalFileName
});
}
var fileInfo = new FileInfo(file.LocalFileName);
var fs = fileInfo.OpenReadWithRetry();
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
using (fs)
{
f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, fs);
}

var saveResult = mediaService.Save(f, Security.CurrentUser.Id);
if (saveResult == false)
{
AddCancelMessage(tempFiles, Services.TextService.Localize("speechBubbles", "operationCancelledText") + " -- " + mediaItemName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying the message param is needed here because otherwise, the AddCancelMessage() will interpret the passed text as the header and the message displayed will end up being just the default operationCancelledText. Will fix up!

}
else
{
tempFiles.Notifications.Add(new Notification(
Services.TextService.Localize("speechBubbles", "operationFailedHeader"),
Services.TextService.Localize("media", "disallowedFileType"),
NotificationStyle.Warning));
tempFiles.UploadedFiles.Add(new ContentPropertyFile
{
FileName = fileName,
PropertyAlias = Constants.Conventions.Media.File,
TempFilePath = file.LocalFileName
});
}
}

Expand All @@ -792,6 +857,29 @@ public async Task<HttpResponseMessage> PostAddFile()
return Request.CreateResponse(HttpStatusCode.OK, tempFiles);
}

private bool IsFolderCreationAllowedHere(int parentId)
{
var allMediaTypes = Services.MediaTypeService.GetAll().ToList();
var isFolderAllowed = false;
if (parentId == Constants.System.Root)
{
var typesAllowedAtRoot = allMediaTypes.Where(ct => ct.AllowedAsRoot).ToList();
isFolderAllowed = typesAllowedAtRoot.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder);
}
else
{
var parentMediaType = Services.MediaService.GetById(parentId);
var mediaFolderType = allMediaTypes.FirstOrDefault(x => x.Alias == parentMediaType.ContentType.Alias);
if (mediaFolderType != null)
{
isFolderAllowed =
mediaFolderType.AllowedContentTypes.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder);
}
}

return isFolderAllowed;
}

private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias)
{
const int pageSize = 500;
Expand Down