Monday, 27 February 2012

Column Security Level in SharePoint? Do it yourself!

One of the common questions, that follows up the “SharePoint security model” chat, is the one asking for Security at Column level. Is it possible to add security at Column level? The answer is that you cannot set it up from the SharePoint UI out of the box. The UI, as we all know, is not the end of our options. It might be the end of our easy options, but there are other ways to answer this requirement using the SharePoint Object Model and its ListFieldIterator class. There is an article in MSDN showing how to create a custom form using the ListFieldIterator class but, I have to say, the article is missing some more detailed explanation about how are things wired together in the background, it has a few typos, or omissions, and there are some assumptions that might make it difficult to just make it work if you don’t have them clear in your mind.

I read somewhere that there are thousands of blog posts talking about SharePoint development, but if you’ve had to look for information in several places in order to make something work, then that’s going to be a good blog post, at least for your own reference. This is the main reason why I started my blog in the first place. Just to sort out my own mess :)

So, I will stop talking and get down to business.

Creating our own ListFieldIterator version
The first part of the mentioned MSDN article is useful. This is more or less a transcript of the easy steps to create a class library project, add a class inheriting from ListFieldIterator and registering to the GAC using the gacutil.exe
  • In Microsoft Visual Studio, click File, point to New, and then click Project.
  • In the New Project dialog box, select the language for your project in the Installed Templates box, select Class Library as the project template, type a name and location for building the project, and then click OK.
  • To add a reference to the Microsoft.SharePoint assembly, right-click the project in Solution Explorer, and on the .NET tab in the Add Reference dialog box, select Microsoft SharePoint, and then click OK.
  • To give your custom assembly a strong name when you build the project, right-click the project in Solution Explorer, click Signing, select Sign the assembly, and specify a name for the strong name key file.
  • Double-click the project .cs or .vb file in Solution Explorer, and add the code below to define a class for a custom control that extends a SharePoint Foundation control. I have simplified even more the new CustomListFieldIterator class in order to check just if the given column is called “AdminOnlyColumn” and if the current user is not an Administrator. If both are true then that means that the column needs to be hidden in those forms.

  • Press CTRL+SHIFT+B to build the solution.
  • Use gacutil.exe, the command-line utility that is installed with the Microsoft .NET Framework 2.0 Software Development Kit, to copy the project DLL to the Global Assembly Cache (GAC). Type a command like the following at the command prompt: gacutil.exe -if "<Full file system path to DLL>".
From this point on the MSDN article starts to have some inconsistencies that make it a bit more difficult to understand or even to make it work. So I will write below something that works step by step.


Everything is a template
SharePoint renders a lot of its content based on templates. Templates are good to be re-used in several places across the platform and managed centrally. So, in order to have an insight on those templates, and to follow the example below, you need to access the DefaultTemplates.ascx file located in %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\CONTROLTEMPLATES



Now that we have the file open, we can have a look and see how all SharePoint forms, fields and functionality is dynamically constructed based on those templates. This is really interesting in order to get to know the SharePoint platform and how it renders its content to the end user. You could even think where you could insert your custom functionality by replacing existing Rendering Templates or parts of them.

As I mentioned earlier, we want to hide a column if the column name is “AdminOnlyColumn” and the current user is not an administrator. We want this behaviour only in Custom List Forms. The MSDN example is trying to do something similar on Document Libraries. If you take a look within the DefaultTemplates.ascx and look for this section:


<SharePoint:RenderingTemplate id="ListForm" runat="server">
 ….
</SharePoint:RenderingTemplate>

And within this section there is a line that is the one we will be replacing with our CustomListFieldIterator.

<SharePoint:ListFieldIterator runat="server"/>


Creating our Custom Rendering Template to override the List Form OOTB
As a quick overview we need to create our custom Rendering Template, using the same name as the one we want to override and, in this case, replace the ListFieldIterator line with our own. You also need to create a full trust project. This functionality cannot be leveraged with Sandbox solutions because we need to deploy files in the ControlTemplates SharePoint mapped folder.


It is necessary now to grab the complete “ListForm” Rendering Template and paste it within the CustomListForm.ascx control. You can have a look at the full ascx code below but the important lines are these:

