The standard SharePoint security adornment in the XsltListViewWebPart works on the list item level – the items in the showed SharePoint list may inherit the permissions from their parent list, but may also have different permissions and based on these the viewing user depending on his rights may see different result sets. In most cases this “horizontal” security adornment will be sufficient for the end users, but there may be cases when a “vertical” type of security adornment is also requested – meaning that certain columns in the list view are showed only to users with particular rights for the target SharePoint list.

So, I made a small “proof-of-concept” type solution that demonstrates how this can be achieved. The implementation is honestly simple and involves only changing of the XSL used by the web part and modifying its “PropertyBindings” property. The modifications of the rendering XSL are obviously the core part of the implementation but you may wonder what the purpose of by the “PropertyBindings” property of the XsltListViewWebPart is (if you are not traditional with the “PropertyBindings” property and how to use it, you can check out my extensive posting on the subject here). The thought is simple – in the “PropertyBindings” property you can keep configuration information that also becomes available in the XSL of the web part. And the configuration data that is needed in our case is the permissions’ data for the list columns that the web part displays – it is about what rights exactly the user should have for the target list, so that he can see one or another column in the list view. The question here is why place this configuration in the web part’s property bindings and not in the fields’ machinate itself for example (the field machinate can easily be extended with custom attributes – e.g. xmlns:PermissionMask="0x7FFFFFFFFFFFFFFF"). The main reason for this is that if you use custom attributes in the field machinate XML, you cannot then use them in the rendering XSL of the XLV web part, custom attributes austerely don’t appear in the view machinate XML that is available through the “$XmlDefinition” XSL parameter (check this MSDN article for a sample view machinate XML in the XLV’s XSL – the field machinate data is in the View/ViewFields/FieldRef elements). Another top here is that even if it were possible to store the field permission data in the field’s machinate, this would impact all list views that use the modified XSL (and show the particular column), and with the setting of the “PropertyBindings” property only the current XLV web part will be affected. It is hard to judge whether this is of advantage or disadvantage  and probably depends on the point case that you may have.

And let me first show you the “PropertyBinding” XML that should be added to the “PropertyBindings” property (I said “added”, because the property normally already contains the XML of other property bindings):

<ParameterBinding Name="ColumnPermissions" DefaultValue="Author|9223372036854775807|Editor|756052856929" />

The “Name” attribute specifies the name of the xsl:param element that will be initialized with the value of the property binding in the web part’s XSL. Its value is in the “DefaultValue” attribute – it is a long string containing values delimited by the ‘|’ character. At odd positions you have field names (internal names really) and at even positions you see huge numbers, which are really the permission masks that should be applied for the list columns which precede the corresponding number. The permission mask is determined by the standard SharePoint SPBasePermissions enumeration: 9223372036854775807 (hex 7FFFFFFFFFFFFFFF) corresponds to the “Full Control” permission level and 756052856929 (hex B008431061) corresponds to the “Contribute” permission level. This means that the user will see the “Author” (“Made by”) column only if he has “Full Control” rights and the “Editor” (“Modified by”) column only if he has “Contribute” rights for the SharePoint list that is showed. Note that all fields that are not specified in the property binding will be always visible to all users.

Let’s now go to the custom XSL that should handle the rendering and show only the columns that the current user has rights to see. Before, I show you the complete source of the custom rendering XSL, I want to draw your attention to one vital thing – the trick that is used in the XSL to check whether the permission masks for the fields have a match with the effectual permissions of the current user for the source SharePoint list. It is very simple really and uses … a standard SharePoint “ddwrt” XSLT additional room method:

<xsl:if test="ddwrt:IfHasRights($checkResult)">

The “IfHasRights” additional room method receives an integer parameter for the permission mask and income right or fake depending on whether the current user has those rights for the SharePoint list of the web part. Note that the check is made for the SharePoint list, not the items of the list and not for its parent SharePoint site.

And here is the complete source of the custom XSL (check the extensive comments inside it for more details)

