1. Custom Membership Conditions Explained
To enable
Author evidence to drive policy resolution, first
you create a custom membership condition class. You can
then use the custom membership condition in code groups to test the
values of any Author evidence presented by an
assembly or application domain. Here are the key requirements of a
custom membership condition class:
Implement IMembershipCondition
-
Membership condition classes must implement the
System.Security.Policy.IMembershipCondition
interface, which extends both the
System.Security.ISecurityPolicyEncodable and
System.Security.ISecurityEncodeable interfaces. We
summarize the members of each interface in Table 1.
Implement default constructor
-
Membership condition classes must implement a default (empty)
constructor. The runtime uses this constructor when it needs to
create a membership condition object from XML contained in the
security policy files.
Make serializable
-
Membership condition classes must be
serializable.
Because of their simplicity, application of the
System.SerializableAttribute is usually
sufficient. Those membership condition classes that have more complex
serialization requirements must implement the
System.Runtime.Serialization.ISerializable
interface.
Unless you implement ISerializable,
deserialization of an object does not result in the execution of a
constructor, and therefore you should not depend on constructor logic
to configure or modify the state of the deserialized object.
|
|
Table 1. Members of the IMembershipCondition, ISecurityPolicyEncodable, and ISecurityEncodeable interfaces
Member
|
Description
|
---|
IMembershipCondition interface
| |
Check
|
Determines whether an Evidence collection contains
evidence that satisfies the membership criterion
|
Copy
|
Returns a copy of the membership condition
|
Equals
|
Tests a System.Object against the
IMembershipCondition object
|
ToString
|
Returns a System.String representation of the
membership condition
|
ISecurityEncodable interface
| |
FromXml
|
Reconstructs the IMembershipCondition
object's state from a specified
SecurityElement
|
ToXml
|
Creates and returns a SecurityElement containing
an XML object model representing the
IMembershipCondition object's
state
|
ISecurityPolicyEncodable
| |
FromXml
|
The same as ISecurityEncodable.ToXml but handles
any policy level-specific state
|
ToXml
|
The same as ISecurityEncodable.FromXml but
includes any policy level-specific state
|
In addition to these requirements. These include:
Make membership conditions simple. Membership conditions classes have access to the entire
Evidence collection of an assembly or application
domain in the Check method, and therefore it is
possible to create custom membership conditions that test complex
membership criteria spanning multiple items of evidence. You must
consider who will use your membership condition class and design it
appropriately. Creating membership classes that test multiple or
complex sets of data can result in security policy that is difficult
for security administrators and users to configure and understand.
Make membership conditions lightweight. You should consider the amount of memory used by your membership
condition classes as well as avoid lengthy processing tasks, such as
network, file, and database access during membership evaluation.
Make membership conditions noninheritable. It is safest to mark your membership condition classes as
sealed in C# and NotInheritable
in Visual Basic .NET so that malicious code cannot subclass them in
an attempt to change its behavior and subvert CAS, or confuse
security administrators.
Override Object.ToString. The security administration tools use ToString to
display a message that describes the condition and value that a
membership is configured to match against. The use of this
information necessitates that the output be concise. For example,
"Site = *.oreilly.com" or
"Author = Peter."
2. Defining the AuthorMembershipCondition Class
Following the naming pattern used in the
.NET class library, we name the membership condition class
AuthorMembershipCondition. The
AuthorMembershipCondition class is relatively
simple; however, there is still a fair amount of code involved
because we have to implement the eight members defined in the
IMembershipCondition interface. We will break the
AuthorMembershipCondition class into manageable
sections for explanation.
The class declaration for
AuthorMembershipCondition specifies that it is
sealed (C#) and NotInheritable
(Visual Basic .NET) to stop malicious code from subclassing it in
order to subvert CAS. You should also specify the implementation of
IMembershipCondition and annotate
AuthorMembershipCondition with the
System.Serializable attribute—satisfying the
two major requirements of membership condition classes.
We define a single private System.String data
member named AuthorName, which will contain the
name of the author on which an
AuthorMembershipCondition object will base its
membership test:
# C#
using System;
using System.Security;
using System.Collections;
using System.Reflection;
using System.Security.Policy;
[assembly:AssemblyKeyFile("Keys.snk")]
[assembly:AssemblyVersion("1.0.0.0")]
namespace ORA.DotNetSecurity.Policy {
[Serializable]
public sealed class AuthorMembershipCondition : IMembershipCondition {
private string AuthorName = "";
# Visual Basic .NET
Imports System
Imports System.Security
Imports System.Collections
Imports System.Reflection
Imports System.Security.Policy
<assembly:AssemblyKeyFile("Keys.snk")>
<assembly:AssemblyVersion("1.0.0.0")>
Namespace ORA.DotNetSecurity.Policy
<Serializable> _
Public NotInheritable Class AuthorMembershipCondition
Implements IMembershipCondition
Private AuthorName As String = ""
We define two constructors for
AuthorMembershipCondition. The first is a default
(empty) constructor. The runtime uses this constructor to create
AuthorMembershipCondition objects before calling
the FromXml method to recreate the state of an
AuthorMembershipCondition from XML stored in the
policy configuration files. The second constructor is for general use
and takes a System.String argument containing the
name of the author on which the
AuthorMembershipCondition should base its
membership test:
# C#
public AuthorMembershipCondition( ) {
this.AuthorName = "";
}
public AuthorMembershipCondition(string author) {
if (VerifyAuthorName(author)) {
this.AuthorName = author;
}
}
# Visual Basic.NET
Public Sub New( )
Me.AuthorName = ""
End Sub
Public Sub New(ByVal author As String)
If VerifyAuthorName(author) Then
Me.AuthorName = author
End If
End Sub
We include a Name property to provide controlled
access to the private AuthorName data member and
the private VerifyAuthorName utility method, which
we use in both the Name property and class
constructor to validate the author name provided. To simplify
AuthorMembershipCondition you check only if the
author name is null (C#) or
Nothing (Visual Basic .NET)—in which case,
you throw a System.ArgumentNullException. In a
production-quality membership condition class, it is good practice to
verify the condition data more thoroughly and throw a
System.ArgumentException if the data is invalid.
For example, it would be a good idea to check for invalid characters
and limit the size of the name provided:
# C#
// Property to get/set the author name
public string Name {
get {
return AuthorName;
}
set {
if (VerifyAuthorName(value)) {
this.AuthorName = value;
}
}
}
// Utility method to verify that the author name
// is not null.
private bool VerifyAuthorName(string author) {
if (author == null) {
throw new ArgumentNullException("author");
} else {
return true;
}
}
# Visual Basic .NET
' Property to get/set the author name
Public Property Name( ) As String
Get
Return AuthorName
End Get
Set (ByVal Value As String)
If VerifyAuthorName(Value) Then
Me.AuthorName = Value
End If
End Set
End Property
' Utility method to verify that the author name
' is not null.
Private Function VerifyAuthorName(ByVal author As String) _
As Boolean
If author Is Nothing Then
Throw New ArgumentNullException("author")
Else
Return True
End If
End Function
The real work of the AuthorMembershipCondition
class takes place in the Check method. The
Check method takes an
Evidence collection and enumerates the contained
evidence objects to determine if any of them are
Author objects. If Check finds
an Author object, it compares the
Author.Name property with its own private
AuthorName data member. If the two names match,
Check returns true; otherwise,
Check returns false.
For simplicity, we have implemented a straightforward case-sensitive
string comparison, but you might also consider support for
non-case-sensitive comparisons and wildcard matches. The membership
condition logic can be arbitrarily complex, but remember that the
runtime may call Check many times during policy
resolution, and time-consuming membership condition evaluations will
affect application performance:
# C#
// Determines whether the Evidence meets the membership
// condition.
public bool Check( Evidence evidence ) {
// Return false if the Evidence is null
if (evidence == null) {
return false;
}
Author auth = null;
// Enumerate across the host and assembly evidence
// collections.
IEnumerator enumerator = evidence.GetEnumerator( );
while (enumerator.MoveNext( ))
{
auth = enumerator.Current as Author;
// If the evidence is of type Author and the
// author names match, the evidence meets the
// membership condition.
if (auth != null) {
if (auth.Name == this.AuthorName) {
return true;
}
}
}
return false;
}
# Visual Basic .NET
' Determines whether the Evidence meets the membership
' condition.
Public Function Check(ByVal evidence As Evidence) As Boolean _
Implements IMembershipCondition.Check
' Return false if the Evidence is Nothing
If evidence Is Nothing Then
Return False
End If
Dim auth As Author = Nothing
' Enumerate across the host and assembly evidence
' collections.
Dim enumerator As IEnumerator = evidence.GetEnumerator( )
While enumerator.MoveNext( )
auth = CType(enumerator.Current, Author)
' If the evidence is of type Author and the
' author names match, the evidence meets the
' membership condition.
If Not auth Is Nothing Then
If auth.Name = Me.AuthorName Then
Return True
End If
End If
End While
Return False
End Function
The remaining members of the
IMembershipCondition interface are straightforward.
The Copy method returns a clone of the current
AuthorMembershipCondition object, and the
Equals method compares two
AuthorMembershipCondition objects for equality
based on the value of their private AuthorName
data members instead of comparing object references. Because
Object also defines an Equals
method, you must override it with the one defined in the
IMembershipCondition interface. As a result, you
must also override the
GetHashCode method:
# C#
// Creates a copy of the membership condition
public IMembershipCondition Copy( ) {
return new AuthorMembershipCondition(this.AuthorName);
}
// Compares an object for equality based on the author's
// name, not the object reference.
public override bool Equals(object obj) {
AuthorMembershipCondition that =
(obj as AuthorMembershipCondition);
if (that != null) {
if (this.AuthorName == that.AuthorName) {
return true;
}
}
return false;
}
// We must override GetHashCode because we override
// Object.Equals. Returns a hash code based on the
// author's name.
public override int GetHashCode( ) {
return this.AuthorName.GetHashCode( );
}
# Visual Basic .NET
' Creates a copy of the membership condition
Public Function Copy( ) As IMembershipCondition _
Implements IMembershipCondition.Copy
Return New AuthorMembershipCondition(Me.AuthorName)
End Function
' Compares an object for equality based on the author's
' name, not the object reference.
Public Overloads Function Equals(ByVal obj As Object) As Boolean _
Implements IMembershipCondition.Equals
Dim that As AuthorMembershipCondition = _
CType(obj,AuthorMembershipCondition)
If Not that Is Nothing Then
If Me.AuthorName = that.AuthorName Then
Return True
End If
End If
Return False
End Function
' We must override GetHashCode because we override
' Object.Equals. Returns a hash code based on the
' author's name.
Public Overrides Function GetHashCode( ) As Integer
Return Me.AuthorName.GetHashCode( )
End Function
All of the standard membership condition classes override
Object.ToString to return a simple human-readable
representation of the membership condition they define. The
administration tools display this information, and it should be
relatively concise. For consistency, we take the same approach:
# C#
// Returns a simple string representation of the
// membership condition
public override string ToString( ) {
return "Author - " + AuthorName;
}
# Visual Basic .NET
' Returns a simple string representation of the
' membership condition
Public Overrides Function ToString( ) As String _
Implements IMembershipCondition.ToString
Return "Author - " & AuthorName
End Function
Both the ISecurityEncodable and
ISecurityPolicyEncodable interfaces define a
ToXml method that returns a
System.Security.SecurityElement containing an XML
object model of the AuthorMembershipCondition
object. The AuthorMembershipCondition class does
not differentiate between different policy levels and therefore
implements both methods the same.
Most importantly, the root XML element must be
"IMembershipCondition" or the
administrative tools will not be able to import the XML describing
the custom membership condition. The XML representation of all
standard membership condition classes includes a
version attribute that represents the XML format
used to represent the membership condition. We have taken the same
approach, which gives flexibility should future versions of the
AuthorMembershipCondition class require a
different structure:
# C#
// Return a SecurityElement containing an XML object
// model representing the membership condition.
public SecurityElement ToXml( ) {
return this.ToXml(null);
}
// Return a SecurityElement containing an XML object
// model representing the membership condition. We have
// no need to differentiate between policy levels and so
// we ignore the "level" argument.
public SecurityElement ToXml(PolicyLevel level) {
// Create a new "IMembershipCondition" element
SecurityElement se =
new SecurityElement("IMembershipCondition");
// Add fully qualified type name for membership condition
se.AddAttribute(
"class",
this.GetType( ).AssemblyQualifiedName
);
// Add an XML version number of "1"
se.AddAttribute("version", "1");
// Add the author name
se.AddAttribute("name", AuthorName);
// Return the new SecurityElement
return se;
}
# Visual Basic .NET
' Return a SecurityElement containing an XML object
' model representing the membership condition.
Public Function ToXml( ) As SecurityElement _
Implements ISecurityEncodable.ToXml
Return Me.ToXml(Nothing)
End Function
' Return a SecurityElement containing an XML object
' model representing the membership condition. We have
' no need to differentiate between policy levels and so
' we ignore the "level" argument.
Public Function ToXml(ByVal level As PolicyLevel) _
As SecurityElement Implements ISecurityPolicyEncodable.ToXml
' Create a new "IMembershipCondition" element
Dim se As SecurityElement = _
New SecurityElement("IMembershipCondition")
' Add fully qualified type name for membership condition
se.AddAttribute( _
"class", _
Me.GetType( ).AssemblyQualifiedName _
)
' Add an XML version number of "1"
se.AddAttribute("version", "1")
' Add the author name
se.AddAttribute("name", AuthorName)
' Return the new SecurityElement
Return se
End Function
The reverse of the ToXml method is
FromXml, which takes a
SecuirtyElement and reconstructs the object state.
As with ToXml, both the
ISecurityEncodable and
ISecurityPolicyEncodable interfaces define a
FromXml method. Again, you implement both methods
the same, because you do not need to perform policy level-specific
state configuration:
# C#
// Reconstruct the state of the membership condition
// object from the SecurityElement provided.
// fromExtract state from a SecurityElement
public void FromXml(SecurityElement e) {
this.FromXml(e, null);
}
// Reconstruct the state of the membership condition
// object from the SecurityElement provided. We have
// no need to differentiate between policy levels and so
// we ignore the "level" argument.
public void FromXml(SecurityElement e, PolicyLevel level) {
// Ensure we have a SecurityElement to work with
if (e == null) throw new ArgumentNullException("e");
// Ensure the SecurityElement is an AuthorMembershipCondition
if (e.Tag != "IMembershipCondition") {
throw new ArgumentException
("Element must be IMembershipCondition");
} else {
// Extract the author name from the SecurityElement
this.AuthorName = e.Attribute("name");
}
}
}
}
# Visual Basic .NET
' Reconstruct the state of the membership condition
' object from the SecurityElement provided.
' fromExtract state from a SecurityElement
Public Sub FromXml(ByVal e As SecurityElement) _
Implements ISecurityEncodable.FromXml
Me.FromXml(e, Nothing)
End Sub
' Reconstruct the state of the membership condition
' object from the SecurityElement provided. We have
' no need to differentiate between policy levels and so
' we ignore the "level" argument.
Public Sub FromXml(ByVal e As SecurityElement, _
ByVal level As PolicyLevel) _
Implements ISecurityPolicyEncodable.FromXml
' Ensure we have a SecurityElement to work with
If e Is Nothing Then
Throw New ArgumentNullException("e")
End If
' Ensure the SecurityElement is an AuthorMembershipCondition
If e.Tag <> "IMembershipCondition" Then
Throw New ArgumentException _
("Element must be IMembershipCondition")
Else
' Extract the author name from the SecurityElement
Me.AuthorName = e.Attribute("name")
End If
End Sub
End Class
End Namespace