Artículo original publicado el miércoles, 30 de marzo de 2011

Un obstáculo bastante frecuente a la hora de hacer aumento de notificaciones en SharePoint 2010 ha sido intentar comprender qué notificaciones tiene un usuario cuando el proveedor de notificaciones personalizado se invoca para hacer aumento de notificaciones.  Por ejemplo, las notificaciones que se van a aumentar para un usuario pueden depender del valor de otras notificaciones que este tiene. Es decir, si el usuario pertenece al rol “Admins. del dominio”, agregue la notificación de rol “usuario superior”; de lo contrario, agregue la notificación “usuario estándar”.  Después de estar frustrado por esto durante mucho tiempo, a mis buenos amigos, Israel V. y Matt Long, se les ocurrió finalmente la solución para este engorroso problema (y merecen el 100% del crédito por esta solución: ¡gracias, amigos!).

 

Uno de los problemas subyacentes al tratar de obtener esta información fuera de los parámetros proporcionados cuando el proveedor de notificaciones se invoca para el aumento es que no se tiene acceso a un HttpContext para poder ver la colección de notificaciones.  Israel y Matt entendieron correctamente esto y dieron con la alternativa, que es el OperationContext.  ¿Y de qué se trata?

 

OperationContext tiene una buena introducción aquí:  http://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontext(v=VS.90).aspx.  En pocas palabras, lo que nos interesa es que permite el acceso a encabezados de mensajes entrantes y propiedades (en el caso de los usuarios de SAML) y al contexto de seguridad (en el caso de los usuarios de Windows).  ¿De qué manera nos ayuda esto?  Cuando el proveedor de notificaciones personalizado se invoca para el aumento, se puede obtener esta información de mensaje entrante para los usuarios de SAML con el siguiente aspecto:

 

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

   <s:Header>

       <a:Action s:mustUnderstand="1">http://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>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

       </a:ReplyTo>

       <a:To s:mustUnderstand="1">http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc</a:To>

   </s:Header>

   <s:Body>

       <trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">

          <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">

              <a:EndpointReference>

                 <a:Address>https://fc1/</a:Address>

              </a:EndpointReference>

          </wsp:AppliesTo>

          <trust:KeyType>http://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="http://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="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">

                        <saml:AttributeValue>testuser@vbtoys.com</saml:AttributeValue>

                     </saml:Attribute>

                     <saml:Attribute AttributeName="role" AttributeNamespace="http://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="http://schemas.microsoft.com/ws/2008/06/identity/claims">

                        <saml:AttributeValue>testuser</saml:AttributeValue>

                     </saml:Attribute>

                     <saml:Attribute AttributeName="primarysid" AttributeNamespace="http://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="http://www.w3.org/2000/09/xmldsig#">

                     <ds:SignedInfo>

                        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

                        </ds:CanonicalizationMethod>

                        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256">

                        </ds:SignatureMethod>

                        <ds:Reference URI="#_8f1d7b46-2b71-4263-859b-c3e358d7ea84">

                           <ds:Transforms>

                               <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature">

                               </ds:Transform>

                               <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">

                               </ds:Transform>

                           </ds:Transforms>

                           <ds:DigestMethod Algorithm="http://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="http://www.w3.org/2000/09/xmldsig#">

                        <X509Data>

                           <X509Certificate>MIIFlzCCBH+gAwIBAgIKHmblahblahblah</X509Certificate>

                        </X509Data>

                     </KeyInfo>

                 </ds:Signature>

              </saml:Assertion>

          </trust:OnBehalfOf>

          <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>

       </trust:RequestSecurityToken>

   </s:Body>

</s:Envelope>

 

<<Una disculpa por el largo y feo fragmento de XML, pero quería que lo vieran>>

 

Ahora, ya que tenemos un fragmento de XML con las notificaciones insertadas, no es un problema abrirlas y usarlas durante el aumento.  Este es un pequeño ejemplo rápido que escribí para hacerlo:

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", "http://www.w3.org/2003/05/soap-envelope");

       xNS.AddNamespace("trust", "http://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

}

 

Eso es todo.  El código es bastante simple y está tan comentado que creo que si mira y pega el XML en un buen editor de XML (como el que viene con Visual Studio .NET), quedará claro cómo funciona.  No tiene ninguna ciencia en este momento, simplemente se trata de XML.

 

También hay un efecto secundario interesante que se puede implementar en función de este modelo, como comenta el lector del blog, Luis A.  El conjunto de notificaciones que se obtienen en RequestMessage incluye todo lo que proviene de IP-STS, incluidas las notificaciones que SharePoint STS eliminará si no se asigna una notificación correspondiente.  Por lo tanto, si sabe qué notificaciones va a eliminar SharePoint y desea conservarlas de todos modos, simplemente puede volver a aumentarlas con el proveedor de notificaciones personalizado.  Solo tiene que extraer de RequestMessage los tipos y valores de las notificaciones, y vuélvalas a agregar.

 

Los usuarios de notificaciones de Windows no tendrán este mensaje asociado con la solicitud, pero se puede obtener un WindowsIdentity para ellos mediante el SecurityContext del OperationContext.  Desde ahí, puede hacer todas las cosas disponibles con un WindowsIdentity, como obtener la lista de grupos a los que pertenece el usuario, etc.  Este es un ejemplo:

 

//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("http://schemas.microsoft.com/ws/2008/06/identity/claims/role",

              groupName));

}

 

Tal vez desee asegurarse de que agrega una instrucción using para System.Security.Principal para obtener WindowsIdentity, IdentityReference, NTAccount, etc. a fin de resolver correctamente.  De lo contrario, el código debería ser bastante sencillo.  Simplemente voy a obtener la lista de grupos del usuario y los voy a colocar en mi colección personalizada de notificaciones como  una notificación de rol estándar.

 

Gracias otra vez, Israel y Matt , por compartir esta información tan valiosa.

Esta entrada de blog es una traducción. Puede consultar el artículo original en How to Get All User Claims at Claims Augmentation Time in SharePoint 2010