SharePoint security should have

A lot is based on security in SharePoint but it is basic at best.

In order for items to be available or not based on who the user is, the essence of security, an administrator needs to administer a lot.

  1. Create groups and populate them. (from external systems like AD or one by one)
  2. In the item location (site, list) assign rights to the groups. Sometimes the assignment is to the item itself (not recommended)

There is administration involved when new users are added to the organization (in SharePoint or outside) to match the existing groups.

Most organizations use the profile service provided by SharePoint, where we can have properties that depict attributes the user has in the organization. The only way to use these attributes, is to create audiences to be used in web parts. And even audiences are created by comparing property values to static values.

So for instance, if you wanted an audience by location and the organization has 1000 locations. You will need to create 1000 audiences and add a new one whenever the organization adds a location. Not very practical.

What if we also had dynamic security rules that match profile properties?

Let’s dream a new SharePoint security feature.

  1. Say we can define a rule at the level of a group. So we can assign rights to the rule rather than the group.
  2. The rule will be constructed as a logical function that compares a property value the user has to a column value the item has.
  3. The compare should be flexible. (Equal, not, in (…), contains (), begins with etc…)
  4. If the rule evaluates to true, the assigned rights will be respected.
  5. The end result is basically SharePoint performing CAML query tailor-made to the current user.

As developers, we do similar things when we customize SharePoint for our organization, but our reach is limited to what we create.

Take the location example from before. If we have a content type with a location column (even multi value), we can now filter a web part to show the user only items whose location column contains the user’s location. But we can’t prevent an item from showing in unrelated search by a user of a location other than the item has.

If we had rule based security on the list or even the content type, (yet another security idea) we would not have to wary about search showing the user what he should not see without creating groups for each location.

 

Microsoft! Please give us rule based security in the next version of SharePoint!

 

 

 

Posted in Uncategorized | Leave a comment

Custom site provisioning provider

Up until a week ago, I thought the only 2 ways to make a template for a site where Site definition and Site Template.

Both ways required that you figure out a lot of XML syntax, feature ids lists ids etc… for any code you want to run on the created site, you need to staple another feature and if you need to run code after the site is provisioned, you need to spawn another thread that will sleep until the site is provisioned. Possible but not very intuitive or easy.

Then when I needed to create a custom publishing site to be used as a template for sub sites, I found custom site provisioning provider while reading this post.

I will probably never code another site template any other way from now on.

The example shows how to do it with run as system but it is not necessary because templates are used to create sites by users with full control so they will have enough rights to run the code. The downloadable code is a good start though.

Using custom site provisioning provider is mainly implementing the SPWebProvisioningProvider interface.

All the tasks are performed in in the: public
override
void Provision(SPWebProvisioningProperties props)

The first thing to do is to apply a standard template to the new site so we have something to start with.

I find it best to start with “STS#1”, the blank template. This gives you an empty provisioned site to add whatever is needed by the requirements. Once a template is implemented with “props.Web.ApplyWebTemplate(SITE_TEMPLATE);” the site is provisioned, so you don’t need to spawn threads and such to work with the created site, it is ready to be modified.

Little XML after all

It will not be SharePoint solution without some xml for good measures right? So here is the minimum.

In a module named “ProvisioningData” add a file named as your project with the features that will be used in the site (Later I will show how to find out the GUIDs for the features)

In the {SharePointRoot}\Template\1033\XML folder , we need a file named webtemp_{your project name}.xml

It will include the name, title, description, image URL, Display category and a reference to the provisioning data.

The Display category and the image URL will allow distinguishing your template in the UI

Image, Category and description

Adding features

The example shows how to iterate the provisioning data for features but they can be added directly in the code too, using the ID.

For the site site.Features.Add(new
Guid({the ID of the feature}));

For the sub site web.Features.Add(new
Guid({the ID of the feature}));

Make sure you know the scope the feature is for and not to add a feature that exists in the scope alread. (Activated)

Adding Lists

What can be better than figuring IDs of list types? Intellisense off course. Adding lists is as simple as this:

You can add fields to your lists at the same time to further your customization without figuring out the XML syntax like building ONET.XML. Again Intellisense is much easier

Adding pages

Pages can be added as files to a module; text can be changed to customize the particular provisioning (for example, embed in the text, the site title or instructions for the administrator) then added to the root or a folder in the site.

See method “AddPage” in the attached example.

Adding web parts in pages

If we started with a blank site, the web parts are not incorporated in the pages yet.

