//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation, Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.InfoCard {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IdentityModel.Claims;
using System.IdentityModel.Policy;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using DotNetOpenAuth.Messaging;
///
/// The decrypted token that was submitted as an Information Card.
///
[ContractVerification(true)]
public class Token {
///
/// Backing field for the property.
///
private IDictionary claims;
///
/// Backing field for the property.
///
private string uniqueId;
///
/// Initializes a new instance of the class.
///
/// Xml token, which may be encrypted.
/// The audience. May be null to avoid audience checking.
/// The decryptor to use to decrypt the token, if necessary..
/// Thrown for any problem decoding or decrypting the token.
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type."), SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive")]
private Token(string tokenXml, Uri audience, TokenDecryptor decryptor) {
Requires.NotNullOrEmpty(tokenXml, "tokenXml");
Requires.True(decryptor != null || !IsEncrypted(tokenXml), null);
Contract.Ensures(this.AuthorizationContext != null);
byte[] decryptedBytes;
string decryptedString;
using (StringReader xmlReader = new StringReader(tokenXml)) {
var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings();
using (XmlReader tokenReader = XmlReader.Create(xmlReader, readerSettings)) {
Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null
if (IsEncrypted(tokenReader)) {
Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml);
decryptedBytes = decryptor.DecryptToken(tokenReader);
decryptedString = Encoding.UTF8.GetString(decryptedBytes);
Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here
} else {
decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
decryptedString = tokenXml;
}
}
}
var stringReader = new StringReader(decryptedString);
try {
this.Xml = new XPathDocument(stringReader).CreateNavigator();
} catch {
stringReader.Dispose();
throw;
}
Logger.InfoCard.DebugFormat("Incoming SAML token, after any decryption: {0}", this.Xml.InnerXml);
this.AuthorizationContext = TokenUtility.AuthenticateToken(this.Xml.ReadSubtree(), audience);
}
///
/// Gets the AuthorizationContext behind this token.
///
public AuthorizationContext AuthorizationContext { get; private set; }
///
/// Gets the the decrypted token XML.
///
public XPathNavigator Xml { get; private set; }
///
/// Gets the UniqueID of this token, usable as a stable username that the user
/// has already verified belongs to him/her.
///
///
/// By default, this uses the PPID and the Issuer's Public Key and hashes them
/// together to generate a UniqueID.
///
public string UniqueId {
get {
if (string.IsNullOrEmpty(this.uniqueId)) {
this.uniqueId = TokenUtility.GetUniqueName(this.AuthorizationContext);
}
return this.uniqueId;
}
}
///
/// Gets the hash of the card issuer's public key.
///
public string IssuerPubKeyHash {
get { return TokenUtility.GetIssuerPubKeyHash(this.AuthorizationContext); }
}
///
/// Gets the Site Specific ID that the user sees in the Identity Selector.
///
public string SiteSpecificId {
get {
Requires.ValidState(this.Claims.ContainsKey(ClaimTypes.PPID) && !string.IsNullOrEmpty(this.Claims[ClaimTypes.PPID]));
string ppidValue;
ErrorUtilities.VerifyOperation(this.Claims.TryGetValue(ClaimTypes.PPID, out ppidValue) && ppidValue != null, InfoCardStrings.PpidClaimRequired);
return TokenUtility.CalculateSiteSpecificID(ppidValue);
}
}
///
/// Gets the claims in all the claimsets as a dictionary of strings.
///
public IDictionary Claims {
get {
if (this.claims == null) {
this.claims = this.GetFlattenedClaims();
}
return this.claims;
}
}
///
/// Deserializes an XML document into a token.
///
/// The token XML.
/// The deserialized token.
public static Token Read(string tokenXml) {
Requires.NotNullOrEmpty(tokenXml, "tokenXml");
return Read(tokenXml, (Uri)null);
}
///
/// Deserializes an XML document into a token.
///
/// The token XML.
/// The URI that this token must have been crafted to be sent to. Use null to accept any intended audience.
/// The deserialized token.
public static Token Read(string tokenXml, Uri audience) {
Requires.NotNullOrEmpty(tokenXml, "tokenXml");
return Read(tokenXml, audience, Enumerable.Empty());
}
///
/// Deserializes an XML document into a token.
///
/// The token XML.
/// Any X.509 certificates that may be used to decrypt the token, if necessary.
/// The deserialized token.
public static Token Read(string tokenXml, IEnumerable decryptionTokens) {
Requires.NotNullOrEmpty(tokenXml, "tokenXml");
Requires.NotNull(decryptionTokens, "decryptionTokens");
return Read(tokenXml, null, decryptionTokens);
}
///
/// Deserializes an XML document into a token.
///
/// The token XML.
/// The URI that this token must have been crafted to be sent to. Use null to accept any intended audience.
/// Any X.509 certificates that may be used to decrypt the token, if necessary.
/// The deserialized token.
public static Token Read(string tokenXml, Uri audience, IEnumerable decryptionTokens) {
Requires.NotNullOrEmpty(tokenXml, "tokenXml");
Requires.NotNull(decryptionTokens, "decryptionTokens");
Contract.Ensures(Contract.Result() != null);
TokenDecryptor decryptor = null;
if (IsEncrypted(tokenXml)) {
decryptor = new TokenDecryptor();
decryptor.Tokens.AddRange(decryptionTokens);
}
return new Token(tokenXml, audience, decryptor);
}
///
/// Determines whether the specified token XML is encrypted.
///
/// The token XML.
///
/// true if the specified token XML is encrypted; otherwise, false.
///
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive"), Pure]
internal static bool IsEncrypted(string tokenXml) {
Requires.NotNull(tokenXml, "tokenXml");
var stringReader = new StringReader(tokenXml);
XmlReader tokenReader;
try {
var readerSettings = MessagingUtilities.CreateUntrustedXmlReaderSettings();
tokenReader = XmlReader.Create(stringReader, readerSettings);
} catch {
stringReader.Dispose();
throw;
}
try {
Contract.Assume(tokenReader != null); // CC missing for XmlReader.Create
return IsEncrypted(tokenReader);
} catch {
IDisposable disposableReader = tokenReader;
disposableReader.Dispose();
throw;
}
}
///
/// Determines whether the specified token XML is encrypted.
///
/// The token XML.
///
/// true if the specified token XML is encrypted; otherwise, false.
///
private static bool IsEncrypted(XmlReader tokenXmlReader) {
Requires.NotNull(tokenXmlReader, "tokenXmlReader");
return tokenXmlReader.IsStartElement(TokenDecryptor.XmlEncryptionStrings.EncryptedData, TokenDecryptor.XmlEncryptionStrings.Namespace);
}
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(this.AuthorizationContext != null);
}
#endif
///
/// Flattens the claims into a dictionary
///
/// A dictionary of claim type URIs and claim values.
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call.")]
[Pure]
private IDictionary GetFlattenedClaims() {
var flattenedClaims = new Dictionary();
foreach (ClaimSet set in this.AuthorizationContext.ClaimSets) {
foreach (Claim claim in set) {
if (claim.Right == Rights.PossessProperty) {
flattenedClaims.Add(claim.ClaimType, TokenUtility.GetResourceValue(claim));
}
}
}
return flattenedClaims;
}
}
}