Slides, Code, and More for my SPSBOS Session

Thanks to everyone who came out to SharePoint Saturday Boston yesterday! Once again, the organizers put together an outstanding event with well over 300 developers, administrators, and end users in attendance. Thanks, also, to all the sponsors who help to make such great events possible.

I had the great privilege of being able to speak at this event. I would like to extend a special thank you to everyone who attended my session on Claims-Based Identity, Facebook, and the Cloud yesterday morning. There were lots of great questions and discussions! Thanks to all of you for your participation. I hope you enjoyed the session and found the information presented to be valuable.

Below are links to my presentation, code, PowerShell script, and CloudShare environment. Additional background information can be found in my blog post Beyond Authentication: Deeper Facebook Integration with SharePoint.

Download presentation as .pptx

Web parts, source code, site template, and required certificates

PowerShell script to configure Azure ACS trusted identity provider

If you would like a copy of the CloudShare virtual machine I used for my demos, follow this link to obtain a 14-day free trial of CloudShare (if you don’t already have an account) and access a copy of the environment. I used the VM called SharePoint 2010 Enterprise SP1 for my demos. If you would like to know more about CloudShare (I love CloudShare!), please contact me.

Also, to take full advantage of the functionality shown in this demo, I strongly encourage you to sign up for a 90-day free trial of Windows Azure. This will give you access to the AppFabric Access Control Service (ACS) capabilities I demonstrated. If you already have a Windows Azure account, or if you choose to convert your trial to a full-blown account, the ACS features are free through December 1, 2012 (after this date, the charge will be $1.99 for every 100,000 transactions).

If you have any questions or issues deploying or running the code and/or scripts above, feel free to leave a comment below. Thanks again to everyone for such a fantastic event!

Using jQuery to Build a More Intuitive Advanced Search Experience in SharePoint 2010 – Part 2: Advanced.aspx

Update 3/28/12: If you have already downloaded the code for this solution, please download it again! Several bugs were fixed in this latest release.

This entry is part 2 of a multi-part blog series on enhancing the advanced search experience for SharePoint 2010 end users using jQuery. Part 1 may be found here. I will link to subsequent entries in this series from here once they have been written.

In part 1 of this series, I wrote about what I believe are some serious flaws with the way advanced searches are handled out of the box in SharePoint 2010. If you so much as follow one navigational link from the search results page after performing an advanced search, then decide you wish to change your advanced search parameters in any way, you are forced to re-enter all your search parameters on an empty advanced search page, even though the page URL contains a query string parameter that includes everything you searched for!

Enter jQuery

I know I can use JavaScript to parse parameters from a query string. This post details one of the most elegant ways I have seen to do this. Since the advanced.aspx URL already includes all of my advanced search criteria (in the k query string parameter), I will use this method to parse that value. Once I have that value, through a combination of jQuery and JavaScript RegExp (regular expression) parsing, I can take all of my search criteria and repopulate the appropriate text boxes, check boxes, and dropdowns so that the end user doesn’t have to needlessly re-enter this information.

The key to being able to populate the appropriate controls is knowing the IDs of each of the controls on the advanced search page. I can then use jQuery to set the values of the respective text boxes and dropdowns using the .val() method for text boxes and the .prop() method for check boxes and dropdowns (although .val() can also be used for setting the selected value in a dropdown, I encountered a problem with the dynamically populated dropdowns in the property restrictions area that required me to set the selectedIndex property of those dropdowns instead). Using my trusty F12 developer tools, I was able to determine how the controls on the advanced search page are named. The image below displays the last part of the “name” value associated with each of these controls. I can then use attribute selectors to choose each specific element (based on the name of that element ending with the given string) and set its value accordingly.

Deploying jQuery and this Script

A thorough discussion of how best to deploy jQuery in a given site collection is beyond the scope of this blog post. Suffice it to say, my preferred approach for deploying jQuery is as a sandboxed solution that includes a jQuery module. Jan Tielens outlines this solution in greater detail here. Keep in mind that you can use any method you would like to deploy jQuery on your Search Center site as long as you properly reference the location of your jQuery script on the advanced.aspx page. The functionality I have created for this blog post requires jQuery 1.6 or above.

Keep in mind that the advanced.aspx page in a SharePoint Search Center is a fully customizable web part page. With that in mind, once you have deployed jQuery in your site collection and referenced it from this page, you can deploy this script in a Content Editor Web Part and be good to go. The general algorithm followed by this script is:

  1. Store the k query string parameter in a variable, k.
  2. Look for regular expression matches in k for the various keyword searches (ALL(), ANY(), “”, and NONE()). Populate the appropriate text boxes with the words found.
  3. Look for regular expression matches in k for the various language filters (DetectedLanguage).
  4. Look for regular expression matches in k for the result type (IsDocument=True or FileExtension=).
  5. Parse out any property restrictions (property, type of match, the text itself, and the and/or operand associated with it). This script should handle the out of the box properties without any issue. It also properly handled the custom mapped properties I had previously configured in my environment (but please feel free to let me know if you run into any specific issues with this script in your environment).

The Solution in Action

Download the script here (it is available as both a standalone text file or an exported Content Editor Web Part) and try it out for yourself. Again, the steps to deploy it are as follows:

  1. Download jQuery 1.6 or greater and deploy it in your environment.
  2. Ensure jQuery is referenced from your advanced search page, advanced.aspx.
  3. Add a Content Editor Web Part to advanced.aspx and reference this script. Alternately, you could import and add the .dwp file that can also be downloaded from the link above.

Let’s try it out!

I’ll start by doing a basic search on my favorite subject, SharePoint!

Here are my results:

Now let’s say I want to filter these results by date. I’ll click the Advanced link.

Nothing special here (yet). The advanced search page knows how to put a simple keyword query’s terms in the “All of these words” text box. Now let’s enhance our search by adding some keyword, language, type, author, and date range restrictions:

Pressing Search yields a new set of search results:

The entire string in the search text box reads:

ALL(sharepoint) ANY(rocks awesome) NONE(stinks) (DetectedLanguage=”en” OR DetectedLanguage=”fr”) (IsDocument=”True”) (Author:customer OR Write>=1/1/12 AND Write<=3/1/12)