<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">

 

  <!– import the standard main.xsl, so we have all standard stuff –>

  <xsl:import href="/_layouts/xsl/main.xsl"/>

  <xsl:productivity method="html" indent="no"/>

 

  <!– we get here the field permissions configuration from the PropertyBinding with the same name –>

  <xsl:param name="ColumnPermissions" />

  <!– this is the standard XmlDefinition parameter – the XLV initializes this one with the view machinate –>

  <xsl:param name="XmlDefinition" />

 

  <!– this variable contains the parsed configuration data like <nominal>Author</nominal><nominal>9223372036854775807</nominal> etc –>

  <xsl:variable name="tokens">

    <xsl:call-template name="Tokenize">

      <xsl:with-param name="string" select="$ColumnPermissions" />

      <xsl:with-param name="delimiter" select="‘|’" />

    </xsl:call-template>

  </xsl:variable>

 

  <!– here we make a copy of the original XmlDefinition removing all View/ViewFields/FieldRef elements for which the user doesn’t have rights –>

  <xsl:variable name="XmlDefinition2Raw">

    <xsl:apply-templates mode="transform-machinate" select="$XmlDefinition" >

      <xsl:with-param name="tokenSet" select="msxsl:node-set($tokens)" />

    </xsl:apply-templates>

  </xsl:variable>

 

  <!– the one above is a sequence of tags, in order that it can be used exactly like the standard $XmlDefinition it should be converted to a node set –>

  <xsl:variable name="XmlDefinition2" select="msxsl:node-set($XmlDefinition2Raw)" />

 

  <!– this one is austerely a copy of the template with the same match from the standard vwstyles.xsl (thus we override it), the only difference is that it uses our trimmed $XmlDefinition2 instead of the standard $XmlDefinition –>

  <xsl:template match="/">

    <xsl:choose>

      <xsl:when test="$RenderCTXOnly=’Right’">

        <xsl:call-template name="CTXGeneration"/>

      </xsl:when>

      <xsl:when test="($ManualRefresh = ‘Right’)">

        <xsl:call-template name="AjaxWrapper" />

      </xsl:when>

      <xsl:otherwise>

        <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition2"/>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

 

  <!– the same as the template above –>

  <xsl:template name="AjaxWrapper" ddwrt:ghost="always">

    <table width="100%" border="0"  cellpadding="0" cellspacing="0">

      <tr>

        <td valign="top">

          <xsl:apply-templates mode="RootTemplate" select="$XmlDefinition2"/>

        </td>

        <td width="1%" class="ms-vb" valign="top">

          <xsl:variable name="onclick">

            javascript: <xsl:call-template name="GenFireServerEvent">

              <xsl:with-param name="param" select="‘cancel’"/>

            </xsl:call-template>

          </xsl:variable>

          <xsl:variable name="alt">

            <xsl:value-of select="$Rows/@store.wss.ManualRefreshText"/>

          </xsl:variable>

          <a href="javascript:" onclick="{$onclick};return fake;">

            <img src="/_layouts/images/staticrefresh.gif" id="ManualRefresh" border="0" alt="{$alt}"/>

          </a>

        </td>

      </tr>

    </table>

  </xsl:template>

 

  <!– this template makes the copy of the standard $XmlDefinition adornment the View/ViewFields/FieldRef elements for which the user doesn’t have rights –>

  <xsl:template mode="transform-machinate" match="View" >

    <xsl:param name="tokenSet" />

    <!– copy the root View element –>

    <xsl:copy>

      <!– copy the root View element’s attributes –>

      <xsl:copy-of select="@*"/>

      <!– copy the child elements of the root View element –>

      <xsl:for-each select="child::*">

        <xsl:choose>

          <xsl:when test="name() = ‘ViewFields’">

            <!– special handling of the ViewFields element –>

            <ViewFields>

              <!– iterate the ViewFields/FieldRef elements here –>

              <xsl:for-each select="child::*">

 

                <!– get the permission mask for the FieldRef element, by the Name attribute –>

                <xsl:variable name="checkResult">

                  <xsl:call-template name="GetValueFromKey">

                    <xsl:with-param name="tokenSet" select="$tokenSet" />

                    <xsl:with-param name="key" select="./@Name" />

                  </xsl:call-template>

                </xsl:variable>

 

                <xsl:choose>

                  <!– if the permission mask is not empty and the ddwrt:IfHasRights income right, copy the field –>

                  <xsl:when test="$checkResult != ”">

                    <!– this is how we check whether the user has sufficient rights for the field (checking the permission mask of the field against the user’s permissions for the source list) –>

                    <xsl:if test="ddwrt:IfHasRights($checkResult)">

                      <xsl:copy-of select="."/>

                    </xsl:if>

                  </xsl:when>

                  <xsl:otherwise>

                    <!– if we don’t have the field in the configuration austerely copy the FieldRef element –>

                    <xsl:copy-of select="."/>

                  </xsl:otherwise>

                </xsl:choose>

 

              </xsl:for-each>

            </ViewFields>

          </xsl:when>

          <xsl:otherwise>

            <xsl:copy-of select="."/>

          </xsl:otherwise>

        </xsl:choose>

      </xsl:for-each>

    </xsl:copy>

  </xsl:template>

 

  <!– several helper templates that parse the configuration string and return the permission mask for the field by providing the field’s internal name –>

  <xsl:template name="GetValueFromKey">

    <xsl:param name="tokenSet" />

    <xsl:param name="key" />

    <xsl:apply-templates select="$tokenSet/nominal[text() = $key]" />

  </xsl:template>

 

  <xsl:template name="NextNode" match="nominal">

    <xsl:value-of select="following-sibling::*"/>

  </xsl:template>

 

  <xsl:template name="Tokenize">

    <xsl:param name="string" />

    <xsl:param name="delimiter" select="‘ ‘" />

    <xsl:choose>

      <xsl:when test="$delimiter and contains($string, $delimiter)">

        <nominal>

          <xsl:value-of select="substring-before($string, $delimiter)" />

        </nominal>

        <xsl:call-template name="Tokenize">

          <xsl:with-param name="string" select="substring-after($string, $delimiter)" />

          <xsl:with-param name="delimiter" select="$delimiter" />

        </xsl:call-template>

      </xsl:when>

      <xsl:otherwise>

        <nominal>

          <xsl:value-of select="$string" />

        </nominal>

        <xsl:text> </xsl:text>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

 

