How to restrict Content Apps by user groups in Umbraco

Posted written by Paul Seal on September 27, 2019 Umbraco

It's no secret that I'm a fan of Content Apps. I've created a few of them since the documentation page was published on how to create them.

My favourite one that I've written so far is the one I co-wrote with Nik Rimington, called ImageFilter.

One thing I don't like is how the decision on who gets to see the Content App is made by the package owner, in that they can code it in to only allow for admin roles etc.

Also there is not much room for too many Content Apps so if we can manage who gets to see them ourselves then it will be a real help.

So I've made a way to allow you to be able to control which groups can see a specific Content App.

The idea is, you add this code to your site, or install the NuGet package when I publish it and then you configure the permissions in a single app setting value.

If we take my Image Filter content app as an example, I can add an app setting which says only show the content app titled 'Filter' to people who are in a group called 'Filter' or 'Administrators'.

<!-- Enter the Content app and allowed groups in this format: 
  ContentApp1Name[AllowedGroup1|AllowedGroup2],ContentApp2Name[AllowedGroup1] -->

<add key="ContentAppsRestrictedByGroup" value="Filter[Filter|Administrators]"/>

I can then create a Group called 'Filter' and add the relevant users to that group.

Now only users who are in the group 'Filter' or 'Administrators' can see the Content App.

Here is the code:

using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;

namespace Our.Umbraco.ContentAppGroupPermissions.Core.Compose
{
    [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
    public class ContentAppGroupPermissionsComposer : ComponentComposer<ContentAppGroupPermissionsComponent>
    {
    }

    public class ContentAppGroupPermissionsComponent : IComponent
    {
        public void Initialize()
        {
            EditorModelEventManager.SendingMediaModel += EditorModelEventManager_SendingMediaModel;
            EditorModelEventManager.SendingContentModel += EditorModelEventManager_SendingContentModel;
        }

        public void Terminate()
        {
        }

        private void EditorModelEventManager_SendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs<MediaItemDisplay> e)
        {
            e.Model.ContentApps = GetAllowedContentApps(e.UmbracoContext.Security.CurrentUser, e.Model.ContentApps);
        }

        private void EditorModelEventManager_SendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e)
        {
            e.Model.ContentApps = GetAllowedContentApps(e.UmbracoContext.Security.CurrentUser, e.Model.ContentApps);
        }

        private IEnumerable<ContentApp> GetAllowedContentApps(IUser user, IEnumerable<ContentApp> contentApps)
        {
            // Read the app setting for restricting content apps
            var contentAppsRestrictedByGroups = ConfigurationManager.AppSettings["ContentAppsRestrictedByGroup"]?.Split(',');

            if (contentAppsRestrictedByGroups != null)
            {
                // Loop through the content app settings to find the content app name and allowed group list
                foreach (var contentAppSetting in contentAppsRestrictedByGroups.Where(x => !string.IsNullOrWhiteSpace(x)))
                {
                    var contentAppName = contentAppSetting.Split('[').FirstOrDefault();
                    var allowedGroupList = GetAllowedGroupList(contentAppSetting);
                    if (!string.IsNullOrWhiteSpace(contentAppName) &amp;&amp; allowedGroupList != null)
                    {
                        // check if the user is in any of the allowed groups
                        // if they are not, then remove the content app from the list
                        contentApps = UserIsInAnyOfTheseGroups(user, allowedGroupList)
                            ? contentApps
                            : contentApps.Where(x => x.Name != contentAppName);
                    }
                }
            }

            return contentApps;
        }

        // Reads the app setting and returns a list of group names that are allowed to use this content app
        private static string[] GetAllowedGroupList(string contentAppSetting)
        {
            var groupNamesStart = contentAppSetting.IndexOf('[');
            var groupNamesEnd = contentAppSetting.IndexOf(']');
            if (groupNamesStart != -1 &amp;&amp; groupNamesEnd != -1)
            {
                var allowedGroups = contentAppSetting.Substring(groupNamesStart + 1, (groupNamesEnd - groupNamesStart) - 1);
                var allowedGroupList = allowedGroups.Split('|');
                return allowedGroupList;
            }

            return null;
        }

        private static bool UserIsInAnyOfTheseGroups(IUser user, IEnumerable<string> groupNames)
        {
            return user?.Groups?.Any(x => groupNames.Contains(x.Name)) ?? false;
        }
    }
}

As you can see, the idea is that if the user is not a member of any of the groups in the app setting for the Content App then it gets removed from the list.

In theory, the Content tab and Info tab are both Content Apps, so you can use this to restrict them too

<!-- Enter the Content app and allowed groups in this format: 
  ContentApp1Name[AllowedGroup1|AllowedGroup2],ContentApp2Name[AllowedGroup1] -->
<add key="ContentAppsRestrictedByGroup" value="Filter[Filter|Administrators],Content[Administrators],Info[Sensitive data]"/>