Enterprise software development and consulting for various organizations, industries and domains.

Saturday, January 29, 2011

Managing Security in SharePoint 2010 Based on Metadata

The most robust way of keeping your SharePoint manageable is to keep it's structure clean and clear, though business often introduces some complex rules and convoluted workflows. Degree of complexity even increases when it comes to governance and security.

One task of such kind that business demands is to secure document based on the metadata values. In SharePoint 2007 it usually leads to custom development or purchasing one of the 3rd party products (like one from the Titus Labs), luckily SharePoint 2010 came up to help. Let's say we were asked to assign custom permission level on the document based on it's category, although to make it harder assume that document can have multiple categories.

The following picture shows security matrix to be implemented:

Here are the steps to achieve this using out-of-the-box Content Organizer feature, folder based security structure and Metadata Navigation.

1. Create "MD Document" content type
2. Add Managed Metadata column "Document Category" to the content type with the following Taxonomy
3. Add MD Document content type to a "Documents" document library.
4. Create three folders in a document library "Public", "Confidental", "Top Secret". Break permissions inheritance on these folder and assign desired permissions in accordance with security matrix.
5. Go to site settings and activate Content Organizer feature.
6. Add content organizer rule to route documents having Accounting category to a Top Secret folder.
7. Create rules for all category types. Less privileged category should have higher rule priority.

Starting from that point all documents that a user uploads to a Documents library will be processed by Content Organizer and placed in a secured folder based on a document category, i.e. secured based on the metadata.

A user will be informed by the following message in the upload document dialog:

And when category field is set up, document will be automatically routed:

Also, I like to use Metadata Navigation and Filtering feature in order to make navigation over categories more convenient. Activate this feature in Site Settings, go to document document library settings, then Metadata Navigation Settings and add Document Category column to the Selected Hierarchy fields. That will add a nice looking category tree to a documents list.

Sunday, November 28, 2010

Adding video to a SharePoint 2010 blog

SharePoint 2010 blog engine does not allow in insert videos in a blog post.

There is a simple way to work around this limitation. Create new post and insert video embed code. I took one from youtube.com for example:


SharePoint will escape all HTML characters when record is being saved in a database. What we have to do is to register a javascript function which will replace escaped HTML back with the original one. I used this jquery code snippet:

<script type="text/javascript">
$(document).ready(function() {
 $(".ms-PostBody").html(function(index, oldHtml) {
  var matchStr = /(\&lt;object.+\/object\&gt;)/i;
  var m = oldHtml.match(matchStr);
 
  $.each(m, function (index, value) {
   oldHtml = oldHtml.replace(value, value.replace(/\&gt;/g, ">").replace(/\&lt;/g, "<"));
  });

  return oldHtml;
 });
});
</script>

It can be either added in a Content Editor web part on the page or registered via feature in a AdditionalPageHead delegate control or added to the master page using SharePoint designer.

Here is the result, enjoying YouTube videos in our blog.


For the security reasons, we might need to limit object codes to be videos only or videos from youtube only. In that case we will need to update regular expression used in the script which captures embed HTML.

Saturday, August 28, 2010

Sharepoint: Create Visitors group programmatically

I noticed interesting thing, when you use unique permissions for a site and want to set up permission groups, SharePoint offers to create Owners and Members group, but Visitors group is inherited from a parent site by default.


And even when I tried to do it programmatically using SPWeb.CreateDefaultAssociatedGroups, it still creates only two groups.

So I ended up with the following code snippet:

var primaryAdmin = web.SiteAdministrators[0];
var groupName = SPResource.GetString("DefaultVisitorGroupName", new object[] { SPHttpUtility.HtmlEncode(web.Title) });
var groupDescription = SPResource.GetString("DefaultVisitorGroupDescriptionRichText", new object[] { SPHttpUtility.HtmlEncode(web.Title), SPHttpUtility.HtmlUrlAttributeEncode(SPHttpUtility.UrlPathEncode(web.ServerRelativeUrl, false)) });
web.SiteGroups.Add(groupName, primaryAdmin, null, groupDescription);
web.AssociatedVisitorGroup = web.SiteGroups[groupName];

var roleAssignment = new SPRoleAssignment(web.AssociatedVisitorGroup);
web.RoleAssignments.Add(roleAssignment);
roleAssignment.RoleDefinitionBindings.Add(web.RoleDefinitions["Read"]);
web.AllowUnsafeUpdates = true;
roleAssignment.Update();

It creates Visitors group and gives it Read permissions.

Saturday, August 21, 2010

How to set managed metadata field value through code

Recently one of our clients reported an issue that setting taxonomy field value through code doesn't update the field value.

We noticed interesting fact, that even though value is stored in a field, UI control still shows an old value. After spending some time in a reflector digging in Microsoft.SharePoint.Taxonomy.dll we found out a way how SharePoint stores managed metadata and enterprise keywords values.

Guys from Microsoft use pretty simple approach, they store technical information in a hidden field created with the following algorithm.

string str;
string str2;
int num = 0;
while (fields.ContainsField(base.Title + "_" + num.ToString(CultureInfo.InvariantCulture)))
{
    num++;
}
if (this.IsKeyword)
{
    str = "TaxKeywordTaxHTField";
    str2 = "TaxKeywordTaxHTField";
}
else
{
    str = base.Title + "_" + num.ToString(CultureInfo.InvariantCulture);
    str2 = base.InternalName + "TaxHTField" + num.ToString(CultureInfo.InvariantCulture);
}