</xsl:stylesheet>

One note about the XSL code – if you have a look at it, you will notice that it replaces (overrides) two of the core XSL templates used in the standard vwstyles.xsl file. It then provides a modified copy of the standard view machinate XSL parameter ($XmlDefinition) in these templates. And on the other hand if you check the vwstyles.xls file, you will notice that there are still other XSL templates in it that also use the standard $XmlDefinition parameter (and thus not the modified copy) – these are the templates that handle aggregations and groupings, which means that the modified XSL above won’t be able to handle by the book these cases.

And finally a few words on how to use this sample: the first thing is to save the XSL to a XSL file in the TEMPLATE\LAYOUTS or TEMPLATE\LAYOUTS\XSL folder (a subfolder of these two is also possible). Then you need to select your XLV web part (it may be an XLV from a standard list view page or an XLV that you placed on a make pleased page) and exchange its PropertyBindings and XslLink properties. You can do that with code or by my web part administrator utility which provides an simple to use UI for that (you can download it from here). For the “PropertyBindings” property, you should append the XML of the field permissions configuration which should look like the sample above. In the “XslLink” property you should specify the link to the XSL file in which you have saved the custom XSLT above – provided you’ve saved the XSL file as TEMPLATE\LAYOUTS\columns_main.xsl, the value that you should place in the “XslLink” property should be: /_layouts/columns_main.xsl.

Check it out:Stefan Stanev’s SharePoint blog