The MSDN Article forgets to mention about registering the following namespaces. They are necessary to make this work.

<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="~/_controltemplates/ToolBar.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="~/_controltemplates/ToolBarButton.ascx" %>

We also need to register our CustomListFieldIterator. Remember that we have deployed that class to the GAC. So you would need to update this line with your class assembly name.

<%@ Register Assembly="CustomOverrideControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4479b423e93a57f0" Namespace="CustomOverrideControls" TagPrefix="CustomOverrideControls" %>

And last but not least, we need to replace the OOTB ListFieldIterator line with our custom one.

<CustomOverrideControls:CustomListFieldIterator ID="CustomListFieldIterator1" runat="server"/>

Here the complete and fully working ascx code. 

Once you have deployed the solution, do the recommended iisreset and if everything has been wired together properly, when you create a custom list, add a few columns and you name one of them AdminOnlyColumn, you will see something similar to:

An Administrator can see the AdminOnlyColumn…




A test user, with just visitor access rights, cannot see the column when the form appears. This column will not be visible in any item form (New, Update, Display…)



Where do we go from here?
Well, there are other options to do something like this, such as 3rd party tools, custom webpart forms or even to use infopath, but this is a simple solution that will work with all forms out of the box without doing anything else than selecting who can see which fields… or better said… who cannot see which fields, because you return true when the field should be excluded. You could also store in the Web Property bag which fields should be hidden and the conditions to do so. Well, the options are quite limitless and now we have another tool to answer specific requirements.

I have also put together an option to show fields in Read-Only Mode... you can check it here!

