Skip to content

Commit f925910

Browse files
committed
Add MultiCredentialSecurityTokenManager to handle CoreWCF service certificates
1 parent f1f865a commit f925910

File tree

9 files changed

+223
-35
lines changed

9 files changed

+223
-35
lines changed

src/System.Private.ServiceModel/tests/Common/Scenarios/ScenarioTestHelpers.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ public static void CloseCommunicationObjects(params ICommunicationObject[] objec
210210
{
211211
comObj.Abort();
212212
}
213+
catch (System.Net.WebSockets.WebSocketException)
214+
{
215+
// WCF Client has a bug which throws WebSocketException when the server closes the connection.
216+
// The remote party closed the WebSocket connection without completing the close handshake.
217+
comObj.Abort();
218+
}
213219
}
214220
}
215221

src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ public static void DuplexCallback_Throws_FaultException_ReturnsFaultedTask()
328328
}
329329

330330
[WcfFact]
331-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
332331
[OuterLoop]
333332
// Verify product throws MessageSecurityException when the Dns identity from the server does not match the expectation
334333
public static void TCP_ServiceCertExpired_Throw_MessageSecurityException()
@@ -372,7 +371,6 @@ public static void TCP_ServiceCertExpired_Throw_MessageSecurityException()
372371
}
373372

374373
[WcfFact]
375-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
376374
[OuterLoop]
377375
// Verify product throws SecurityNegotiationException when the service cert is revoked
378376
public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException()
@@ -420,7 +418,6 @@ public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException()
420418
}
421419

422420
[WcfFact]
423-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
424421
[OuterLoop]
425422
// Verify product throws SecurityNegotiationException when the service cert only has the ClientAuth usage
426423
public static void TCP_ServiceCertInvalidEKU_Throw_SecurityNegotiationException()

src/System.Private.ServiceModel/tests/Scenarios/Extensibility/WebSockets/WebSocketTests.4.1.0.cs

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,12 @@ public static void WebSocket_Http_Duplex_Buffered(NetHttpMessageEncoding message
168168
"The logging done by the Server was not returned via the Callback.");
169169

