Overview

EntitySecurity provides a method to determine if an operation can be performed by a subject (user) to an object.

The difference between this and other methods of protecting items is that the ReferenceMonitor is a facility in itself. Often you might see code being implemented within an object to determine if a user can access something based on their user class or something similar.

You're already familiar with how this operates as it is the basis for WindowsNT file permissions; and although differently implemented the way that Unix permissions work is similar.

Introduction to then Reference Monitor concept

Best to read https://en.wikipedia.org/wiki/Reference_monitor ; in a nutshell it's a tried and tested way of implementing security/

Why you shouldn't use roles

I've often been asked why not just use a user class. The answer is quite simply because it's wrong and results in implementations of security that are based around a simplistic class of user approach; often called roles.

The problems with this are many;
  • It's upside down as the user defines what can be accessed rather than the object having a defined access
  • As you are granted access based on a simple is in role it can result in a lot of roles; approaching a role per class of object.
  • Even granting access to a class of objects via a role does not necessarily mean that actually all of those users should be able to access every object. The examples for this are numerous, such as a list of processes, one for each company, that the company should be able to view the logs.
  • Implementations tend to be within the object; so a check is made before allowing a user to do something. This often fails because the check is wrong, or a check is omitted.

So what we do with EntitySecurity and a Reference Monitor is to define how the permissions and protections are setup in a consistent manner and allow this to be wired in at a low level.

Application within an ORM

Part of the design of the ReferenceMonitor was to allow easy usage within an ORM framework. Most ORM implementations provide some sort of event callback before an object is read (or directly after reading), and the same for write. This allows easy insertion of a call to the Reference Monitor to validate the security and fail at the very lowest level - which provides a robust security model that is hard to defeat because it is wired in at the object level.


Use of a ReferenceMonitor within Entity Framework

Use of a ReferenceMonitor within DataObjects.Net

Implementation

There are interfaces that need to be implemented to allow the ReferenceMonitor to decide whether a subject can access any given object.

A subject is usually a user; but could be a process, a system, or anything that is granted permission to access.
  • Subjects own objects
  • Subjects are identified by an Id
  • Subjects can be members of groups
  • Subjects can be granted privileges to allow access or operations that would not be granted

An object is usually a something; for example a record in a database, or an C# Object.
  • Objects have a set of protections
  • A set of protections contains
    1. System
    2. Owner
    3. Group
    4. World
  • Each protection has four components
    1. Read
    2. Write
    3. Execute
    4. Delete
  • Objects contain a list of groups; the intersection of this and a given subject's groups defines whether to grant access via the group protection
  • Groups can be restricted such that access is only granted based on the operation being performed

Operations are the third part; and operation is something that a subject wishes to perform on an object.
  • Read from an object
  • Write to an object.
  • Delete an object
  • Create an object. Requires write permission so not completely seperated from Write in that having write access is enough to create or update; extra controls would have to be implemented by overriding PermissionRequiredForOperation
  • List contents of an object
  • Perform Security operations; usually to prevent group or world from changing ownership or protection of an object.
  • Assign something to an object; usually implies a subset of write. possible subset of execute or write.
  • Cancel an object. possible subset of execute.
  • View an object; possible subset of read.
  • Move an object; possible subset of write.
  • Submit an object for a secondary action or process. possible subset of execute.
  • Impersonate or act as another user. This does not necessarily relate to the impersonate via privileges model that is can be used to control the user context - rather it is to allow an operation to another object

Example of a subject

    public class User : ISubject
    {
        private int PrivilegeMask = 0;
        private List<ISecurityGroup> Groups = new List<ISecurityGroup>();
        private TestUser user;
        private MyReferenceMonitor ReferenceMonitor = new MyReferenceMonitor();

        public Guid Id { get { return user.Id; } }

        public User(Guid p1, ISecurityGroup readGroup, ISecurityGroup writeGroup = null)
        {
            user = new TestUser(p1);
            Groups.Add(readGroup);
            if (writeGroup != null) 
                Groups.Add(writeGroup);
            PrivilegeMask = 0;
        }

        public bool IsOwnerEquivalent(IControlledObjectOperation operation, IControlledObject obj)
        {
            return obj.UserId == user.Id;
        }

        public void AddPrivilege(Privilege p)
        {
            PrivilegeMask |= (int)p;
        }

        public void RemovePrivilege(Privilege p)
        {
            PrivilegeMask &= ~(int)p;
        }

        public bool IsGroupEquivalent(IControlledObjectOperation operation, IControlledObject obj)
        {
            var l1 = obj.Groups.Where(og => og.ApplicableOperation.Contains(operation) && Groups.Any(xx => xx.Id == og.Id));
            return l1.Any();
        }

        public bool HasPrivilege(Privilege p)
        {
            return (PrivilegeMask & (int)p) == (int)p;
        }

        public string Identity
        {
            get { return this.Id.ToString(); }
        }
    }

Example of an object

    public class TestItem : IControlledObject
    {
        public TestItem(User User, TestGroup readGroup, TestGroup writeGroup, IProtection Protection)
        {
            this.owner = User;
            _groups.Add(readGroup);
            _groups.Add(writeGroup);
            this.protection = Protection;
        }

        public User owner { get; set; }

        public IProtection protection { get; set; }

        public Zaretto.Security.IProtection Protection
        {
            get { return protection; }
        }

        public Guid UserId
        {
            get
            {
                return owner.Id;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public string SimpleId
        {
            get {
            return "SimplieId";
            }
        }

        public string OwnerDescription
        {
            get
            {
                return owner.Id.ToString();
            }
        }
        public List<TestGroup> readGroups = new List<TestGroup>();
        public List<IControlledObjectGroup> _groups = new List<IControlledObjectGroup>();

        public List<IControlledObjectGroup> Groups
        {
           get{return _groups;}
        }
    }

Example of determining if access can be granted

This is as simple as calling ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, subject, _object));

An extract from the tests is below.
    ReferenceMonitor ReferenceMonitor = new ReferenceMonitor();
            var g1read = new TestGroup(readGroupId1, IControlledObjectOperation.Read);
            var g2read = new TestGroup(readGroupId2, IControlledObjectOperation.Read);
            var g1write = new TestGroup(writeGroupId1, IControlledObjectOperation.Write.Append(IControlledObjectOperation.Read).Append(IControlledObjectOperation.Delete));
            var g2write = new TestGroup(writeGroupId2, IControlledObjectOperation.Write);
            var User1 = new User(Id1, g1read, g1write);
            var User2 = new User(Id2, g2read, g2write);

            var protection = new Protection(Permissions.Standard);
            var o1 = new TestItem(User1, g1read, g1write, protection);

            var o2 = new TestItem(User2, g2read, g1write, new Protection(0xff00));
            var o3 = new TestItem(User2, g2read, g1write, new Protection(0xffd1));

            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User1, o1));

            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User1, o3));
            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User2, o3));

            Assert.IsFalse(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Write, User1, o3));
            Assert.IsFalse(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Security, User1, o3));

            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Write, User2, o3));

            Assert.IsFalse(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User2, o1));
            Assert.IsFalse(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User1, o2));

            Assert.IsFalse(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Security, User1, o2));
            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Security, User1, o1));

            User1.AddPrivilege(Privilege.BYPASS);
            Assert.IsTrue(ReferenceMonitor.IsPermitted(Zaretto.Security.IControlledObjectOperation.Read, User1, o2));


Last edited Feb 21, 2016 at 2:48 PM by RichardHarrison, version 2

Comments

No comments yet.