How to Get All User Claims at Claims Augmentation Time in SharePoint 2010
A fairly constant hurdle when doing claims augmentation in SharePoint 2010 has been trying to figure out what claims a user has when your custom claims provider is invoked to do claims augmentation. For example, the claims you want to augment for a person may depend on the value of other claims the user has, e.g. if the person belongs to the role “Domain Admins” then add the role claim “Uber User”, otherwise add the claim “Standard User”. After being frustrated by this for a long time, my good friend Israel V. and Matt Long finally came up with the solution to this sticky problem (and deserves 100% of the credit for this solution, so thank you guys!).
One of the underlying problems in trying to get to this information outside of the parameters provided when your claim provider is invoked for augmentation is that you don’t have any access to an HttpContext to look at the collection of claims. Israel and Matt correctly figured this out and figured out the alternative, which is the OperationContext. So what is that?
Well, OperationContext has a good overview here: https://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontext(v=VS.90).aspx. In a nutshell, the thing we care about, is it allows you to access incoming message headers and properties (for SAML users) and the security context (for Windows users). So how does this help us? Well, when your custom claims provider is invoked for augmentation, you can get to this incoming message info for SAML users which looks like this:
<s:Envelope xmlns:s="https://www.w3.org/2003/05/soap-envelope" xmlns:a="https://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">https://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:MessageID>urn:uuid:85a0daaa-2288-4d0a-bda8-5fac05ea61cf</a:MessageID>
<a:ReplyTo>
<a:Address>https://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc</a:To>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust="https://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="https://schemas.xmlsoap.org/ws/2004/09/policy">
<a:EndpointReference>
<a:Address>https://fc1/</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>https://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
<trust:OnBehalfOf>
<saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_8f1d7b46-2b71-4263-859b-c3e358d7ea84" Issuer="https://myadfsserver/adfs/services/trust" IssueInstant="2011-03-26T18:51:54.671Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:Conditions NotBefore="2011-03-26T18:51:33.198Z" NotOnOrAfter="2011-03-26T19:51:33.198Z">
<saml:AudienceRestrictionCondition>
<saml:Audience>urn:sharepoint:fc1</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="emailaddress" AttributeNamespace="https://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="role" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>pOregon Marketing</saml:AttributeValue>
<saml:AttributeValue>Domain Users</saml:AttributeValue>
<saml:AttributeValue>pSales</saml:AttributeValue>
<saml:AttributeValue>Portal People</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="windowsaccountname" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>testuser</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="primarysid" AttributeNamespace="https://schemas.microsoft.com/ws/2008/06/identity/claims">
<saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:federation:authentication:windows" AuthenticationInstant="2011-03-26T18:51:33.069Z">
<saml:Subject>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="https://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="https://www.w3.org/2001/10/xml-exc-c14n#">
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="https://www.w3.org/2001/04/xmldsig-more#rsa-sha256">
</ds:SignatureMethod>
<ds:Reference URI="#_8f1d7b46-2b71-4263-859b-c3e358d7ea84">
<ds:Transforms>
<ds:Transform Algorithm="https://www.w3.org/2000/09/xmldsig#enveloped-signature">
</ds:Transform>
<ds:Transform Algorithm="https://www.w3.org/2001/10/xml-exc-c14n#">
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="https://www.w3.org/2001/04/xmlenc#sha256">
</ds:DigestMethod>
<ds:DigestValue>5Qvu+blahblah=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>VUSrynYjN8NOcUexqJOCblahblah</ds:SignatureValue>
<KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIIFlzCCBH+gAwIBAgIKHmblahblahblah</X509Certificate>
</X509Data>
</KeyInfo>
</ds:Signature>
</saml:Assertion>
</trust:OnBehalfOf>
<trust:RequestType>https://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
<<apologies for what I realize is a long chunk of ugly Xml, but I wanted you to see it>>
Now, since we have a chunk of Xml with our claims embedded inside, it’s no big deal to unwrap them and be able to use them during augmentation. Here’s a quick little sample that I wrote to do just that:
using System.Xml;
…
private class IncomingClaim
{
public string claimType { get; set; }
public string claimValue { get; set; }
public IncomingClaim(string claimType, string claimValue)
{
this.claimType = claimType;
this.claimValue = claimValue;
}
}
protected override void FillClaimsForEntity(Uri context, SPClaim entity,
List<SPClaim> claims)
{
//get the request envelope with the claims information
string rqst =
System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString();
//create a list to store the results
List<IncomingClaim> incomingClaims = new List<IncomingClaim>();
//create an Xml document for parsing the results and load the data
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(rqst);
//create a new namespace table so we can query
XmlNamespaceManager xNS = new XmlNamespaceManager(xDoc.NameTable);
//add the namespaces we'll be using
xNS.AddNamespace("s", "https://www.w3.org/2003/05/soap-envelope");
xNS.AddNamespace("trust", "https://docs.oasis-open.org/ws-sx/ws-trust/200512");
xNS.AddNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");
//get the list of claim nodes
XmlNodeList xList =
xDoc.SelectNodes("s:Envelope/s:Body/trust:RequestSecurityToken/trust:OnBehalfOf/saml:Assertion/saml:AttributeStatement/saml:Attribute", xNS);
//get the matches
if ((xList != null) && (xList.Count > 0))
{
//enumerate through the matches to get the claim list
foreach (XmlNode xNode in xList)
{
//get the claim type first
string currentClaimType =
xNode.Attributes["AttributeNamespace"].Value +
"/" + xNode.Attributes["AttributeName"].Value;
//each claim type will have one to many child nodes with the values
foreach (XmlNode claimNode in xNode.ChildNodes)
{
incomingClaims.Add(new IncomingClaim(currentClaimType,
claimNode.InnerText));
}
}
}
//now you can do whatever you want with the list of claims and finish
//your augmentation
…
}
That’s pretty much it. The code is simple enough and commented out so verbosely that I believe if you look at it, and paste the Xml into a nice Xml editor (like the one that comes with Visual Studio .NET) that it should be pretty clear how it works. No rocket science at this point, just Xml.
There is also an interesting side effect you can implement based on this pattern that blog reader Luis A. points out. The set of claims you will get in the RequestMessage includes everything that came from your IP-STS, including any claims that the SharePoint STS will be dropping if there isn’t a corresponding claim mapped. So, if you know which claims SharePoint is going to be dropping and you want to keep them anyways, you can just augment them yourself again with your custom claims provider. Just pull the claim values and types out of the RequestMessage and add them back in.
Windows claims users won’t have this message associated with their request, but you can get a WindowsIdentity for them by using the SecurityContext of the OperationContext. From that you can do all the things available with a WindowsIdentity, like getting the list of groups the user belongs to, etc. Here’s an example of that:
//do windows
WindowsIdentity wid =
System.ServiceModel.OperationContext.Current.ServiceSecurityContext.WindowsIdentity;
//get a reference to the groups to which the user belongs
IdentityReferenceCollection grps = wid.Groups;
//enumerate each group (which will be represented as a SID)
//NOTE that this includes ALL groups - builtin, local, domain, etc.
foreach (IdentityReference grp in grps)
{
//get the plain name of the group
SecurityIdentifier sid = new SecurityIdentifier(grp.Value);
NTAccount groupAccount = (NTAccount)sid.Translate(typeof(NTAccount));
string groupName = groupAccount.ToString();
//for domain groups remove the domain\ prefix from the group
//name in order to have feature parity with SAML users
if (groupName.Contains("\\"))
groupName = groupName.Substring(groupName.IndexOf("\\") + 1);
//add the claim
incomingClaims.Add(new
IncomingClaim("https://schemas.microsoft.com/ws/2008/06/identity/claims/role",
groupName));
}
You’ll want to make sure you add a using statement for System.Security.Principal to get the WindowsIdentity, IdentityReference, NTAccount, etc. to resolve correctly. Otherwise the code should again be fairly straightforward. I’m just getting the list of groups for the user and putting them in my custom collection of claims as a standard Role claim.
Thanks again Israel and Matt for sharing this super valuable information.
How to Get All User Claims at Claims Augmentation Time in SharePoint 2010.docx
Comments
Anonymous
January 01, 2003
Sorry, what I meant by my reply is that SharePoint is still going to drop those unmapped claims. You will see all claims that come from your IP-STS, so if you have some unmapped claims come through then you can capture them in this event and augment them yourself. This is actually an interesting work-around (if you know which ones you need to grab that is) so I will update the blog post with that info too. Thanks for the question.Anonymous
January 01, 2003
thanksAnonymous
January 01, 2003
The comment has been removedAnonymous
January 01, 2003
No, that is something different. What is stated in that post still applies.Anonymous
March 29, 2011
Hi Steve, if i understood correctly, this solution overcomes the problem you mention on your earlier post blogs.technet.com/.../figuring-out-what-claims-you-have-in-sharepoint-2010.aspx "If your STS sends back other claims besides what SharePoint is expecting, SharePoint will IGNORE all of those additional claims and it WILL NOT be added to the SPClaim". is this correct ? thanksAnonymous
March 31, 2011
Hi, i thought that the message came from the sts provider. What would be the best aproach to overcome the problem mentioned? ThanksAnonymous
December 10, 2011
how can i get custom groups (AD groups in the claims) is see only few groups . how can i get all groups associated to the token programticaly. thanksAnonymous
November 20, 2012
how to get the claims of a different user? Let say as a admin i want to see the claims of other user by passing userIDAnonymous
September 18, 2014
The comment has been removedAnonymous
March 02, 2015
In Part 1 of this series, we went through how to configure SharePoint to use ACS and Azure Active DirectoryAnonymous
March 02, 2015
Okay, I’m going to preface everything in this post by saying what I’m going to be describing