At this point, the Advanced link points to javascript:history.back(), so clicking it would bring up the advanced search page just as it was when I submitted my query.

Let’s say I want to change the date range being searched, remove one of the selected languages, and put size and URL restrictions in place. I can manually change the search string to read:

ALL(sharepoint) ANY(rocks awesome) NONE(stinks) (DetectedLanguage=”en”) (IsDocument=”True”) (-Path:SitePages AND Path:contoso OR Write>=3/1/12 AND Write<=3/27/12 AND Size>17000)

Pressing the search button yields the following results:

At this point, the Advanced link now points to advanced.aspx with a messy query string parameter called k. Without this script in place, clicking that link would yield a woefully empty advanced search page:

(As an aside, I find it strange that SharePoint has somehow forgotten how to even parse the “All of these words” keywords!)

But with this script installed, the page “magically” reflects all the parameters of my updated query automatically!

Pretty cool, huh? Try it out (you can download the code in a standalone text file or as an exported Content Editor Web Part) and let me know what you think. Feel free to leave any comments, concerns, or ideas for improvement in the comments. Stay tuned for part 3, where I will use jQuery to enhance the advanced search interface directly on the search results page!

Using jQuery to Build a More Intuitive Advanced Search Experience in SharePoint 2010 – Part 1: Introduction

This entry is part 1 of a multi-part blog series on enhancing the advanced search experience for SharePoint 2010 end users using jQuery. Part 2, using jQuery to enhance the advanced search page advanced.aspx can be found here. I will link to subsequent entries in this series from here once they have been written.

The default advanced search experience in a SharePoint 2010 Search Center (basic or enterprise) has always disappointed me. The interface is clean enough, but the execution and navigation leave a lot to be desired, particularly when end users wish to make changes to the search string from the search results page. Out of the box, performing the different types of keyword searches (ALL of these words, ANY of these words, the exact phrase, etc.) displays the combined SharePoint search syntax for that query in the search box. For instance, if I wanted to find documents that contained ALL of the words SharePoint and awesome, the exact phrase Danny Jessee, ANY of the words rocks or rules, and NONE of the words sad or sorry, I would enter the following in the advanced search page:

And the results (if any) appear on the results page, results.aspx:

As you can see, my advanced query was converted to a single search string where the words I entered in the All of these words: were surrounded with ALL(), the words I entered in The exact phrase: were surrounded with quotation marks, and so on with ANY() and NONE(). The entire string that appears in the search box looks like this:

ALL(SharePoint awesome) “Danny Jessee” ANY(rocks rules) NONE(sad sorry)

This (URL-encoded) string also appears as the k query string parameter in the search results page URL:

results.aspx?k=ALL(SharePoint%20awesome)%20%22Danny%20Jessee%22%20ANY(rocks%20rules)%20NONE(sad%20sorry)

This is intuitive enough for most users to follow, and in fact gives users the opportunity to understand some basic SharePoint search syntax. There’s certainly nothing wrong with that. Things start to get more complex, however, when you start to add property restrictions into the mix. What if, in addition to my search query above, I only wanted to return results that were either modified between March 1 and March 10, or were less than 18000 bytes in size? My advanced search page would now look like this:

And the entire string that appears in the search box on the search results page now looks like this:

ALL(SharePoint awesome) “Danny Jessee” ANY(rocks rules) NONE(sad sorry) (Write>=2/29/12 AND Write<=3/11/12 OR Size<18000)

The URL to my results page now looks like this:

results.aspx?k=ALL(SharePoint%20awesome)%20%22Danny%20Jessee%22%20ANY(rocks%20rules)%20NONE(sad%20sorry)%20(Write%3E%3D2%2F29%2F12%20AND%20Write%3C%3D3%2F11%2F12%20OR%20Size%3C18000)

Although this syntax is still fairly intuitive, I have found that some end users begin to feel intimidated (or at least uncomfortable) as the size of this search string grows (especially when you start nesting clauses in parentheses or have to enclose property values that include spaces in quotation marks).

It gets better!

Did you know that if you include enough advanced search parameters in your query, the string that SharePoint puts in the search box for you is truncated so that it ends in “…” instead?

That’s all well and good, but what if the user makes (what he or she believes to be) a simple change to the search query, such as changing the spelling of a word at the beginning of the search string? Pressing the search button yields the following unpleasant result:

Your query is malformed. Please rephrase your query. Wow.

But back to the problem at hand…

What if I had meant for that query to include any document less than 20,000 bytes in size? I could click the Advanced link to adjust my query. Note that on the first results page displayed after submitting a query from the advanced search page, this link actually invokes the JavaScript method history.back(). On any subsequent results pages (or if you follow a link that returns you to the first result page), the link instead goes to advanced.aspx, repeating the k query string parameter that also exists in the URL to the results.aspx page.

Why does this matter?

When you follow a link that goes to javascript:history.back(), your browser reloads the previous page from its history list. This means that the state of controls on this page is preserved from when the form on that page was submitted and you can easily tweak the search parameters you specified before pressing the Search button. In the case of the example above, clicking the Advanced link on the “no results found” page reloads the advanced search form just as I had left it. But what happens if I change my search parameters in the search box instead?

If I update my search string to be ALL(SharePoint awesome) “Danny Jessee” ANY(rocks rules) NONE(sad sorry) (Write>=2/29/12 AND Write<=3/11/12 OR Size<20000) and press the search button (magnifying glass), the search results page appears (reflecting my updated query). However, since I was not directed to results.aspx from advanced.aspx, the Advanced link will now point to:

advanced.aspx?k=ALL%28SharePoint%20awesome%29%20%22Danny%20Jessee%22%20ANY%28rocks%20rules%29%20NONE%28sad%20sorry%29%20%28Write%3E%3D2%2F29%2F12%20AND%20Write%3C%3D3%2F11%2F12%20OR%20Size%3C20000%29

If I want to change my search parameters again (perhaps by adding another property restriction), clicking this link should enable me to do that with relative ease, right?

WHAT?! I have to enter ALL of my advanced search parameters again? No way!

