Creating a Yammer-Centric Security Setup for SharePoint 2013

Okay, I’m going to preface everything in this post by saying what I’m going to be describing is not what you would consider the most secure SharePoint web application in the world. If you are working with sensitive content then this is probably (but not absolutely) NOT the best solution for you. However, if you are totally into Yammer and the fluid collaboration that it provides, but also love the features that SharePoint brings to the table, such as document management, search, etc., then this is something worth considering. In fact I would say that yeah, I may be something of a SAML sympathizer, but this is actually pretty cool. In a nutshell, here’s what I’ve done: I’ve created a web application in SharePoint that uses Yammer “security principals” for authorization. Along the way, I’ve incorporated Azure Active Directory (AAD) and Windows Azure storage, the Yammer API, and a custom claims provider to create the solution. Let me start with a little bit of an overview.

First, let’s talk about the directory. In this scenario, I am NOT using Active Directory on premises or ADFS. This is a huge deal! Because of the capabilities in AAD and Yammer, it was not needed for this solution. Think about all of the onsite management that you no longer have to deal with, operate, patch, and maintain. I can tell you for all for all of the lab setups I use for this blog, it was pretty amazing and satisfying to not have to go through all of the typical pain I endure for something like this – creating one or more Active Directory controllers, building a new test forest, creating one or more ADFS servers, doing all the certificate setup, all the DNS setup, blah, blah, blah. Suffice to say it makes me tired. :-) This was a fantastic alternative. The solution itself looks something like this:

At a high level then it works like this: I have a Windows Azure subscription, and with that subscription is an AAD instance. In this case, my AAD instance is yammo.onmicrosoft.com. I also have an Office 365 tenant, and it also uses the “yammo” namespace, so it is at yammo.sharepoint.com and it is configured to use my yammo AAD instance. That means everything in my Office 365 tenant (if I want to use my cloud version of SharePoint) is secured with principals in my AAD yammo directory. In addition to that, I have a Yammer tenant that came with my Office 365 subscription, so my Yammer tenant also has a name of yammo.onmicrosoft.com. In this case my directories have been synced already, so they are unified. In an environment with an on premises Active Directory, I would do dirsync between it and both my AAD instance as well as Yammer to create that unified directory experience. That’s what the top three clouds in the diagram and their corresponding arrows represent.

Now, inside Yammer I have all of the users from the AAD yammo directory. In addition to that, I have Yammer groups. You can create any number of Yammer groups, and you can add whatever users you want to different Yammer groups (or they can create groups and add themselves). This model of self-deployment of groups and members is why I say that this solution is not for super sensitive content. The reason I say that is because the users and groups from Yammer become the security principals that will be used in SharePoint. There are two important pieces of code that make this happen:

  1. Scheduled Sync Job – I have written a console application that is scheduled to run periodically, and it queries Yammer to get a list of all the users and all of the groups in my Yammer tenant. The code for this sync job is based on the patterns and code I described previously in this post:  https://blogs.technet.com/b/speschka/archive/2013/10/05/using-the-yammer-api-in-a-net-client-application.aspx. I think the model should be fairly straightforward if you’ve read that post and developed your own code based on that, so I’m not even including the source for the sync job in this post. Depending on how many users and groups you have it’s possible for it to take a LLLLOOONNNNGGGG time to retrieve all of them (potentially several hours if you have thousands and thousands). This is why it’s implemented as a sync job – we don’t want to try and retrieve all of them in real time. Instead, the sync job runs on its schedule and then it writes it all out to an Xml file. Xml is relatively easy to work with and XPath is pretty fast. Once that Xml file has been created, I store it in blob storage in Windows Azure storage, and that’s why you see that cloud included in this diagram. All of this also means that unless you have a fairly small directory, it won’t really be feasible to have this work in near real time. That just means when you add new groups or change group memberships in Yammer, you need to wait until the sync job runs again for that data updated for use with SharePoint.
  2. Custom Claims Provider – the custom claims provider looks for the Xml file in the local server cache. If it finds it then it uses the copy it has there, otherwise it reaches out to blob storage in Windows Azure, retrieves the Xml file, and sticks it in cache for future use. Then it loads it up and uses it for the XPath queries that are used in the various interface implementations of my custom claims provider. Easy peezy, right?? Okay, maybe not, but it’s not horrible at all either.

