Security Model

Jan 9, 2009 at 7:46 PM
Hi there!  First off, N2 seems like a fantastic piece of work and something that I will be able to use going forward for content management on our website.  I'm trying to figure out a good way to separate concerns in the security model.  I will have different editing "content owners" and using the page permissions seems like it would be an ideal way to manage this -- I would love for a content manager to be able to log into the edit interface and only see the items that they have edit access too.  However, at the moment, I want all content on the end-user side of things to be available to everyone.  I'm not really seeing an easy way of doing this unless I am missing something.  Any suggestions would be great!  -- The alternative of course would be to create multiple content item types and add access role attributes on an item by item basis but this seems a much more tedious way of doing things especially if the types are essentially the same in other ways (basically just a content page).  Also, when using this method there does not seem to be a way to add permission attributes to the Title and URL segement fields.
Coordinator
Jan 11, 2009 at 4:28 PM
As you have discovered N2 have a simple security model. You can restrict who can read what but to do more you need to get your hands dirty. What you could do is overriding some of the default behaviour:

Create an item filter:

public class MyFilter : ItemFilter ...

Create an custom editor manager and use the filter:

public class MyEditManager : EditManager
{
    public ItemFilter GetEditorFilter(IPrincipal user)
    {
        return new MyFilter();
    }
}

Replace the existing edit manager with your improvements:
<n2><engine><components>
    <add service="N2.Edit.IEditManager, N2" implementation="MyEditManager, My" />

Hope this works. Good luck!

Jan 12, 2009 at 3:49 PM
Thanks for your reply.  What I really would like to be able to do is manage Read access in 2 different contexts i.e. there would be a set of AllowedRoles for "edit-mode" (say, admin and a specific content editor role) and then another set for "public view-mode" (everyone by default, but possibly a restricted set of userRoles if need be).  Unfortunately the N2AllowedRole persisted object is pretty much a "one size fits all" kind of deal.  It seems like I have to resort to breaking the functionality on one side or the other (edit or public view access) or do some major hackery.
Jan 20, 2009 at 1:50 PM
Hi,

doesn't sound like you can, but can you have it so that you when you define an item to be restricted only to administrators that editors can still expand these node in red to view and edit the sub items they are allowed to edit?

I just tried restricting the main pages, and wanted editors to be able to add articles to one of these restricted nodes, but not edit or delete the restricted node. As admin I can see the node, but it is highlighted red.
I was expecting when logging in as an editor to presented with the same view, but just for the right click context menu to be removed for these red items.

So is the above possible, what do I need to do to fix this. It sounds like I just need to fix the tree navigation control in the edit pages.

I look forward to resolving this issue.
Jan 20, 2009 at 4:55 PM
I've added the class
public class MyEditManager : EditManager, IEditManager
    {

        public MyEditManager(IDefinitionManager definitions, IPersister persister, IVersionManager versioner, ISecurityManager securityManager, IPluginFinder pluginFinder, NavigationSettings settings) : base (definitions, persister, versioner, securityManager, pluginFinder, settings)
        {

        }

        public MyEditManager(IDefinitionManager definitions, IPersister persister, IVersionManager versioner, ISecurityManager securityManager, IPluginFinder pluginFinder, NavigationSettings settings, EditSection config) : base ( definitions, persister, versioner, securityManager, pluginFinder, settings, config)
        {
        }

        public new ItemFilter GetEditorFilter(IPrincipal user)
        {
            return new NullFilter();
        }
    }

and now when logged in as an editor I can see the items in the left tree navigation, but unless I am logged in as admin when I try to view the restricted page, even outside the editor, I get

Permission denied
Exception Details: N2.Security.PermissionDeniedException: Permission denied
N2.Security.SecurityEnforcer.AuthorizeRequest()

So to solve that I have Implemented MySecurityEnforcer, and MySecurityManager. Seperating out the permissions for saving and viewing. Since it seems pretty rediculous that as an Editor I can't view a page, but someone who isn't logged in can view the same page.

Can a work item be created to put this seperation of viewing an editing into the main source of the code? I'm having to work outside of the interface at the moment.

Also I want to go further to disable the context menu, and any edit pages they lead to.

public class MySecurityEnforcer : N2.Security.SecurityEnforcer, ISecurityEnforcer
    {
        private MySecurityManager _securityManger;
        private IWebContext _webContext;

        public MySecurityEnforcer(IPersister persister, ISecurityManager security, IUrlParser urlParser, IWebContext webContext) :
            base(persister, security, urlParser, webContext)
        {
            _securityManger = security as MySecurityManager;
            _webContext = webContext;
        }

        protected override void OnItemSaving(ContentItem item)
        {
            if (!_securityManger.IsAuthorizedToSave(item, this._webContext.User))
                throw new PermissionDeniedException(item, this._webContext.User);
            IPrincipal user = this._webContext.User;
            if (user != null)
                item.SavedBy = user.Identity.Name;
            else
                item.SavedBy = null;
        }
    }

