Include permissions (ACL) when importing content
I recently had to import about 500 pages all with their own specific settings on what roles have edit persmission. After trying a couple of different search terms I found an old blog post by Johan Björnfot https://world.optimizely.com/Blogs/Johan-Bjornfot/Dates1/2011/3/Include-ACL-during-Import-Mirroring-and-Copy/ that had a code snippet that hooks into the built in import feature and sets the access rights, the only downside being it was written for Episerver CMS 5 and 6 and can't just be copy-pasted.
While it did have a lot of errors when added to a project with Episerver 11 it offered a great starting point. The upgraded version (see below) was actually easy to create. Thanks to the Optimizely developers including helpful "please dont ues this, use that instead"-warnings as well as providing the events that makes this possible!
If you want to know what more can be done with these events you can take a look at this blog post by Antti Alasvuo https://world.optimizely.com/blogs/Antti-Alasvuo/Dates/2021/1/migrating-episerver-content-with-custom-import-processing/.
using EPiServer.Core;
using EPiServer.Core.Transfer;
using EPiServer.DataAbstraction;
using EPiServer.Enterprise;
using EPiServer.Enterprise.Transfer;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Plugins.AclImporter
{
/// <summary>
/// <para>
/// Registers event handlers for when content is imported with the built in import data admin tool.
/// This module extends the import to include ACL (permissions) for imported content.
/// This is not handled by default and is the same as the parent content.
/// </para>
/// <para>
/// This code is an update version of code written by Johan Björnfot for Episerver 5 and 6.
/// Source: https://world.optimizely.com/Blogs/Johan-Bjornfot/Dates1/2011/3/Include-ACL-during-Import-Mirroring-and-Copy/
/// </para>
/// </summary>
[InitializableModule]
public class AclImporterModule : IInitializableModule
{
//The module will be a singleton so it needs to be thread safe
private readonly object _lockObject = new object();
private readonly Dictionary<ITransferContext, AclImporter> _aclImporters =
new Dictionary<ITransferContext, AclImporter>();
public void Initialize(InitializationEngine context)
{
var dataImportEvents = context.Locate.Advanced.GetInstance<IDataImportEvents>();
dataImportEvents.Starting += new ImportingEventHandler(DataImportEvents_Starting);
dataImportEvents.Completed += new ImportingEventHandler(DataImportEvents_Completed);
dataImportEvents.ContentImporting += DataImportEvents_ContentImporting;
dataImportEvents.ContentImported += DataImportEvents_ContentImported;
}
public void Preload(string[] parameters)
{
}
public void Uninitialize(InitializationEngine context)
{
var dataImportEvents = context.Locate.Advanced.GetInstance<IDataImportEvents>();
dataImportEvents.Starting -= new ImportingEventHandler(DataImportEvents_Starting);
dataImportEvents.Completed -= new ImportingEventHandler(DataImportEvents_Completed);
dataImportEvents.ContentImporting -= DataImportEvents_ContentImporting;
dataImportEvents.ContentImported -= DataImportEvents_ContentImported;
}
private void DataImportEvents_Starting(ITransferContext transferContext, DataImporterContextEventArgs e)
{
lock (_lockObject)
{
_aclImporters.Add(transferContext, new AclImporter(transferContext));
}
}
private void DataImportEvents_Completed(ITransferContext transferContext, DataImporterContextEventArgs e)
{
lock (_lockObject)
{
_aclImporters.Remove(transferContext);
}
}
private void DataImportEvents_ContentImporting(ITransferContext transferContext, ContentImportingEventArgs e)
{
AclImporter aclImporter;
lock (_lockObject)
{
aclImporter = _aclImporters[transferContext];
}
aclImporter.ImportingContent(e.TransferContentData);
}
private void DataImportEvents_ContentImported(ITransferContext transferContext, ContentImportedEventArgs e)
{
AclImporter aclImporter;
lock (_lockObject)
{
aclImporter = _aclImporters[transferContext];
}
aclImporter.ImportedContent(e.ContentLink);
}
}
public class AclImporter
{
private readonly Dictionary<Guid, RawACE[]> _importedAce = new Dictionary<Guid, RawACE[]>();
private readonly ITransferContext _context;
private readonly IPermanentLinkMapper _mapper;
private readonly IContentSecurityRepository _contentSecurity;
public AclImporter(ITransferContext context)
{
_context = context;
_mapper = ServiceLocator.Current.GetInstance<IPermanentLinkMapper>();
_contentSecurity = ServiceLocator.Current.GetInstance<IContentSecurityRepository>();
}
internal void ImportingContent(EPiServer.Core.RawPage rawPage)
{
_importedAce[GetContentGuid(rawPage)] = rawPage.ACL;
}
internal void ImportingContent(ITransferContentData transferContentData)
{
var rawData = transferContentData.RawContentData;
_importedAce[GetContentGuid(rawData)] = rawData.ACL;
}
internal void ImportedContent(ContentReference contentReference)
{
//PageData pageData = _contentLoader.Get<PageData>(contentReference);
//Note: We can not use pageData.PageGuid here since
//PageData instance is not complete
PermanentLinkMap pageMap = _mapper.Find(contentReference);
Guid oldPageGuid = _context.LinkGuidMap.First(pair => pair.Value == pageMap.Guid).Key;
RawACE[] aces = _importedAce[oldPageGuid];
var importedAcl = new ContentAccessControlList(aces)
{
ContentLink = contentReference
};
_contentSecurity.Save(contentReference, importedAcl, SecuritySaveType.Replace);
}
private Guid GetContentGuid(RawContent rawContent)
{
return new Guid(rawContent.Property.Single(prop => prop.Name == "PageGUID").Value);
}
}
}
Comments