Being able to do security
trimming is important in many corporations. This can be a challenge,
especially if the LOB data system uses custom security descriptors.
Extending the database .NET connector described in the previous section
will show how this can be accomplished. Here we will assume one
particular form of security descriptor, but in reality it could be in
any format that supports mapping between the user context and the
descriptor.
First a rights table must be added to the model to support security trimming. Here we have created a CustomerAccessRights table containing the SecurityDescriptor, Rights, and CustomerKey. The SecurityDescriptor is a binary unique value for a particular user. Rights will contain a simple numeric schema representing user rights to a particular row. It also contains creation rights.
No Entry means the user has no rights to this data row represented by the CustomerKey.
In a production environment, a different and more fine-grained access
rights mapping might be desired, but this should give a good idea about
how to implement security trimming that allows multiple users with
different access to the same data rows. Given the customer table
contains this information, we can create a CustomerAccessRights table containing the security mappings. The SecurityDescriptor should be based on the same method used by the model to trim security. In this example, it is the GetSecurityDescriptor() method displayed in Tables 1 and 2.
Table 2. Customer Access Rights Table
Rights | CustomerKey | SecurityDescriptor |
---|
1 2 | | <binary data> |
2 2 | | <binary data> |
2 5 | | <binary data> |
2 6 | | <binary data> |
2 7 | | <binary data> |
To add the CustomerAccessRights table to the model, add a new LINQ to SQL Classes item to the project and name it CustomerAccessRights. In the Server Explorer, add a connection to the Customers database if it does not already exist. Then drag the CustomerAccessRights table, and drop it on the CustomerAccessRights.dbml design surface.
Next the required method for computing the SecurityDescriptor is added as in Listing 1. This method can be added to the CustomerService.cs class that also contains the Customer methods. This method computes a security descriptor in the form of a byte array.
Example 1. Implementation of Method for Getting a Security Descriptor
static Byte[] GetSecurityDescriptor(string domain, string username)
{
NTAccount acc = new NTAccount(domain, username);
SecurityIdentifier sid = (SecurityIdentifier)acc.Translate(typeof(SecurityIdentifier));
CommonSecurityDescriptor sd = new CommonSecurityDescriptor(false, false,
ControlFlags.None, sid, null, null, null);
sd.SetDiscretionaryAclProtection(true, false);
//Deny access to everyone
SecurityIdentifier everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
sd.DiscretionaryAcl.RemoveAccess( AccessControlType.Allow, everyone,
unchecked((int)0xffffffffL), InheritanceFlags.None, PropagationFlags.None);
//Grant full access to specified user
sd.DiscretionaryAcl.AddAccess( AccessControlType.Allow, sid,
unchecked((int)0xffffffffL), InheritanceFlags.None, PropagationFlags.None);
byte[] secDes = new Byte[sd.BinaryLength];
sd.GetBinaryForm(secDes, 0);
return secDes;
}
|
Having the Rights table and the security descriptor method in place, the next step is to modify the Customers methods for updating, reading, etc., such that they are trimmed based on the security descriptor. Here (Listing 2) the Reader methods are updated to apply security trimming during search.
Example 2. Adding Security Trimming to the BDC Method Instances
public static IEnumerable<Customer> ReadList()
{
CustomerDataContext context = new CustomerDataContext();
CustomerAccessRightsDataContext accessContext = new CustomerAccessRightsDataContext();
List<Customer> tempCustList = new List<Customer>();
foreach(Customer customer in context.Customers)
{
CustomerAccessRight custAccess = accessContext.CustomerAccessRights.SingleOrDefault(
c => c.CustomerKey == customer.CustomerKey && c.SecurityDescriptor.ToArray()
== GetSecurityDescriptor(Environment.UserDomainName,Environment.UserName));
if(custAccess.Rights > 0)
tempCustList.Add(customer);
}
IEnumerable<Customer> custList = tempCustList;
return custList;
}
public static Customer ReadItem(string customersKey)
{
CustomerDataContext context = new CustomerDataContext();
Customer cust = context.Customers.Single(c => c.CustomerKey == customersKey);
CustomerAccessRightsDataContext accessContext = new CustomerAccessRightsDataContext();
CustomerAccessRight custAccess = accessContext.CustomerAccessRights.SingleOrDefault(
c => c.CustomerKey == cust.CustomerKey && c.SecurityDescriptor.ToArray()
== GetSecurityDescriptor(Environment.UserDomainName, Environment.UserName));
if (custAccess.Rights > 0)
return cust;
else
return null;
}
|
Using this methodology as a
baseline, it is possible to create simple security trimming. When doing
security trimming, performance of the trimming mechanism is relevant.
Different caching mechanics can be applied with success to increase
performance. Also other security descriptor implementations that better
fit specific requirements can be implemented using this example as a
template for how to approach the topic. Microsoft does provide some
resources on this topic.