Sadly, it’s true. Even though the advanced search page URL includes all of my search parameters encoded in the k query string parameter, the out of the box advanced search page has no idea what to do with it! While that definitely explains why SharePoint weakly attempts to hack around this limitation with the history.back() link, it does not satisfy a user base who is accustomed to using search engines that provide a more intelligent advanced search interface. Something needs to be done to address this limitation.

jQuery to the rescue…

In part 2 of this series, I will demonstrate how to use jQuery and JavaScript to parse the k query string parameter on the advanced.aspx page and populate the appropriate controls on that page with the parameters of the active search query. This way, users can feel empowered to change the parameters of their advanced searches without having to re-enter everything on the advanced search page.

In part 3, I will update the results.aspx page itself to display a more intuitive mechanism for inputting and updating search parameters directly on the results page without having to modify a complex search string…all using jQuery and JavaScript!

If you have any ideas about how the default advanced search experience should be improved in SharePoint 2010, or if you have any gripes or frustrations of your own to share, I would love to hear about them in the comments!

Using LinkedIn as an Identity Provider for SharePoint 2010

Updated 1/27/2013: I am pleased to report that the steps below also work for setting up LinkedIn as an identity provider for SharePoint 2013! The SharePoint screens will look a little different, but the steps to set up the integration are the same. If you run into any issues with this setup in SharePoint 2013, please leave a comment at the bottom of this post.

Updated 1/22/2013: After almost a year, I thought I would go back and revisit this post (as it continues to be one of the most popular posts on my blog). As it turns out, some updates to the code were required and many of the LinkedIn user interfaces required to complete this integration had changed. I can verify that the steps below work properly and that all screenshots are current as of January 22, 2013. Please leave a comment on this post if you run into any issues.

Those of you who have seen me speak at a user group or SharePoint Saturday event recently know how much I love Windows Azure AppFabric’s Access Control Services and how easy ACS makes it to configure SharePoint 2010 to allow users to log in with OpenID identity providers such as Facebook, Google, Yahoo!, and Windows Live. But what about LinkedIn? While LinkedIn isn’t an OpenID provider per se, numerous sites on the web allow users to sign in using their LinkedIn account credentials via OAuth. When the subject of external identity providers for a future version of the SharePoint User Group of Washington, DC site came up during my recent presentation there, the audience overwhelmingly agreed that LinkedIn was the most “professional” identity provider to integrate (and certainly one that most SharePoint professionals would feel more comfortable using than Facebook). There was only one problem…Azure AppFabric ACS does not natively support LinkedIn as an identity provider. What was I to do?

As one astute observer pointed out during my presentation, ACS is not required to configure SharePoint 2010 to interact with any external identity provider. While ACS greatly simplifies the management and configuration required to set up an external identity provider and its associated claim rules, it is possible to write code that leverages a custom STS (Security Token Service) to manage all of this without involving Azure at all.

Getting Started

Luckily for me, the vast majority of the technical “heavy lifting” required to accomplish this integration had already been done by Travis Nielsen. In this blog post, Travis details the steps (and development prerequisites, including the Windows Identity Foundation SDK 4.0) required to integrate SharePoint 2010 with Facebook as an identity provider using a custom STS (and without using Azure ACS). The steps for integrating LinkedIn are basically the same as they are for Facebook. Below I will detail how configuring the two identity providers differs.

C# OAuth Class for LinkedIn

I was preparing to have to adapt the Facebook OAuth class Travis had found to work with LinkedIn. Fortunately, I stumbled upon a C# OAuth class for LinkedIn that had already been developed by Fatih YASAR. All credit for this aspect of the solution belongs to him.

Creating the Application

Much like configuring Facebook as an identity provider requires the creation of an “application” within Facebook, LinkedIn requires the creation of an application as well.

Anyone with a LinkedIn account can create an application through the LinkedIn Developer Network.

1. Go to https://www.linkedin.com/secure/developer.

lidn

2. Click Add New Application. Fill out the form to register your new application. Any values you enter here can be changed later on.

3. Press Add Application. The following screen will confirm creation of your LinkedIn application.

newlinkedinapp

4. As with Facebook, it is important that you take note of the API Key and Secret Key values that are displayed. We will need to use these values in our code (note that you are also provided with OAuth User Token and OAuth User Secret values; these are not required for this setup). Press Done. We will return to our application setup when we have identified the SharePoint 2010 web application with which we want to integrate LinkedIn.

Customizing the STS

First, add the oAuthBase2 and oAuthLinkedIn classes from Fatih YASAR to the App_Code folder of your STS project. You shouldn’t need to make any changes to the two .cs files, but you will notice that the oAuthLinkedIn class expects to find your API Key and Secret Key values in the <appSettings> of your STS’s Web.config, so add them there:

apiKeys

While we are here, it’s worth noting that the STS is configured by default to use the certificate with CN=STSTestCert to sign the SAML tokens it generates containing the claims we configure it to send. This certificate is installed as part of the Windows Identity Framework SDK. We will need to export this certificate so that we can configure SharePoint 2010 to use our custom STS as a Trusted Identity Provider and add this certificate to its trusted certificate store. I was able to find and export this certificate by loading the Certificates (Local Computer) snap-in and navigating to Trusted People > Certificates:

ststestcert

It goes without saying that you would not want to use this certificate in a production environment. Remember to update the SigningCertificateName in your STS’s Web.config to match the name of your production certificate.

Back to the STS code…I tied everything together in Login.aspx.cs. When the login page first loads (with no oauth_token value present in the query string), the user is redirected to the appropriate authorization link (hosted by LinkedIn). Once the user has an access token from LinkedIn, we are able to populate a series of claims for that user. Much like Travis did with Facebook, I make a call to the LinkedIn API to get profile information associated with the current user and define a series of output claims based on this information. The claims I have defined are:

  • Name – concatenated first and last name
  • Webpage – the LinkedIn user’s profile URL
  • NameIdentifier – the LinkedIn user’s profile ID (parsed from the profile URL)
  • GivenName and Surname (just because I could)

Below is the code that I used. In a production environment, you will want to include better error handling and more robust XML parsing!

