Category Archives: Security

SharePoint Security and the Object Model – Part 3

Authorization and Access Control – Evaluating Permissions

This is the third part of a three part post on security object model basics in SharePoint. The other posts are: SharePoint Security and the Object Model – Part 1 and SharePoint Security and the Object Model – Part 2. The first post covers users and groups, the second post covers the mechanics of defining and applying permissions. This post is about using and applying user, groups, and permissions to control the user’s access and experience in a site in the UI and in code.

Why is this Necessary?

Before I talk about using permissions to control access (which is very easy), let’s consider why you need to know anything beyond the first two posts in this series. In the first post, you learned how to work with users and groups. That post even covered how to add users to groups based on claims with SPClaim and SPClaimProviderManager. If you know how users and groups work, why not write code that checks permission based on user or group names?

I’d bet that most of you instinctively know why this is a very bad idea – users and groups change. Obviously you wouldn’t want to write your security code based on them. But, what about claims? If you are using claims you will be tempted to write code that checks for a claim via ClaimsIdentity.Claims or a custom claim manager. If you’ve seen my Claims Identity presentation, you may recall that I use an example where the doorman lets Mary enter because she has a claim that says she is over 21. Writing code that checks for the claim directly is just as big a mistake as checking for a specific user or group, but for slightly different reasons.

Checking for a claim directly creates a dependency between your code and Windows Identity Foundation. Depending on how you do it, it might also create a dependency on a specific identity provider. It also makes your code very inflexible and requires development if the rules change.

You should never perform authorization checks based on users, groups, or claims. Instead you should use permission levels and role assignments as discussed in part 2 of this series. To perform authorization checks you should always use the simple techniques discussed below for maximum flexibility. Although doing all the work discussed in parts one and two to enable these techniques may seem like a lot of work, it is the least amount of work that doesn’t create lots of trouble over time.

DoesUserHavePermissions

ISecurableObject defines a method named DoesUserHavePermissions. The abstract SPSecurableObject provides an implementation of this method that is overridden in the various securable object implementations, i.e. SPWeb, SPList, and SPListItem. The base definition of DoeasUserHavePermissions is simple (see part 2 for more on SPBasePermissions):

public
virtual
bool DoesUserHavePermissions(SPBasePermissions permissionMask)


 

It returns true or false given a permission mask for a given web, list, or item for the current user. For example:

bool hasPermission = SPContext.Current.Item.DoesUserHavePermissions(SPBasePermissions.EditListItems);
writer.Write("User can edit this item = " + hasPermission.ToString());


 

Note:
In older versions of SharePoint this method has the bizarre behavior of throwing UnauthorizedException
instead of returning false.
This alternative code produces the same result:

permissionMask == SPBasePermissions.EmptyMask || (itemToCheck.EffectiveBasePermissions & permissionMask) == permissionMask;

(itemToCheck is a valid instance of an SPSecurableObject)

SPSecurityTrimmedControl

SPSecurityTrimmedControl is a handy web control that provides a container for other controls and markup. You simply provide a permission mask for the PermissionString property and SharePoint takes care of the rest! You can use it on pages and in user controls and web parts. Unfortunately, this control is located in the Microsoft.SharePoint.WebControls namespace and is therefore unavailable to sandbox code. Code is the operative word – you can still use it on pages as long as it is in the page markup and not in a control. You can specify multiple permissions by separating the values with commas.

The documentation for this control is good and there are plenty of posts out there the describe its full functionality. The SDK article is here.

CustomAction Rights

The CustomAction feature element allows you to add items to most menus and also to the ribbon. It exposes a Rights attribute that specifies a set of rights that the user must have for the link to be visible, for example, "ViewListItems,ManageAlerts". Simillar functionality is exposed by the SPUserCustomAction class. It has a Rights property that accepts a permissions mask.

Author: Doug Ware

SharePoint Security and the Object Model – Part 2

Permissions

This is the second part of a three part post on security object model basics in SharePoint. The other posts are SharePoint Security and the Object Model – Part 1 and SharePoint Security and the Object Model – Part 3.

Defining and Applying Permissions

