Creating a SecurityElement from XML

Most of the .NET security system can be serialized out to XML, and knows how to deserialize itself from an XML stream. This would seem to make it easy to create security objects (such as PermissionSet's) from XML documents, or maybe use an XPath query or XSLT transform on one of these objects. However, instead of using standard System.Xml classes to represent our XML data, the security system uses its own XML class, System.Security.SecurityElement.

There are several reasons for this, including the fact that security code must execute very quickly, and is executed quite frequently. The overhead of a full-fledged XML parser would be too much. Even if you accept the fact that we need a lightweight security XML object, we can't even provide utility methods on SecurityElement to convert back and forth System.Xml objects, since the CAS code lives in mscorlib.dll, and mscorlib cannot take a dependency on external DLL's. (Think of what would happen if mscorlib depended on System.Xml.dll, and System.Xml.dll depended on mscorlib ...). As if this weren't enough, there are at least 3 distinct XML parsers in v1.1 of the framework (System.Xml, SecurityElement, and a lightweight parser in mscoree.dll which handles parsing .config files ... this was actually optimized to be able to fit into no more than two pages of memory). Whidbey will be adding yet another parser to handle parsing ClickOnce manifests.

With all these different XML representations, commonly people need to convert between them. Since you'll most likely never be handling the mscoree representation, I'll show some sample code for converting between System.Xml and SecurityElement.

Converting SecurityElement to XmlElement

Going from SecurityElement to XmlElement is relatively easy, since everything expressable within the SecurityElement object model is also available in System.Xml. All that needs to be done is to make a new XmlElement with a tag matching the SecurityElement, create any XmlAttributes on the element, and copy over any inner text that might exist. This needs to be done recursively for each child node of the SecurityElement.

/// <summary>
/// Convert a security element XML tree into an System.Xml XML tree
/// </summary>
/// <param name="se">security element at the root of the tree</param>
/// <returns>an XML Node at the root of the System.Xml Tree</returns>
public static XmlNode SecurityElementToXml(SecurityElement se)
{
    return SecurityElementToXmlInternal(se, new XmlDocument());
}

/// <summary>
/// Convert a security element XML tree into an System.Xml XML tree
/// </summary>
/// <param name="se">security element at the root of the tree</param>
/// <param name="doc">XML Document context to create new XML nodes from</param>
/// <returns>an XML Node at the root of the System.Xml Tree</returns>
private static XmlNode SecurityElementToXmlInternal(SecurityElement se, XmlDocument doc)
{
    XmlNode root = doc.CreateElement(se.Tag);

    if(se.Text != null)
      root.AppendChild(doc.CreateTextNode(se.Text));

    if(se.Attributes != null)
    {
      foreach(string attr in se.Attributes.Keys)
      {
        XmlAttribute xmlAttr = doc.CreateAttribute(attr);
        xmlAttr.Value = (string)se.Attributes[attr];
        root.Attributes.Append(xmlAttr);
      }
    }

    if(se.Children != null)
      foreach(SecurityElement child in se.Children)
        root.AppendChild(SecurityElementToXmlInternal(child, doc));

    return root;
}

Converting from XmlElement to SecurityElement

Going from System.Xml to SecurityElement is a little more tricky, since SecurityElement cannot express every bit of valid XML. The only XML nodes in the SecurityElement object model are elements, attributes, and text, so anything in an XmlDocument that does not fall into one of these three categories cannot be converted. The following code performs the conversion by looking at each child of the XmlElement, and deciding what to do based on its type. If the child is an XmlElement, it recursively calls itself and appends the child node to the new SecurityElement's children. If it's an attribute, the attribute is added to the security element, and text is added as the inner text of the new SecurityElement. Comments are skipped over, and an InvalidOperationException is thrown if any other type of element is discovered.

/// <summary>
/// Convert an XML Element into a SecurityElement
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException if there are nodes that are not
/// of type XmlElement, XmlAttribute or XmlText in the tree.
/// </remarks>
/// <param name="xml">xml element to convert</param>
/// <returns>SecurityElement representation</returns>
public static SecurityElement XmlToSecurityElement(XmlNode xml)
{
    SecurityElement root = new SecurityElement(xml.Name);
    foreach(XmlNode node in xml.ChildNodes)
    {
      if(node is XmlElement)
        root.AddChild(XmlToSecurityElement((XmlElement)node));
      else if(node is XmlText)
        root.Text = ((XmlText)node).Value;
      else if(node is XmlComment)
        continue;
      else
        throw new InvalidOperationException("Cannot convert XML with nodes of type " + node.GetType());
    }
    foreach(XmlAttribute attr in xml.Attributes)
      root.AddAttribute(attr.Name, attr.Value);

    return root;
}

Given this function, one nice piece of utility code to convert a string of XML into a SecurityElement (which is not possible with the current object model) is:

/// <summary>
/// Convert an XML string to a security element
/// </summary>
/// <param name="xml">string of XML to convert</param>
/// <returns>SecurityElement that the string represents</returns>
public static SecurityElement StringToSecurityElement(string xml)
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);

    return XmlToSecurityElement(doc.DocumentElement);
}