Where str and str2 are DisplayName and InternalName of a hidden field respectively.

So, in order to set taxonomy field value, either Managed Meatadata or Enterprise Keywords field, we used this piece of code:

var currentItem = SPContext.Current.Item;

// set field value
item[fieldName] = currentItem[fieldName];

// find hidden field and set it's value
SPField htField = GetHTField(item, fieldName);
item[htField.StaticName] = currentItem[htField.StaticName];

GetHTField method is shown below:

private SPField GetHTField(SPItem item, string fieldName)
{
    foreach (SPField field in item.Fields)
    {
        if (field.StaticName.StartsWith(fieldName) && field.StaticName.Contains("TaxHTField"))
        {
            return field;
        }
    }
    return null;
}

This code was used in a code behind of a layout page with FormComponent control thus we were able to get current item from SPContext.

In the next post, for those who are not able to use values from SPContext's current item, I will tell about format of a taxonomy hidden field value and show how to calculate it manually.

Tuesday, August 17, 2010

Using Form Web Part and Data View Web Part to search and filter data

Present data in a grid view with an ability to filter records by a set of fields is a common requirement in an enterprise world. Built-in SharePoint list view filters and search cover basic scenarios, but filtering capabilities are often insufficient to effectively search over particular metadata. For instance,

  • a user might need to search only by particular columns;
  • search results have to be limited by the certain lists;
  • portal is internet facing and configured in a lockdown mode;
  • search has to be performed over external data source;

This is where Data View web part may help, it provides a lot of options for customization since it uses XSLT templates. Very good article on how to use Data View web part for filtering was posted by Phillip Wicklund.

So, author used two connected web parts to feed Data View web part with a values from the Form web part, though every consecutive search filter previously filtered data. It happens because a Data View web part keeps parameter values and variables between postbacks.

A Data View web part thinks that no parameter is provided when value is equal to an empty string. To walk around this issue we used a little script. What we have to do is to let Data View web part think, that value has been changed. Let's replace empty string with some token before submit a form and then substitute it back to an empty string before performing the actual search.

Assume we would like to filter data by "County" and "Zip Code" fields, then we define Data Form web part containing two text inputs accordingly:

<table>
  <tr>
    <td>Zip Code</td>
    <td><input type="text" id="qZipCode" name="qZipCode"></td>
  </tr>
  <tr>
    <td>County</td>
    <td><input type="text" id="qCounty" name="qCounty"></td>
  </tr>
  <tr>
<table>

Place Submit and Reset buttons below the input fields:

<input type="submit" name="search" value="Search" onclick="javascript:beforeSubmit();_SFSUBMIT_"/>&nbsp;&nbsp;
<input type="reset" value="Reset" name="reset" onclick="doReset()"/>

The trick goes with the onclick handler of the Submit button. Add the following script block to the Data Form web part. It replaces empty string with the space.

<script>
 var ctls = ['qZipCode', 'qCounty'];
 
 function trimControls() {
  for(var i=0;i<ctls.length;i++) {           
   var ctl = document.getElementById(ctls[i]);
   if (ctl.value == ' ') {
    ctl.value = '';
   }
  }
 }
 trimControls();
 function beforeSubmit() {
  for(var i=0;i<ctls.length;i++) {           
   var ctl = document.getElementById(ctls[i]);
     if (ctl.value == '') {
      ctl.value = ' ';
     }
    }
 }
 function doReset() {
  window.location.href = window.location.href;
 }         
</script>

Then we have to remove spaces in the Data View web part. We use normalize-space function.

<xsl:variable name="qZipCodeU" select="normalize-space($qZipCode)" />
<xsl:variable name="qCountyU" select="translate(normalize-space($qCounty), $smallcase, $uppercase)" />
<xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row[$search='off'][contains(@ZipCode,$qZipCodeU)][contains(translate(@County,$smallcase,$uppercase),$qCountyU)]" />

Notice $search='off' condition, it allows to perform search only if a user clicks the Search button.

Another nice option of Data View web part is an ability to use various ASP.NET DataSource controls (generally, any control which implements IDataSource), for example:
  • SPDataSource for searching over SharePoint lists;
  • SPSqlDataSource for searching over external database;
  • BdcDataSource for searching within BDC applications;
  • SoapDataSource to filter data from web services.

Wednesday, July 28, 2010

Sharepoint 2010 Ribbon: Enrich custom dialogs

Standard dialogs in SharePoint 2010 come with ribbon component. In order to enrich your custom dialog with ribbon follow these steps.

1. Define CustomAction element in a declarative XML. I recommend to put this in a feature as described here MSDN: CustomAction Element

Note: Do NOT use dots in CustomAction Id, otherwise all buttons with associated actions will be disabled.

2. Register Ribbon on a page. I made it programmatically by the following code in the PreRender event handler.
protected void Page_PreRender(object sender, EventArgs e)
{
    SPRibbon current = SPRibbon.GetCurrent(this);

    if (current != null)
    {
        current.MakeTabAvailable("Ribbon.ClassifyDialog.Edit");
        current.InitialTabId = "Ribbon.ClassifyDialog.Edit";
        current.CommandUIVisible = true;
        current.Minimized = false;
    }
} 

3. Register javascript component containing client side logic on a page using SP.Ribbon.PageManager.get_instance().addPageComponent()
Link with method description is here MSDN: addPageComponent.

This is it. Enjoy professional look'n'feel of your dialogs.