Background
When WSS and MOSS crawl content and store that content to an index they can also store authorization information (ACL) to the data. This makes it easy for a search query to only provide results to which the search user has access. WSS search is limited to SharePoint sites, but MOSS search can go beyond that to web sites, file shares, exchange public folders, the BDC, and others. While some content such as SharePoint sites, file shares, and exchange public folders contain ACLs, others such as web sites and BDC do not.
The solution to trimming MOSS search results that do not contain ACLs is to use a security trimmer. A security trimmer is very simple; it takes a list of URLs and returns a BitArray indicating if the current user has access to each URL. A security trimmer runs at query time so there is a performance cost, but I've found that the story here isn't too bad since the security trimmer gets called in batches based on the number of search results shown to the user on a page. Basically if the ratio of allowed access to total possible results is high, the number of items to check for security trimming at a time should be kept to a minimum. In addition there is a way to specify a limit on the number of crawl URLs to check.
There is a BDC Security Trimmer or you can write your own Custom Security Trimmer. That last link has a good overview and walkthrough of how to write, deploy, and register a custom security trimmer. I recommend it for further reading. However, the walkthrough only shows how to register a security trimmer using stsadm. It does not show how to do it via code. In fact, on the stsadm command you provide the crawl rule path indicating that security trimmer references the craw rule, which is not the case (it is the other way around).
I needed to do this via code as part of a custom shared service provider administration screen. Since I had a little bit of trouble figuring this out and couldn't find anyone else that did it, I wanted to blog about it here once I found the solution.
Show Me Some Code!
OK, enough background, let's see some code on how to do this.
- First, you're code will need to reference Microsoft.Office.Server.Search.dll which can be found in the ISAPI folder under the 12 Hive for a MOSS install. In addition, all of my code below uses the following using statement.
using SearchAdmin = Microsoft.Office.Server.Search.Administration;
- Now you can register your security trimmer. You will need the fully qualified type name for your security trimmer or access to it via code (as I have done below). In addition you need to specify the security trimmer id (an Int32 of any value of your choice assuming another security trimmer is not already registered with that value). If you don't have the context of the shared service provider you'll have to do a little more work.
// Get the security trimmer manager
// Note: no need to call SetSearchContextToUse as it is determined implicitly through HttpContext
SearchAdmin.Security.PluggableSecurityTrimmerManager manager = SearchAdmin.Security.PluggableSecurityTrimmerManager.Instance;
// Register the security trimmer
// No need to provide any custom properties (must provide an empty named value collection)
string fullyQualifiedTypeName = typeof(MyCustomSecurityTrimmer).AssemblyQualifiedName;
manager.RegisterPluggableSecurityTrimmer(securityTrimmerId, fullyQualifiedTypeName, new NameValueCollection());
- Then you will need to create or update your crawl rule to give it the security trimmer Id. The code below shows creating a crawl rule. If you don't have the context of the shared service provider, you'll have to do a little more work.
// This page is in the context of the shared service provider, so this call should get our search context
// otherwise we would need to use the ServerContext object instead and call SearchContext.GetContext(serverContext);
// Note that ServerContext is in the Microsoft.Office.Server namespace (Microsoft.Office.Server.dll)
SearchAdmin.SearchContext searchContext = SearchAdmin.SearchContext.Current;
// Get the content object which is needed for access to content sources and crawl rules
SearchAdmin.Content content = new SearchAdmin.Content(searchContext);
// Create crawl rule
SearchAdmin.CrawlRule crawlRule = content.CrawlRules.Create(SearchAdmin.CrawlRuleType.InclusionRule, rulePath);
// Set other crawl rule properties here…
// Set the security trimmer id and save the changes
crawlRule.PluggableSecurityTrimmerId = securityTrimmerId;
crawlRule.Update();
That's it. Fairly simple, especially if you already have the appropriate context as my code does since it runs within the context of the shared service provider.
As you can see, the crawl rule references the security trimmer Id and the security trimmer does not reference the crawl rule.
Note that your security trimmer will not be in effect unless you crawl (probably a full crawl) after you register your security trimmer even though the security trimmer runs as query time.