< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.IdentityModel.Tokens.SamlSecurityTokenHandler
Assembly: CoreWCF.Primitives
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/IdentityModel/Tokens/SamlSecurityTokenHandler.cs
Line coverage
54%
Covered lines: 75
Uncovered lines: 62
Coverable lines: 137
Total lines: 411
Line coverage: 54.7%
Branch coverage
46%
Covered branches: 46
Total branches: 98
Branch coverage: 46.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
.ctor(...)50%22100%
.ctor()100%11100%
.ctor(...)100%110%
.ctor(...)100%110%
ValidateToken(...)90%101095.45%
ValidateSubjectConfirmations(...)88.46%262694.11%
CanReadToken(...)100%11100%
ReadToken(...)100%11100%
TryResolveIssuerToken(...)50%6662.5%
WriteToken(...)0%660%
GetTokenTypeIdentifiers()100%11100%
BuildCryptoList(...)32.14%282821.73%
ResolveSubjectKeyIdentifier(...)0%12120%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/IdentityModel/Tokens/SamlSecurityTokenHandler.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Collections.ObjectModel;
 7using System.Security.Claims;
 8using System.Xml;
 9using CoreWCF.IdentityModel.Selectors;
 10using CoreWCF.Runtime;
 11using Microsoft.IdentityModel.Tokens.Saml;
 12using MSIdentityTokens = Microsoft.IdentityModel.Tokens;
 13
 14namespace CoreWCF.IdentityModel.Tokens
 15{
 16    /// <summary>
 17    /// This class implements a SecurityTokenHandler for a Saml11 token.  It contains functionality for: Creating, Seria
 18    /// a Saml 11 Token.
 19    /// </summary>
 20    public class SamlSecurityTokenHandler : SecurityTokenHandler
 21    {
 222        private static readonly string[] s_tokenTypeIdentifiers = new string[] { SecurityTokenTypes.SamlTokenProfile11, 
 23        private SecurityTokenSerializer _keyInfoSerializer;
 24        private readonly MSIdentityTokens.Saml.SamlSecurityTokenHandler _internalSamlSecurityTokenHandler;
 1025        private readonly object _syncObject = new object();
 26        private readonly SamlSecurityTokenRequirement _samlSecurityTokenRequirement;
 27
 28        /// <summary>
 29        /// Initializes an instance of <see cref="SamlSecurityTokenHandler"/>
 30        /// </summary>
 31        public SamlSecurityTokenHandler()
 1032            : this(new SamlSecurityTokenRequirement())
 33        {
 1034            _internalSamlSecurityTokenHandler = new MSIdentityTokens.Saml.SamlSecurityTokenHandler();
 1035        }
 36
 37        /// <summary>
 38        /// Initializes an instance of <see cref="SamlSecurityTokenHandler"/>
 39        /// </summary>
 40        /// <param name="samlSecurityTokenRequirement">The SamlSecurityTokenRequirement to be used by the Saml11Security
 1041        public SamlSecurityTokenHandler(SamlSecurityTokenRequirement samlSecurityTokenRequirement)
 42        {
 1043            _samlSecurityTokenRequirement = samlSecurityTokenRequirement ?? throw DiagnosticUtility.ExceptionUtility.Thr
 1044        }
 45
 46        /// <summary>
 47        /// Method exposed for extensibility
 48        /// </summary>
 49        /// <param name="saml2SecurityTokenHandler"></param>
 50        public SamlSecurityTokenHandler(MSIdentityTokens.Saml.SamlSecurityTokenHandler samlSecurityTokenHandler)
 051            : this(samlSecurityTokenHandler, new SamlSecurityTokenRequirement())
 52        {
 053        }
 54
 055        public SamlSecurityTokenHandler(MSIdentityTokens.Saml.SamlSecurityTokenHandler samlSecurityTokenHandler, SamlSec
 56        {
 057            _internalSamlSecurityTokenHandler = samlSecurityTokenHandler;
 058            _samlSecurityTokenRequirement = samlSecurityTokenRequirement;
 059        }
 60
 61        #region TokenValidation
 62        /// <summary>
 63        /// Returns value indicates if this handler can validate tokens of type
 64        /// SamlSecurityToken.
 65        /// </summary>
 066        public override bool CanValidateToken => _internalSamlSecurityTokenHandler.CanValidateToken;
 67
 68        /// <summary>
 69        /// Validates a <see cref="SamlSecurityToken"/>.
 70        /// </summary>
 71        /// <param name="token">The <see cref="SamlSecurityToken"/> to validate.</param>
 72        /// <returns>The <see cref="ReadOnlyCollection{T}"/> of <see cref="ClaimsIdentity"/> representing the identities
 73        /// <exception cref="ArgumentNullException">The parameter 'token' is null.</exception>
 74        /// <exception cref="ArgumentException">The token is not assignable from <see cref="SamlSecurityToken"/>.</excep
 75        /// <exception cref="InvalidOperationException">Configuration <see cref="SecurityTokenHandlerConfiguration"/>is 
 76        /// <exception cref="ArgumentException">SamlSecurityToken.Assertion is null.</exception>
 77        /// <exception cref="SecurityTokenValidationException">Thrown if SamlSecurityToken.Assertion.SigningToken is nul
 78        /// <exception cref="SecurityTokenValidationException">Thrown if the certificate associated with the token issue
 79        public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
 80        {
 5481            if (token == null)
 82            {
 183                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(token));
 84            }
 85
 5386            SamlSecurityToken samlToken = token as SamlSecurityToken;
 5387            if (samlToken == null)
 88            {
 189                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(token), SR.Format(SR.ID1033, token.G
 90            }
 91
 5292            if (Configuration == null)
 93            {
 194                throw DiagnosticUtility.ExceptionUtility.ThrowHelperInvalidOperation(SR.Format(SR.ID4274));
 95            }
 96
 5197            if (samlToken.Assertion == null)
 98            {
 199                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(token), SR.Format(SR.ID1034));
 100            }
 101
 50102            ValidateSubjectConfirmations(samlToken.Assertion);
 103
 48104            string assertionXML = samlToken.AssertionXML;
 48105            SamlTokenValidationParameters tokenValidation = new SamlTokenValidationParameters();
 48106            ClaimsPrincipal claim = _internalSamlSecurityTokenHandler.ValidateToken(assertionXML,
 48107                tokenValidation.ConvertToTokenValidationParameters(Configuration, samlToken, _samlSecurityTokenRequireme
 30108            ClaimsIdentity claimsIdentity = (ClaimsIdentity)claim.Identity;
 30109            if (Configuration.SaveBootstrapContext)
 110            {
 28111                claimsIdentity.BootstrapContext = new BootstrapContext(token, this);
 112            }
 30113            List<ClaimsIdentity> identities = new List<ClaimsIdentity>(1)
 30114            {
 30115                claimsIdentity
 30116            };
 30117            return identities.AsReadOnly();
 118        }
 119
 120        // SAML 1.1 confirmation methods we recognize.  Anything outside this set is
 121        // refused at validation time so that the application is not asked to bind
 122        // claims it has no policy for.
 2123        private static readonly HashSet<string> s_recognizedConfirmationMethods = new HashSet<string>(StringComparer.Ord
 2124        {
 2125            "urn:oasis:names:tc:SAML:1.0:cm:holder-of-key",
 2126            "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches",
 2127            "urn:oasis:names:tc:SAML:1.0:cm:bearer",
 2128        };
 129
 130        private const string HolderOfKeyConfirmationMethod = "urn:oasis:names:tc:SAML:1.0:cm:holder-of-key";
 131
 132        private static void ValidateSubjectConfirmations(SamlAssertion assertion)
 133        {
 50134            if (assertion?.Statements == null)
 135            {
 0136                return;
 137            }
 138
 202139            foreach (SamlStatement statement in assertion.Statements)
 140            {
 52141                if (!(statement is SamlSubjectStatement subjectStatement))
 142                {
 143                    continue;
 144                }
 145
 52146                SamlSubject subject = subjectStatement.Subject;
 52147                if (subject == null || subject.ConfirmationMethods == null)
 148                {
 149                    continue;
 150                }
 151
 52152                bool sawHolderOfKey = false;
 209153                foreach (string method in subject.ConfirmationMethods)
 154                {
 53155                    if (string.IsNullOrEmpty(method) || !s_recognizedConfirmationMethods.Contains(method))
 156                    {
 1157                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
 1158                            new SecurityTokenValidationException(SR.Format(SR.SAMLUnrecognizedConfirmationMethod, method
 159                    }
 160
 52161                    if (string.Equals(method, HolderOfKeyConfirmationMethod, StringComparison.Ordinal))
 162                    {
 1163                        sawHolderOfKey = true;
 164                    }
 165                }
 166
 51167                if (sawHolderOfKey && (subject.KeyInfo == null))
 168                {
 1169                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
 1170                        new SecurityTokenValidationException(SR.SAMLHolderOfKeyRequiresKeyInfo));
 171                }
 172            }
 48173        }
 174        #endregion
 175
 176        #region TokenSerialization
 177        /// <summary>
 178        /// Indicates whether the current XML element can be read as a token
 179        /// of the type handled by this instance.
 180        /// </summary>
 181        /// <param name="reader">An XML reader positioned at a start
 182        /// element. The reader should not be advanced.</param>
 183        /// <returns>'True' if the ReadToken method can the element.</returns>
 53184        public override bool CanReadToken(XmlReader reader) => _internalSamlSecurityTokenHandler.CanReadToken(reader);
 185
 186        /// <summary>
 187        /// Deserializes from XML a token of the type handled by this instance.
 188        /// </summary>
 189        /// <param name="reader">An XML reader positioned at the token's start
 190        /// element.</param>
 191        /// <returns>An instance of <see cref="SamlSecurityToken"/>.</returns>
 192        /// <exception cref="InvalidOperationException">Is thrown if 'Configuration' or 'Configruation.IssuerTokenResolv
 193        public override SecurityToken ReadToken(XmlReader reader)
 194        {
 55195            XmlDocument doc = new XmlDocument();
 55196            XmlElement rstXml = (doc.ReadNode(reader) as XmlElement);
 55197            MSIdentityTokens.Saml.SamlSecurityToken internalSecurityToken = _internalSamlSecurityTokenHandler.ReadSamlTo
 50198            TryResolveIssuerToken(internalSecurityToken.Assertion, Configuration.IssuerTokenResolver, out SecurityToken 
 50199            SamlSecurityToken samlSecurityToken = new SamlSecurityToken(internalSecurityToken, BuildCryptoList(internalS
 50200            {
 50201                SigningToken = token,
 50202                AssertionXML = rstXml.OuterXml
 50203            };
 50204            return samlSecurityToken;
 205        }
 206
 207        protected virtual bool TryResolveIssuerToken(SamlAssertion assertion, SecurityTokenResolver issuerResolver, out 
 208        {
 50209            if (null == assertion)
 210            {
 0211                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(assertion));
 212            }
 213
 50214            SecurityKeyIdentifier keyIdentifier = CoreWCF.Security.SecurityUtils.CreateSecurityKeyIdentifier(assertion.S
 215
 50216            if (keyIdentifier != null
 50217               && issuerResolver != null)
 218            {
 219                Fx.Assert(keyIdentifier.Count == 1, "There should only be one key identifier clause");
 50220                return issuerResolver.TryResolveToken(keyIdentifier, out token);
 221            }
 222            else
 223            {
 0224                token = null;
 0225                return false;
 226            }
 227
 228        }
 229
 230        /// <summary>
 231        /// Gets a boolean indicating if the SecurityTokenHandler can Serialize Tokens. Return true by default.
 232        /// </summary>
 0233        public override bool CanWriteToken => true;
 234
 235        /// <summary>
 236        /// Serializes the given SecurityToken to the XmlWriter.
 237        /// </summary>
 238        /// <param name="writer">XmlWriter into which the token is serialized.</param>
 239        /// <param name="token">SecurityToken to be serialized.</param>
 240        /// <exception cref="ArgumentNullException">Input parameter 'writer' or 'token' is null.</exception>
 241        /// <exception cref="SecurityTokenException">The given 'token' is not a SamlSecurityToken.</exception>
 242        public override void WriteToken(XmlWriter writer, SecurityToken token)
 243        {
 0244            if (writer == null)
 245            {
 0246                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(writer));
 247            }
 248
 0249            if (token == null)
 250            {
 0251                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(token));
 252            }
 253
 0254            SamlSecurityToken samlToken = token as SamlSecurityToken;
 0255            var wrappedSamlSecurityToken = samlToken.WrappedSamlSecurityToken;
 256
 0257            if (null != wrappedSamlSecurityToken)
 258            {
 0259                _internalSamlSecurityTokenHandler.WriteToken(writer, wrappedSamlSecurityToken);
 260            }
 261            else
 262            {
 0263                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(token), SR.Format(SR.ID4160));
 264            }
 265        }
 266        #endregion
 267
 268        /// <summary>
 269        /// Returns the saml token's token type that is supported by this handler.
 270        /// </summary>
 8271        public override string[] GetTokenTypeIdentifiers() => s_tokenTypeIdentifiers;
 272
 273        /// <summary>
 274        /// Gets or Sets a SecurityTokenSerializers that will be used to serialize and deserializer
 275        /// SecurtyKeyIdentifier. For example, SamlSubject SecurityKeyIdentifier or Signature
 276        /// SecurityKeyIdentifier.
 277        /// </summary>
 278        public SecurityTokenSerializer KeyInfoSerializer
 279        {
 280            get
 281            {
 0282                if (_keyInfoSerializer == null)
 283                {
 0284                    lock (_syncObject)
 285                    {
 0286                        if (_keyInfoSerializer == null)
 287                        {
 0288                            SecurityTokenHandlerCollection sthc = ContainingCollection ?? throw new NotSupportedExceptio
 0289                            _keyInfoSerializer = new SecurityTokenSerializerAdapter(sthc);
 290                        }
 0291                    }
 292                }
 293
 0294                return _keyInfoSerializer;
 295            }
 296            set
 297            {
 0298                _keyInfoSerializer = value ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(va
 0299            }
 300        }
 301
 302        /// <summary>
 303        /// Gets the System.Type of the SecurityToken is supported by ththis handler.
 304        /// </summary>
 14305        public override Type TokenType => typeof(SamlSecurityToken);
 306
 307
 308        ////https://github.com/microsoft/referencesource/blob/4e6dea7a9c7cbb4e6b000b05a099e7168d1b6960/System.IdentityMo
 309        //Below is the the way SecurityKey is populated. The only difference is we extract crypto at the same time while
 310        //over SamlSubject whereas in WCF it's populated from reader.
 311        private ReadOnlyCollection<SecurityKey> BuildCryptoList(SamlAssertion assertion)
 312        {
 50313            List<SecurityKey> cryptoList = new List<SecurityKey>();
 314
 204315            for (int i = 0; i < assertion.Statements.Count; ++i)
 316            {
 52317                SamlSubjectStatement statement = assertion.Statements[i] as SamlSubjectStatement;
 52318                if (statement != null && statement.Subject !=null && statement.Subject.KeyInfo !=null)
 319                {
 0320                    bool skipCrypto = false;
 321
 322                    //This code is simplified version of
 323                    //https://github.com/microsoft/referencesource/blob/4e6dea7a9c7cbb4e6b000b05a099e7168d1b6960/System.
 0324                    SecurityKeyIdentifier keyIdentifier = CoreWCF.Security.SecurityUtils.CreateSecurityKeyIdentifier(sta
 325                    //subject.KeyIdentifier = ReadSubjectKeyInfo(reader);
 0326                    SecurityKey crypto = ResolveSubjectKeyIdentifier(keyIdentifier);
 0327                    if(crypto == null)
 328                    {
 0329                        crypto = new SecurityKeyElement(keyIdentifier, Configuration.ServiceTokenResolver);
 330                    }
 331                    //end of crypto population
 332
 0333                    InMemorySymmetricSecurityKey inMemorySymmetricSecurityKey = crypto as InMemorySymmetricSecurityKey;
 0334                    if (inMemorySymmetricSecurityKey != null)
 335                    {
 336
 337                        // Verify that you have not already added this to crypto list.
 0338                        for (int j = 0; j < cryptoList.Count; ++j)
 339                        {
 0340                            if ((cryptoList[j] is InMemorySymmetricSecurityKey) && (cryptoList[j].KeySize == inMemorySym
 341                            {
 0342                                byte[] key1 = ((InMemorySymmetricSecurityKey)cryptoList[j]).GetSymmetricKey();
 0343                                byte[] key2 = inMemorySymmetricSecurityKey.GetSymmetricKey();
 0344                                int k = 0;
 0345                                for (k = 0; k < key1.Length; ++k)
 346                                {
 0347                                    if (key1[k] != key2[k])
 348                                    {
 349                                        break;
 350                                    }
 351                                }
 0352                                skipCrypto = (k == key1.Length);
 353                            }
 354
 0355                            if (skipCrypto)
 356                                break;
 357                        }
 358                    }
 0359                    if (!skipCrypto && (crypto != null))
 360                    {
 0361                        cryptoList.Add(crypto);
 362                    }
 363                }
 364            }
 365
 50366            return cryptoList.AsReadOnly();
 367
 368        }
 369
 370        /// <summary>
 371        /// Resolves the SecurityKeyIdentifier specified in a saml:Subject element.
 372        /// </summary>
 373        /// <param name="subjectKeyIdentifier">SecurityKeyIdentifier to resolve into a key.</param>
 374        /// <returns>SecurityKey</returns>
 375        /// <exception cref="ArgumentNullException">The input parameter 'subjectKeyIdentifier' is null.</exception>
 376        protected virtual SecurityKey ResolveSubjectKeyIdentifier(SecurityKeyIdentifier subjectKeyIdentifier)
 377        {
 0378            if (subjectKeyIdentifier == null)
 379            {
 0380                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(subjectKeyIdentifier));
 381            }
 382
 0383            if (Configuration == null)
 384            {
 0385                throw DiagnosticUtility.ExceptionUtility.ThrowHelperInvalidOperation(SR.Format(SR.ID4274));
 386            }
 387
 0388            if (Configuration.ServiceTokenResolver == null)
 389            {
 0390                throw DiagnosticUtility.ExceptionUtility.ThrowHelperInvalidOperation(SR.Format(SR.ID4276));
 391            }
 392
 0393            SecurityKey key = null;
 0394            foreach (SecurityKeyIdentifierClause clause in subjectKeyIdentifier)
 395            {
 0396                if (Configuration.ServiceTokenResolver.TryResolveSecurityKey(clause, out key))
 397                {
 0398                    return key;
 399                }
 400            }
 401
 0402            if (subjectKeyIdentifier.CanCreateKey)
 403            {
 0404                return subjectKeyIdentifier.CreateKey();
 405            }
 406
 0407            return null;
 0408        }
 409
 410    }
 411}