27 comments:

  1. I am very interested in your post because it is more interesting and useful for me. Thanks for share this post.

    ReplyDelete
  2. Great! I'm glad it helps other people as well. I will keep posting articles in the future... thanks for reading it!

    ReplyDelete
  3. Hello. Thank you for great article but is possible to add this only for NEW form. Or how can you setup this for every form unique. So I want that this field is visible in display form for every one but in edit form only for admin. Hmm.... Thank You.

    ReplyDelete
    Replies
    1. Hi (to the last comment author)

      Yes it is possible. Check below the if and I have added the appropriate validation that will hide the field when it is in display mode but visible in new and edit. I'm sure you can take it from there :)

      if (field.Title == "AdminOnlyColumn" && (site.CurrentUser.IsSiteAdmin == false || SPContext.Current.FormContext.FormMode==SPControlMode.Display))
      {
      return true;
      }

      Delete
    2. Hello. Thank you very much for your reply. I am testing this right now for a day but still this step by step doesn't work for me.
      1. I have created class library and installed to gac but to .net 4.0 2. after that I have created new sharepoint empty project and created new ascx file and deployed to sharepoint.
      3. I have changed in DefaultTemplates.ascx
      to this line

      But I am not sure if this step is correct. Do I need to changed something in DefaultTemplates.ascx?
      I am not sure what I am doing wrong because I have created dll and installed it after that I have created ascx control and deployed it to sharepoint but still this not work for me and I am not sure if the 3rd step is ok...Hmm... Thank you very much for you help
      Enti

      Delete
    3. Hi Enti,

      A few things you need to make sure:

      1- Never to touch any file in the 14 hive. That includes DefaultTemplates.ascx

      2- You need to create the class library project using .net 3.5 because SharePoint 2010 is not .net 4

      3- Do an IISReset after you have deployed both projects (The dll and the ascx control as it is in the article)

      4- Create a Custom List and name a column "AdminOnlyColumn"

      5- Enjoy the running code and improve it to answer your specific requirements :)

      Delete
  4. Hello. Thank you very much. Tommorrow I start it again from begining:) But I have one little question when I look into my DefaultTemplates.ascx after this line



    I have there different code like you. I have checked your code in example but my code is http://www.sharepointguys.eu/Default.txt So I have created new ascx from my code not from you code. Is this OK?

    And I don't understand why you have such code:


    what is this:
    Hmm I don't have such code in my original code...

    Sorry for asking so many questions but I really want to understand all your code...

    BTW I have tested one thing: I have removed all code from DefaultTemplates.ascx and restared IIS and evrything in sharepoint was still working:):):) Again thank you very much for your help.

    ReplyDelete
    Replies
    1. Hi,

      I cannot answer specific questions about your code or why it is not working. It would be too difficult to troubleshoot it this way.

      All the answers are in this article because it is a simple step by step of what needs to be done. I would recommend you just follow this without mixing it with your code and, once it is working, start to adapt it to answer your requirements.

      You keep saying things like "removing code from DefaultTemplates.ascx" and I never said you had to change anything in that file. That file can help you understand how SharePoint wires things together in the background, using templates, but you shouldn't change anything in there, or in any other file in the 14 hive.

      Your txt file is using the SharePoint:ListFieldIterator out of the box, you need to replace that line, as I say in the article, with your custom iterator class.

      Good luck!

      Delete
    2. Hello. Thank you very much for answeres but I am sorry I have tested this for three times without any success. I have created dll:
      http://www.sharepointguys.eu/CustomOverrideControls.zip
      in .net 3.5 and installed it with gacutil. Result: http://www.sharepointguys.eu/1.png
      After that I have created ascx file with my new template:
      http://www.sharepointguys.eu/CustomListFormFields.zip
      Restarted the IIS and created new custom list form with field AdminOnlyColumn but without any result field is still there for all accounts.
      I try to debug the class library but breakpoints are never reached...
      I am not sure what I am doing wrong... Thank you for this article and any help. enti

      Delete
    3. Hi Enti,

      I think you are in one of those cases where IIS and SharePoint caches are playing against you. This is not really the scope of the article. Follow this approach as the last try:

      1- Retract all your solutions. Uninstall the dll from the GAC and make sure it is not there. Also retract your custom ascx control template
      2- Delete the site collection
      3- Do an IISReset
      4- Deploy your solutions, first the one going to the GAC and second the custom ascx control template
      5- Do an IISReset
      6- Create a new Site Collection
      7- Create a new custom list with an AdminOnlyColumn

      If you follow these steps and it is still not working... I can only think of you getting some good drinks and start banging your head against the SharePoint wall... I heard somewhere that if you have not banged your head against a wall, until you start bleeding, with a SharePoint development problem is because you have not developed long enough with this platform.

      Just keep trying different things until you make it work because it is just a cache issue.

      Cheers!

      Delete
    4. Hello. Thank you for all your help. I have found what was the problem. On my enviroment I have only Visual Studio 2010 with .net 4.0 and so when I use gacutil.exe the dll was everytime installed to .net 4.0 GAC (But The project was .net 3.5) Right now I have installed the MS SDK .NET 2.0 with gacutil.exe. And right now everything is working without any problem. Again Thank you very much for this HELP!!!
      enti

      Delete
    5. Hi Enti,

      I'm glad you found the issue. I assumed you were always using .net 3.5 from start to end. This is one of those gotchas that you will never forget :)

      Great that the article and my comments helped you out and you got it running!

      Delete
  5. Thanks for sharing your post, will this security be applied if SharEpoint list is accessed viz web-service, Or if I open the list in Ms access, will this security work Or any other way?

    ReplyDelete
    Replies
    1. Hi Azra,

      This method will only work when our CustomListFieldIterator will be called. This will happen when the ListForm template is used (Display, New and Edit) but if you access the list item from Access or from the SharePoint WebServices, this template is not used.

      You would need to think about another approach, such as limiting MS Access on the list, creating a custom list form or even separating the list in two and linking them with lookups... SharePoint is not providing security at column level and therefore there is not a single solution to cover every SharePoint platform functionality.

      Delete
  6. Thanks for the article. Is possible with this procedure somehow create readonly fields? Hmm... Thank you
    Vladimir

    ReplyDelete
    Replies
    1. Hi Vladimir,

      This procedure is valid to determine whether a field will be displayed or not in a form. You could either filter the field to be shown only in display mode using SPContext.Current.FormContext.FormMode==SPControlMode.Display or, if that doesn't work, then create a custom field type and force it to be read-only in all SharePoint Modes (Display, Edit, New)

      Delete
    2. Hi Vladimir,

      I was reading comments in my blog and I saw that my answer to your question could be more accurate. There is another way to do it and that would be overriding the CreateChildControls method. I've created a quick post explaining that because I thought it would be interesting to keep a note of this option.

      http://www.helpmeonsharepoint.com/2012/02/column-security-level-in-sharepoint-do.html

      Delete
  7. Can you add another post for the other option which creates a custom web part for column level security?

    Thanks.

    ReplyDelete
  8. This didn't work for me... I had a couple questions:
    Do I have to do an " Typically, in my VB world (I know) I have to reference the namespace with the RootNamespace.NamespaceInCode format, but I don't see what your root namespace is here.

    Thanks again, I'm just trying to wrap my head around this...

    ReplyDelete
  9. Hi Josh,

    I don't know if I understand your question correctly but I'm already using the root namespace for that project... CustomOverrideControls

    I created a different projects for the ListFieldIterator class and the ascx template to override.

    I hope it helps!

    ReplyDelete
    Replies
    1. I wouldn't understand it either -- it got truncated all goofy. I will try to rewrite them without any markup.

      When I've done projects like this in the past and set the root namespace (project properties) to Value1 and the Namespace (in the .vb code file) to Value2, I've had to reference the namespace as Value1.Value2 ... however, in your Register Assembly node (at top of ascx file), you set Namespace to just CustomOverrideControls - which is the same name as the Assembly itself. Using my example, would I set my Register Assembly node (at tope of the ascx file) to Value2 instead of Value1.Value2?

      Another question I had is... how does SP know to use my ascx file at all? Does SP just load in every ascx it finds in the CONTROLTEMPLATES directory? If so, I would prefer to plop my files in a subdirectory; would this get picked up as well? When it does get "picked up", how does SP differentiate between the two renderingtemplates(e.g. my ascx and the DefaultTemplates.ascx)? How does it know which one is preferred? I understand the idea of using the same ID of SharePoint:RenderingTemplate as the one in DefaultTemplates.ascx, but I don't see where I specify it to be preferred over the OOTB, DefaultTemplates.ascx definition. This piece also doesn't seem to be working for me.

      I originally had a third question, but can't for the life of me remember it now. Thank you again for your prompt response.

      Josh

      Delete
    2. Hi Josh,

      Quick answers to your questions:

      1- If your namespace within the code is Value1.Value2 reference it in that way. It could be something like this Register Assembly="Value1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4479b423e93a57f0" Namespace="Value1.Value2" TagPrefix="CustomOverrideControls"

      But it would be important for you to test with your specific binaries.

      2- Quick answer to this one. SharePoint loads all controls within the Templates Site alphabetically and uses the first one that has the named template. For instance, if your ascx file was called ZZTopControl.ascx, you wouldn't see your template because the system will use DefaultTemplates.ascx...

      I know what you might be thinking... this is not a real bullet proof approach... but that's how it works. Name your ascx controls creatively and they should be the first ones loaded and displayed. Even before any other 3rd party tool :)

      Good luck with it!

      Delete
    3. Re #1 - Gotcha. I'll figure that one out.

      Re #2 - That would explain why it didn't work for me. My acsx started with an "R". I see what you're saying about the weirdness of its approach, but at least it makes sense. Subdirectories are no good then? I will do it without, first, then mess around with trying to organize it.

      Thank you again - I will try that all out now... Great article!

      Delete
    4. Worked like a charm, thanks. Now I just have to figure out how to hide it from listviews (and Access/Excel interaction)... google.com, here we come.

      Delete
    5. Great! Thanks for reading and commenting the article... you could post in here what you did to sort out those two options you are "googling" now.

      A couple of workarounds... you could limit the access to a view with that "secret" field in it... and you could also disable the Access/Excel integration for that list. This is just to give you some other options. Not as clean as limiting the field individually but hey... SharePoint is rendering templates in alphabetical order and that's also not the most rocket-science piece of functionality either :)

      Delete
  10. this is a helpfull tools to secure columns access
    http://splistsecurity.codeplex.com/documentation

    ReplyDelete
    Replies
    1. Hi Anonymous! Thanks for sharing the link. I've downloaded the code and it is using the custom ListFieldIterator as described in this post. There are a lot more things done in this project. So I think I would recommend everyone trying to get the grips of SharePoint development to follow my post and once they understand how this piece works, then download the code from the codeplex solution and learn even more by checking how it's been implemented. This is a really healthy and useful exercise... thanks for sharing the link!

      Delete