Authorization in SharePoint is done based on a user’s permission level
for a given context. You can view and manage permission levels by clicking the Permission Levels ribbon button on the People and Groups page.

In the object model the SPRoleDefinition class represents a permission level. The SPRoleDefinition BasePermissions property is a bit mask of SPBasePermission enumeration values. You can bind a role definition to any ISecurableObject via the RoleAssignments property.

The following code shows how to create a permission level that allows editing, but not creation or deletion of items.

SPRoleDefinition def = new SPRoleDefinition();
def.Name = "Edit Not Delete";
def.Description = "Can edit, but can't delete.";


 

def.BasePermissions = SPBasePermissions.ViewListItems |
SPBasePermissions.EditListItems |
SPBasePermissions.Open |
SPBasePermissions.ViewPages;

web.RoleDefinitions.Add(def);

            

Securable Objects and Inheritance

By default, all objects inherit permissions from their parent level. However, as you saw in the previous exercise, you can break inheritance at any level with different permissions for an object and all of its descendants.

Breaking Inheritance

ISecurableObject requires implementation of the BreakRoleInheritance method. SPWeb, SPList, and SPListItem implement ISecurableObject and expose this method.

BreakRoleInheritance accepts a single Boolean argument. BreakRoleInheritance(true) breaks inheritance and copies the role assignments from parent. BreakRoleInheritance(false) breaks role inheritance with no role assignments from parent. A common error is to invoke BreakRoleInheritance(true) when trying to create a unique set of permissions. Because passing true copies the role assignments from the parent, until you modify the role assignments that child object has the same permissions it had before inheritance was broken.

Applying Permissions

Permissions are applied to an ISecurableObject via the RoleAssignments collection. RoleAssignments is a property of type SPRoleAssignmentCollection and contains SPRoleAssignment instances. A role assignment consists of an SPPrincipal (the base class of SPUser and SPGroup) and a collection of RoleDefinitionBindings.

The following code applies a role definition named Create and Edit to a list item and the current SPUser of the web.

void SetPermission(SPListItem item)
{
SPWeb web = item.Web;
item.BreakRoleInheritance(false);

                
SPRoleAssignment assign = new SPRoleAssignment(web.CurrentUser);

assign.RoleDefinitionBindings.Add(
web.RoleDefinitions["Create and Edit"]);

item.RoleAssignments.Add(assign);
}

Author: Doug Ware

SharePoint Security and the Object Model – Part 1

This is the first part of a three part series. The other two parts are SharePoint Security and the Object Model – Part 2 and SharePoint Security and the Object Model – Part 3.

Users and Groups

In the spirit of my post the other day on claims and sign out, I decided to post a complete how-to that also goes along with a talk I do that you may have seen at a user group or SharePoint Saturday. This post is about creating groups and users. The last bit is about how to create a user from a claim.

Creating a Group

The SPGroupCollection class is a collection of SPGroup objects. An SPGroup represents a group in a SharePoint site. Properties of SPGroup include:

  • LoginName
  • Name
  • Description
  • ContainsCurrentUser
  • Owner
  • More…

Two key classes expose properties of type SPGroupCollection. The first is SPWeb. It exposes a Groups collection that contains all of the groups with permissions in the site. It also exposes a second property, SiteGroups, which contains all of the groups in the site collection.

Code to create a group


web.SiteGroups.Add("My Group", web.SiteAdministrators[0], null, "I made this with code!");
web.Update();

                
			

Get the newly created group as an SPGroup and add it to the AssociatedGroups collection so it displays on the quick launch on the Manage Permissions page.

SPGroup group = web.SiteGroups["My Group"];
web.AssociatedGroups.Add(group);
web.Update();

            

Creating a User

The SPUserCollection class is a collection of SPUser objects. SPWeb exposes three properties of type SPUserCollection.

  • AllUsers – Users with explicit membership or who have browsed to the site as authenticated users
  • SiteUsers – All users that belong to the site collection
  • Users – Users with explicit permissions in the site

The names of the first two, AllUsers and SiteUsers, is unfortunate and confusing. AllUsers applies to the site and SiteUsers applies to the entire site collection. Therefore, SiteUsers usually has more items than AllUsers!