170170
// *** CLEANUP *** \\
171-
((ICommunicationObject)client).Close();
172-
channelFactory.Close();
171+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
172+
if (!ScenarioTestHelpers.IsLocalHost())
173+
{
174+
((ICommunicationObject)client).Close();
175+
channelFactory.Close();
176+
}
173177
}
174178
finally
175179
{
@@ -236,8 +240,12 @@ public static void WebSocket_Http_Duplex_Buffered_KeepAlive(NetHttpMessageEncodi
236240
"The logging done by the Server was not returned via the Callback.");
237241

238242
// *** CLEANUP *** \\
239-
((ICommunicationObject)client).Close();
240-
channelFactory.Close();
243+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
244+
if (!ScenarioTestHelpers.IsLocalHost())
245+
{
246+
((ICommunicationObject)client).Close();
247+
channelFactory.Close();
248+
}
241249
}
242250
finally
243251
{
@@ -416,7 +424,6 @@ public static void WebSocket_Https_Duplex_Buffered(NetHttpMessageEncoding messag
416424
}
417425

418426
[WcfTheory]
419-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
420427
[InlineData(NetHttpMessageEncoding.Binary)]
421428
[InlineData(NetHttpMessageEncoding.Text)]
422429
[InlineData(NetHttpMessageEncoding.Mtom)]
@@ -472,8 +479,12 @@ public static void WebSocket_Http_RequestReply_Streamed(NetHttpMessageEncoding m
472479
}
473480

474481
// *** CLEANUP *** \\
475-
((ICommunicationObject)client).Close();
476-
channelFactory.Close();
482+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
483+
if (!ScenarioTestHelpers.IsLocalHost())
484+
{
485+
((ICommunicationObject)client).Close();
486+
channelFactory.Close();
487+
}
477488
}
478489
finally
479490
{
@@ -487,7 +498,6 @@ public static void WebSocket_Http_RequestReply_Streamed(NetHttpMessageEncoding m
487498
[InlineData(NetHttpMessageEncoding.Text)]
488499
[InlineData(NetHttpMessageEncoding.Mtom)]
489500
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
490-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
491501
[OuterLoop]
492502
public static void WebSocket_Http_RequestReply_Buffered(NetHttpMessageEncoding messageEncoding)
493503
{
@@ -527,8 +537,12 @@ public static void WebSocket_Http_RequestReply_Buffered(NetHttpMessageEncoding m
527537
}
528538

529539
// *** CLEANUP *** \\
530-
((ICommunicationObject)client).Close();
531-
channelFactory.Close();
540+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
541+
if (!ScenarioTestHelpers.IsLocalHost())
542+
{
543+
((ICommunicationObject)client).Close();
544+
channelFactory.Close();
545+
}
532546
}
533547
finally
534548
{
@@ -541,7 +555,6 @@ public static void WebSocket_Http_RequestReply_Buffered(NetHttpMessageEncoding m
541555
[InlineData(NetHttpMessageEncoding.Binary)]
542556
[InlineData(NetHttpMessageEncoding.Text)]
543557
[InlineData(NetHttpMessageEncoding.Mtom)]
544-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
545558
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
546559
[OuterLoop]
547560
public static void WebSocket_Http_RequestReply_Buffered_KeepAlive(NetHttpMessageEncoding messageEncoding)
@@ -582,8 +595,12 @@ public static void WebSocket_Http_RequestReply_Buffered_KeepAlive(NetHttpMessage
582595
}
583596

584597
// *** CLEANUP *** \\
585-
((ICommunicationObject)client).Close();
586-
channelFactory.Close();
598+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
599+
if (!ScenarioTestHelpers.IsLocalHost())
600+
{
601+
((ICommunicationObject)client).Close();
602+
channelFactory.Close();
603+
}
587604
}
588605
finally
589606
{
@@ -596,8 +613,7 @@ public static void WebSocket_Http_RequestReply_Buffered_KeepAlive(NetHttpMessage
596613
[InlineData(NetHttpMessageEncoding.Binary)]
597614
[InlineData(NetHttpMessageEncoding.Text)]
598615
[InlineData(NetHttpMessageEncoding.Mtom)]
599-
[Condition(nameof(Root_Certificate_Installed),
600-
nameof(Skip_CoreWCFService_FailedTest))]
616+
[Condition(nameof(Root_Certificate_Installed))]
601617
[Issue(3572, OS = OSID.OSX)]
602618
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
603619
[OuterLoop]
@@ -638,8 +654,12 @@ public static void WebSocket_Https_RequestReply_Buffered(NetHttpMessageEncoding
638654
}
639655

640656
// *** CLEANUP *** \\
641-
((ICommunicationObject)client).Close();
642-
channelFactory.Close();
657+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
658+
if (!ScenarioTestHelpers.IsLocalHost())
659+
{
660+
((ICommunicationObject)client).Close();
661+
channelFactory.Close();
662+
}
643663
}
644664
finally
645665
{
@@ -652,8 +672,7 @@ public static void WebSocket_Https_RequestReply_Buffered(NetHttpMessageEncoding
652672
[InlineData(NetHttpMessageEncoding.Binary)]
653673
[InlineData(NetHttpMessageEncoding.Text)]
654674
[InlineData(NetHttpMessageEncoding.Mtom)]
655-
[Condition(nameof(Root_Certificate_Installed),
656-
nameof(Skip_CoreWCFService_FailedTest))]
675+
[Condition(nameof(Root_Certificate_Installed))]
657676
[Issue(3572, OS = OSID.OSX)]
658677
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
659678
[OuterLoop]
@@ -696,8 +715,12 @@ public static void WebSocket_Https_RequestReply_Buffered_KeepAlive(NetHttpMessag
696715
}
697716

698717
// *** CLEANUP *** \\
699-
((ICommunicationObject)client).Close();
700-
channelFactory.Close();
718+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
719+
if (!ScenarioTestHelpers.IsLocalHost())
720+
{
721+
((ICommunicationObject)client).Close();
722+
channelFactory.Close();
723+
}
701724
}
702725
finally
703726
{
@@ -862,8 +885,12 @@ public static void WebSocket_Http_VerifyWebSocketsUsed()
862885
Assert.True(responseFromService, String.Format("Response from the service was not expected. Expected: 'True' but got {0}", responseFromService));
863886

864887
// *** CLEANUP *** \\
865-
((ICommunicationObject)client).Close();
866-
channelFactory.Close();
888+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method (on Linux).
889+
if (!ScenarioTestHelpers.IsLocalHost())
890+
{
891+
((ICommunicationObject)client).Close();
892+
channelFactory.Close();
893+
}
867894
}
868895
finally
869896
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET
6+
using CoreWCF.Description;
7+
using CoreWCF.IdentityModel.Selectors;
8+
using CoreWCF.Security;
9+
using CoreWCF.Security.Tokens;
10+
11+
namespace WcfService
12+
{
13+
public class MultiCredentialSecurityTokenManager : ServiceCredentialsSecurityTokenManager
14+
{
15+
private readonly MultiCredentialServiceCredentials _parent;
16+
private readonly Dictionary<string, ServiceCredentials> _map;
17+
18+
public MultiCredentialSecurityTokenManager(
19+
MultiCredentialServiceCredentials parent,
20+
Dictionary<string, ServiceCredentials> map)
21+
: base(parent)
22+
{
23+
_parent = parent;
24+
_map = map;
25+
}
26+
27+
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
28+
{
29+
if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement)
30+
{
31+
var uri = recipientRequirement.ListenUri?.AbsolutePath;
32+
if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds))
33+
{
34+
return creds.CreateSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement);
35+
}
36+
}
37+
return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement);
38+
}
39+
40+
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
41+
{
42+
if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement)
43+
{
44+
var uri = recipientRequirement.ListenUri?.AbsolutePath;
45+
if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds))
46+
{
47+
return creds.CreateSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
48+
}
49+
}
50+
51+
return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
52+
}
53+
}
54+
}
55+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET
6+
using CoreWCF.Description;
7+
using CoreWCF.IdentityModel.Selectors;
8+
9+
namespace WcfService
10+
{
11+
public class MultiCredentialServiceCredentials : ServiceCredentials
12+
{
13+
private readonly Dictionary<string, ServiceCredentials> _serviceCredentialsMap = new();
14+
15+
public void AddServiceCredentials(string path, ServiceCredentials credentials)
16+
{
17+
_serviceCredentialsMap[path] = credentials;
18+
}
19+
20+
public IReadOnlyDictionary<string, ServiceCredentials> ServiceCredentialsMap => _serviceCredentialsMap;
21+
22+
public override SecurityTokenManager CreateSecurityTokenManager()
23+
{
24+
return new MultiCredentialSecurityTokenManager(this, _serviceCredentialsMap);
25+
}
26+
27+
internal SecurityTokenManager CreateOriginalSecurityTokenManager()
28+
{
29+
return base.CreateSecurityTokenManager();
30+
}
31+
32+
protected override ServiceCredentials CloneCore()
33+
{
34+
var clone = new MultiCredentialServiceCredentials();
35+
foreach (var kvp in _serviceCredentialsMap)
36+
{
37+
clone.AddServiceCredentials(kvp.Key, kvp.Value.Clone());
38+
}
39+
return clone;
40+
}
41+
}
42+
}
43+
#endif