public partial class Login : System.Web.UI.Page
{
    private oAuthLinkedIn liAuth = new oAuthLinkedIn();
    private string profileId = string.Empty;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.QueryString["oauth_token"] == null)
        {
            // If "oauth_token" does not appear in the query string, the user has not been authenticated
            // This call sets the Token and TokenSecret values
            string authLink = liAuth.AuthorizationLinkGet();
            Application["requestToken"] = liAuth.Token;
            Application["requestTokenSecret"] = liAuth.TokenSecret;
            Response.Redirect(authLink);
        }
        else
        {
            // User has been authenticated
            // Use the request token to get the access token
            liAuth.Token = (string)Application["requestToken"];
            liAuth.TokenSecret = (string)Application["requestTokenSecret"];
            liAuth.Verifier = Request.QueryString["oauth_verifier"];
            liAuth.AccessTokenGet(Request.QueryString["oauth_token"]);

            if (liAuth.Token.Length > 0)
            {
                // Make a LinkedIn API call to get claim information
                Dictionary<string, string> claims = GetClaims();
                HttpContext.Current.Session.Add("LinkedInClaims", claims);
                // Set FedAuth cookie (without this line, SharePoint will not consider the user logged in)
                FormsAuthentication.SetAuthCookie(profileId, false);
                // Pass along the query string - STS default.aspx will redirect to SharePoint
                Response.Redirect("default.aspx?" + Request.QueryString.ToString());
            }
        }
    }

    private Dictionary<string, string> GetClaims()
    {
        Dictionary<string, string> claims = new Dictionary<string, string>();
        string response = liAuth.APIWebRequest("GET", "https://api.linkedin.com/v1/people/~", null);

        if (!string.IsNullOrEmpty(response))
        {
            string firstName = string.Empty;
            string lastName = string.Empty;
            string headline = string.Empty;
            string url = string.Empty;

            // Parse values from response XML
            using (XmlReader reader = XmlReader.Create(new StringReader(response)))
            {
                reader.ReadToFollowing("first-name");
                firstName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("last-name");
                lastName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("headline");
                headline = reader.ReadElementContentAsString();
                reader.ReadToFollowing("url");
                url = reader.ReadElementContentAsString();
                profileId = GetProfileId(url);
            }

            claims.Add(System.IdentityModel.Claims.ClaimTypes.Name, string.Format("{0} {1}", firstName, lastName));
            claims.Add(System.IdentityModel.Claims.ClaimTypes.Webpage, url);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.NameIdentifier, profileId);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.GivenName, firstName);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.Surname, lastName);
        }

        return claims;
    }

    private string GetProfileId(string url)
    {
        // Parse the profile ID from the profile URL
        string id = url.Substring(url.IndexOf("key=") + 4);
        return id.Substring(0, id.IndexOf('&'));
    }
}

Also, don’t forget to update the GetOutputClaimsIdentity() function in the CustomSecurityTokenService.cs class to make use of the output claims that are stored in a Session variable:

protected override IClaimsIdentity GetOutputClaimsIdentity( IClaimsPrincipal principal, RequestSecurityToken request, Scope scope )
{
    if ( null == principal )
    {
        throw new ArgumentNullException( "principal" );
    }

    ClaimsIdentity outputIdentity = new ClaimsIdentity();

    var oAuth20Claims = HttpContext.Current.Session["LinkedInClaims"] as Dictionary<string, string>;
    if (oAuth20Claims != null)
    {
        foreach (var claim in oAuth20Claims)
        {
            outputIdentity.Claims.Add(new Claim(claim.Key, claim.Value));
        }
    }

    return outputIdentity;
}

Deploying the Custom STS and Updating the OAuth Redirect URL for the LinkedIn Application

Following the steps outlined in Travis’ blog post will give you an ASP.NET 4.0 web site that serves as your Security Token Service. You must deploy or publish this web site so that your SharePoint users will have access to it. For the purposes of this blog post, I have set up an IIS web site at http://sts.contoso.com that points to my custom STS. (Remember to set the .NET Framework version of your STS application pool to v4.0!)

Once I have deployed my STS, I need to return to my LinkedIn application setup and provide the URL to this STS as the OAuth Redirection URL, with a couple of important query string parameters appended:

1. Return to https://www.linkedin.com/secure/developer and click the name of your application.

2. In the OAuth User Agreement section near the bottom of the form, enter the URL to the Default.aspx page at the root of your STS site with the following query string:
?wa=wsignin1.0&wtrealm=http%3a%2f%2fintranet.contoso.com%2f_trust%2f

oauthagree

These parameters are defined in greater detail here. The wtrealm parameter should be the URL-encoded value of your SharePoint web application with /_trust/ (%2f_trust%2f) appended. As you can see, you also have the option of specifying a different URL if the user selects “Cancel” from the authorization dialog. Finally, you may also optionally specify a URL to an 80×80 logo image for your app (that must use SSL).

3. Press Save to update your application.

Configuring the Trusted Identity Provider for SharePoint 2010

We will configure our LinkedIn Trusted Identity Provider for SharePoint 2010 to map the following claim types that are included in the SAML tokens we receive from our custom STS:

  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier (LinkedIn profile ID)
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name (First and last name)
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage (LinkedIn profile URL)

This is done by running the following PowerShell script:

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("c:\STSTestCert.cer")
$map1 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -IncomingClaimTypeDisplayName "LinkedIn ID" -LocalClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
$map2 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" -IncomingClaimTypeDisplayName "Display Name" -LocalClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
$map3 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage" -IncomingClaimTypeDisplayName "LinkedIn URL" -SameAsIncoming
$realm = "http://intranet.contoso.com/_trust"
$signinurl = "http://sts.contoso.com/"
New-SPTrustedIdentityTokenIssuer -Name "LinkedIn" -Description "LinkedIn custom STS" -Realm $realm -ImportTrustCertificate $cert -ClaimsMappings $map1,$map2,$map3 -SignInUrl $signinurl -IdentifierClaim $map1.InputClaimType
New-SPTrustedRootAuthority -Name "LinkedIn" -Certificate $cert