The SPGroup.Users property is also of type SPUserCollection. It contains the users with explicit membership in the group.

SPWeb.EnsureUser

The previous section uses the term ‘explicit membership’ a couple of times. It is important to understand the difference between explicit and implicit membership in a site or a group.

Recall that an SPUser is a security principal recognized by the authentication provider and that it can be a user, group, role, or claim. If you add a user directly to a SharePoint group the user has explicit membership in the group. If you add a security principal like an AD group to a SharePoint group, the SPGroup.Users collection will contain a single SPUser instance that represents the AD group. Any users inside the AD group have implicit membership in the SharePoint group. The same is true of claims.

Implicit membership presents a challenge when writing code that deals with users and user information because it is possible and usually certain that there are users with access to a site that SharePoint does not know about via membership in a group. When a user logs in to a site for the first time SharePoint adds an SPUser to the site’s SiteUsers collection along with information about the user, but it is not uncommon to need information about a user who has implicit membership and has never logged in.

SPWeb’s EnsureUser method solves this problem. It ensures that a given name resolves by the authentication system (assuming this functionality is provided) and that it exists in the SiteUsers collection. Attempting to access an SPUser that does not exist throws an exception, and so you should always call EnsureUser whenever it is possible to have a login name and need to access user information for anyone other the current user. Examples include:

  • User name is in item metadata, e.g. Assigned To in a task
  • Code needs email address to send mail given user name
  • Co-worker or workflow assigns task to user who possibly has never accessed the site

The key takeaway is that the best way to get an instance of SPUser is to invoke EnsureUser.

Using EnsureUser with Claims

A claims name is an encoded value that consists of the claim type, issuer, and value, e.g. c:0ȉ.t|stsportalit|staff. The encoded value is what EnsureUser accepts to return an SPUser instance. The SPClaimProviderManager provides methods to encode and decode claims. To encode a claim, first create a new SPClaim. The SPClaim constructor accepts four arguments: claim type, value, value type, and original issuer. For example:

SPClaim claim = new
			

                
SPClaim(@"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
"Fred", ClaimValueTypes.String, "TrustedProvider:TrustedProviderName");

(Replace TrustedProviderName with the actual name of your provider!)

The next step is to decode the claim. First, get the local claim provider manager and then use it to get the encoded name.

string claimName = claimManager.EncodeClaim(claim);


 

Finally, pass the encoded claim to EnsureUser to get an SPUser instance.

SPUser user = SPContext.Current.Web.EnsureUser(claimName);


 

Adding an SPUser to an SPGroup

To add a user to a group, call the AddUser method of SPGroup.

SPContext.Current.Web.SiteGroups["My Group"].AddUser(user);

Author: Doug Ware

Fixing SharePoint, Claims Authentication, and Sign Out

One of my clients has a project that includes claims based authentication with a custom identity provider (IP-STS). This part of the project has been helped by the fact that the documentation gets better each month – sometimes it seems as if the question we have was documented a few days before we needed to ask it!

There is a lot of documentation in the Windows Identity Foundation SDK and in the Identity Developer Training Kit. But in my opinion, the best reference material to date is in the book A Guide to Claims–based Identity and Access Control from Microsoft Patterns and Practices. The companion code for this book is the most complete and well documented example of many scenarios I’ve seen. In particular, the first sample for Single Sign On was most recently helpful to understand what should happen when a user signs out via claims based SSO – something that SharePoint 2010 does incorrectly at the moment! This post is about what should happen and the approach I took to make this work seamlessly with both our custom sites and stock sites that use the SSO.

The Problem

If you use the Sign Out or Sign in as a Different User links in the welcome menu you will most likely be redirected back to the sites home page instead of signing out! This happens based on the following flow.

  1. You sign in to the IP-STS which writes a persisted authentication cookie for the STS on your machine.
  2. You are redirected to the SharePoint site which (by default) writes a persisted authentication cookie for the SharePoint site on your machine.
  3. You click Sign Out whereupon SharePoint deletes the authentication cookie for the SharePoint site and redirects you to the STS (via the SharePoint Sign On page).
  4. The STS uses the authentication cookie it stored on the machine to determine you are already logged in and sends you back to the SharePoint site.

