File: system\security\policy\codegroup.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//  CodeGroup.cs
// 
// <OWNER>Microsoft</OWNER>
//
//  Representation for code groups used for the policy mechanism
//
 
namespace System.Security.Policy {
    
    using System;
    using System.Security.Util;
    using System.Security;
    using System.Collections;
    using System.Globalization;
    using System.Diagnostics.Contracts;
    
    internal interface IUnionSemanticCodeGroup
    {
        PolicyStatement InternalResolve( Evidence evidence );
    }
 
    [Serializable]
    [System.Runtime.InteropServices.ComVisible(true)]
    abstract public class CodeGroup
    {
        private IMembershipCondition m_membershipCondition;
        private IList m_children;
        private PolicyStatement m_policy;
        private SecurityElement m_element;
        private PolicyLevel m_parentLevel;
        private String m_name;
        private String m_description;
        
        internal CodeGroup()
        {
        }
        
        internal CodeGroup( IMembershipCondition membershipCondition, PermissionSet permSet )
        {
            Contract.Assert( membershipCondition != null, "membershipCondition != null" );
            Contract.Assert( permSet != null, "permSet != null" );
 
            m_membershipCondition = membershipCondition;
            m_policy = new PolicyStatement();
            m_policy.SetPermissionSetNoCopy( permSet );
            m_children = ArrayList.Synchronized( new ArrayList() );
            m_element = null;
            m_parentLevel = null;
        }
        
