File: System\Security\AccessControl\CommonObjectSecurity.cs
Web Access
Project: src\src\runtime\src\libraries\System.Security.AccessControl\src\System.Security.AccessControl.csproj (System.Security.AccessControl)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*============================================================
**
** Classes:  Common Object Security class
**
**
===========================================================*/

using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32;

namespace System.Security.AccessControl
{
    public abstract class CommonObjectSecurity : ObjectSecurity
    {
        #region Constructors

        protected CommonObjectSecurity(bool isContainer)
            : base(isContainer, false)
        {
        }

        internal CommonObjectSecurity(CommonSecurityDescriptor securityDescriptor)
            : base(securityDescriptor)
        {
        }

        #endregion

        #region Private Methods
        // Ported from NDP\clr\src\BCL\System\Security\Principal\SID.cs since we can't access System.Security.Principal.IdentityReference's internals
        private static bool IsValidTargetTypeStatic(Type targetType)
        {
            if (targetType == typeof(NTAccount))
            {
                return true;
            }
            else if (targetType == typeof(SecurityIdentifier))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private AuthorizationRuleCollection GetRules(bool access, bool includeExplicit, bool includeInherited, System.Type targetType)
        {
            ReadLock();

            try
            {
                AuthorizationRuleCollection result = new AuthorizationRuleCollection();

                if (!IsValidTargetTypeStatic(targetType))
                {
                    throw new ArgumentException(
                        SR.Arg_MustBeIdentityReferenceType,
                        nameof(targetType));
                }

                CommonAcl? acl = null;

                if (access)
                {
                    if ((_securityDescriptor.ControlFlags & ControlFlags.DiscretionaryAclPresent) != 0)
                    {
                        acl = _securityDescriptor.DiscretionaryAcl;
                    }
                }
                else // !access == audit
                {
                    if ((_securityDescriptor.ControlFlags & ControlFlags.SystemAclPresent) != 0)
                    {
                        acl = _securityDescriptor.SystemAcl;
                    }
                }

                if (acl == null)
                {
                    //
                    // The required ACL was not present; return an empty collection.
                    //
                    return result;
                }

                IdentityReferenceCollection? irTarget = null;

                if (targetType != typeof(SecurityIdentifier))
                {
                    IdentityReferenceCollection irSource = new IdentityReferenceCollection(acl.Count);

                    for (int i = 0; i < acl.Count; i++)
                    {
                        //
                        // Calling the indexer on a common ACL results in cloning,
                        // (which would not be the case if we were to use the internal RawAcl property)
                        // but also ensures that the resulting order of ACEs is proper
                        // However, this is a big price to pay - cloning all the ACEs just so that
                        // the canonical order could be ascertained just once.
                        // A better way would be to have an internal method that would canonicalize the ACL
                        // and call it once, then use the RawAcl.
                        //
                        CommonAce? ace = acl[i] as CommonAce;
                        if (AceNeedsTranslation(ace, access, includeExplicit, includeInherited))
                        {
                            irSource.Add(ace.SecurityIdentifier);
                        }
                    }

                    irTarget = irSource.Translate(targetType);
                }

                int targetIndex = 0;
                for (int i = 0; i < acl.Count; i++)
                {
                    //
                    // Calling the indexer on a common ACL results in cloning,
                    // (which would not be the case if we were to use the internal RawAcl property)
                    // but also ensures that the resulting order of ACEs is proper
                    // However, this is a big price to pay - cloning all the ACEs just so that
                    // the canonical order could be ascertained just once.
                    // A better way would be to have an internal method that would canonicalize the ACL
                    // and call it once, then use the RawAcl.
                    //

                    CommonAce? ace = acl[i] as CommonAce;
                    if (AceNeedsTranslation(ace, access, includeExplicit, includeInherited))
                    {
                        IdentityReference iref = (targetType == typeof(SecurityIdentifier)) ? ace.SecurityIdentifier : irTarget![targetIndex++];

                        if (access)
                        {
                            AccessControlType type;

                            if (ace.AceQualifier == AceQualifier.AccessAllowed)
                            {
                                type = AccessControlType.Allow;
                            }
                            else
                            {
                                type = AccessControlType.Deny;
                            }

                            result.AddRule(
                                AccessRuleFactory(
                                    iref,
                                    ace.AccessMask,
                                    ace.IsInherited,
                                    ace.InheritanceFlags,
                                    ace.PropagationFlags,
                                    type));
                        }
                        else
                        {
                            result.AddRule(
                                AuditRuleFactory(
                                    iref,
                                    ace.AccessMask,
                                    ace.IsInherited,
                                    ace.InheritanceFlags,
                                    ace.PropagationFlags,
                                    ace.AuditFlags));
                        }
                    }
                }

                return result;
            }
            finally
            {
                ReadUnlock();
            }
        }

        private static bool AceNeedsTranslation([NotNullWhen(true)] CommonAce? ace, bool isAccessAce, bool includeExplicit, bool includeInherited)
        {
            if (ace == null)
            {
                //
                // Only consider common ACEs
                //

                return false;
            }

            if (isAccessAce)
            {
                if (ace.AceQualifier != AceQualifier.AccessAllowed &&
                    ace.AceQualifier != AceQualifier.AccessDenied)
                {
                    return false;
                }
            }
            else
            {
                if (ace.AceQualifier != AceQualifier.SystemAudit)
                {
                    return false;
                }
            }

            if ((includeExplicit &&
                ((ace.AceFlags & AceFlags.Inherited) == 0)) ||
                (includeInherited &&
                ((ace.AceFlags & AceFlags.Inherited) != 0)))
            {
                return true;
            }

            return false;
        }

        //
        // Modifies the DACL
        //
        protected override bool ModifyAccess(AccessControlModification modification, AccessRule rule, out bool modified)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();
            try
            {
                bool result = true;

                if (_securityDescriptor.DiscretionaryAcl == null)
                {
                    if (modification == AccessControlModification.Remove ||
                        modification == AccessControlModification.RemoveAll ||
                        modification == AccessControlModification.RemoveSpecific)
                    {
                        modified = false;
                        return result;
                    }

                    _securityDescriptor.DiscretionaryAcl = new DiscretionaryAcl(IsContainer, IsDS, GenericAcl.AclRevision, 1);
                    _securityDescriptor.AddControlFlags(ControlFlags.DiscretionaryAclPresent);
                }

                SecurityIdentifier sid = (SecurityIdentifier)rule.IdentityReference.Translate(typeof(SecurityIdentifier));

                if (rule.AccessControlType == AccessControlType.Allow)
                {
                    switch (modification)
                    {
                        case AccessControlModification.Add:
                            _securityDescriptor.DiscretionaryAcl.AddAccess(AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Set:
                            _securityDescriptor.DiscretionaryAcl.SetAccess(AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Reset:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Deny, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0);
                            _securityDescriptor.DiscretionaryAcl.SetAccess(AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Remove:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.RemoveAll:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Allow, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0);
                            if (!result)
                            {
                                Debug.Fail("Invalid operation");
                                throw new InvalidOperationException();
                            }

                            break;

                        case AccessControlModification.RemoveSpecific:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccessSpecific(AccessControlType.Allow, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        default:
                            throw new ArgumentOutOfRangeException(
                                nameof(modification),
                                SR.ArgumentOutOfRange_Enum);
                    }
                }
                else if (rule.AccessControlType == AccessControlType.Deny)
                {
                    switch (modification)
                    {
                        case AccessControlModification.Add:
                            _securityDescriptor.DiscretionaryAcl.AddAccess(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Set:
                            _securityDescriptor.DiscretionaryAcl.SetAccess(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Reset:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Allow, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0);
                            _securityDescriptor.DiscretionaryAcl.SetAccess(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.Remove:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        case AccessControlModification.RemoveAll:
                            result = _securityDescriptor.DiscretionaryAcl.RemoveAccess(AccessControlType.Deny, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0);
                            if (!result)
                            {
                                Debug.Fail("Invalid operation");
                                throw new InvalidOperationException();
                            }

                            break;

                        case AccessControlModification.RemoveSpecific:
                            _securityDescriptor.DiscretionaryAcl.RemoveAccessSpecific(AccessControlType.Deny, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                            break;

                        default:
                            throw new ArgumentOutOfRangeException(
                                nameof(modification),
                                SR.ArgumentOutOfRange_Enum);
                    }
                }
                else
                {
                    Debug.Fail("rule.AccessControlType unrecognized");
                    throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)rule.AccessControlType), nameof(rule));
                }

                modified = result;
                AccessRulesModified |= modified;
                return result;
            }
            finally
            {
                WriteUnlock();
            }
        }

        //
        // Modifies the SACL
        //

        protected override bool ModifyAudit(AccessControlModification modification, AuditRule rule, out bool modified)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();
            try
            {
                bool result = true;

                if (_securityDescriptor.SystemAcl == null)
                {
                    if (modification == AccessControlModification.Remove ||
                        modification == AccessControlModification.RemoveAll ||
                        modification == AccessControlModification.RemoveSpecific)
                    {
                        modified = false;
                        return result;
                    }

                    _securityDescriptor.SystemAcl = new SystemAcl(IsContainer, IsDS, GenericAcl.AclRevision, 1);
                    _securityDescriptor.AddControlFlags(ControlFlags.SystemAclPresent);
                }

                SecurityIdentifier sid = (SecurityIdentifier)rule.IdentityReference.Translate(typeof(SecurityIdentifier));

                switch (modification)
                {
                    case AccessControlModification.Add:
                        _securityDescriptor.SystemAcl.AddAudit(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                        break;

                    case AccessControlModification.Set:
                        _securityDescriptor.SystemAcl.SetAudit(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                        break;

                    case AccessControlModification.Reset:
                        _securityDescriptor.SystemAcl.SetAudit(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                        break;

                    case AccessControlModification.Remove:
                        result = _securityDescriptor.SystemAcl.RemoveAudit(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                        break;

                    case AccessControlModification.RemoveAll:
                        result = _securityDescriptor.SystemAcl.RemoveAudit(AuditFlags.Failure | AuditFlags.Success, sid, -1, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, 0);
                        if (!result)
                        {
                            throw new InvalidOperationException();
                        }

                        break;

                    case AccessControlModification.RemoveSpecific:
                        _securityDescriptor.SystemAcl.RemoveAuditSpecific(rule.AuditFlags, sid, rule.AccessMask, rule.InheritanceFlags, rule.PropagationFlags);
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(
                            nameof(modification),
                            SR.ArgumentOutOfRange_Enum);
                }

                modified = result;
                AuditRulesModified |= modified;
                return result;
            }
            finally
            {
                WriteUnlock();
            }
        }

        #endregion

        #region Protected Methods

        #endregion

        #region Public Methods

        protected void AddAccessRule(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAccess(AccessControlModification.Add, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void SetAccessRule(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAccess(AccessControlModification.Set, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void ResetAccessRule(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAccess(AccessControlModification.Reset, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }

            return;
        }

        protected bool RemoveAccessRule(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                if (_securityDescriptor == null)
                {
                    return true;
                }

                return ModifyAccess(AccessControlModification.Remove, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAccessRuleAll(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                if (_securityDescriptor == null)
                {
                    return;
                }

                ModifyAccess(AccessControlModification.RemoveAll, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }

            return;
        }

        protected void RemoveAccessRuleSpecific(AccessRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                if (_securityDescriptor == null)
                {
                    return;
                }

                ModifyAccess(AccessControlModification.RemoveSpecific, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void AddAuditRule(AuditRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAudit(AccessControlModification.Add, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void SetAuditRule(AuditRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAudit(AccessControlModification.Set, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected bool RemoveAuditRule(AuditRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                return ModifyAudit(AccessControlModification.Remove, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAuditRuleAll(AuditRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAudit(AccessControlModification.RemoveAll, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        protected void RemoveAuditRuleSpecific(AuditRule rule)
        {
            ArgumentNullException.ThrowIfNull(rule);

            WriteLock();

            try
            {
                ModifyAudit(AccessControlModification.RemoveSpecific, rule, out _);
            }
            finally
            {
                WriteUnlock();
            }
        }

        public AuthorizationRuleCollection GetAccessRules(bool includeExplicit, bool includeInherited, System.Type targetType)
        {
            return GetRules(true, includeExplicit, includeInherited, targetType);
        }

        public AuthorizationRuleCollection GetAuditRules(bool includeExplicit, bool includeInherited, System.Type targetType)
        {
            return GetRules(false, includeExplicit, includeInherited, targetType);
        }
        #endregion
    }
}