The script is fairly straightforward. A couple items to note:

  • The $cert defined in line 1 is created based on the exported STS signing certificate mentioned previously. In this case, I exported the certificate to my C:\ drive and reference it there.
  • Because certain claim type mappings are already used by SharePoint (such as name and nameidentifier), I had to define different LocalClaimType values for 2 of my 3 claim mappings.
  • I use the LinkedIn profile ID (and not the first and last name of the user) as the IdentifierClaim because only the profile ID is guaranteed to be unique (e.g., two different users could have the same first and last names). I will write some custom code to update the display name property of the SPUser objects associated with LinkedIn users so that they see their first and last name (instead of that 7-digit ID) at the top of the screen when they sign in to SharePoint.

Running this script will add a Trusted Identity Provider called LinkedIn to the list of Trusted Identity Providers that can be added to any web application through Central Administration:

After adding the new Trusted Identity Provider, it helps to define a User Permission policy for the web application that allows any users who authenticate using this Trusted Identity Provider to be authorized to have read access to the web application:

addusers

The Moment of Truth

Users should now be able to sign in to SharePoint with their LinkedIn accounts. Let’s give it a shot! Depending on the different authentication providers configured for a given web application, you may or may not see a sign-in page allowing you to choose which credentials to use to log in to SharePoint. We will choose LinkedIn:

A series of HTTP redirects will take place. If the user has previously logged in to LinkedIn and has a cookie, that user will not need to enter his/her credentials again. In this screen, the user is agreeing to allow the LinkedIn application to have access to his or her account information (which consists of name, profile headline, and profile URL):

signin2

If the user has not previously logged in to LinkedIn and/or does not have a cookie, the following screen will appear:

signin1

Users always retain the ability to revoke your application’s permission at any time. Press Allow access and some more HTTP redirects will take place that should eventually land the user back in SharePoint. I have used Travis Nielsen’s Claims Web Part on the team site where users log in with LinkedIn. Here you can see the claims mappings we configured earlier and how those claims are presented to SharePoint from our custom STS:

cvwp3

Because we are using the user’s LinkedIn profile ID as the nameidentifier claim, that value initially appears at the top right of the page when the user first signs in:

To improve the personalization experience for the end user, we can write the following code (in a web part, for instance) to update the DisplayName property of the SPUser to read the value from the givenname claim instead:

public class LinkedInNameChanger : WebPart
{
    string givenName = string.Empty;
    IClaimsIdentity claimsIdentity;
    Label lblOutput = new Label();
    Button btnFixUserName = new Button();

    protected override void OnLoad(EventArgs e)
    {
        // Read the incoming claims and assign values
        IClaimsPrincipal claimsPrincipal = Page.User as IClaimsPrincipal;
        claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;

        foreach (Claim c in claimsIdentity.Claims)
        {
            if (c.ClaimType.EndsWith("givenname"))
            {
                givenName = c.Value;
                break;
            }
        }
    }

    protected override void CreateChildControls()
    {
        btnFixUserName.Text = "Fix Display Name";
        btnFixUserName.Click += new EventHandler(btnFixUserName_Click);
        this.Controls.Add(btnFixUserName);
        this.Controls.Add(lblOutput);
    }

    void btnFixUserName_Click(object sender, EventArgs e)
    {
        try
        {
            Guid siteId = SPContext.Current.Site.ID;
            Guid webId = SPContext.Current.Web.ID;
            SPUser currentUser = SPContext.Current.Web.CurrentUser;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(siteId))
                {
                    using (SPWeb web = site.OpenWeb(webId))
                    {
                        web.AllowUnsafeUpdates = true;
                        SPUser user = web.AllUsers[currentUser.LoginName];
                        user.Name = givenName;
                        user.Update();
                        lblOutput.Text = "</pre>
<span style="color: green;"><b>Display name successfully updated.</b></span>
<pre>
";
                    }
                }
            });
        }
        catch (Exception ex)
        {
            lblOutput.Text = "</pre>
<span style="color: red;"><b>Exception: </b>" + ex.Message + "</span>
<pre>
";
        }
    }
}

There now, that’s better!

Beyond Authentication: Deeper Facebook Integration with SharePoint (with code!)

I had the privilege of speaking at SharePoint Saturday in Virginia Beach yesterday. This event is of particular significance each year within the community because the first ever SharePoint Saturday was held in Virginia Beach back in 2009 and SPSVB is seen as the “kickoff” for a new year of SharePoint Saturdays. As always, I learned so much at this event and had a great time meeting with and getting to know many of the speakers, volunteers, and attendees. I would like to thank each person who gave me 70 minutes of their time yesterday to learn more about Claims-based identity in SharePoint 2010 and see how we can do more than just log in to SharePoint with Facebook. I would especially like to thank those who provided me with feedback. I plan to refine and improve this presentation for future community events, so stay tuned!

Below is the slide deck I used in my session. In addition to introducing Claims-based identity in SharePoint 2010 and detailing some of the things to look out for when working with Claims, it illustrates how to configure Windows Azure AppFabric’s Access Control Services to support logging in to SharePoint with Facebook accounts. More detailed information about this process can be found here.

But we can do more than just log in…

Much of the feedback I received yesterday related to the various demonstrations of how I used the Facebook C# SDK to surface data from Facebook in SharePoint (and vice versa) using Facebook’s Graph API. When it comes to the potential of the integration of Facebook and SharePoint, the sky is truly the limit. The AccessToken claim that Facebook includes in the OAuth token it generates will provide your application with access to any data it requests (while still obeying the privacy settings you and everyone in your friends list have in place).

More about the Facebook C# SDK

You can download the Facebook C# SDK here. The “Assemblies only” version is all you need to get started, although it is interesting and informative to be able to see the source code. (Having access to the source code helped me troubleshoot this issue as well.) The SDK includes support for .NET Framework 3.5/4.0 and Silverlight. Obviously, we must use the .NET Framework 3.5 version in conjunction with our SharePoint development. The project is well documented and includes some great examples here and at Prabir’s blog here.

Data returned from the Facebook Graph API is in JSON format. To parse this data quickly and efficiently, my project includes the Json.NET framework.

If you download the source code for my demos here, you will see it includes a series of SharePoint project items. They include:

ClaimsWebPart