The problem is that SharePoint should redirect you to the identity provider with the query string parameter wa=wsignout1.0. Upon receiving this parameter, the STS will delete its cookie and optionally reply to the relying party via an HTML <img> tag whose src attribute is the relying party with query string parameter wa=wsignoutcleanup1.0. When the RP, in this case SharePoint receives a GET for the URL with this parameter it should delete whatever cookies it must to clean up the session. In the SSO sample that comes from patterns and practices the Sign Out page looks like this:

The check mark is the result of the img tag! <img title="Signout request: http://sp2010:8088/_trust/default.aspx?wa=wsignoutcleanup1.0" src="http://sp2010:8088/_trust/default.aspx?wa=wsignoutcleanup1.0"/> (obvious right?)

A Solution

You can write your own sign out control, but we require the traditional welcome links to work. We need SharePoint sites without our customizations to work as well. One option is to replace the welcome.ascx control, but this is a bad solution – you shouldn’t replace built-in files with your own versions of the same.

The first solution I tried was based on HideCustomAction and CustomAction features to change out the links, but although you can add links to the welcome links menu, the control itself sets the visibility of the stock links in its code-behind and ignores any HideCustomActions! One bug was keeping me from addressing the other!

The actual behavior for Sign Out is in _layouts/signout.aspx. For Sign In as a Different user the page is _layouts/CloseConnection.aspx. Since I could not easily affect the links in the UI I decided to create a simple HttpModule whose salient code is shown below. This code simply redirects to a new page, ClaimsSignOut.aspx if either of the original pages is requested.

void context_BeginRequest(object sender, EventArgs e)
{
var context = (HttpApplication)sender;
string url = context.Request.Url.ToString().ToLowerInvariant();
if(url.Contains(@"/signout.aspx"))
{
context.Response.AddHeader("Location",
url.Replace("/signout.aspx", "/ClaimsSignOut/ClaimsSignOut.aspx"));
context.Response.StatusCode = 301;
context.Response.End();
}
if(url.Contains(@"/closeconnection.aspx"))
{
context.Response.AddHeader("Location",
url.Replace("/closeconnection.aspx", "/ClaimsSignOut/ClaimsSignOut.aspx"));
context.Response.StatusCode = 301;
context.Response.End();
}
}

            

The implementation of ClaimsSignOut.aspx was created by copying SignOut.aspx and using IlSpy to decompile the code behind which I subsequently modified to fix the bug. The fix consists of a simple function to get the IP’s address from the current principal:

private string GetIPUrl()
{
IClaimsPrincipal user = Page.User as IClaimsPrincipal;
if(user == null) return string.Empty;

string ipUrl = string.Empty;
try
{
string providerName = (user.Identity.Name).Split('|')[1];
ipUrl = SPSecurityTokenServiceManager.Local.TrustedLoginProviders[providerName].ProviderUri.AbsoluteUri;
}
catch
{
//TODO:Log it
}
return ipUrl;
}

            

And the following modification to the page’s RemoveCookiesAndRedirect method.

if (iisSettingsWithFallback.UseClaimsAuthentication)
{
string ipUrl = GetIPUrl();
if(ipUrl != string.Empty)
{
string replyUrl = HttpUtility.UrlEncode(SPContext.Current.Site.RootWeb.Url);
string redirect = ipUrl + "?wa=wsignout1.0&wreply=" + replyUrl;

//This next line shouldn't be neccesary if the IP-STS is correct, but clears
//cookies just in case the STS doesn't call back to cleanup the session.
FederatedAuthentication.SessionAuthenticationModule.SignOut();

if (Context.Session != null) Context.Session.Abandon();

Context.Response.Redirect(redirect);
}
else
{
//Original code per reflection – DTW
FederatedAuthentication.SessionAuthenticationModule.SignOut();
int num = 0;

            

Hopefully there will be a hotfix for this at some point and this solution will no longer be needed! In the meantime, you can download the solution from here.

If you see any issues or can make any improvements, please let me know via the comments section!

–Doug

Author: Doug Ware