public class MySecurityManager : SecurityManager
    {
        public MySecurityManager(IWebContext webContext)
            : base(webContext)
        {
        }

        public MySecurityManager(IWebContext webContext, EditSection config)
            : base( webContext, config)
        {

        }

        public override bool IsAuthorized(ContentItem item, IPrincipal principal)
        {
            if (!Enabled || !ScopeEnabled || IsAdmin(principal) || IsEditor(principal))
            {
                // Disabled security manager or Editor means full access
                return true;
            }
            else if (!IsEditor(principal) && !IsPublished(item))
            {
                // Non-editors cannot load unpublished items
                return false;
            }
            return item.IsAuthorized(principal);
        }

        public bool IsAuthorizedToSave(ContentItem item, IPrincipal principal)
        {
            if (!Enabled || !ScopeEnabled || IsAdmin(principal))
            {
                // Disabled security manager or Editor means full access
                return true;
            }
            else if (!IsEditor(principal) && !IsPublished(item))
            {
                // Non-editors cannot load unpublished items
                return false;
            }
            return item.IsAuthorized(principal);
        }
    }

this is un-tested, but want to get some feedback incase there is a better root before I make any more changes.










Jan 20, 2009 at 5:08 PM
Shame, nearly worked, can now edit existing sub items, but not yet add items to a parent that is restricted.

Need to find out where the code is for this, as don't want to allow adding child nodes to most restricted nodes. But want to allow adding child nodes to some restricted nodes. Possibly the need for another class attribute?

Ideas please. Will come back to this later.
Jan 20, 2009 at 7:15 PM
Interesting,  you are actually touching on a separate issue that I was anticipating but hadn't gotten that far.  Far more immediate is the fact that once you restrict access to an item, you will notice that if you log-out of the Edit admin and browse the site as an anonymous user all of the pages/items you have restricted access to will no longer be there -- which is an issue if you want to have a "public" site but restrict access on the Edit side of things.  I haven't looked at this in a week -- I'm going to go over your code and see what I can come up with.
Jan 21, 2009 at 8:27 AM
Here is a revised SecurityManager, where Admins and Editors can view anything, and anything published is also visible.
Admin users can save anything, and Editors use the item permissions.