Largely based on this blog post, the Claims Web Part displays all of the claims included in the logged in user’s identity token in grid format. I added some Facebook-specific items in here for debugging purposes, such as displaying the user’s Facebook access token (parsed from the identity token), then using that token and the Facebook C# SDK to get the user’s current city, hometown, and Facebook user name.

The Claims Web Part is a great troubleshooting/debugging tool for developers and administrators who are new to working with Claims. It is a quick and easy way to verify the claim rules you configured when creating your Trusted Identity Provider are behaving the way they should.

SilverlightToFacebook

In my demo, a Silverlight application interfaces with the user’s webcam and saves snapshots to a SharePoint document library (hat tip to MossLover for that code). This class contains an event receiver that then takes those photos added to the SharePoint document library and uploads them to Facebook.

SPSVBDemos

This web part is the “dashboard” I used to do a handful of quick proof-of-concept demos, including the following:

  • Changing the display name of the currently logged in SPUser to match the name claim returned by Facebook (instead of the user’s email address or Claims-encoded username).
  • Adding information from the user’s Facebook profile (name, city, birthday, employer, job title, etc.) to a contacts list.
  • Populating a calendar list with recurring events for all of your friends’ birthdays (based on friends whose privacy settings allow sharing of this information).
  • Uploading a video from the file system to the user’s Facebook profile.

StatusUpdateWebPart

This web part allows the user to update his/her Facebook status directly from SharePoint. Optionally, the user may also include a link (with image, caption, description, etc.) with each status update.

WeatherWebPart

This web part determines the current user’s city from his/her Facebook profile, then constructs a request to the URL-driven Weather Underground API to retrieve the current weather conditions in that city. While many of these web parts may only have value in demos, I believe this web part represents a meaningful way for SharePoint site owners to provide a nice personalization experience to end users who log in to SharePoint with Facebook accounts.

Site Template

I also created a site template (SPSVB.WSP) that contains the custom lists and web parts I used in my demo. It is included in the code download.

Thanks again to everyone who helped to make SharePoint Saturday Virginia Beach such a great success! If you have any questions or suggestions about this code, please feel free to post them in the comments.

Download

SPSVB web parts, source code, site template, and required certificates to configure SharePoint trust for Facebook

Required Trust Relationships for the Facebook C# SDK in SharePoint 2010

I recently started using the Facebook C# SDK from CodePlex in my efforts to link SharePoint 2010, Claims-Based Identity, Azure ACS, and Facebook into one killer demo for my presentation on Claims-Based Identity that I will be giving at SharePoint Saturday Virginia Beach next month.

Without giving too much away, I intend to leverage the AccessToken input claim type provided by Facebook through Azure ACS to reach back into the user’s Facebook profile and obtain more information about the user who has just logged in to SharePoint from Facebook.