src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class ServiceHost
1414
private ServiceHostBase _serviceHostBase = null;
1515
private readonly Type _serviceType;
1616
private readonly List<Endpoint> _endpoints = new List<Endpoint>();
17+
private ServiceCredentials _localCredentials = null;
1718

1819
public ServiceHost(Type serviceType, params Uri[] baseAddresses)
1920
{
@@ -50,7 +51,47 @@ public class Endpoint
5051

5152
public Type ServiceType => _serviceHostBase != null ? _serviceHostBase.Description.ServiceType : _serviceType;
5253

53-
public ServiceCredentials Credentials => _serviceHostBase != null ? _serviceHostBase.Credentials : new ServiceCredentials();
54+
public ServiceCredentials Credentials
55+
{
56+
get
57+
{
58+
if (_localCredentials != null)
59+
{
60+
return _localCredentials;
61+
}
62+
63+
_localCredentials = new ServiceCredentials();
64+
var multiCreds = _serviceHostBase.Credentials as MultiCredentialServiceCredentials;
65+
if (multiCreds == null)
66+
{
67+
throw new Exception("Credentials should have been initialized with MultiCredentialServiceCredentials");
68+
}
69+
var attributes = this.GetType().GetCustomAttributes(typeof(TestServiceDefinitionAttribute), false);
70+
if (attributes != null)
71+
{
72+
foreach (var attribute in attributes)
73+
{
74+
var basePath = "/" + ((TestServiceDefinitionAttribute)attribute).BasePath;
75+
if (!string.IsNullOrEmpty(basePath))
76+
{
77+
foreach (var endpoint in _endpoints)
78+
{
79+
var path = string.IsNullOrEmpty(endpoint.Address) ? basePath : basePath + "/" + endpoint.Address;
80+
if (!multiCreds.ServiceCredentialsMap.TryGetValue(path, out var creds))
81+
{
82+
multiCreds.AddServiceCredentials(path, _localCredentials);
83+
}
84+
else
85+
{
86+
_localCredentials = creds;
87+
}
88+
}
89+
}
90+
}
91+
}
92+
return _localCredentials;
93+
}
94+
}
5495

5596
public ServiceDescription Description => _serviceHostBase != null ? _serviceHostBase.Description : new ServiceDescription();
5697
}

src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
7171
{
7272
bool success = true;
7373
var serviceTestHostOptionsDict = new Dictionary<string, ServiceTestHostOptions>();
74+
var multiCreds = new MultiCredentialServiceCredentials();
7475

7576
var webHostBuilder = new WebHostBuilder()
7677
.ConfigureLogging((ILoggingBuilder logging) =>
@@ -256,7 +257,13 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
256257

257258
smb.HttpGetEnabled = true;
258259
}
259-
260+
261+
var creds = serviceHostBase.Description.Behaviors.Find<ServiceCredentials>();
262+
if (creds != null)
263+
{
264+
serviceHostBase.Description.Behaviors.Remove(creds);
265+
}
266+
serviceHostBase.Description.Behaviors.Add(multiCreds);
260267
serviceHost.ApplyConfig(serviceHostBase);
261268
});
262269
}
@@ -272,8 +279,7 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
272279
Console.BackgroundColor = bg;
273280
Console.ForegroundColor = fg;
274281
}
275-
}
276-
282+
}
277283
});
278284
});
279285

0 commit comments

Comments
 (0)