        protected CodeGroup( IMembershipCondition membershipCondition, PolicyStatement policy )
        {
            if (membershipCondition == null)
                throw new ArgumentNullException( "membershipCondition" );
            Contract.EndContractBlock();
 
            if (policy == null)
                m_policy = null;
            else
                m_policy = policy.Copy();
        
            m_membershipCondition = membershipCondition.Copy();
            m_children = ArrayList.Synchronized( new ArrayList() );
            m_element = null;
            m_parentLevel = null;
        }
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        public void AddChild( CodeGroup group )
        {
            if (group == null)
                throw new ArgumentNullException("group");
            Contract.EndContractBlock();
                
            if (m_children == null)
                ParseChildren();
            
            lock (this)
            {
                m_children.Add( group.Copy() );
            }
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        internal void AddChildInternal( CodeGroup group )
        {
            if (group == null)
                throw new ArgumentNullException("group");
            Contract.EndContractBlock();
                
            if (m_children == null)
                ParseChildren();
 
            lock (this)
            {
                m_children.Add( group );
            }
        }            
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        public void RemoveChild( CodeGroup group )
        {
            if (group == null)
                return;
        
            if (m_children == null)
                ParseChildren();
            
            lock (this )
            {
                int index = m_children.IndexOf( group );
            
                if (index != -1)
                {
                    m_children.RemoveAt( index );
                }
            }
        }        
        
        public IList Children
        {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get
            {
                if (m_children == null)
                    ParseChildren();
        
                lock (this)
                {
                    IList newList = new ArrayList( m_children.Count );
 
                    IEnumerator enumerator = m_children.GetEnumerator();
 
                    while (enumerator.MoveNext())
                    {
                        newList.Add( ((CodeGroup)enumerator.Current).Copy() );
                    }
 
                    return newList;
                }
            }
 
            set
            {
                if (value == null)
                    throw new ArgumentNullException( "Children" );
                Contract.EndContractBlock();
 
                ArrayList children = ArrayList.Synchronized( new ArrayList( value.Count ) );
 
                IEnumerator enumerator = value.GetEnumerator();
 
                while (enumerator.MoveNext())
                {
                    CodeGroup group = enumerator.Current as CodeGroup;
 
                    if (group == null)
                        throw new ArgumentException( Environment.GetResourceString( "Argument_CodeGroupChildrenMustBeCodeGroups" ) );
 
                    children.Add( group.Copy() );
                }
 
                m_children = children;
            }
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        internal IList GetChildrenInternal()
        {
            if (m_children == null)
                ParseChildren();
        
            return m_children;
        }
                
        public IMembershipCondition MembershipCondition
        {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get
            {
                if (m_membershipCondition == null && m_element != null)
                    ParseMembershipCondition();
        
                return m_membershipCondition.Copy();
            }
            
            set
            {
                if (value == null)
                    throw new ArgumentNullException( "MembershipCondition" );
                Contract.EndContractBlock();
 
                m_membershipCondition = value.Copy();
            }
        }
        
        public PolicyStatement PolicyStatement
        {
            get
            {
                if (m_policy == null && m_element != null)
                    ParsePolicy();
 
                if (m_policy != null)
                    return m_policy.Copy();
                else
                    return null;
            }
            
            set
            {
                if (value != null)
                    m_policy = value.Copy();
                else
                    m_policy = null;
            }
        }
        
        public String Name
        {
            get
            {
                return m_name;
            }
 
            set
            {
                m_name = value;
            }
        }
 
        public String Description
        {
            get
            {
                return m_description;
            }
 
            set
            {
                m_description = value;
            }
        }
        
        public abstract PolicyStatement Resolve( Evidence evidence );
 
        public abstract CodeGroup ResolveMatchingCodeGroups( Evidence evidence );
        
        public abstract CodeGroup Copy();
        
        public virtual String PermissionSetName
        {
            get
            {
                if (m_policy == null && m_element != null)
                    ParsePolicy();
 
                if (m_policy == null)
                    return null;
                    
                NamedPermissionSet permSet = m_policy.GetPermissionSetNoCopy() as NamedPermissionSet;
            
                if (permSet != null)
                {
                    return permSet.Name;
                }
                else
                {
                    return null;
                }
            }
        }
        
        public virtual String AttributeString
        {
            get
            {
                if (m_policy == null && m_element != null)
                    ParsePolicy();
                    
                if (m_policy != null)
                    return m_policy.AttributeString;
                else
                    return null;
            }
        }  
        
        public abstract String MergeLogic
        {
            get;
        }
              
 
        public SecurityElement ToXml()
        {
            return ToXml( null );
        }
 
        public void FromXml( SecurityElement e )
        {
            FromXml( e, null );
        }
 
        [System.Security.SecuritySafeCritical]  // auto-generated
        public SecurityElement ToXml( PolicyLevel level )
        {
            return ToXml( level, GetTypeName() );
        }
 
        internal virtual String GetTypeName()
        {
            return this.GetType().FullName;
        }
    
        [System.Security.SecurityCritical]  // auto-generated
        internal SecurityElement ToXml( PolicyLevel level, String policyClassName )
        {
            if (m_membershipCondition == null && m_element != null)
                ParseMembershipCondition();
                
            if (m_children == null)
                ParseChildren();
                
            if (m_policy == null && m_element != null)
                ParsePolicy();
        
            SecurityElement e = new SecurityElement( "CodeGroup" );
            System.Security.Util.XMLUtil.AddClassAttribute( e, this.GetType(), policyClassName );
            // If you hit this assert then most likely you are trying to change the name of this class. 
            // This is ok as long as you change the hard coded string above and change the assert below.
            Contract.Assert( this.GetType().FullName.Equals( policyClassName ), "Incorrect class name passed in! Was: " + policyClassName + " Should be " + this.GetType().FullName);
        
            e.AddAttribute( "version", "1" );
 
            e.AddChild( m_membershipCondition.ToXml( level ) );
 
            // Grab the inerts of the policy statement's xml and just stick it
            // into the code group xml directly. We do this to hide the policy statement from
            // users in the config file.
            
            if (m_policy != null)
            {
                PermissionSet permSet = m_policy.GetPermissionSetNoCopy();
                NamedPermissionSet namedPermSet = permSet as NamedPermissionSet;
 
                if (namedPermSet != null && level != null && level.GetNamedPermissionSetInternal( namedPermSet.Name ) != null)
                {
                    e.AddAttribute( "PermissionSetName", namedPermSet.Name );
                }
                else
                {
                    if (!permSet.IsEmpty())
                        e.AddChild( permSet.ToXml() );
                }
 
                if (m_policy.Attributes != PolicyStatementAttribute.Nothing)
                    e.AddAttribute( "Attributes", XMLUtil.BitFieldEnumToString( typeof( PolicyStatementAttribute ), m_policy.Attributes ) );
                }
            
                if (m_children.Count > 0)
                {
                    lock (this)
                    {
                        IEnumerator enumerator = m_children.GetEnumerator();
            
                        while (enumerator.MoveNext())
                        {
                            e.AddChild( ((CodeGroup)enumerator.Current).ToXml( level ) );
                        }
                    }
                }
 
            if (m_name != null)
            {
                e.AddAttribute( "Name", SecurityElement.Escape( m_name ) );
            }
 
            if (m_description != null)
            {
                e.AddAttribute( "Description", SecurityElement.Escape( m_description ) );
            }
 
            CreateXml( e, level );
            
            return e;
        }
 
        protected virtual void CreateXml( SecurityElement element, PolicyLevel level )
        {
        }
        
        public void FromXml( SecurityElement e, PolicyLevel level )
        {
            if (e == null)
                throw new ArgumentNullException("e");
            Contract.EndContractBlock();
 
            lock (this)
            {
                m_element = e;
                m_parentLevel = level;
                m_children = null;
                m_membershipCondition = null;
                m_policy = null;
 
                m_name = e.Attribute( "Name" );
                m_description = e.Attribute( "Description" );
 
                ParseXml( e, level );
            }
        }
        
        protected virtual void ParseXml( SecurityElement e, PolicyLevel level )
        {
        } 
            
        [System.Security.SecurityCritical]  // auto-generated
        private bool ParseMembershipCondition( bool safeLoad )
        {
            lock (this)
            {
                IMembershipCondition membershipCondition = null;
                SecurityElement elMembershipCondition = m_element.SearchForChildByTag( "IMembershipCondition" );
                if (elMembershipCondition != null)
                {
                    try
                    {
                        membershipCondition = System.Security.Util.XMLUtil.CreateMembershipCondition( elMembershipCondition );
 
                        if (membershipCondition == null)
                            return false;
                    }
                    catch (Exception ex)
                    {
                        throw new ArgumentException( Environment.GetResourceString( "Argument_MembershipConditionElement" ), ex );
                    }
//                    catch
//                    {
//                        throw new ArgumentException( Environment.GetResourceString( "Argument_MembershipConditionElement" ) );
//                    }
                    membershipCondition.FromXml( elMembershipCondition, m_parentLevel );
                }
                else
                {
                    throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, Environment.GetResourceString( "Argument_InvalidXMLElement" ),  "IMembershipCondition", this.GetType().FullName ) );
                }
                
                m_membershipCondition = membershipCondition;
                return true;
            }
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        private void ParseMembershipCondition()
        {
            ParseMembershipCondition( false );
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        internal void ParseChildren()
        {
            lock (this)
            {
                ArrayList childrenList = ArrayList.Synchronized( new ArrayList() );
        
                if (m_element != null && m_element.InternalChildren != null)
                {    
                    // We set the elements childrenList to a list that contains no code groups that are in
                    // assemblies that have not yet been loaded.  This guarantees that if 
                    // we recurse while loading the child code groups or their membership conditions
                    // that we won't get back to this point to create an infinite recursion.
 
                    m_element.Children = (ArrayList)m_element.InternalChildren.Clone();
 
                    // We need to keep track of which children are not parsed and created in our
                    // first pass through the list of children, including what position they were
                    // at originally so that we can add them back in as we do parse them.
 
                    ArrayList unparsedChildren = ArrayList.Synchronized( new ArrayList() );
 
                    Evidence evidence = new Evidence();
 
                    int childCount = m_element.InternalChildren.Count;
                    int i = 0;
                    while (i < childCount)
                    {
                        SecurityElement elGroup = (SecurityElement)m_element.Children[i];
            
                        if (elGroup.Tag.Equals( "CodeGroup" ))
                        {
                            // Using safe load here to guarantee that we don't load any assemblies that aren't
                            // already loaded.  If we find a code group or membership condition that is defined
                            // in an assembly that is not yet loaded, we will remove the corresponding element
                            // from the list of child elements as to avoid the infinite recursion, and then
                            // add them back in later.
 
                            CodeGroup group = System.Security.Util.XMLUtil.CreateCodeGroup( elGroup );
                
                            if (group != null)
                            {
                                group.FromXml( elGroup, m_parentLevel );
 
                                // We want to touch the membership condition to make sure it is loaded
                                // before we put the code group in a place where Resolve will touch it.
                                // This is critical in negotiating our recursive resolve scenario.
 
                                if (ParseMembershipCondition( true ))
                                {
                                    // In addition, we need to touch several methods to make sure they are jitted previous
                                    // to a Resolve happening with this code gropu in the hierarchy.  We can run into
                                    // recursive cases where if you have a method that touchs an assembly that does
                                    // a resolve at load time (permission request, execution checking) that you recurse around
                                    // and end up trying to jit the same method again.
                            
                                    group.Resolve( evidence );
                                    group.MembershipCondition.Check( evidence );
 
                                    // Now it should be ok to add the group to the hierarchy.
 
                                    childrenList.Add( group );
 
                                    // Increment the count since we are done with this child
 
                                    ++i;
                                }
                                else
                                {
                                    // Assembly that holds the membership condition is not loaded, remove
                                    // the child from the list.
 
                                    m_element.InternalChildren.RemoveAt( i );
 
                                    // Note: we do not increment the counter since the value at 'i' should
                                    // now be what was at 'i+1' previous to the RemoveAt( i ) above.  However,
                                    // we do need to update the count of children in the list
 
                                    childCount = m_element.InternalChildren.Count;
 
                                    // Add this child to the unparsed child list.
 
                                    unparsedChildren.Add( new CodeGroupPositionMarker( i, childrenList.Count, elGroup ) );
                                }
                            }
                            else
                            {
                                // Assembly that holds the code group is not loaded, remove
                                // the child from the list.
 
                                m_element.InternalChildren.RemoveAt( i );
 
                                // Note: we do not increment the counter since the value at 'i' should
                                // now be what was at 'i+1' previous to the RemoveAt( i ) above.  However,
                                // we do need to update the count of children in the list
 
                                childCount = m_element.InternalChildren.Count;
 
                                // Add this child to the unparsed child list.
 
                                unparsedChildren.Add( new CodeGroupPositionMarker( i, childrenList.Count, elGroup ) );
                            }
                        }
                        else
                        {
                            // The current tag is not an <CodeGroup> tag, so we just skip it.
 
                            ++i;
                        }
                    }
 
                    // Now we have parsed all the children that only use classes in already loaded
                    // assemblies.  Now go through the process of loading the needed classes (and
                    // therefore assemblies) and building the objects in the order that they
                    // appear in the list of children (which is the same as they now appear in the
                    // list of unparsed children since we always added to the back of the list).
                    // As each is parsed, add that child back into the list of children since they
                    // can now be parsed without loading any additional assemblies.
 
                    IEnumerator enumerator = unparsedChildren.GetEnumerator();
 
                    while (enumerator.MoveNext())
                    {
                        CodeGroupPositionMarker marker = (CodeGroupPositionMarker)enumerator.Current;
 
                        CodeGroup group = System.Security.Util.XMLUtil.CreateCodeGroup( marker.element );
            
                        if (group != null)
                        {
                            group.FromXml( marker.element, m_parentLevel );
 
                            // We want to touch the membership condition to make sure it is loaded
                            // before we put the code group in a place where Resolve will touch it.
                            // This is critical in negotiating our recursive resolve scenario.
 
                            group.Resolve( evidence );
                            group.MembershipCondition.Check( evidence );
 
                            // Now it should be ok to add the group to the hierarchy.
 
                            childrenList.Insert( marker.groupIndex, group );
 
                            // Add the element back into the child list in the proper spot.
 
                            m_element.InternalChildren.Insert( marker.elementIndex, marker.element );
                        }
                        else
                        {
                            throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, Environment.GetResourceString( "Argument_FailedCodeGroup" ), marker.element.Attribute( "class" ) ) );
                        }
                    }
 
                }
                m_children = childrenList;
            }
 
        }
        
