Come ottenere tutte le attestazioni degli utenti durante l'aggiunta di attestazioni in SharePoint 2010
Articolo originale pubblicato mercoledì 30 marzo 2011
Durante l'aggiunta di attestazioni in SharePoint 2010, un problema abbastanza frequente è rappresentato dal tentativo di determinare di quali attestazioni disponga un utente quando il provider di attestazioni personalizzate viene richiamato per eseguire l'aggiunta. Ad esempio, le attestazioni da aggiungere per un utente possono dipendere dal valore di altre attestazioni di cui l'utente già dispone. Se ad esempio la persona appartiene al ruolo "Domain Admins", aggiungere l'attestazione di "utente con privilegi avanzati", altrimenti aggiungere l'attestazione di "utente standard". Dopo lunghi periodi di frustrazione, il mio grande amico Israel V. e Matt Long alla fine sono riusciti a trovare una soluzione per questo difficile problema e meritano il pieno riconoscimento per questa scoperta. Perciò grazie, ragazzi!.
Uno dei problemi di fondo che si incontrano tentando di ottenere queste informazioni al di fuori dei parametri forniti quando il provider di attestazioni viene richiamato per l'aggiunta di attestazioni è il fatto di non disporre dell'accesso ad alcun elemento HttpContext per poter esaminare l'insieme di attestazioni. Israel e Matt hanno ben focalizzato questo aspetto e hanno pensato a un'alternativa, ovvero all'elemento OperationContext. Ma, allora, di che si tratta?
È possibile trovare una panoramica esauriente su OperationContext all'indirizzo seguente: https://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontext(v=VS.90).aspx. Per sintetizzare, l'aspetto che ci interessa è che consente di accedere alle proprietà e alle intestazioni dei messaggi in arrivo (per gli utenti SAML) e al contesto di sicurezza (per gli utenti Windows). In che modo tutto ciò può esserci utile? Quando il provider di attestazioni personalizzate viene richiamato per l'aggiunta di attestazioni, per gli utenti SAML è possibile accedere alle informazioni seguenti del messaggio in arrivo:
<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>
<<le mie scuse per questo che, mi rendo conto, è un lungo blocco di codice XML non proprio chiarissimo, ma volevo che lo vedeste>>
Dal momento che abbiamo un blocco di codice XML con le attestazioni incorporate all'interno, non è poi così difficile eseguire l'unwrapping per poterle utilizzare durante l'aggiunta di attestazioni. Di seguito ecco un breve e rapido esempio che ho scritto a tale scopo:
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
…
}
Ecco fatto. Il codice è sufficientemente semplice e commentato in modo dettagliato. Ritengo perciò che, esaminandolo e incollandolo in un editor XML (come quello fornito con Visual Studio .NET), il funzionamento risulti abbastanza chiaro. Niente fantascienza quindi, ma solo codice XML.
In tal modo, è possibile inoltre beneficiare di un interessante vantaggio collaterale evidenziato da un lettore di questo blog, Luis A. Nel set di attestazioni che si otterrà nell'elemento RequestMessage è incluso tutto quanto proviene dal servizio token di sicurezza di provider di identità, comprese le eventuali attestazioni che il servizio token di sicurezza di SharePoint eliminerà se non esiste un'attestazione mappata corrispondente. Se pertanto si conoscono le attestazioni che verranno eliminate da SharePoint e si desidera mantenerle comunque, sarà possibile semplicemente aggiungerle di nuovo con il provider di attestazioni personalizzate. È infatti sufficiente estrarre i tipi e i valori delle attestazioni dall'elemento RequestMessage e quindi aggiungerle di nuovo.
Questo messaggio non sarà associato alla richiesta degli utenti di attestazioni di Windows, ma per tali utenti è possibile ottenere un elemento WindowsIdentity utilizzando l'elemento SecurityContext di OperationContext. Da quel punto è quindi possibile eseguire tutte le operazioni consentite con un elemento WindowsIdentity, ad esempio ottenere l'elenco dei gruppi a cui appartiene l'utente e così via. Ecco un esempio:
//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));
}
Ricordarsi di aggiungere un'istruzione using per System.Security.Principal per ottenere WindowsIdentity, IdentityReference, NTAccount e così via, in modo che la risoluzione avvenga correttamente. In ogni caso, il codice dovrebbe essere di nuovo piuttosto chiaro. Io ottengo l'elenco dei gruppi relativi all'utente e li inserisco nel mio insieme personalizzato di attestazioni come attestazione di ruolo standard.
Grazie ancora a Israel e Matt per avere condiviso con noi queste informazioni super preziose.
Questo è un post di blog localizzato. L'articolo originale è disponibile in How to Get All User Claims at Claims Augmentation Time in SharePoint 2010.