diff --git a/src/Microsoft.IdentityModel.Protocols/HttpRequestData.cs b/src/Microsoft.IdentityModel.Protocols/HttpRequestData.cs index 2954a8926b..7d0a65b842 100644 --- a/src/Microsoft.IdentityModel.Protocols/HttpRequestData.cs +++ b/src/Microsoft.IdentityModel.Protocols/HttpRequestData.cs @@ -17,6 +17,8 @@ public class HttpRequestData { private IDictionary> _headers = new Dictionary>(StringComparer.OrdinalIgnoreCase); private X509Certificate2Collection _clientCertificates; + private Action _lazyClientCertificates; + private readonly object _lazyClientCertificatesLock = new object(); /// /// Gets or sets the http request URI. @@ -51,9 +53,29 @@ public IDictionary> Headers /// /// Gets the certificate collection involved in authenticating the client against the server. /// - public X509Certificate2Collection ClientCertificates => _clientCertificates ?? - Interlocked.CompareExchange(ref _clientCertificates, [], null) ?? - _clientCertificates; + public X509Certificate2Collection ClientCertificates + { + get + { + _clientCertificates ??= + Interlocked.CompareExchange(ref _clientCertificates, [], null) ?? + _clientCertificates; + + if (_lazyClientCertificates != null) + { + lock (_lazyClientCertificatesLock) + { + if (_lazyClientCertificates != null) + { + _lazyClientCertificates(_clientCertificates); + _lazyClientCertificates = null; + } + } + } + + return _clientCertificates; + } + } /// /// Gets or sets an that enables custom extensibility scenarios. @@ -77,5 +99,17 @@ public void AppendHeaders(HttpHeaders headers) Headers.Add(header.Key, header.Value); } } + + /// + /// Sets an action to lazily populate the property. + /// + /// The action to lazily populate the property. + public void SetLazyClientCertificates(Action lazyClientCertificates) + { + if (lazyClientCertificates == null) + return; + + _lazyClientCertificates = lazyClientCertificates; + } } } diff --git a/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt index e69de29bb2..1605533742 100644 --- a/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Protocols/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +Microsoft.IdentityModel.Protocols.HttpRequestData.SetLazyClientCertificates(System.Action lazyClientCertificates) -> void diff --git a/test/Microsoft.IdentityModel.Protocols.Tests/HttpRequestDataTests.cs b/test/Microsoft.IdentityModel.Protocols.Tests/HttpRequestDataTests.cs index c1b4e97b52..7cac832658 100644 --- a/test/Microsoft.IdentityModel.Protocols.Tests/HttpRequestDataTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.Tests/HttpRequestDataTests.cs @@ -22,5 +22,33 @@ public void ClientCertificates() Assert.Single(httpRequestData.ClientCertificates); Assert.Equal(cert, httpRequestData.ClientCertificates[0]); } + + [Fact] + public void LazyClientCertificates() + { + var httpRequestData = new HttpRequestData(); + Assert.NotNull(httpRequestData.ClientCertificates); + Assert.Empty(httpRequestData.ClientCertificates); + + X509Certificate2 cert = TestUtils.CertificateHelper.LoadX509Certificate(KeyingMaterial.AADCertData); + + int numberCertsPopulatedCalled = 0; + httpRequestData.SetLazyClientCertificates((c) => + { + numberCertsPopulatedCalled++; + c.Add(cert); + }); + + Assert.Equal(0, numberCertsPopulatedCalled); + + Assert.Single(httpRequestData.ClientCertificates); + Assert.Equal(cert, httpRequestData.ClientCertificates[0]); + Assert.Equal(1, numberCertsPopulatedCalled); + + // Invoke again, should not call the delegate again + _ = httpRequestData.ClientCertificates; + + Assert.Equal(1, numberCertsPopulatedCalled); + } } }