After rushing into coding a new web part (as any good developer would), I immediately encountered the following exception upon deployment:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
at FluentHttp.HttpHelper.OpenRead()
at Facebook.FacebookClient.Api(String path, IDictionary`2 parameters, HttpMethod httpMethod, Type resultType)

This error was met with the following corresponding entry in the Event Viewer:

In order to resolve this error, it is important to remember that SharePoint 2010 maintains its own certificate store, where separate trusts must be configured and maintained. Even though the root of the certificate chain may already be trusted everywhere else, you will receive this error unless an explicit trust is configured for SharePoint in Central Administration (or through PowerShell).

After much trial and error, I discovered that two trusts must be configured to support Facebook. First, go to the DigiCert Root Certificate site and download the following certificates as .cer files:

  • Under Root Certificates: DigiCert High Assurance EV Root CA
  • Under Intermediate Certificates: DigiCert High Assurance CA-3

Now that you are armed with these two .cer files, go to Central Administration. Go to Security, then under “General Security,” choose Manage Trust.

For each of the two .cer files, perform the following steps:

  1. From the ribbon, select New.
  2. In the “Establish Trust Relationship” dialog that appears, give each certificate a friendly name. In the “Root Authority Certificate” section, press Browse… and navigate to each .cer file.
  3. Press OK.

Perform an IISRESET after adding both certificates, and you should be ready to leverage the Facebook C# SDK from SharePoint 2010. Happy coding, and I hope to see you at SPSVB on January 7th!

Ensure an SPUser Exists in an Application Page Within an Anonymously Accessible Site

I was recently confronted with a requirement to develop an application page that could retrieve information about the currently logged in user from an external system based on that user’s email address. Piece of cake, I thought: I would just use the SPContext.Current.Web.CurrentUser object, which contains a string property called Email containing that SPUser‘s email address. I would take that string, pass it to the external system, get my information back, and call it a day. There was only one problem: the application page had to run within a site that had anonymous access enabled, and none of the users who would be accessing the page were explicitly assigned any permissions within the site.

A quick aside about anonymously accessible application pages

The default “Application Page” SPI within Visual Studio 2010 creates a page that inherits from the LayoutsPageBase class. A frequent complaint I hear is that an application page is prompting a user to log in even though that page is being accessed from within the context of an anonymously accessible site. If I had wanted my application page to be anonymously accessible, I would have to make the following tweaks to what Visual Studio 2010 gives me by default:

  • First, ensure that anonymous access is enabled for the Entire Web Site. I would do this from Site Actions > Site Permissions in SharePoint.
  • Change the page inheritance so that it inherits from the UnsecuredLayoutsPageBase class (shown below).
  • Explicitly override the AllowAnonymousAccess property and set its value to true (shown below).

But back to the problem at hand…

Since I want to force users to log in to access my application page (even with anonymous access enabled at the site level), I will leave the default base class of LayoutsPageBase. Can anyone see what problem might arise with my scenario?

It turns out that since none of the users accessing the page were explicitly assigned any permissions within the site (nor belonged to any site groups), SPContext.Current.Web.CurrentUser was returning null!

Fortunately, there is some good news here: even though the SPContext may not know who the current user is, the System.Web.HttpContext does! The property this.Context.User.Identity.Name contains the login name of the current user, which I can then pass to the SPWeb.EnsureUser() function to create the SPUser in the given SPWeb based on that login name. The EnsureUser() function returns an SPUser object that is populated with the email address of the user (along with the other properties of the SPUser object such as LoginName, Name, ID,
etc.)

NOTE: Do NOT try to call SPWeb.EnsureUser() in the context of the currently logged in user. It is important to run this code inside a RunWithElevatedPrivileges() delegate because the System Account will have the necessary permissions to add the specified user to the SPWeb. After all, if the specified user already had permission to do this, we wouldn’t need to be adding him or her in the first place!

The final code to accomplish everything (with proper error checking and handling omitted for brevity) looks like this:

Note that I don’t wrap the entire method with a RunWithElevatedPrivileges() delegate or web.CurrentUser would return the identity of the System Account, which is definitely not what I want.

Has anyone else encountered a similar scenario and solved the problem differently? Please feel free to discuss in the comments below!

Fun with BCS, External Lists, and PowerShell!

A recent question on SharePoint Stack Exchange inspired me to want to learn more about interacting with SharePoint lists using PowerShell, especially external lists created through Business Connectivity Services (BCS) in SharePoint 2010. With my background being more in .NET development using the object model, I was surprised to discover some of the inconsistencies one sees when interacting with an external list in PowerShell (compared with a standard out-of-the-box list).

For the purposes of this discussion, I will assume that you are familiar with how to use SharePoint Designer to create an external content type mapped to a SQL Server data source. From there, I created an external list for this external content type.

What initially made my learning journey such a confusing one was that the SQL Server table I used as the data source for my external content type contained a column named ID of type integer. It is possible that any external line-of-business (LOB) data source you consume through BCS may have a similar column as well. The values I initially inserted into the database table had auto-increment values of 1, 2, 3, etc. This enabled me to interact with the items in my external list using the following PowerShell syntax:

The first two lines may be unfamiliar to those of you who have not programmed with BCS before. With BCS, it is necessary to establish an SPServiceContext within an SPServiceContextScope that represents the HTTP context of the Business Data Connectivity Service.

If you ever see the following error:

“The shim execution failed unexpectedly – Proxy creation failed. Default context not found.”

Make sure you instantiate an SPServiceContext within an SPServiceContextScope as I have above.

It appears that there are some things you can do without properly obtaining the SPServiceContext within an SPServiceContextScope (such as enumerating the Fields collection of the external list). That said, the safest approach appears to be to always instantiate the SPServiceContextScope as shown above. If anyone can shed any additional light on why this is, please feel free to share in the comments below!

Fun Fact: An ID is not always an ID!

Here is my external list, viewed through the browser:

As you can see, the list contains an ID column with integer values, mapped directly to the similarly configured column in my SQL Server database table:

This allows me to run the following script to obtain access to the item with an ID value of 2:

Seems reasonable, right? After all, the second item we insert into a new out-of-the-box SharePoint list would be assigned an ID value of 2 as well (without us having to do anything to make it happen). And since I was able to call GetItemById(2), that must mean the ID property of the list item is 2, right?

Wrong!

Well, this is interesting. It turns out that my call to GetItemById(2) only works for the following reasons:

  • My external list contains a column named ID.
  • That column is of data type integer.

For instance, I cannot create another external list mapped to a data source with a column named ID but of a different type (i.e., nchar(10)), even if that column only contains integer values. Here’s what happens in that case:

Of course, it goes without saying that for an out-of-the-box list, the behavior is as you would expect:

Another item of interest is that the ItemCount property of external lists always returns 0, no matter how many items are in the list:

If you have any fun stories or observations about dealing with external lists in PowerShell, please feel free to share them in the comments below!

Using Claims Authentication to Configure Multiple Authentication Providers in a CloudShare Environment

This post originally appeared in the CloudShare Community Blog on October 25th.

CloudShare’s very own Chris Riley recently shared an environment of mine in a blog post about network orchestration. This environment leverages some exciting new capabilities in SharePoint 2010: namely Claims Authentication in a SharePoint 2010 web application and the ability to configure multiple authentication providers in a single zone. These technologies offer exciting prospects to anyone who previously had to configure separate host headers and URLs for different extranet customers, depending on the mechanism required for each subset of users to log in.

How does this look to end users? To see Claims in action, fire up the “SharePoint 2010 (Clean Installation)” image from the shared environment, open Internet Explorer and navigate to http://intranet.contoso.com.

You’ll see an empty Team Site with a “Claims Web Part” at the top. Since anonymous access to the site is enabled, the Claims Web Part will initially be empty.

Press the “Sign In” link at the top right of the page. The page that appears, http://intranet.contoso.com/_login/default.aspx, is new in SharePoint 2010 and allows users to choose from all the different authentication providers configured for that web application. (As a side note, there are some exciting opportunities for custom code development here, including the ability to automatically redirect users to a specific authentication provider based on parameters such as their IP address, bypassing this page of options entirely! After all, not all end users are going to appreciate the distinction between “Windows Authentication” and “Forms Authentication.”)

In this web application, I have configured the following providers:

  • Windows Authentication – standard NTLM credentials.
  • Forms-Based Authentication (FBA) – you may remember this option from MOSS. In this environment, I have created a custom membership provider that validates inputted credentials against a SQL Server database. In practice, FBA can be used to authenticate external users against Active Directory or any other account database.
  • Azure Access Control Services (ACS) v2 – allows users to log in with credentials from Open ID providers such as Windows Live ID and Facebook.

For a more in-depth read about how to configure Azure ACS v2 as an authentication provider in SharePoint, including adding other Open ID providers such as Google and Yahoo, check out this blog post by Travis Nielsen.

If I choose Azure ACS v2 (this can be given a friendlier name when you set it up as a Trusted Identity Provider through PowerShell), I will see the following screen. Claims Authentication relies on a series of HTTP redirects to seamlessly direct users between SharePoint and external trusted identity providers such as Azure ACS to log them in. This page is hosted completely outside of my SharePoint environment:

By choosing “Windows Live ID,” I will be redirected to https://login.live.com, where I will be prompted to enter my Windows Live ID credentials. (Similarly, you will be redirected to any of the other Open ID providers’ sites should you choose them instead.) After I sign in, another series of HTTP redirects takes place that eventually lands me back in my SharePoint environment, all logged in. The Claims web part on the page shows the various Claims that were sent by the Trusted Identity Provider in an XML-based Security Assertion Markup Language (SAML) token back to SharePoint:

As an application developer, I can leverage these Claims (you see some examples of Claims in the screenshot above include nameidentifier, emailaddress, userid, name, etc.) to make various authorization (what resources may this authenticated user access) and personalization (how is this particular user or class of user’s experience customized) decisions about how to handle this user.

Claims-based authentication and Azure ACS offer exciting possibilities to application developers who are liberated from having to maintain (or even worse, design and develop!) a user management system and all the nightmares that go along with it (think about password resets, forgotten passwords, security requirements for maintaining account information, etc.) As Chris mentions in his blog post, the same paradigm can be applied using Active Directory Federation Services (AD FS v2) to support users logging in to a SharePoint environment using credentials from a trusted external domain.

That said, Claims-based authentication is not always as easy to set up and work with as it may seem. Using CloudShare, developers and IT professionals can focus their valuable time and energy on solving the issues surrounding the implementation of Claims-based identity in SharePoint 2010 and not be concerned with software licensing, hardware, or other infrastructure concerns.

SPSTCDC 2011 Recap

It was a privilege to attend and be part of SharePoint Saturday-The Conference last week. I am always humbled by how much I have yet to learn about this dynamic and exciting platform. As a developer by trade, I made a point to branch out and try to learn as much as I possibly could on the administrative side of things (while staying true to my developer roots and attending a few developer sessions as well). I also got to take part in a blogging project in conjunction with the conference for Engage in SharePoint. I have listed the sessions I attended during the conference with some brief thoughts on each below. Where I contributed a blog post for Engage in SharePoint for a particular session, I have linked to that post as well.

Day 1 – Thursday, August 11

On Thursday, I attended the all-day workshop on SharePoint and Windows Azure Development. Sahil Malik and Girish Raja really know their stuff and deliver their knowledge in a very compelling manner. Although I don’t currently get to do much “in the cloud” for my current job, I am hopeful to leverage this platform a lot more in the future.

SUGDC – Thursday night

Thursday evening, I presented a session on Claims-Based Identity in SharePoint 2010 at the SharePoint Users Group of Washington, DC, which held their August meeting at the conference venue. I want to extend my sincere thanks to everyone who stayed around to watch and ask questions during my presentation. For those who were unable to attend, here are my slides:

Day 2 – Friday, August 12

Session 1 – My first session was Setting up Kerberos configuration in a SharePoint farm with L. Carlos Rodriguez. Carlos really knows his stuff and delivered an energetic, knowledgeable, and lively discussion of the trials and tribulations of setting up Kerberos. I really enjoyed this session and wish it could have been a half or full-day workshop to give this subject the depth of attention it deserves.

Session 2 – The second session I attended was Accelerate Your SharePoint Development & Testing, led by my good friends Chris Riley and Zvi Guterman at CloudShare. I have been a fan of CloudShare for a couple months now, and after this presentation, I’m even more psyched about what the future holds. I’m sure there will be many more posts on this blog showing off the cool new things I can do with CloudShare in the future.

Session 3 – After lunch, I attended Mike Oryszak’s session on Getting the Most from User Profiles. Mike provided some great information about user profiles in SharePoint 2010 along with some valuable tips on how best to manage custom attributes and the synchronization of profile attributes with other line-of-business systems.

Session 4 – The next session I attended was Liam Cleary’s Are you who you say you are? SharePoint Authentication and Authorization. Liam is a rock star who really knows his stuff. The knowledge I gained about Claims-Based Identity and Trusted Identity Providers will greatly improve any future presentations I give on the subject.

Session 5 – My final session of the day was Beyond Approval: Intro to Creating Custom Workflow Actions with Ben Jones. Ben showed us just how easy it really is to define, implement, and deploy custom workflow actions that can be leveraged by end users in defining SharePoint Designer workflows. This is sure to be a time saver for a lot of developers out there!

Of course, no discussion of Friday would be complete without a discussion of SharePoint Got Talent. A big thank you to my friends who stuck around to watch me perform, and thanks to everyone who voted for me. A big congratulations to Marcy Kellar, the winner, and Tiffany Songvilay, the runner-up. Videos of most of the evening’s thoroughly entertaining performances (including my own) can be found here.

Day 3 – Saturday, August 13

Session 1 – My first session was Business Connectivity Services Explained by Example with Kirk Evans. Kirk blew our collective minds when he told us that using the native SQL Server method of connecting to SQL Server data was not the best approach. I will be following Kirk’s advice and using WCF services or .NET types from now on!

Session 2 – The next session I attended was Integrating SSRS 2008R2 with SharePoint 2010 with Kevin S. Goff. Kevin provided a lot of great insight on how to set up and configure SQL Server Reporting Services 2008 R2 with SharePoint 2010 and gave some very cool demos. I will definitely be setting this up in my development environment soon.

Session 3 – After lunch, I attended a great deep dive session called SharePoint Federation: Leveraging ADFSv2 and Claims Based Authentication to integrate with Partners with Pirooz Javan. Pirooz led a great discussion with a very engaged audience. I gained a lot of valuable real-world perspective from the many relevant questions asked by folks who are leveraging these technologies in the real world every day.

Session 4 – The next session I attended was Becky Isserman’s Epic SharePoint Battle: HTML 5 vs. Silverlight 5. Besides the awesome free mustache and candy, I learned a lot about the state-of-the-art with current browsers and two emerging technologies: HTML 5 and Silverlight 5 (currently in beta). Walking in to this session, I would have thought Silverlight stood no chance, but after seeing Becky’s demos and hearing her perspective, I’m not ready to write Silverlight off just yet.

Session 5 – The final session I attended was Johnathan Lightfoot’s So You Want Your Name in Print. I have gone back and forth in my own mind about whether I would ever want to invest the time and effort to become a published author in the SharePoint world. Johnathan provided tons of valuable advice from his own experiences as an author that has given me a lot more insight into the process of writing and publishing a book. It will certainly help influence any decisions I make about becoming an author in the future.

Summary

The conference was an outstanding experience overall. I cannot say enough great things about all the volunteers who worked to make my experience such a great one. As I tweeted on Saturday morning, I got a $1,300 conference experience for 3% of the price.