SharePoint 2010 でクレーム拡張時にすべてのユーザー クレームを取得する方法
原文の記事の投稿日: 2011 年 3 月 30 日 (水曜日)
SharePoint 2010 でクレーム認証を行う場合にほぼ常に障害となっていたのが、カスタム クレーム プロバイダーがクレーム拡張のために呼び出されたときにユーザーのクレームが何であるかを判別しようとすることでした。 たとえば、あるユーザー用に拡張するクレームは、そのユーザーが所有する他のクレームの値によって異なることがあります。たとえば、ユーザーがロール “Domain Admins” に属している場合は、ロール クレーム “Uber User” を追加します。それ以外の場合は、クレーム “Standard User” を追加します。 これには長い間悩まされましたが、ようやく友人の Israel V. 氏と Matt Long 氏がこの難題の解決策を見つけました (この解決策は 100% の称賛に値するので、彼らに感謝します)。
クレーム プロバイダーがクレーム拡張のために呼び出されたときに提供されるパラメーターの外側にあるこの情報にアクセスしようとする場合の基本的な問題の 1 つは、クレームのコレクションを確認するための HttpContext にアクセスできないことです。 Israel 氏と Matt 氏は、この点を正しく見極め、代替策を考えました。それが OperationContext です。 では、これは何でしょうか。
OperationContext の適切な概要については、 https://msdn.microsoft.com/ja-jp/library/system.servicemodel.operationcontext(VS.90).aspx を参照してください。 要するに、ここで大事なのは、これを使用することで着信メッセージのヘッダーとプロパティ (SAML ユーザーの場合) およびセキュリティ コンテキスト (Windows ユーザーの場合) にアクセスできることです。 それでは、これはどう役に立つのでしょうか。 カスタム クレーム プロバイダーがクレーム拡張のために呼び出された場合、SAML ユーザーでは以下のような着信メッセージ情報にアクセスできます。
<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>
<<長くて読みにくい XML になってしまって申し訳ありませんが、これを見てもらいたかったのです>>
クレームが内側に埋め込まれた長い XML があるので、折り返しを解除して、拡張時に使用できるようにすることなど大した問題ではありません。 以下は、それを行うために私が作成した簡単なサンプルです。
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
…
}
だいたい、このような感じです。 このコードはとても簡単で、非常にわかりやすくコメント アウトされているので、これを確認して、XML を便利な XML エディター (Visual Studio .NET に付属のエディターなど) に貼り付ければ、どのように動作するかがすぐにわかるはずです。 ここまでは何も難解ではありません。単なる XML なのですから。
このパターンを基に実装できる興味深い副次的な効果もあります。これは、ブログ読者の Luis A. 氏が指摘してくれました。 RequestMessage で取得する一連のクレームには、IP-STS から得たすべてのクレームが含まれます。これには、マップ先の対応するクレームがないと SharePoint STS が無視するクレームも含まれます。 そのため、SharePoint がどのクレームを無視するかがわかっていて、それらをどうしても保持する必要がある場合は、カスタム クレーム プロバイダーを使用して該当するクレームを再び自分で拡張できます。 RequestMessage からクレームの値と種類を引き出し、それらを再び追加するだけです。
Windows クレーム ユーザーには、要求に関連付けられたこのメッセージはありませんが、OperationContext の SecurityContext を使用することで Windows クレームの WindowsIdentity を取得できます。 ここから、ユーザーが属するグループのリストを取得するなど、WindowsIdentity で利用可能なことをすべて実行できます。 以下に、その例を示します。
//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));
}
System.Security.Principal の Using ステートメントを追加することで WindowsIdentity、IdentityReference、NTAccount などを取得して適切に解決できます。 追加しなければ、コードはさらにとても単純になります。 私は、ユーザーのグループ リストを取得し、それを標準ロール クレームとしてクレームのカスタム コレクション に挿入しています。
この非常に貴重な情報を提供してくれた Israel 氏と Matt 氏に改めて感謝します。
これはローカライズされたブログ投稿です。原文の記事は、「How to Get All User Claims at Claims Augmentation Time in SharePoint 2010」をご覧ください。