So we have the opportunity to insert web parts in the zones we want in our pages. We must know the zone IDs of the page and the order in the zone we want the web parts to go in.

There are few types of web parts; the example uses 2 of them:

  1. WebPart for this type you need the existence of the template to be in the web part gallery of the site collection (activated)
  2. ListViewWebPart this will show an existing list or library with a view.

Controlling Navigation

We also have the opportunity to customize the top and quick launch links. The example shows the publishing navigation because the template has the publishing feature but the same can be done with none publishing only the classes will be from Microsoft.SharePoint.Navigation Namespace rather than publishing.

We can:

  1. Programmatically decide how pages and sub sites will be referenced.
    1. “Global” refers to the top navigation
    2. “Current” refers to the quick launch.
  2. Programmatically remove the headers (Libraries, Lists etc…)
  3. Programmatically reorder the links and add new custom ones.

 

Finding GUIDs of features

I found out from a utility that gives you the name by GUID of feature in CodePlex that you can enumerate the Feature Definitions of a farm like so:

SPFarm oFarm = SPFarm.Local;

foreach (var oFeatureDev in oFarm.FeatureDefinitions)

{

System.Diagnostics.Debug.WriteLine(string.Format(“Feature {0} Id= {1} scope {2} ”

, oFeatureDev.DisplayName, oFeatureDev.Id ,oFeatureDev.Scope));

 

So you can get a dictionary of name and ID of features that you can use.

You can also use PowerShell

 

 

The attached solution includes 2 types of provisioning projects, guess which of them I preferJ.

Posted in Uncategorized | Leave a comment

Filter by management hierarchy

 

Goal:

Allow filtering of list items based on the management position of the logon user.

Issue:

Often organizations need to allow managers to see list items or documents only for their own employees.

SharePoint’s security can use group membership or direct assignment to a user but has no usage to the organization structure.

Some organizations use workflow or event receivers to assign rights to an item when it is added to a list based on a custom process that checks the item’s user, his manager then his manager and so on then give only these users access to the item.

This method is flowed because of 2 reasons:

  1. The hierarchy of managers may change over time so at one point in the future, the rights to the items will not be correct. For example, a new manger will not be able to access an item that was entered for a person that reports to him because someone else was the manager when the item was entered to SharePoint.
  2. Applying detailed rights to each item causes SharePoint to create one user groups for each right and that adds a lot of data about one item which at the end reduces performance.

A preferred method would be to limit access to items based on the current management hierarchy.

Query the management hierarchy:

The assembly “Microsoft.Office.Server.UserProfiles” that works with the “User Profile Service Application” has a GetDirectReports() method in the UserProfile class that returns an array of UserProfile type. Business wise, this is all the known SharePoint users how report to a given user.

In order to get all the hierarchy of people who report to a manager and the people how report to them, we can use this method recursively all the way down to people how have no one reporting to them.

In a big tall Pyramid-type organization, this can be a very long list if we make it for managers close to the CEO level. So we can device a "how deep" parameter, that will limit the level under the person we query. This can be used in a wrapper method like:
				DirectReportsOfUser(string user, UserProfileManager upm, int howDeep, int currentDepth)    
				The method is calling itself until currentDepth = howDeep

The result is a list of "ReportTo" type (string User, int Level)
					

 

Using the query to filter a list:

For the filter to work, the list should have a field with user name denoting who the item is about or who created it.

If we want the results to include the logged on person too (not just people reporting to him) we need to add him to the list of “ReportTo“.

For the actual filtering we use CAMAL query syntax (see http://lushpedia.com/post/Alternative-of-In-Clause-for-CAML-Query-for-SharePoint-2007.aspx) only we build the “<Value Type=’Text’> Value1</Value>” items with the items in the list of “ReportTo“.

To use it we need to develop a web part to show the filtered items.

If and when SharePoint will evolve to combine user defined or programmed methods in views definitions, the methods will be useful in any view an administrator can come up with less need for a developer.

 

 

 

 

 

 

Posted in Uncategorized | Leave a comment

Anything Picker for SharePoint 2010

We are all familiar with the people picker used in SharePoint web parts and application pages to find and select single or multi users from AD or any form authentication provider. The control is actually built to inherit from a more general purpose control named “EntityEditorWithPicker”.

I will show how you can build a picker from any searchable source. The particular demo is for employees in a SQL table.

I learnt how to do this from http://www.chakkaradeep.com/post/SharePoint-Customising-the-EntityEditorWithPicker.aspx

So the steps to incorporate the custom control in SharePoint web parts and pages are:

  1. Create a class file in the same project as the web parts and in it, Extend the 3 classes
    1. public class EmployeeEditor : EntityEditorWithPicker //This will be the reusable control
    2. CustomQueryControl : SimpleQueryControl //settings for the popup control
    3. public class CustomPickerDialog : PickerDialog // settings for the tabular results show
  2. In the “CustomQueryControl” class we can customize the popup dialog with several “search by” options. I used Name and job title but based on the context of the usage, it can be anything. Each “search by” will need its own search method but the results should be the same columns. In my demo it is the ID of the employee, his full name and job title.
  3. The CustomPickerDialog class is just settings for the tabular results to show columns names and width.
  4. The EmployeeEditor class is where most of the work is done. It has methods for search unique or lists and determine entries validation. There is also mapping of the results fields to the entity fields (key, display text and description. I chose the employee ID to be the key
  5. Once the classes are generated and built, the control can be used in web part and pages code like any web control. private EmployeeEditor eeEmployeeToFind = new EmployeeEditor(); off course all the attributes of the base are available. For example, I used MultiSelect to allow single or multiple results.
  6. Since the control is not managed by the SharePoint’s project template, it will not get a “Safe control” line in the web.config file. And if you don’t add it yourself, you get:
    1. VS2010 will make these web.config entries
      1. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2.MultiEmployeeEdit” TypeName=”*” Safe=”True” SafeAgainstScript=”True” />
      2. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2.SearchEmployee” TypeName=”*” Safe=”True” SafeAgainstScript=”False” />
      3. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2.EditEmployee” TypeName=”*” Safe=”True” SafeAgainstScript=”False” />
      4. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2.TerminateEmployee” TypeName=”*” Safe=”True” SafeAgainstScript=”False” />
      5. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2.TransferEmployee” TypeName=”*” Safe=”True” SafeAgainstScript=”False” />
      6. </SafeControls>
  7. You need to add:
    1. <SafeControl Assembly=”Peters.Wirtz.HR.WebPartsI2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58b09c82ceb21b3d” Namespace=”Peters.Wirtz.HR.WebPartsI2″ TypeName=”*” Safe=”True” SafeAgainstScript=”False” />
  8. Off course replace “Peters.Wirtz.HR.WebPartsI2” with your namespace
  9. Here is the code :
using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Text; using Microsoft.SharePoint.WebControls; using System.Data; using Microsoft.SharePoint;
namespace Peters.Wirtz.HR.WebPartsI2 {     // delegate declaration     public delegate void WasValidatedHandler(object sender, EmployeeArgs e);       public class EmployeeEditor : EntityEditorWithPicker  //This will be the reusable control     {         public string FilteredByCompany { get; set; }         // event declaration         public event WasValidatedHandler WasValidated;
protected override void OnInit(EventArgs e)         {             base.OnInit(e);             PickerDialogType = typeof(CustomPickerDialog);         }         public override PickerEntity ValidateEntity(PickerEntity entity)  // Find exact match to entity.DisplayText         {             if (entity.IsResolved) //Was found before             {                 EmployeeArgs ea = new EmployeeArgs(entity.Key);                 return entity;             }             if (!string.IsNullOrEmpty(entity.DisplayText))             {                 //————My database search—————————-                 HRDataDataContext hr = new HRDataDataContext(SPContext.Current.Web.GetProperty(“HRConnectionString”).ToString());                 var employee = from x in hr.EmployeeMasters where (x.FirstName + ” ” + x.LastName).Equals(entity.DisplayText) select x;                 if (employee.Count() > 0)                 {                     entity.DisplayText = employee.First().FirstName + ” ” + employee.First().LastName;                     entity.Key = employee.First().ID.ToString();                     entity.IsResolved = true;                       //Mark as found                     EmployeeArgs ea = new EmployeeArgs(entity.Key);                 }             }             return entity;         }
protected override PickerEntity[] ResolveErrorBySearch(string unresolvedText) // find many close Machs         {             //————My database search ( Where xxxx- like ‘%the text%’————————–             HRDataDataContext hr = new HRDataDataContext(SPContext.Current.Web.GetProperty(“HRConnectionString”).ToString());             var matches = from x in hr.EmployeeMasters                           where (x.FirstName + ” ” + x.LastName).Contains(unresolvedText)                           orderby (x.FirstName + ” ” + x.LastName)                           select new PickerEntity                           {                               Key = x.ID.ToString(),                               DisplayText = x.FirstName + ” ” + x.LastName,                               Description = x.JobTitle,                               IsResolved = true,                           };             if (matches.Count() == 0)                 return null;             return matches.ToArray();         }
public string CommaSeparatedEmployees         {             get             {                 StringBuilder sb = new StringBuilder();                 foreach (PickerEntity entity in this.ResolvedEntities)                 {                     if (sb.Length > 0)                         sb.Append(“, “);                     sb.Append(entity.Key);                 }                 return sb.ToString();             }             set             {                 this.SetEmployees(value);             }         }
public void SetEmployees(string commaSeparatedIds) //Find By the IDs (keys) of the selcted items         {             if (!string.IsNullOrEmpty(commaSeparatedIds))             {                 string[] EmployeeIDs = commaSeparatedIds.Split(‘,’);                 if (!this.MultiSelect)                     EmployeeIDs = EmployeeIDs.Take(1).ToArray();                 ArrayList entities = new ArrayList();                 HRDataDataContext hr = new HRDataDataContext(SPContext.Current.Web.GetProperty(“HRConnectionString”).ToString());                 var exactMatches = from x in hr.EmployeeMasters where EmployeeIDs.Contains(x.ID.ToString()) select x;                 foreach (var employee in exactMatches)                 {                     PickerEntity entity = new PickerEntity();                     entity.Key = employee.ID.ToString();                     entity.DisplayText = employee.FirstName + ” ” + employee.LastName;                     entity.Description = employee.JobTitle;                     entity.IsResolved = true;                     entities.Add(entity);                 }                 this.UpdateEntities(entities);                 this.Validate();             }             else             {                 this.UpdateEntities(new ArrayList());                 this.Validate();             }         }
protected virtual void OnValidate(EmployeeArgs e)         {             WasValidatedHandler handler = WasValidated;             if (handler != null)             {                 // Invokes the delegates.                 handler(this, e);             }         }
}
public class CustomQueryControl : SimpleQueryControl  //settings for the popup control     {         public CustomQueryControl()         {             Load += CustomQueryControl_Load;         }
void CustomQueryControl_Load(object sender, EventArgs e)         {             if (!Page.IsPostBack)             {                 EnsureChildControls();                 mColumnList.Items.Add(“Name”);              //Here can determine the “Search by entities” you want to provide                 mColumnList.Items.Add(“Job Title”);         // You can have as many as you can figure searches for             }         }
protected override int IssueQuery(string search, string groupName, int pageIndex, int pageSize)         {             DataTable employeeTable = GetEmployeeTable(search, groupName);             PickerDialog.Results = employeeTable;             PickerDialog.ResultControl.PageSize = employeeTable.Rows.Count;             return employeeTable.Rows.Count;         }
private DataTable GetEmployeeTable(string search, string groupName)         {             DataTable dummyTable = new DataTable();             dummyTable.Columns.Add(“DBID”);             dummyTable.Columns.Add(“Name”);             dummyTable.Columns.Add(“Job Title”);             //————My database search—————————-             string connString = SPContext.Current.Web.GetProperty(“HRConnectionString”).ToString();             HRDataDataContext hr = new HRDataDataContext(connString);
// here you supply the search statement based on the user’s selection             var employee = (groupName == “Name”) ?                 from x in hr.EmployeeMasters where (x.FirstName + ” ” + x.LastName).Contains(search) select x :                 from x in hr.EmployeeMasters where (x.JobTitle).Contains(search) select x;
foreach (var item in employee)             {                 DataRow row = dummyTable.NewRow();                 row[“DBID”] = item.ID;                 row[“Name”] = item.FirstName + ” ” + item.LastName;                 row[“Job Title”] = item.JobTitle;                 dummyTable.Rows.Add(row);             }
return dummyTable;         }
public override PickerEntity GetEntity(DataRow dr)      //Here you match the entity’s fields to the data you want to use.         {             PickerEntity entity = new PickerEntity();             entity.DisplayText = dr[“Name”].ToString();         //I chose the employee full name to be the Display text             entity.Key = dr[“DBID”].ToString();                 //I chose the employee ID to be the key             entity.Description = dr[“Job Title”].ToString();             entity.IsResolved = true;                           // if it was found, no need to validate again so it is marked IsResolved             return entity;         }     }
public class CustomPickerDialog : PickerDialog     // settings for the tabular results show     {         public CustomPickerDialog()             : base(new CustomQueryControl(), new TableResultControl(), new EmployeeEditor())         {             ArrayList columnDisplayNames = ((TableResultControl)base.ResultControl).ColumnDisplayNames;             columnDisplayNames.Clear();             columnDisplayNames.Add(“DBID”);             columnDisplayNames.Add(“Name”);             columnDisplayNames.Add(“Job Title”);             ArrayList columnNames = ((TableResultControl)base.ResultControl).ColumnNames;             columnNames.Clear();             columnNames.Add(“DBID”);             columnNames.Add(“Name”);             columnNames.Add(“Job Title”);             ArrayList columnWidths = ((TableResultControl)base.ResultControl).ColumnWidths;             columnWidths.Clear();             columnWidths.Add(“0%”);             columnWidths.Add(“30%”);             columnWidths.Add(“70%”);         }     }
//// custom attributes     public class EmployeeArgs : System.EventArgs     {         private string key;         public EmployeeArgs(string m)         {             this.key = m;         }         public string Key         {             get { return key; }         }     }
}
Posted in SharePoint Development | Leave a comment

Override the pages for lists items new, edit and display

The goal and the need (or why bother override)

When addressing some business needs by customizing a SharePoint installation, it helps users if the customization feels like out of the box SharePoint.

Users get used to the new item and edit UX that shows all the fields the list or the document library has and the way it is laid out with the order the list/library was created (or ordered). The users must fill required fields and can enter data with limited validation.

The customization may need extra effort in creating UX and validation and special order of the fields but requiring the users to do some extra step to create new item or editing existing in a manner different then what they are used to, is not desired. Also, if they can use the existing interface SharePoint provides, they may enter information bypassing the validation rules that are present in the customized version.

It will feel natural and be safer if the customized versions will show up as the default for new edit and display item actions.

The methods

There few ways to achieve this behavior, I will deal with 2 of them available for developers.

This will be Just Part 1 Using SharePoint Designer. Part 2 that I will write later, will discuss the Visual Studio way.  

The SPD method and resources

I learned this way by watching this free video: http://www.sharepoint-videos.com/sp10-webinar-modifying-sharepoint-list-forms-using-sharepoint-designer-2010-and-infopath-2010/

It also shows how to develop the customization with InfoPath but this is not what I want to write about. (And usually recommend not doing)

Steps

  1.      Open SPD and select the library or list the customization is for
  2.       Create New default forms using your own names (so they can survive a future upgrade and be obvious customization)
    1.       {Your}DispForm.aspx as default for Display
    2.       {Your}EditForm.aspx as default for Edit
    3.       {Your}NewForm.aspx as default for New
  3.       Edit {Your}DispForm.aspx
    1.       If the main place holder shows “WebPartPages:ListFormWebPart”, highlight it and delete it. From the insert tab, choose “Display Item Form” and select the name of your list.  
    2.       When selecting a field in the design surface, the ribbon will have a “Data View Tools” section. It has sub tabs with tools helping the design.
    3.       The view tab has a “Task Panes” dropdown, select “Data Source Details”; it will help moving fields around on the page.
    4.       Save and close when done applying your changes.
  4.      Edit {Your}EditForm.aspx
    1.       If the main place holder shows “WebPartPages:ListFormWebPart”, highlight it and delete it. From the insert tab, choose “Edit Item Form” and select the name of your list.  
    2.       When selecting a field in the design surface, the ribbon will have a “Data View Tools” section. It has sub tabs with tools helping the design.
    3.       The view tab has a “Task Panes” dropdown, select “Data Source Details”; it will help moving fields around on the page.
    4.       Save and close when done applying your changes.
  5.       Edit {Your}NewForm.aspx
    1.       If the main place holder shows “WebPartPages:ListFormWebPart”, highlight it and delete it. From the insert tab, choose “New Item Form” and select the name of your list.  
    2.       When selecting a field in the design surface, the ribbon will have a “Data View Tools” section. It has sub tabs with tools helping the design.
    3.       The view tab has a “Task Panes” dropdown, select “Data Source Details”; it will help moving fields around on the page.
    4.       Save and close when done applying your changes.
  6.       If you have web parts developed for your list or library, you can embed them in the pages. You will have to figure out the connection to and from the web part to make sure you access the right item.
  7.       Some client functionality can be done with JavaScript on the page you edit.

Conclusion

 

  1.       The SharePoint designer tool is not the easiest to figure out. There is not much explanation what each item does. So do a lot of experiments.
  2.       Changing forms style, sometimes causes some functionality (such as save) to be missing. Keep versions to go back to.
  3.       The additional functionality you can add to the forms is limited if you do not add web parts. The next part will show how to do it in visual studio and add all the functionality you can think of.

 

Posted in SharePoint Development | Tagged , | Leave a comment