256 lines
11 KiB
C#
256 lines
11 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.IdentityModel.Selectors
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Text;
|
|
using System.IdentityModel.Claims;
|
|
using System.IdentityModel.Tokens;
|
|
using System.IdentityModel.Policy;
|
|
using System.Security.Principal;
|
|
using System.Security;
|
|
|
|
public class SamlSecurityTokenAuthenticator : SecurityTokenAuthenticator
|
|
{
|
|
List<SecurityTokenAuthenticator> supportingAuthenticators;
|
|
Collection<string> allowedAudienceUris;
|
|
AudienceUriMode audienceUriMode;
|
|
TimeSpan maxClockSkew;
|
|
|
|
public SamlSecurityTokenAuthenticator(IList<SecurityTokenAuthenticator> supportingAuthenticators)
|
|
: this(supportingAuthenticators, TimeSpan.Zero)
|
|
{ }
|
|
|
|
public SamlSecurityTokenAuthenticator(IList<SecurityTokenAuthenticator> supportingAuthenticators, TimeSpan maxClockSkew)
|
|
{
|
|
|
|
if (supportingAuthenticators == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("supportingAuthenticators");
|
|
|
|
this.supportingAuthenticators = new List<SecurityTokenAuthenticator>(supportingAuthenticators.Count);
|
|
for (int i = 0; i < supportingAuthenticators.Count; ++i)
|
|
{
|
|
this.supportingAuthenticators.Add(supportingAuthenticators[i]);
|
|
}
|
|
|
|
this.maxClockSkew = maxClockSkew;
|
|
this.audienceUriMode = AudienceUriMode.Always;
|
|
this.allowedAudienceUris = new Collection<string>();
|
|
}
|
|
|
|
public AudienceUriMode AudienceUriMode
|
|
{
|
|
get { return this.audienceUriMode; }
|
|
set
|
|
{
|
|
AudienceUriModeValidationHelper.Validate(audienceUriMode);
|
|
this.audienceUriMode = value;
|
|
}
|
|
}
|
|
|
|
public IList<string> AllowedAudienceUris
|
|
{
|
|
get { return this.allowedAudienceUris; }
|
|
}
|
|
|
|
protected override bool CanValidateTokenCore(SecurityToken token)
|
|
{
|
|
return token is SamlSecurityToken;
|
|
}
|
|
|
|
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
|
|
SamlSecurityToken samlToken = token as SamlSecurityToken;
|
|
|
|
if (samlToken == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SamlTokenAuthenticatorCanOnlyProcessSamlTokens, token.GetType().ToString())));
|
|
|
|
if (samlToken.Assertion.Signature == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SamlTokenMissingSignature)));
|
|
|
|
if (!this.IsCurrentlyTimeEffective(samlToken))
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SAMLTokenTimeInvalid, DateTime.UtcNow.ToUniversalTime(), samlToken.ValidFrom.ToString(), samlToken.ValidTo.ToString())));
|
|
|
|
if (samlToken.Assertion.SigningToken == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SamlSigningTokenMissing)));
|
|
|
|
// Build the Issuer ClaimSet for this Saml token.
|
|
ClaimSet issuer = null;
|
|
bool canBeValidated = false;
|
|
for (int i = 0; i < this.supportingAuthenticators.Count; ++i)
|
|
{
|
|
canBeValidated = this.supportingAuthenticators[i].CanValidateToken(samlToken.Assertion.SigningToken);
|
|
if (canBeValidated)
|
|
break;
|
|
}
|
|
if (!canBeValidated)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SamlInvalidSigningToken)));
|
|
issuer = ResolveClaimSet(samlToken.Assertion.SigningToken) ?? ClaimSet.Anonymous;
|
|
|
|
List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>();
|
|
for (int i = 0; i < samlToken.Assertion.Statements.Count; ++i)
|
|
{
|
|
policies.Add(samlToken.Assertion.Statements[i].CreatePolicy(issuer, this));
|
|
}
|
|
|
|
|
|
// Check AudienceUri if required
|
|
// AudienceUriMode != Never - don't need to check can only be one of three
|
|
// AudienceUriMode == Always
|
|
// AudienceUriMode == BearerKey and there are no proof keys
|
|
//
|
|
if ((this.audienceUriMode == AudienceUriMode.Always)
|
|
|| (this.audienceUriMode == AudienceUriMode.BearerKeyOnly) && (samlToken.SecurityKeys.Count < 1))
|
|
{
|
|
// throws if not found.
|
|
bool foundAudienceCondition = false;
|
|
if (this.allowedAudienceUris == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SAMLAudienceUrisNotFound)));
|
|
}
|
|
|
|
for (int i = 0; i < samlToken.Assertion.Conditions.Conditions.Count; i++)
|
|
{
|
|
|
|
SamlAudienceRestrictionCondition audienceCondition = samlToken.Assertion.Conditions.Conditions[i] as SamlAudienceRestrictionCondition;
|
|
if (audienceCondition == null)
|
|
continue;
|
|
|
|
foundAudienceCondition = true;
|
|
if (!ValidateAudienceRestriction(audienceCondition))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SAMLAudienceUriValidationFailed)));
|
|
}
|
|
}
|
|
|
|
if (!foundAudienceCondition)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SAMLAudienceUriValidationFailed)));
|
|
}
|
|
|
|
return policies.AsReadOnly();
|
|
}
|
|
|
|
protected virtual bool ValidateAudienceRestriction(SamlAudienceRestrictionCondition audienceRestrictionCondition)
|
|
{
|
|
for (int i = 0; i < audienceRestrictionCondition.Audiences.Count; i++)
|
|
{
|
|
if (audienceRestrictionCondition.Audiences[i] == null)
|
|
continue;
|
|
|
|
for (int j = 0; j < this.allowedAudienceUris.Count; j++)
|
|
{
|
|
if (StringComparer.Ordinal.Compare(audienceRestrictionCondition.Audiences[i].AbsoluteUri, this.allowedAudienceUris[j]) == 0)
|
|
return true;
|
|
else if (Uri.IsWellFormedUriString(this.allowedAudienceUris[j], UriKind.Absolute))
|
|
{
|
|
Uri uri = new Uri(this.allowedAudienceUris[j]);
|
|
if (audienceRestrictionCondition.Audiences[i].Equals(uri))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual ClaimSet ResolveClaimSet(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
|
|
for (int i = 0; i < this.supportingAuthenticators.Count; ++i)
|
|
{
|
|
if (this.supportingAuthenticators[i].CanValidateToken(token))
|
|
{
|
|
ReadOnlyCollection<IAuthorizationPolicy> authorizationPolicies = this.supportingAuthenticators[i].ValidateToken(token);
|
|
AuthorizationContext authContext = AuthorizationContext.CreateDefaultAuthorizationContext(authorizationPolicies);
|
|
if (authContext.ClaimSets.Count > 0)
|
|
{
|
|
return authContext.ClaimSets[0];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public virtual ClaimSet ResolveClaimSet(SecurityKeyIdentifier keyIdentifier)
|
|
{
|
|
if (keyIdentifier == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifier");
|
|
|
|
RsaKeyIdentifierClause rsaKeyIdentifierClause;
|
|
EncryptedKeyIdentifierClause encryptedKeyIdentifierClause;
|
|
if (keyIdentifier.TryFind<RsaKeyIdentifierClause>(out rsaKeyIdentifierClause))
|
|
{
|
|
return new DefaultClaimSet(new Claim(ClaimTypes.Rsa, rsaKeyIdentifierClause.Rsa, Rights.PossessProperty));
|
|
}
|
|
else if (keyIdentifier.TryFind<EncryptedKeyIdentifierClause>(out encryptedKeyIdentifierClause))
|
|
{
|
|
return new DefaultClaimSet(Claim.CreateHashClaim(encryptedKeyIdentifierClause.GetBuffer()));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public virtual IIdentity ResolveIdentity(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
|
|
for (int i = 0; i < this.supportingAuthenticators.Count; ++i)
|
|
{
|
|
if (this.supportingAuthenticators[i].CanValidateToken(token))
|
|
{
|
|
ReadOnlyCollection<IAuthorizationPolicy> authorizationPolicies = this.supportingAuthenticators[i].ValidateToken(token);
|
|
if (authorizationPolicies != null && authorizationPolicies.Count != 0)
|
|
{
|
|
for (int j = 0; j < authorizationPolicies.Count; ++j)
|
|
{
|
|
IAuthorizationPolicy policy = authorizationPolicies[j];
|
|
if (policy is UnconditionalPolicy)
|
|
{
|
|
return ((UnconditionalPolicy)policy).PrimaryIdentity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public virtual IIdentity ResolveIdentity(SecurityKeyIdentifier keyIdentifier)
|
|
{
|
|
if (keyIdentifier == null)
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifier");
|
|
|
|
RsaKeyIdentifierClause rsaKeyIdentifierClause;
|
|
if (keyIdentifier.TryFind<RsaKeyIdentifierClause>(out rsaKeyIdentifierClause))
|
|
{
|
|
return SecurityUtils.CreateIdentity(rsaKeyIdentifierClause.Rsa.ToXmlString(false), this.GetType().Name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool IsCurrentlyTimeEffective(SamlSecurityToken token)
|
|
{
|
|
if (token.Assertion.Conditions != null)
|
|
{
|
|
return SecurityUtils.IsCurrentlyTimeEffective(token.Assertion.Conditions.NotBefore, token.Assertion.Conditions.NotOnOrAfter, this.maxClockSkew);
|
|
}
|
|
|
|
// If SAML Condition is not present then the assertion is valid at any given time.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|