Our client recently went through a few rather large organizational changes which naturally had some rippling effects running down into the security management of their EPiServer based website; i.e. when the editor role responsible for national content no longer could be considered an independant organization, but rather had to be merged into one of the Swedish county regions used on the site, we had to make a couple of changes. One of these changes was the matter of mass updating EPiServer’s AccessControlLists (ACL) for about 31k of the site’s existing pages. Since we for various reasons wanted to avoid doing this by running a SQL script on the database, I created a simple recursive deployment job; not exactly rocket science, but thought I’d share it.
Mass updating the EPiServer AccessControlList (ACL) recursively from codebehind
What we wanted to do was add a new role with a different name to every page with a certain role in it’s AccessControlList. This new role was to have the same access level as the old one for every page respectively. First, a few lines of output methods for confirmation and possible debugging.
public partial class SprintJobs : SystemPageBase { private StringBuilder _outputBuilder; private void LogAddition(PageData page, AccessControlEntry ace) { const string s = "Added: '{0}' to: '{1}' (id:'{2}') access: '{3}'.<br />"; _outputBuilder.AppendFormat(s, ace.Name, page.PageName, page.PageLink, ace.Access); } private void LogRemoval(PageData page, AccessControlEntry ace) { const string s = "Removed: '{0}' from: '{1}' (id:'{2}') had access: '{3}'.<br />"; _outputBuilder.AppendFormat(s, ace.Name, page.PageName, page.PageLink, ace.Access); }
Due to a short transition period during which both EPiServer roles needed to be available we did this in separate steps; one for adding the new role, and later a second one for deleting the old entries. For this post, I have merged the methods together to make things less cluttered. The RunJob method below is called from a button click event on a page, but we have some base classes etc set up to make this kind of jobs less painful, so I have just extracted the interesting parts.
private const string NationalEditorNewRole = "nationalEditor New County Name"; private const string NationalEditorOldRole = "nationalEditor Old Organization Name"; private string RunJob() { _outputBuilder = new StringBuilder(); var numChanges = UpdateRolesRecursive(PageReference.RootPage); return string.Format("Added role '{0}' to, and removed role '{1}' from {2} pages.<br />{3}", NationalEditorNewRole, NationalEditorOldRole, numChanges, _outputBuilder); } private int UpdateRolesRecursive(PageReference pageReference) { var count = 0; foreach (var page in DataFactory.Instance.GetChildren(pageReference)) { var writablePage = page.CreateWritableClone(); if (writablePage.ACL.Exists(NationalEditorOldRole)) { UpdateAclFor(writablePage); count++; } count += UpdateRolesRecursive(writablePage.PageLink); } return count; }
The method doing the actual work first creates a new AccessControlEntry based on the old one and then adds or updates (54-61) the currently processed page’s AccessControlList with the new role. After this, the old role is removed by setting it’s AccessLevel to NoAccess (63).
private void UpdateAclFor(PageData page) { var oldRoleACE = page.ACL[NationalEditorOldRole]; var newRoleACE = new AccessControlEntry(NationalEditorNewRole, oldRoleACE.Access, oldRoleACE.EntityType); if (page.ACL.Exists(NationalEditorNewRole)) { page.ACL[NationalEditorNewRole] = newRoleACE; } else { page.ACL.Add(newRoleACE); } page.ACL[NationalEditorOldRole] = new AccessControlEntry(oldRoleACE.Name, AccessLevel.NoAccess, oldRoleACE.EntityType); page.ACL.Save(SecuritySaveType.Modify); LogAddition(page, newRoleACE); LogRemoval(page, oldRoleACE); }
Page access in the EPiServer database – tblAccess and tblAccessType
The various AccessLevel of the individual pages is stored in the tblAccess table of the EPiServer database. Here you can see one row for each role, or user for that matter, that is involved with each page; in the image below for instance, the pages with PageIDs 1-4 have four roles in their AccessControlLists respectively.
While IsRole specifies whether Name contains a role name or the name of a user, the values in the AccessMask column have ties to another of the EPiServer database tables; tblAccessType. Here we can see the different levels of access that a role may have on each individual page. The AccessMask is the sum of the IDs for the access levels selected; thus resulting in 1 being Read access, 31 being Read + Create + Edit + Delete + Publish, and 63 giving FullAccess.
Manipulating the AccessControlList through SQL queries should not be much of a problem.