Pages

Friday 24 January 2014

InfoPath: Restrict visibility to users in a SharePoint Group

I receive lots of questions about how to limit a Section, a Button, or a View to only users within a specific SharePoint Group.  This is absolutely do-able, but a little tricky.  The trick is to use the UserGroup.asmx web service, and specifically the GetUserCollectionFromGroup method.
If you're familiar at all with the way that the Secondary Data Connections work in InfoPath, you will realize that the web service will pull in one or more resultant fields into an XML format structure that you can then retrieve with XSL - in other words, the data structure returned looks like and acts like the same as your Primary data source.  You can view secondary data sources by going to the Data Source Pane (Fields Pane in 2010), and clicking the dropdown at the top, and choosing your secondary data source.
 Data Source Dropdown 2007
Data Source Dropdown - InfoPath 2007
 Fields Dropdown 2010
Fields Dropdown - InfoPath 2010
The problem with the GetUserCollectionFromGroup web service, or more specifically with InfoPath itself (both 2007 and 2010) is that the data fields don't get pulled in correctly.
Using the references I list at the bottom of this post, I learned how to fix this.  However, I was hard pressed to find anyone who took it the final step and listed how to hook up the Button, Section, or View with Conditional Formatting and/or Rules to make it all happen.
So here I will put it all together for you.  Please let me be clear: the credit for the fixing of the service goes to Ian Chivers; I did not come up with this myself.  However I will list it here to make it easier for you to get this all in one post.
[EDIT - From here, I'm changing around the rest of my post based on trial and error. It requires more work, but is much more reliable.]

UserProfileService.asmx Web Service – GetUserProfileByName method

The first thing you need to do before you fiddle with getting the Group’s users, is to get the actual account name of the user that’s currently logged in. You need to do this because that is what you will use to compare against the list of users that come in from the Group.

Create the Data Connection

Steps for InfoPath 2007
 
Steps for InfoPath 2010
  • Open the Data Source Tool Pane.  If it is not open, go to View --> Task Pane and click "Data Source"
  • On the very bottom of the Data Source task pane, click "Manage Data Connections ..."
  • Click "Add..."
  • Click "Create a new connection to:" and choose "Receive Data"
  • Click "Next"
  • Click "Web service"
  • Click "Next"
  • Type in the url to your UserProfileService.asmx service.  It should be on your site you wish to get the group from.  Example:
    https://YourURLToServer/YourPath
    /YourSite/_vti_bin/UserProfileService.asmx
  • Click "Next"
  • You may have to authenticate at this point
  • Scroll down the list of methods and choose "GetUserProfileByName"
  • Click "Next"
  • You do not need to type in an Actual Value
  • Click "Next"
  • Click "Next"
  • Give the data connection a new name or keep the default, and make sure the checkbox is checked to automatically retrieve the data
  • Click "Finish"
 
  • Click the "Data" Ribbon Tab
  • Click "From Web Service"
  • Click "From SOAP Web Service"
  • Type in the url to your UserProfileService.asmx service.  It should be on your site you wish to get the group from.  Example:
    https://YourURLToServer/YourPath
    /YourSite/_vti_bin/UserProfileService.asmx
  • Click "Next"
  • You may have to authenticate at this point
  • Scroll down the list of methods and choose "GetUserProfileByName"
  • Click "Next"
  • You do not need to type in an Actual Value
  • Click "Next"
  • Click "Next"
  • Give the data connection a new name or keep the default, and make sure the checkbox is checked to automatically retrieve the data
  • Click "Finish"

Set up the first Dummy Field

Create a new field called CurrentUserAccountNameof type String.
  • Show the correct Pane
    • In InfoPath 2007, click to show the Data Source pane on the right-hand side
    • In InfoPath 2010, click to show the Fields pane on the right-hand side
  • Right-Click on the myFields or Top-Level data group (folder).
  • Click Add...
  • Type in CurrentUserAccountName for the field name
  • Keep the defaults of Field for Type and Text (string) for Data Type.  Press OK.

Set up the first Form Load Rule

Now we’re going to set a Form Opening (Form Load) rule so that we can populate the CurrentUserAccountName field. Keep in mind that the Form Opening rules will run after all the data connections retrieve their data (assuming you checked the checkbox to load the data as soon as the form opened).
In InfoPath 2007, you can get to this by selecting Tools --> Form Options --> Open and Save --> Open behavior --> Rules. In InfoPath 2010, you get to this by clicking the Data ribbon tab and then clicking “Form Load.”
  • Create a new Rule (Action rule in 2010)
  • Name the rule “Set Current User”
  • Do NOT set a condition – we want this to run every time the form loads
  • Add an action – “Set a Field’s Value”
  • For the field to set, Choose CurrentUserAccountName
  • For the value, press fx
  • Click “Insert Field or Group...”
  • On the top, press the dropdown box next to the word “Main”. Choose “GetUserProfileByName (Secondary)” .
  • Expand dataFields all the way out.
  • Expand Values all the way out.
  • Single-Click on Value – DO NOT PRESS OK
    GetUserProfileByName
  • Click “Filter Data...”
  • Click “Add...”
  • Click the first dropdown and select “Select a Field or Group”
  • Choose the field “Name” right underneath “PropertyData”
    GetUserProfileByName
  • Press OK
  • Keep “is equal to” in the second dropdown
  • Choose “Type text…” in the third Dropdown
  • Type in:
    AccountName
  • Press OK, Press OK (filter data), Press OK (select a field or group)
  • Your expression should look like the following:
    Insert Formula
  • Modify this function to make it all uppercase
    • Use the translate function to ensure that the AccountName that comes back is in all UpperCase
    • Formula is like this:
      translate(ValueYouWantToTranslate, "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
      Insert Formula
    • Click “Verify Formula” to ensure you typed in the function correctly.
  • Press OK, and press OK

UserGroup.asmx Web Service – GetUserCollectionFromGroup method

Now we need to pull in the list of users that are in the Group in question – the SharePoint group you set up with the people authorized to click that button.
Steps for InfoPath 2007
 
Steps for InfoPath 2010
  • Open the Data Source Tool Pane.  If it is not open, go to View --> Task Pane and click "Data Source"
  • On the very bottom of the Data Source task pane, click "Manage Data Connections ..."
  • Click "Add..."
  • Click "Create a new connection to:" and choose "Receive Data"
  • Click "Next"
  • Click "Web service"
  • Click "Next"
  • Type in the url to your UserGroup.asmx service.  It should be on your site you wish to get the group from.  Example:
    https://YourURLToServer/YourPath
    /YourSite/_vti_bin/UserGroup.asmx
  • Click "Next"
  • You may have to authenticate at this point
  • Scroll down the list of methods and choose "GetUserCollectionFromGroup"
  • Click "Next"
  • Click "Set Sample Value..."
  • Type in the name of the group you want to check against and press "OK"
  • Click "Next"
  • Click "Set Value..."
  • Again type in the name of the group you want to check against and press "OK"
  • Click "Next"
  • Click "Next"
  • Give the data connection a new name or keep the default, and make sure the checkbox is checked to automatically retrieve the data
  • Click "Finish"
 
  • Click the "Data" Ribbon Tab
  • Click "From Web Service"
  • Click "From SOAP Web Service"
  • Type in the url to your UserGroup.asmx service.  It should be on your site you wish to get the group from.  Example:
    https://YourURLToServer/YourPath
    /YourSite/_vti_bin/UserGroup.asmx
  • Click "Next"
  • You may have to authenticate at this point
  • Scroll down the list of methods and choose "GetUserCollectionFromGroup"
  • Click "Next"
  • Click "Set Sample Value..."
  • Type in the name of the group you want to check against and press "OK"
  • Click "Next"
  • Click "Set Value..."
  • Again type in the name of the group you want to check against and press "OK"
  • Click "Next"
  • Click "Next"
  • Give the data connection a new name or keep the default, and make sure the checkbox is checked to automatically retrieve the data
  • Click "Finish"

Take a look at what has been returned for Web Service

Remember in the other web service, that when we went to get the value for the Account Name, it presented us with information we had to drill down to. That was correct. However, for whatever reason, the GetUserCollectionFromGroup web service does not return the correct information. Let’s take a quick look at what it returns:
GetUserCollectionFromGroup
What we have to do here to fix this, is we have to actually manipulate the source files of the InfoPath form.

Create a Folder for your Source Files

On your file system (C Drive or Desktop for example), navigate to where you have saved your InfoPath form template. Create a new empty folder by the name of “TemplateName Source Files”. So if your Template Name is "Training Request Form” then name the folder "Training Request Form Source Files.”

Save as Source Files

In InfoPath 2007, Click the File menu, and click “Save As Source Files”. In InfoPath 2010, click the File ribbon tab, click “Publish” on the left hand side, and then click “Export Source Files”. Save the source files in the folder you just created. Close InfoPath completely.

Fix the GetUserCollectionFromGroup1.xsd file

Open the folder you created with the source files in it. You should see quite a few files in this folder. Using NOTEPAD, open the file named GetUserCollectionFromGroup1.xsd (choose GetUserCollectionFromGroup.xsd, GetUserCollectionFromGroup1.xsd, or GetUserCollectionFromGroup2.xsd based on which is the file with the greatest file size. This is usually the GetUserCollectionFromGroup1.xsd file).
At the top of the file, locate the following line:
<s:import namespace="http://www.w3.org/2001/XMLSchema"></s:import>
Right underneath that line, insert the following code:
<!-- Beginning of insert -->
<s:complexType name="GetUserCollectionFromGroupType">
 <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" name="Users">
   <s:complexType>
    <s:sequence>
     <s:element maxOccurs="unbounded" name="User" >
      <s:complexType>
       <s:attribute name="Notes" type="s:string"></s:attribute>
       <s:attribute name="Name" type="s:string"></s:attribute>
       <s:attribute name="IsSiteAdmin" type="s:string"></s:attribute>
       <s:attribute name="Sid" type="s:string"></s:attribute>
       <s:attribute name="ID" type="s:string"></s:attribute>
       <s:attribute name="LoginName" type="s:string"></s:attribute>
       <s:attribute name="Email" type="s:string"></s:attribute>
       <s:attribute name="IsDomainGroup" type="s:string"></s:attribute>
      </s:complexType>
     </s:element>
    </s:sequence>
    </s:complexType>
  </s:element>
 </s:sequence>
</s:complexType>
<!-- End of Insert -->

[EDIT:  Please make sure there is no space in the above </s:complexType>]

Now find the following code:
<s:element name="GetUserCollectionFromGroup">
  <s:complexType>
    <s:sequence>
      <s:element minOccurs="0" maxOccurs="1" name="groupName" type="s:string"></s:element>
    </s:sequence>
  </s:complexType>
</s:element>

Comment out that code by placing a <!-- before the beginning and an --> after the end.  Result should look like this:
<!--<s:element name="GetUserCollectionFromGroup">
  <s:complexType>
    <s:sequence>
      <s:element minOccurs="0" maxOccurs="1" name="groupName" type="s:string"></s:element>
    </s:sequence>
  </s:complexType>
</s:element>-->

Right underneath that commented out code, insert the following code:
<!-- Beginning of Insert -->
<s:element name="GetUserCollectionFromGroup" type="tns:GetUserCollectionFromGroupType" />
<!-- End of Insert -->

Save and close the file
Go back to the folder where you saved those Source Files to.  Find the file "manifest.xsf" and RIGHT-click the file and choose "Design".  This will open your InfoPath file back up.  Click SAVE AS and save as a new .xsn file.

A few Notes to keep in mind, before we go on

It is important to note the following 2 things:
  • Don't modify your GetUserCollectionFromGroup secondary data source (or if you do, you'll have to do this all over again, from the part where you open up the files).
  • Make sure the group in question allows "Everyone" to see the membership of the group
    • Site Actions --> Site Settings --> People and Groups
    • Click on "Groups" on the left
    • Locate your Group
    • Click on the Edit button next to it
    • For "Who can view the membership of the Group" click the option "Everyone"

Take a new look at the fields for the Web Service

Let's take a quick look at what it returns now:
GetUserCollectionFromGroup Modified
These are the fields that we need.

Set up another dummy Field

Create a new field called IsCurrentUserInGroupof type True/False (boolean).
  • Show the correct Pane
    • In InfoPath 2007, click to show the Data Source pane on the right-hand side
    • In InfoPath 2010, click to show the Fields pane on the right-hand side
  • Right-Click on the myFields or Top-Level data group (folder).
  • Click Add...
  • Type in IsCurrentUserInGroup for the field name
  • Keep the defaults of Field for Type
  • Change the Data Type to True/False (boolean). Keep the default value equal to FALSE.
  • Press OK.

Set up another Form Load Rule

Now we’re going to set a Form Opening (Form Load) rule so that we can populate the IsCurrentUserInGroup field.
In InfoPath 2007, you can get to this by selecting Tools --> Form Options --> Open and Save --> Open behavior --> Rules. In InfoPath 2010, you get to this by clicking the Data ribbon tab and then clicking “Form Load.”
  • Create a new Rule (Action rule in 2010)
  • Name the rule “Set Current User”
  • Do NOT set a condition – we want this to run every time the form loads
  • Add an action – “Set a Field’s Value”
  • For the field to set, Choose IsCurrentUserInGroup
  • For the value, press fx
  • Click “Insert Field or Group...”
  • On the top, press the dropdown box next to the word “Main”. Choose “GetUserCollectionFromGroup (Secondary)” .
  • Expand dataFields all the way out.
  • Single-Click on LoginName. DO NOT PRESS OK.
    Select Field or Group
  • Press “Filter Data...”
  • Click “Add...”
  • In the first dropdown, keep “LoginName”
  • Keep the second dropdown at “is equal to”
  • Click the third dropdown and click “Select a Field or Group”
  • Change the Data Source (top dropdown) back to Main
  • Select the CurrentUserAccountName field
  • Press OK, press OK (filter Data), press OK (select a field or group)
  • Do not close the window “Insert Formula”. It now looks like this:
    Insert Formula
  • Add the boolean and count functions, to turn this into a True/False
    • We’re going to type in the following:
      boolean(count(expression) > 0)
    • So for our example, it should now look like this:
      Insert Formula

Modify the XPath Directly to set to Upper Case

Unfortunately, this does not give us exactly what we want, but it gets us really close. We’ll use what we currently have to change the XPath expression directly into what we need it to be.
  • Click the checkbox “Edit XPath (advanced)”
  • You will now see something like this:
    Insert Formula
  • Notice how I highlighted the period (.) in the picture above, right after @LoginName[
  • We’re going to put the translate function around that period:
    translate(., "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
  • So it should now look like this:
    Insert Formula
  • Click the checkbox again “Edit XPath (advanced)”
  • This will take it out of advanced mode and give you a little more simplified of a view.
  • It should read something like
    boolean(count(@LoginName[translate(., "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ") = CurrentUserAccountName]) > 0)
To recap, what this function is saying is this:
  • Go get the @LoginName from the web service
  • Translate it all to Upper Case
  • Find the number of times that the Upper Case LoginName is equal to the CurrentUserAccountName field
  • If it’s greater than 0 times, return true
  • Change the “true” word to a Boolean value

Set up the condition on the Button

And finally after all of that trouble, we use a reasonably simple Conditional Formatting condition to get the button to Hide when the current user is not part of that group.
  • In InfoPath 2007, right-click the button and choose Conditional Formatting
  • In InfoPath 2010, click Manage Rules in the Home ribbon tab, and add a new “Formatting” rule
  • Set the Condition
    • In the first dropdown, click “Select a Field or Group”
    • Choose “IsCurrentUserInGroup” from the Main data source
    • Keep “is equal to” in the second dropdown
    • Choose “FALSE” in the third dropdown (remember, we’re HIDING the button when the user is NOT in the group)
    • Click OK
  • Set the Formatting
    • Click “Hide this control”

Test the functionality

We need to make sure that we test the functionality. Even if you are the administrator for the site, you won’t see the button if you’re not in that group, so you can effectively test.
  • Publish the form to your SharePoint Site. Ensure that the Forms Library is set to serve up the form as a Web Page.
  • Put yourself INTO the group you set up in the form
  • Test the form by clicking New and switching views – do you see the button? You should.
  • Close the new InfoPath form.
  • Take yourself Out of the group you set up in the form
  • Test the form by clicking New and switching views – do you see the button? You should not.

1 comment:

  1. Hi Karthik,
    Ur post is very useful to me.You save my time.
    Thanks a lot.
    Keep going on....

    ReplyDelete