        private void ParsePolicy()
        {
            // There is a potential deadlock situation here
            // since the PolicyStatement.FromXml method calls
            // into PolicyLevel and we are holding this CodeGroup's lock.
            // We solve this by releasing the lock for the duration of
            // the FromXml call, but this leads us into some race conditions
            // with other threads trying to alter the state of this object.
            // The trickiest of these is the case from FromXml gets called on
            // this object, in which case we will loop and try the decode again.
 
            while (true)
            {
                PolicyStatement policy = new PolicyStatement();
                bool needToParse = false;
 
                SecurityElement elPolicy = new SecurityElement( "PolicyStatement" );
                elPolicy.AddAttribute( "version", "1" );
 
                SecurityElement localRef = m_element;
 
                lock (this)
                {
 
                    // We create an xml representation of a policy statement from the
                    // xml for a code group.  We do this to hide the policy statement from
                    // users in the config file.
 
                    if (m_element != null)
                    {
                        String permSetName = m_element.Attribute( "PermissionSetName" );
 
                        if (permSetName != null)
                        {
                            elPolicy.AddAttribute( "PermissionSetName", permSetName );
                            needToParse = true;
                        }
                        else
                        {
                            SecurityElement elPermSet = m_element.SearchForChildByTag( "PermissionSet" );
 
                            if (elPermSet != null)
                            {
                                elPolicy.AddChild( elPermSet );
                                needToParse = true;
                            }
                            else
                            {
                                elPolicy.AddChild( new PermissionSet( false ).ToXml() );
                                needToParse = true;
                            }
                        }
 
                        String attributes = m_element.Attribute( "Attributes" );
 
                        if (attributes != null)
                        {
                            elPolicy.AddAttribute( "Attributes", attributes );
                            needToParse = true;
                        }
                    }
                }
 
                if (needToParse)
                    policy.FromXml( elPolicy, m_parentLevel );
                else
                    policy.PermissionSet = null;
 
                lock (this)
                {
                    if (localRef == m_element && m_policy == null)
                    {
                        m_policy = policy;
                        break;
                    }
                    else if (m_policy != null)
                    {
                        break;
                    }
                }
            }
 
            if (m_policy != null && m_children != null && m_membershipCondition != null)
            {
                //m_element = null;
                //m_parentLevel = null;
            }
 
        }
 
