diff --git a/Extensibility/Security/TokenProvider/Client/Client.csproj b/Extensibility/Security/TokenProvider/Client/Client.csproj new file mode 100644 index 0000000..f565d98 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/Client.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/ConnectedService.json b/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/ConnectedService.json new file mode 100644 index 0000000..6e0e603 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/ConnectedService.json @@ -0,0 +1,17 @@ +{ + "ExtendedData": { + "inputs": [ + "https://localhost:5001/CalculatorService?wsdl" + ], + "collectionTypes": [ + "System.Array", + "System.Collections.Generic.Dictionary`2" + ], + "namespaceMappings": [ + "*, CoreWcf.Samples.TokenProvider" + ], + "sync": true, + "targetFramework": "net6.0", + "typeReuseMode": "All" + } +} \ No newline at end of file diff --git a/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/Reference.cs b/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/Reference.cs new file mode 100644 index 0000000..c38709d --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/Connected Services/CoreWcf.Samples.TokenProvider/Reference.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CoreWcf.Samples.TokenProvider +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.1.0")] + [System.ServiceModel.ServiceContractAttribute(Namespace="http://CoreWcf.Samples.TokenProvider", ConfigurationName="CoreWcf.Samples.TokenProvider.ICalculatorService")] + public interface ICalculatorService + { + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Add", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/AddResponse")] + double Add(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Add", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/AddResponse")] + System.Threading.Tasks.Task AddAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Subtract", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/SubtractResponse")] + double Subtract(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Subtract", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/SubtractResponse")] + System.Threading.Tasks.Task SubtractAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Multiply", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/MultiplyResponse")] + double Multiply(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Multiply", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/MultiplyResponse")] + System.Threading.Tasks.Task MultiplyAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Divide", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/DivideResponse")] + double Divide(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.TokenProvider/ICalculatorService/Divide", ReplyAction="http://CoreWcf.Samples.TokenProvider/ICalculatorService/DivideResponse")] + System.Threading.Tasks.Task DivideAsync(double n1, double n2); + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.1.0")] + public interface ICalculatorServiceChannel : CoreWcf.Samples.TokenProvider.ICalculatorService, System.ServiceModel.IClientChannel + { + } + + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.1.0")] + public partial class CalculatorServiceClient : System.ServiceModel.ClientBase, CoreWcf.Samples.TokenProvider.ICalculatorService + { + + /// + /// Implement this partial method to configure the service endpoint. + /// + /// The endpoint to configure + /// The client credentials + static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials); + + public CalculatorServiceClient() : + base(CalculatorServiceClient.GetDefaultBinding(), CalculatorServiceClient.GetDefaultEndpointAddress()) + { + this.Endpoint.Name = EndpointConfiguration.WSHttpBinding_ICalculatorService.ToString(); + ConfigureEndpoint(this.Endpoint, this.ClientCredentials); + } + + public CalculatorServiceClient(EndpointConfiguration endpointConfiguration) : + base(CalculatorServiceClient.GetBindingForEndpoint(endpointConfiguration), CalculatorServiceClient.GetEndpointAddress(endpointConfiguration)) + { + this.Endpoint.Name = endpointConfiguration.ToString(); + ConfigureEndpoint(this.Endpoint, this.ClientCredentials); + } + + public CalculatorServiceClient(EndpointConfiguration endpointConfiguration, string remoteAddress) : + base(CalculatorServiceClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress)) + { + this.Endpoint.Name = endpointConfiguration.ToString(); + ConfigureEndpoint(this.Endpoint, this.ClientCredentials); + } + + public CalculatorServiceClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) : + base(CalculatorServiceClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress) + { + this.Endpoint.Name = endpointConfiguration.ToString(); + ConfigureEndpoint(this.Endpoint, this.ClientCredentials); + } + + public CalculatorServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : + base(binding, remoteAddress) + { + } + + public double Add(double n1, double n2) + { + return base.Channel.Add(n1, n2); + } + + public System.Threading.Tasks.Task AddAsync(double n1, double n2) + { + return base.Channel.AddAsync(n1, n2); + } + + public double Subtract(double n1, double n2) + { + return base.Channel.Subtract(n1, n2); + } + + public System.Threading.Tasks.Task SubtractAsync(double n1, double n2) + { + return base.Channel.SubtractAsync(n1, n2); + } + + public double Multiply(double n1, double n2) + { + return base.Channel.Multiply(n1, n2); + } + + public System.Threading.Tasks.Task MultiplyAsync(double n1, double n2) + { + return base.Channel.MultiplyAsync(n1, n2); + } + + public double Divide(double n1, double n2) + { + return base.Channel.Divide(n1, n2); + } + + public System.Threading.Tasks.Task DivideAsync(double n1, double n2) + { + return base.Channel.DivideAsync(n1, n2); + } + + public virtual System.Threading.Tasks.Task OpenAsync() + { + return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action(((System.ServiceModel.ICommunicationObject)(this)).EndOpen)); + } + + private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration) + { + if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_ICalculatorService)) + { + System.ServiceModel.WSHttpBinding result = new System.ServiceModel.WSHttpBinding(); + result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max; + result.MaxReceivedMessageSize = int.MaxValue; + result.AllowCookies = true; + result.Security.Mode = System.ServiceModel.SecurityMode.TransportWithMessageCredential; + result.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.None; + result.Security.Message.ClientCredentialType = System.ServiceModel.MessageCredentialType.UserName; + return result; + } + throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration)); + } + + private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration) + { + if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_ICalculatorService)) + { + return new System.ServiceModel.EndpointAddress("https://localhost:5001/CalculatorService"); + } + throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration)); + } + + private static System.ServiceModel.Channels.Binding GetDefaultBinding() + { + return CalculatorServiceClient.GetBindingForEndpoint(EndpointConfiguration.WSHttpBinding_ICalculatorService); + } + + private static System.ServiceModel.EndpointAddress GetDefaultEndpointAddress() + { + return CalculatorServiceClient.GetEndpointAddress(EndpointConfiguration.WSHttpBinding_ICalculatorService); + } + + public enum EndpointConfiguration + { + + WSHttpBinding_ICalculatorService, + } + } +} diff --git a/Extensibility/Security/TokenProvider/Client/MyUserNameClientCredential.cs b/Extensibility/Security/TokenProvider/Client/MyUserNameClientCredential.cs new file mode 100644 index 0000000..a966c5d --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/MyUserNameClientCredential.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IdentityModel.Selectors; +using System.ServiceModel.Description; + +namespace CoreWcf.Samples.TokenProvider +{ + public class MyUserNameClientCredentials : ClientCredentials + { + public MyUserNameClientCredentials() + : base() + { + } + + protected override ClientCredentials CloneCore() + { + return new MyUserNameClientCredentials(); + } + + public override SecurityTokenManager CreateSecurityTokenManager() + { + // return custom security token manager + return new MyUserNameSecurityTokenManager(this); + } + } +} + diff --git a/Extensibility/Security/TokenProvider/Client/MyUserNameSecurityTokenManager.cs b/Extensibility/Security/TokenProvider/Client/MyUserNameSecurityTokenManager.cs new file mode 100644 index 0000000..5d35520 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/MyUserNameSecurityTokenManager.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IdentityModel.Selectors; +using System.ServiceModel; + +namespace CoreWcf.Samples.TokenProvider +{ + public class MyUserNameSecurityTokenManager : ClientCredentialsSecurityTokenManager + { + MyUserNameClientCredentials myUserNameClientCredentials; + private readonly string _userNameTokenType = "http://schemas.microsoft.com/ws/2006/05/identitymodel/tokens/UserName"; + + public MyUserNameSecurityTokenManager(MyUserNameClientCredentials myUserNameClientCredentials) + : base(myUserNameClientCredentials) + { + this.myUserNameClientCredentials = myUserNameClientCredentials; + } + + public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) + { + // if token requirement matches username token return custom username token provider + // otherwise use base implementation + if (tokenRequirement.TokenType == _userNameTokenType) + { + return new MyUserNameTokenProvider(); + } + else + { + return base.CreateSecurityTokenProvider(tokenRequirement); + } + } + } +} + diff --git a/Extensibility/Security/TokenProvider/Client/MyUserNameTokenProvider.cs b/Extensibility/Security/TokenProvider/Client/MyUserNameTokenProvider.cs new file mode 100644 index 0000000..8bf0053 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/MyUserNameTokenProvider.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IdentityModel.Selectors; +using System.IdentityModel.Tokens; + +namespace CoreWcf.Samples.TokenProvider +{ + class MyUserNameTokenProvider : SecurityTokenProvider + { + static string GetUserName() + { + Console.WriteLine("Username authentication required."); + Console.WriteLine(" Enter username:"); + string username = Console.ReadLine(); + return username; + } + + static string GetPassword() + { + Console.WriteLine("Enter password:"); + string password = ""; + ConsoleKeyInfo info = Console.ReadKey(true); + while (info.Key != ConsoleKey.Enter) + { + if (info.Key != ConsoleKey.Backspace) + { + password += info.KeyChar; + info = Console.ReadKey(true); + } + else if (info.Key == ConsoleKey.Backspace) + { + if (password != "") + { + password = password.Substring(0, password.Length - 1); + + } + info = Console.ReadKey(true); + } + } + + for (int i = 0; i < password.Length; i++) + Console.Write("*"); + + Console.WriteLine(); + + return password; + } + + protected override Task GetTokenCoreAsync(TimeSpan timeout) + { + // obtain username and password from the user using console window + string username = GetUserName(); + string password = GetPassword(); + Console.WriteLine("username: {0}", username); + + // return UserNameSecurityToken containing information obtained from user + return Task.FromResult(new UserNameSecurityToken(username, password)); + } + + protected override SecurityToken GetTokenCore(TimeSpan timeout) + { + // obtain username and password from the user using console window + string username = GetUserName(); + string password = GetPassword(); + Console.WriteLine("username: {0}", username); + + // return UserNameSecurityToken containing information obtained from user + return new UserNameSecurityToken(username, password); + } + } +} + diff --git a/Extensibility/Security/TokenProvider/Client/Program.cs b/Extensibility/Security/TokenProvider/Client/Program.cs new file mode 100644 index 0000000..6f994a8 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Client/Program.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.ServiceModel.Description; +using System.ServiceModel.Security; + +//The service contract is defined using Connected Service "WCF Web Service", generated from the service by the dotnet svcutil tool. + +CalculatorServiceClient client = new CalculatorServiceClient(); +// set new credentials +client.ChannelFactory.Endpoint.EndpointBehaviors.Remove(typeof(ClientCredentials)); +client.ChannelFactory.Endpoint.EndpointBehaviors.Add(new MyUserNameClientCredentials()); +/* +Setting the CertificateValidationMode to PeerOrChainTrust means that if the certificate +is in the Trusted People store, then it will be trusted without performing a +validation of the certificate's issuer chain. This setting is used here for convenience so that the +sample can be run without having to have certificates issued by a certificate authority (CA). +This setting is less secure than the default, ChainTrust. The security implications of this +setting should be carefully considered before using PeerOrChainTrust in production code. +*/ +client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust; + +try +{ + // Call the Add service operation. + double value1 = 100.00D; + double value2 = 15.99D; + double result = client.Add(value1, value2); + Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result); + + // Call the Subtract service operation. + value1 = 145.00D; + value2 = 76.54D; + result = client.Subtract(value1, value2); + Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result); + + // Call the Multiply service operation. + value1 = 9.00D; + value2 = 81.25D; + result = client.Multiply(value1, value2); + Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result); + + // Call the Divide service operation. + value1 = 22.00D; + value2 = 7.00D; + result = client.Divide(value1, value2); + Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result); +} +catch (Exception e) +{ + Console.WriteLine("Call failed : {0}", e.Message); +} + +//Closing the client gracefully closes the connection and cleans up resources +await client.CloseAsync(); + +Console.WriteLine(); +Console.WriteLine("Press to terminate client."); +Console.ReadLine(); + diff --git a/Extensibility/Security/TokenProvider/Extensibility.Security.TokenProvider.sln b/Extensibility/Security/TokenProvider/Extensibility.Security.TokenProvider.sln new file mode 100644 index 0000000..16cf586 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Extensibility.Security.TokenProvider.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32422.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{BF126326-3393-407C-B24A-8FCCC388BE27}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{B533CADA-93BB-40E1-8FBA-FE37100062C3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BF126326-3393-407C-B24A-8FCCC388BE27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF126326-3393-407C-B24A-8FCCC388BE27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF126326-3393-407C-B24A-8FCCC388BE27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF126326-3393-407C-B24A-8FCCC388BE27}.Release|Any CPU.Build.0 = Release|Any CPU + {B533CADA-93BB-40E1-8FBA-FE37100062C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B533CADA-93BB-40E1-8FBA-FE37100062C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B533CADA-93BB-40E1-8FBA-FE37100062C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B533CADA-93BB-40E1-8FBA-FE37100062C3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AD996EFD-70DC-4431-B411-5A2771DD02D3} + EndGlobalSection +EndGlobal diff --git a/Extensibility/Security/TokenProvider/GetComputerName.vbs b/Extensibility/Security/TokenProvider/GetComputerName.vbs new file mode 100644 index 0000000..9bdad3e --- /dev/null +++ b/Extensibility/Security/TokenProvider/GetComputerName.vbs @@ -0,0 +1,17 @@ +' +' This script uses WMI to get the name of the machine be used as the CN ' for the certificates for WCF security samples. +' +set wmi = Getobject("winmgmts:") +wql = "select * from win32_computersystem" +set results = wmi.execquery(wql) +for each compsys in results + 'check if the machine is in the workgroup or domain + if compsys.PartOfDomain = 0 or compsys.Domain = compsys.Workgroup then + ' only get the name of the machine + WScript.echo compsys.name + else + ' get the fully qualified name of the machine + n = compsys.name & "." & compsys.domain + WScript.echo n + end if +next \ No newline at end of file diff --git a/Extensibility/Security/TokenProvider/Service/CalculatorService.cs b/Extensibility/Security/TokenProvider/Service/CalculatorService.cs new file mode 100644 index 0000000..90d7982 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/CalculatorService.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CoreWcf.Samples.TokenProvider +{ + // Service class which implements the service contract interface. + // Added code to write output to the console window + public class CalculatorService : ICalculatorService + { + static void DisplayIdentityInformation() + { + Console.WriteLine("\t\tSecurity context identity : {0}", ServiceSecurityContext.Current.PrimaryIdentity.Name); + } + + public double Add(double n1, double n2) + { + DisplayIdentityInformation(); + double result = n1 + n2; + Console.WriteLine("Received Add({0},{1})", n1, n2); + Console.WriteLine("Return: {0}", result); + return result; + } + + public double Subtract(double n1, double n2) + { + DisplayIdentityInformation(); + double result = n1 - n2; + Console.WriteLine("Received Subtract({0},{1})", n1, n2); + Console.WriteLine("Return: {0}", result); + return result; + } + + public double Multiply(double n1, double n2) + { + DisplayIdentityInformation(); + double result = n1 * n2; + Console.WriteLine("Received Multiply({0},{1})", n1, n2); + Console.WriteLine("Return: {0}", result); + return result; + } + + public double Divide(double n1, double n2) + { + DisplayIdentityInformation(); + double result = n1 / n2; + Console.WriteLine("Received Divide({0},{1})", n1, n2); + Console.WriteLine("Return: {0}", result); + return result; + } + } +} diff --git a/Extensibility/Security/TokenProvider/Service/ICalculatorService.cs b/Extensibility/Security/TokenProvider/Service/ICalculatorService.cs new file mode 100644 index 0000000..72919e9 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/ICalculatorService.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CoreWcf.Samples.TokenProvider +{ + // Define a service contract. + [ServiceContract(Namespace = "http://CoreWcf.Samples.TokenProvider")] + public interface ICalculatorService + { + [OperationContract] + double Add(double n1, double n2); + [OperationContract] + double Subtract(double n1, double n2); + [OperationContract] + double Multiply(double n1, double n2); + [OperationContract] + double Divide(double n1, double n2); + } +} diff --git a/Extensibility/Security/TokenProvider/Service/Program.cs b/Extensibility/Security/TokenProvider/Service/Program.cs new file mode 100644 index 0000000..cba3b87 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/Program.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography.X509Certificates; +using CoreWCF.IdentityModel.Selectors; +using CoreWCF.IdentityModel.Tokens; + +var builder = WebApplication.CreateBuilder(); + +//Enable CoreWCF Services, with metadata (WSDL) support +builder.Services.AddServiceModelServices() + .AddServiceModelMetadata() + .AddSingleton(); + +var app = builder.Build(); + +app.UseServiceModel(builder => +{ + // Add the Calculator Service + builder.AddService(serviceOptions => { }) + // Add WSHttpBinding endpoint + .AddServiceEndpoint(ServiceWSHttpBinding(), "CalculatorService"); + + Action serviceHost = host => ChangeHostBehavior(host); + builder.ConfigureServiceHostBase(serviceHost); + + // Configure WSDL to be available + var serviceMetadataBehavior = app.Services.GetRequiredService(); + serviceMetadataBehavior.HttpsGetEnabled = true; +}); + +app.Run(); + +static Binding ServiceWSHttpBinding() +{ + WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential); + binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; + return binding; +} + +void ChangeHostBehavior(ServiceHostBase host) +{ + var srvCredentials = new ServiceCredentials(); + srvCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "localhost"); + srvCredentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; + srvCredentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyUserNamePasswordValidator(); + host.Description.Behaviors.Add(srvCredentials); +} + +public class MyUserNamePasswordValidator : UserNamePasswordValidator +{ + public override ValueTask ValidateAsync(string userName, string password) + { + // check that username matches password + if (null == userName || userName != password) + { + Console.WriteLine("Invalid username or password"); + throw new SecurityTokenValidationException("Invalid username or password"); + } + return ValueTask.CompletedTask; + } +} diff --git a/Extensibility/Security/TokenProvider/Service/Properties/launchSettings.json b/Extensibility/Security/TokenProvider/Service/Properties/launchSettings.json new file mode 100644 index 0000000..941fac5 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "https://localhost:5001/CalculatorService", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Extensibility/Security/TokenProvider/Service/Service.csproj b/Extensibility/Security/TokenProvider/Service/Service.csproj new file mode 100644 index 0000000..9d3aeeb --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/Service.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + true + InProcess + + + + + + + + + + + + + + + + diff --git a/Extensibility/Security/TokenProvider/Service/appsettings.Development.json b/Extensibility/Security/TokenProvider/Service/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Extensibility/Security/TokenProvider/Service/appsettings.json b/Extensibility/Security/TokenProvider/Service/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Extensibility/Security/TokenProvider/Service/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Extensibility/Security/TokenProvider/cleanup.bat b/Extensibility/Security/TokenProvider/cleanup.bat new file mode 100644 index 0000000..91e82a7 --- /dev/null +++ b/Extensibility/Security/TokenProvider/cleanup.bat @@ -0,0 +1,67 @@ +echo off +setlocal +set CLIENT_NAME=test1 +call :setcomputername +ECHO **************************************************************** +ECHO WARNING: This script will not remove service certificates on a +ECHO client machine from a cross machine run of this +ECHO sample. + +ECHO If you have run WCF samples that use Certs across machines, +ECHO be sure to clear the service certs that have been installed in +ECHO the CurrentUser - TrustedPeople store. +ECHO To do this, use the following command: + +ECHO "certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n " + +ECHO For example: + +ECHO "certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com" +call :cleancerts +DEL client.cer > NUL 2>&1 +DEL service.cer > NUL 2>&1 +GOTO end + +:cleancerts +REM cleans up certs from previous runs. +echo **************** +echo Cleanup starting +echo **************** + +echo ------------------------- +echo del client certs +echo ------------------------- +certmgr.exe -del -r CurrentUser -s My -c -n %CLIENT_NAME% +certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n localhost + +echo ------------------------- +echo del service certs +echo ------------------------- +certmgr.exe -del -r LocalMachine -s My -c -n localhost +certmgr.exe -del -r LocalMachine -s TrustedPeople -c -n %CLIENT_NAME% +certmgr.exe -put -r LocalMachine -s My -c -n %COMPUTER_NAME% computer.cer +IF %ERRORLEVEL% EQU 0 ( + DEL computer.cer + echo **************** + echo "You have a certificate with a Subject name matching your Machine name." + echo "If this certificate is from a cross machine run of WCF samples press any key to delete it." + echo "Otherwise press Ctrl + C to abort this script." + pause + certmgr.exe -del -r LocalMachine -s My -c -n %COMPUTER_NAME% +) + +:cleanupcompleted +echo ***************** +echo Cleanup completed +echo ***************** + +GOTO :EOF + + +:setcomputername +REM Puts the Fully Qualified Name of the Computer into a variable named COMPUTER_NAME +for /F "delims=" %%i in ('cscript /nologo GetComputerName.vbs') do set COMPUTER_NAME=%%i +GOTO :EOF + +:end + diff --git a/Extensibility/Security/TokenProvider/importclientcert.bat b/Extensibility/Security/TokenProvider/importclientcert.bat new file mode 100644 index 0000000..6106ab6 --- /dev/null +++ b/Extensibility/Security/TokenProvider/importclientcert.bat @@ -0,0 +1,7 @@ +echo off +echo ************ +echo Client cert import starting +echo ************ +echo copying client cert to server's CurrentUser store +echo ************ +certmgr.exe -add client.cer -r CurrentUser -s TrustedPeople diff --git a/Extensibility/Security/TokenProvider/importservicecert.bat b/Extensibility/Security/TokenProvider/importservicecert.bat new file mode 100644 index 0000000..fd4bef4 --- /dev/null +++ b/Extensibility/Security/TokenProvider/importservicecert.bat @@ -0,0 +1,7 @@ +echo off +echo ************ +echo Server cert import starting +echo ************ +echo copying server cert to client's CurrentUser store +echo ************ +certmgr.exe -add service.cer -r CurrentUser -s TrustedPeople diff --git a/Extensibility/Security/TokenProvider/setup.bat b/Extensibility/Security/TokenProvider/setup.bat new file mode 100644 index 0000000..0459365 --- /dev/null +++ b/Extensibility/Security/TokenProvider/setup.bat @@ -0,0 +1,143 @@ +echo off +setlocal +echo ************ +echo cert setup starting +echo ************ + +call :setscriptvariables %1 +IF NOT DEFINED SUPPORTED_MODE call :displayusage +IF DEFINED SUPPORTED_MODE call :cleancerts +IF DEFINED SETUP_SERVICE call :setupservice +IF DEFINED SETUP_CLIENT call :setupclient +GOTO end + +:cleancerts +REM cleans up certs from previous runs. +echo **************** +echo Cleanup starting +echo **************** + +echo ------------------------- +echo del client certs +echo ------------------------- +certmgr.exe -del -r CurrentUser -s My -c -n %CLIENT_NAME% +certmgr.exe -del -r CurrentUser -s TrustedPeople -c -n localhost + +echo ------------------------- +echo del service certs +echo ------------------------- +certmgr.exe -del -r LocalMachine -s My -c -n localhost +certmgr.exe -del -r LocalMachine -s TrustedPeople -c -n %CLIENT_NAME% +certmgr.exe -put -r LocalMachine -s My -c -n %COMPUTER_NAME% computer.cer +IF %ERRORLEVEL% EQU 0 ( + DEL computer.cer + echo **************** + echo "You have a certificate with a Subject name matching your Machine name: %COMPUTER_NAME%" + echo "If this certificate is from a cross machine run of WCF samples press any key to delete it." + echo "Otherwise press Ctrl + C to abort this script." + pause + certmgr.exe -del -r LocalMachine -s My -c -n %COMPUTER_NAME% +) + +:cleanupcompleted +echo ***************** +echo Cleanup completed +echo ***************** + +GOTO :EOF + +:setupclient + +echo ************ +echo making client cert +echo ************ +makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe + +IF DEFINED EXPORT_CLIENT ( + echo ************ + echo exporting client cert to client.cer + echo ************ + certmgr.exe -put -r CurrentUser -s My -c -n %CLIENT_NAME% client.cer +) ELSE ( + echo ************ + echo copying client cert to server's LocalMachine store + echo ************ + certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople +) +GOTO :EOF + +:setupservice + +echo ************ +echo Server cert setup starting +echo %SERVER_NAME% +echo ************ +echo making server cert +echo ************ +makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe + +IF DEFINED EXPORT_SERVICE ( + echo ************ + echo exporting service cert to service.cer + echo ************ + certmgr.exe -put -r LocalMachine -s My -c -n %SERVER_NAME% service.cer +) ELSE ( + echo ************ + echo copying server cert to client's CurrentUser store + echo ************ + certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople +) +GOTO :EOF + +:setscriptvariables +REM Parses the input to determine if we are setting this up for a single machine, client, or server +REM sets the appropriate name variables +call :setcomputername +IF [%1]==[] CALL :singlemachine +IF [%1]==[service] CALL :service +IF [%1]==[client] CALL :client + +set CLIENT_NAME=test1 + +GOTO :EOF + +:singlemachine +echo ************ +echo Running setup script for Single Machine +echo ************ +SET SUPPORTED_MODE=1 +SET SETUP_CLIENT=1 +SET SETUP_SERVICE=1 +SET SERVER_NAME=localhost +GOTO :EOF + +:service +echo ************ +echo Running setup script for Service +echo ************ +SET SUPPORTED_MODE=1 +SET SETUP_SERVICE=1 +SET EXPORT_SERVICE=1 +SET SERVER_NAME=%COMPUTER_NAME% +GOTO :EOF + +:client +echo ************ +echo Running setup script for Client +echo ************ +SET SUPPORTED_MODE=1 +SET SETUP_CLIENT=1 +SET EXPORT_CLIENT=1 +GOTO :EOF + +:setcomputername +REM Puts the Fully Qualified Name of the Computer into a variable named COMPUTER_NAME +for /F "delims=" %%i in ('cscript /nologo GetComputerName.vbs') do set COMPUTER_NAME=%%i +GOTO :EOF + +:displayusage +ECHO Correct usage: +ECHO Single Machine - Setup.bat +ECHO Client Machine - Setup.bat client +ECHO Service Machine - Setup.bat service +:end