public class MySecurityManager : SecurityManager
    {
        public MySecurityManager(IWebContext webContext)
            : base(webContext)
        {
        }

        public MySecurityManager(IWebContext webContext, EditSection config)
            : base( webContext, config)
        {

        }

        public override bool IsAuthorized(ContentItem item, IPrincipal principal)
        {
            if (!Enabled || !ScopeEnabled || IsAdmin(principal) || IsEditor(principal) || IsPublished(item))
            {
                // Disabled security manager or Editor means full access
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool IsAuthorizedToSave(ContentItem item, IPrincipal principal)
        {
            if (!Enabled || !ScopeEnabled || IsAdmin(principal))
            {
                // Disabled security manager for Admin
                return true;
            }
            else if (!IsEditor(principal))
            {
                return false;
            }
            return item.IsAuthorized(principal);
        }
    }
Jan 21, 2009 at 9:07 AM
Ideas for extra security options

Item Security Settings


View Edit Move Delete Default child Node permissions
[] Everyone
---------
[] Everyone
[] Members
[] Editors
[] Administrators
[] Everyone
---------
[] Everyone
[] Members
[] Editors
[] Administrators
[] Everyone
---------
[] Everyone
[] Members
[] Editors
[] Administrators
[] Everyone
---------
[] Everyone
[] Members
[] Editors
[] Administrators
[] Everyone
---------
[] Everyone
[] Members
[] Editors
[] Administrators


SecurityEnforcer
OnItemMoving
OnItemDeleting
OnItemSaving

to call seperate methods in SecurityManager

SecurityManager
IsAuthorizedToMove
IsAuthorizedToDelete
IsAuthorizedToSave
IsAuthorizedToView

DefinitionManager
protected virtual void OnItemCreating(ContentItem item, ContentItem parentItem)
{
    if (parentItem != null)
    {
        ItemDefinition parentDefinition = GetDefinition(parentItem.GetType());
        ItemDefinition itemDefinition = GetDefinition(item.GetType());
        if(!parentDefinition.IsChildAllowed(itemDefinition))
            throw new NotAllowedParentException(itemDefinition, parentItem.GetType());

        item.Parent = parentItem;
        foreach (AuthorizedRole role in parentItem.DefaultChildAuthorizedRoles)
            item.AuthorizedRoles.Add(new AuthorizedRole(item, role.Role));
    }
        notifier.Notifiy(item);
        if (ItemCreated != null)
            ItemCreated.Invoke(this, new ItemEventArgs(item));
    }
Jan 21, 2009 at 9:42 AM
Edited Jan 21, 2009 at 9:43 AM
Not sure about the DefaultChildAuthorizedRoles, seems strange again that can create the item and then falls over when comes to save it.

Maybe just have the fith column to be Create Child Nodes, and in the DefinitionManager, having checked that the current logged in user has permissions to create child nodes for this item, then add the role (could be more than one, but really only interested in Admin or Editor role) the user is currently in to View, Edit, Move, Delete and create additional child nodes to the created object. Having done that they can change the permissions as necissary.

The ContentItem needs to be updated to include seperate AuthorizedRole lists for
  • AuthorizedViewRoles
  • AuthorizedEditRoles
  • AuthorizedMoveRoles
  • AuthorizedDeleteRoles
  • AuthorizedCreateChildRoles
As well as updating the method IsAuthorized to
  • IsViewingAuthorized
  • IsEditingAuthorized
  • IsMovingAuthorized
  • IsDeletingAuthorized
  • IsCreatingChildNodesAuthorized
Jan 21, 2009 at 11:45 AM
The Security/Default.aspx page needs to be revisited also as if I go to

Edit/Security/Default.aspx?selected=%2fstart%2fwelcome%2f

(where selected= the root to the restricted page, right click properties in the Frame to view the url)

and I am not in a role which can edit the item, I can add the role I am in, and then press save and it adds the role. This works without the modifications I have made also (i think).
If you then try to remove the current role, or just not add the role then you get the permission denied error upon saving.

This page should check with the IsEditingAuthorized first for the selectedcontent item before loading to prevent access to edit the content via a back door.
Jan 21, 2009 at 1:10 PM
Wow, I wish I had as much time to spend on this as you apparently do.  I myself made a command decision that Administrators and Administrators alone should set ContentItem security permissions so I added a  AuthorizedRoles= new string[] {"Administrators"} to the N2.Edit.ToolbarPlugin attribute on the Security/Default.aspx.  However your idea would make sense if you didn't go that route.  I like your ideas for the extra security options and was leaning towards setting something like that up myself -- I'm not sure how you would persist those settings without adding an extra column to the N2AllowedRole table -- something I was trying to avoid at all costs but I'm not sure there is a way around.
Jan 21, 2009 at 1:39 PM
Thanks for the tip about the Toolbar.

I've looked at nhibernate before, but I'll let someone else do this. I don't want to go off too far on a tangent.

Can the main code be modified to have a permission column on the n2AllowedRole table, defaulting to view.

and then for ContentItem some sort of filter be put in for to allow this table to be spit out based on the value in the permission column of the n2AllowedRole table.

        <bag name="AuthorizedViewRoles" inverse="true" cascade="all-delete-orphan" generic="true" fetch="join" lazy="true">
            <cache usage="read-write" />
            <key column="ItemID"/>
            <one-to-many class="N2.Security.AuthorizedRole, N2"/>
        </bag>

        <bag name="AuthorizedEditRoles" inverse="true" cascade="all-delete-orphan" generic="true" fetch="join" lazy="true">
            <cache usage="read-write" />
            <key column="ItemID"/>
            <one-to-many class="N2.Security.AuthorizedRole, N2"/>
        </bag>

It's either that or duplicating tables.
Jan 21, 2009 at 2:04 PM
I believe you could do something like:

        <bag name="AuthorizedViewRoles" inverse="true" cascade="all-delete-orphan" generic="true" fetch="join" lazy="true" where="permission = 'view'">
            <cache usage="read-write" />
            <key column="ItemID"/>
            <one-to-many class="N2.Security.AuthorizedRole, N2"/>
        </bag>


And then make the necessary modifications to the ContentItem class
Jan 21, 2009 at 4:04 PM
Cool, sounds like a small change to put in the extra column.

I guess you are working on the main source then to be able to modify the attribute on the security default.aspx page. So far everything I have done is overriding the functionality, but I guess I am rapidly approaching the point where I am going to have to make changes to the main project. Like adding in a permission column, and seperating out AuthorisedRoles on the contentitem class, and securing the Security/Default.aspx page.

Ideally I want to just be extending the code so that I can update my code with the core of n2 as things progress.

Is this functionality wanted by other people? How is it best to go about ensuring that as I extend the code it is compatible with future versions.


Maybe to make it a generic as possible AuthorizedRoles can become a keyed list of lists, where the key being permission.
item.AuthorizedRoles["view"]
item.AuthorizedRoles["edit"]

or perhaps to not break functionality
item.Permissions["view"].AuthorizedRoles
or
item.Permissions["view"]

and item.AuthorizedRoles
can call item.Permissions["view"]




and IsAuthorized can become

public virtual bool IsAuthorized(ContentItem item, IPrincipal principal, string permission)

you can even keep the existing interface
public virtual bool IsAuthorized(ContentItem item, IPrincipal principal)


which interally can call
IsAuthorized(item, principal, "view")

what would be the nhibernate mapping for storing this KeyedCollection of lists of roles, keyed on permission?

Then other than securing the security page I would be able to extend as required, and I'm sure other people will have other requirements on how to restrict the content.

Can someone extend N2cms to do this, or can I get added as a dev?

Coordinator
Jan 21, 2009 at 6:16 PM
Interesting discussion. This is one of the areas where N2 has taken a shortcut. I've been pondering on a solution to this for a while and would like to propose an idea.

Firstly introduce a bool or flags enum indicating whether a certain item has particular restrictions (i.e. not open to everyone). The reason for this would be avoiding additional database calls in the default scenario. The IsAuthorized method could check the bit and avoid a potential select N+1 problem. While this wouldn't particularly help in your situation it would make me happier about introducing more joined tables.

Secondly deprecate the n2authorizedRoles table and move this information into DetailCollections. So for example checking for delete permission would go something like this:

//this is pseudocode (might not work at all)
[Flags] enum Permission { Unrestricted = 0, Read = 1, Write = 2...etc.}
ContentItem:
Permission ModifiedPermissions {get;set;}
bool IsAuthorized(IPrincipal user, Permission permissionToCheck)
{
    if(permissionToCheck & ModifiedPermissions) == Permission.Unrestricted)
        return true;
    var allowedRoles = GetDetailCollection(GetPermissionCollectionNameFor(permissionToCheck), false);
    if(allowedRoles != null)
       foreach(string role in allowedRoles)
            if(user.IsInRole(role))
                return true;
   return false;
}

What do you think? I must warn that don't have time to put any serious work on this in the immediate future but a patch would certanly be appreciated.
Jan 22, 2009 at 8:45 AM
Sounds good, a content management system with only three tables :)