        [System.Security.SecuritySafeCritical]  // auto-generated
        public override bool Equals( Object o)
        {
            CodeGroup that = (o as CodeGroup);
 
            if (that != null && this.GetType().Equals( that.GetType() ))
            {
                if (Equals( this.m_name, that.m_name ) &&
                    Equals( this.m_description, that.m_description ))
                {
                    if (this.m_membershipCondition == null && this.m_element != null)
                        this.ParseMembershipCondition();
                    if (that.m_membershipCondition == null && that.m_element != null)
                        that.ParseMembershipCondition();
 
                    if (Equals( this.m_membershipCondition, that.m_membershipCondition ))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        public bool Equals( CodeGroup cg, bool compareChildren)
        {
            if (!this.Equals( cg )) return false;
            
            if (compareChildren)
            {
                if (this.m_children == null)
                    this.ParseChildren();
                if (cg.m_children == null)
                    cg.ParseChildren();
 
                ArrayList list1 = new ArrayList(this.m_children);
                ArrayList list2 = new ArrayList(cg.m_children);
                
                if (list1.Count != list2.Count) return false;
                
                for (int i = 0; i < list1.Count; i++)
                {
                    if (!((CodeGroup) list1[i]).Equals( (CodeGroup) list2[i], true ))
                    {
                        return false;
                    }
                }
            }
            
            return true;
        }
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        public override int GetHashCode()
        {
            if (m_membershipCondition == null && m_element != null)
                ParseMembershipCondition();
 
            if (m_name != null || m_membershipCondition != null)
            {
                return (m_name == null ? 0 : m_name.GetHashCode())
                     + (m_membershipCondition == null ? 0 : m_membershipCondition.GetHashCode());
            }
            else
            {
                return GetType().GetHashCode();
            }
        }
    }
 
    internal class CodeGroupPositionMarker
    {
        internal int elementIndex;
        internal int groupIndex;
        internal SecurityElement element;
 
        internal CodeGroupPositionMarker( int elementIndex, int groupIndex, SecurityElement element )
        {
            this.elementIndex = elementIndex;
            this.groupIndex = groupIndex;
            this.element = element;
        }
    }
}