Como obter todas as declarações de usuário em tempo de aumento de declarações no SharePoint 2010
Artigo original publicado no quarta-feira, dia 30 de março de 2011
Um obstáculo razoavelmente constante durante o aumento de declarações no SharePoint 2010 tem tentado descobrir que declarações um usuário tem quando seu provedor de declarações personalizado é chamado para executar o aumento de declarações. Por exemplo, as declarações que você deseja aumentar para uma pessoa poderão depender do valor de outras declarações do usuário, por exemplo, se a pessoa pertence à função "Admins. do Domínio" e em seguida adicionam a declaração de função "Super Usuário” ou adicionam a declaração "Usuário Padrão". Depois de ficar frustrado com isso por um longo tempo, meu bom amigo Israel V. e Matt Long finalmente acharam a solução para este problema complexo (e que merecem 100% de crédito para esta solução, portanto, obrigado rapazes!).
Um dos problemas subjacentes em tentar obter as informações fora dos parâmetros fornecidos quando seu provedor de declarações é chamado para aumento é que você não tem qualquer acesso a um HttpContext para ver o conjunto de declarações. Israel e Matt descobriram isso e descobriram a alternativa, que é o OperationContext. E o que é isso?
Bem, OperationContext tem uma boa visão geral aqui: https://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontext(v=VS.90).aspx. Em resumo, o que nos interessa, é que ele permite a você acessar cabeçalhos de mensagens recebidas e propriedades (para usuários de SAML) e o contexto de segurança (para usuários do Windows). E como isso nos ajuda? Bem, quando seu provedor de declarações personalizado é chamado para aumento, você pode obter estas informações de mensagem recebida para usuários de SAML com esta aparência:
<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();
//criar uma lista para armazenar os resultados
List<IncomingClaim> incomingClaims = new List<IncomingClaim>();
//criar um documento Xml para analisar os resultados e carregar os dados
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(rqst);
//criar a nova tabela de namespace para que possamos consultar
XmlNamespaceManager xNS = new XmlNamespaceManager(xDoc.NameTable);
//adicionar os namespaces que usaremos
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");
//obter a lista de nós de declaração
XmlNodeList xList =
xDoc.SelectNodes("s:Envelope/s:Body/trust:RequestSecurityToken/trust:OnBehalfOf/saml:Assertion/saml:AttributeStatement/saml:Attribute", xNS);
//obter as correspondências
if ((xList != null) && (xList.Count > 0))
{
//enumerar por meio das correspondências para obter a lista de declarações
foreach (XmlNode xNode in xList)
{
//obter o tipo de declaração primeiro
string currentClaimType =
xNode.Attributes["AttributeNamespace"].Value +
"/" + xNode.Attributes["AttributeName"].Value;
//cada tipo de declaração terá nós filhos um para muitos com os valores
foreach (XmlNode claimNode in xNode.ChildNodes)
{
incomingClaims.Add(new IncomingClaim(currentClaimType,
claimNode.InnerText));
}
}
}
//agora você pode fazer o que quiser com a lista de declarações e concluir
//seu aumento
…
}
Isto é tudo. O código é simples e tão cheio de comentários que acredito que, se você o examinar e colar o Xml em um bom editor de Xml (como o que vem com o Visual Studio .NET), seu funcionamento ficará bastante claro. Nada muito complicado neste ponto, somente Xml.
Também há um efeito colateral interessante que você pode implementar com base neste padrão, apontado pelo leitor do blog Luis A.. O conjunto de declarações obtido em RequestMessage inclui tudo que veio de seu IP-STS, incluindo qualquer declaração que o STS do SharePoint descartará se não houver uma declaração correspondente mapeada. Dessa forma, se você souber que declarações o SharePoint descartará e se quiser mantê-las de qualquer jeito, poderá simplesmente aumentá-las você mesmo novamente com seu provedor de declarações personalizado. Simplesmente retire os valores e tipos de declaração de RequestMessage e os adicione de volta.
Os usuários de declarações do Windows não terão essa mensagem associada às solicitações deles, mas você pode obter uma WindowsIdentity para eles usando SecurityContext de OperationContext. A partir daí, você pode fazer tudo o que estiver disponível usando uma WindowsIdentity, como obter a lista de grupos aos quais o usuário pertence etc. Veja um exemplo:
//criar janelas
WindowsIdentity wid =
System.ServiceModel.OperationContext.Current.ServiceSecurityContext.WindowsIdentity;
//obter uma referência aos grupos aos quais o usuário pertence
IdentityReferenceCollection grps = wid.Groups;
//enumerar cada grupo (que será representado como um SID)
//OBSERVE que isto inclui TODOS os grupos - builtin, local, domínio etc.
foreach (IdentityReference grp in grps)
{
//obter o nome comum do grupo
SecurityIdentifier sid = new SecurityIdentifier(grp.Value);
NTAccount groupAccount = (NTAccount)sid.Translate(typeof(NTAccount));
string groupName = groupAccount.ToString();
//para grupos do domínio, remover o prefixo domínio\ do grupo
//nome em ordem para obter paridade de recursos com usuários de SAML
if (groupName.Contains("\\"))
groupName = groupName.Substring(groupName.IndexOf("\\") + 1);
//adicionar a declaração
incomingClaims.Add(new
IncomingClaim("https://schemas.microsoft.com/ws/2008/06/identity/claims/role",
groupName));
}
Garanta a adição de uma declaração using para System.Security.Principal para fazer com que WindowsIdentity, IdentityReference, NTAccount etc. sejam resolvidos corretamente. De qualquer forma, o código deve, novamente, ser bastante simples. Estou simplesmente obtendo a lista de grupos para o usuário e colocando-os em meu conjunto personalizado de declarações como a standard Role claim.
Obrigado novamente a Israel e Matt por compartilharem estas informações super valiosas.
Esta é uma postagem de blog traduzida. Encontre o artigo original em How to Get All User Claims at Claims Augmentation Time in SharePoint 2010