The only downside I can see of using the enum is that if someone else wanted to add an extra permission they would need to go into the n2 code to modify the enum.

I can see different content items requiring different permission, like a calendar might require a list of roles that can create child nodes, but a calendar picture wouldn't support child nodes, so has no need for this.

You could have some custom four eyes review process, and the content item could store a status unsubmitted|submitted|approved, and then you might want to store permissions for which roles could view|edit the content at these various stages in the review process.

If each content item could override the default list of permissions, then this list could be used to populate the security page, and then override the IsAuthorised(IPrincipal user, string permission) to do any bespoke security checks like for the four eyes approval process.

I would suggest rather than using string literals to use constants, and to add them to a keyedcollection, or dictionary to ensure uniqueness as the property type on the ContentItem which returns the list of available permissions.

Coordinator
Jan 23, 2009 at 8:44 PM
You mean something like

  if(!ModifiedPermissions.Contains(permission))
    return true;

where ModifiedPermissions is a string stored in the n2item table? In addition to this a status field would be introduced?

How many custom permissions groups can you imagine for a site? What about reserving ranges of the enum instead? (I'm a sucker for bit fields and there are at least 32 spots to play with =))
Mar 20, 2009 at 4:35 PM
Hi there,

We may need to use similar permission functionality to this in a new project, has this been progressed on at all?
Oct 25, 2010 at 8:48 AM

Not sure if I'm adressing the same issue as all you guys, but I've just solved something that I think is somewhat similar.
We have a site that is translated into a number of different languages. Each language version is a subsite under the main site.  

We needed for each subsite to have its own editors, that can edit that site, but not the other ones.

The standard N2 security model did not fit these requirements, so inspired by libardo's first comment to this issue, I did the following:

  1. Store the usernames of users with edit access to a subsite as a property on languageroot. 
  2. Inherit from the standard EditManager and SecurityManager. Overwrite EditManager.GetEditorFilter and SecurityManager.IsAuthorized to only allow Writers and Editors access if they are registered on the languageroot.
  3. I also created a RoleProvider that checks if the username exists in ActiveDirectory, to ensure former employees don't have unintented access.
  4. Registered the new components in web.config.

The best thing about this solution was that I didn't have to alter the sourcecode of the N2 system in any way, to achieve the results I wanted.