I started out by creating a new Office 365 demo tenant (which includes Yammer). In this part I must admit, I cheated just a little from what you will experience if you don’t work for Microsoft. The “cheat” here is that we have a spot where we can create a demo tenant, but it also creates a number of sites, adds content (both SharePoint and Yammer groups and posts), and adds 40 or 50 demo users. If you’re doing this yourself though that’s okay – you probably just want to create something from scratch anyways so you can put in your own company’s users. Given that you’re going to want to use some Azure pieces to make all of this work by the time you’re done, the easiest thing to do to start with is just create a new Windows Azure subscription. Here’s the bad part – you will have to provide a credit card to do this. Here’s the good part – Windows Azure Active Directory is free with your subscription so it won’t be charged. In fact, now every Windows Azure subscription is associated with an auto created directory in Windows Azure Active Directory. So when you create your subscription you’ll get your AAD instance ready to go as well. Once that’s set up, you can create your trial Office 365 tenant by going here:  https://portal.microsoftonline.com/Signup/MainSignUp15.aspx?OfferId=B07A1127-DE83-4a6d-9F85-2C104BDAE8B4&dl=ENTERPRISEPACK.

At this point, I have my Office 365 tenant, AAD instance, and Yammer tenant all created and working. Next, I created a storage account with the Azure subscription I have for this demo so I can store my Xml file in blob storage. Now for this, you WILL get charged. However, the rates for using blob storage (as of the time I wrote this) are $0.095 per GB for storage and $0.01 per 100,000 transactions. So in my case, roughly ten cents a month. Yeah, I can live with that. 

Now to hook all the pieces up there are two things in SharePoint I need to be concerned about: authentication and authorization. For authentication, I’m going to use AAD (since that’s where all my accounts are at), and thankfully as I pointed out above, I will NOT be using ADFS and a local AD instance. As I point out in this blog posting –   https://blogs.technet.com/b/speschka/archive/2013/05/10/integrating-sharepoint-2013-with-azure-active-directory-part-1-configuration.aspx – it’s not possible to connect SharePoint directly to AAD (read the post if you need the details). So instead I went through the steps described in that posting to create a new ACS namespace with my Windows Azure subscription, and then add my AAD instance as an identity provider to it. I then added my on premises SharePoint farm as a relying party, created a rule group and was pretty much good to go at that point. Again, for complete details on this process just read the blog post above, I just followed that.

Now that authentication is taken care of, I need to think about authorization. As I mentioned at the beginning of this post, I want the authorization for this SharePoint web app to be based on the model used in Yammer. To do that, I need to integrate the Yammer group concept in my authorization rules. The way I do that is with a custom claims provider. My provider is really going to do three big things:

  1. When a user authenticates, I’m going to figure out who they are and then query Yammer to get a list of the Yammer groups they belong to (yes, I know that’s a dangling modifier but it just sounds better). I’m then going to add a role claim for each Yammer group.
  2. When search is invoked, I’m going to look in my Xml file that contains all of my Yammer users and groups and pull results from there for the people picker.
  3. When a user or group is selected I’m going to resolve the selected entities using the content in the Xml file.

For the claims augmentation piece, I use the same approach described here to figure out “who” the user is:  https://blogs.technet.com/b/speschka/archive/2011/03/29/how-to-get-all-user-claims-at-claims-augmentation-time-in-sharepoint-2010.aspx. For my SPTrustedIdentityTokenIssuer I defined email address as the identity claim. I extract that, then do a lookup in the Xml file to get the Yammer ID for that person. Once I have that I make a REST call to Yammer to get the list of groups for the user, and then add each one to their list of role claims. The code is based on my previous post on using Yammer from a .NET client that I linked to above (https://blogs.technet.com/b/speschka/archive/2013/10/05/using-the-yammer-api-in-a-net-client-application.aspx) and looks like this:

//look for the user

string qry = "Yammer/Entry[@email='" + upn.ToLower() + "']";

xNode = xDoc.SelectSingleNode(qry);

if (xNode != null)

{

//get the user ID

string ID = xNode.Attributes["id"].Value;

 

//query Yammer for the person

string response = MakeGetRequest(oneUserUrl.Replace("[:id]", ID), accessToken);

List<YammerGroup> userGroups =

JsonConvert.DeserializeObject<List<YammerGroup>>(response);

 

if (userGroups != null)

{

//enumerate through all the groups and add them as role claims

foreach (YammerGroup yg in userGroups)

{

claims.Add(new SPClaim(ROLE_CLAIM, yg.Name,

Microsoft.IdentityModel.Claims.ClaimValueTypes.String,

SPOriginalIssuers.Format(SPOriginalIssuerType.TrustedProvider,

SPTrustedIdentityTokenIssuerName)));

}

}

}

A few things worth pointing out in this code:

  • To get the full context of this like the Urls being used, the XPath query, etc., look at the source code included with this post.
  • I’m using the NewtonSoft.Json assembly to parse the list of groups for the user in this case; for this particular scenario and the format of the returned data it’s much easier than DataContractJsonSerializer.
  • Note that since my custom claims provider is the default provider for the SPTrustedIdentityTokenIssuer, I’m using the format you see above for adding claims. I have multiple posts where I’ve described how and why you do that.

In terms of searching data, it’s really pretty straightforward – I just have what is effectively a wildcard XPath search to look for matches, and return PickerEntity instances for each match. The key code in search looks like this:

string qry = "Yammer/Entry[@name[starts-with(.,'" + searchPattern.ToLower() +

"')] or @firstname[starts-with(.,'" + searchPattern.ToLower() +

"')] or @lastname[starts-with(.,'" + searchPattern.ToLower() + "')]]";

 

XmlDocument xDoc = new XmlDocument();

xDoc.LoadXml(xml);

nl = xDoc.SelectNodes(qry);

So nothing earth-shattering there, just figuring out the XPath took a bit of time but once you have it down it all “just works” pretty well.

Finally resolving names is pretty similar to search, as you would expect. The only real difference is in the version of FillResolve that includes a claim, I know whether I’m looking for a user or group so I end up modifying my XPath slightly; the simplified version of it looks like this: 

if (resolveInput.ClaimType == USER_CLAIM)

{

//look for the user

string qry = "Yammer/Entry[@email='" + resolveInput.Value.ToLower() + "']"; ;

//do stuff with the query here

else

{

//look for the group

string qry = "Yammer/Entry[@name='" + resolveInput.Value.ToLower() + "'

and @type='group']";

//do stuff with the query here

}

After that, as I alluded to above, I simply make my custom claims provider the default provider and I’m good to go. Here’s a screenshot of my claim set, which includes my Yammer groups:

Here’s a couple of screenshots where I’ve added a Yammer group to a SharePoint group to authorize it to the site:

Finally, here’s an example of a user that’s logged in just by virtue of her membership in the Yammer “Operations” group:

That pretty much wraps this up. I’ve included the source code to the custom claims provider I used. You will of course have to modify the account settings to get it to work in your environment. If you’re full on social then sharing this can be an interesting option for integrating SharePoint with your Yammer content.

YammoSamlBlog.zip

Comments

  • Anonymous
    January 01, 2003
    thanks
  • Anonymous
    January 01, 2003
    thanks for sharing.
  • Anonymous
    January 01, 2003
    The comment has been removed
  • Anonymous
    March 04, 2014
    Hi Steve,
    Awesome post man, this is really informative in terms of the problem I'm trying to solve. One question though - would it be possible to run some variation of this solution completely in Office 365 - i.e. something that allows an O365 equivalent to a Custom Claims Provider to provide custom authorisation claims to SharePoint 2013 Online?
    Thanks for the blog, keep the posts coming!
  • Anonymous
    September 18, 2014
    The comment has been removed