diff --git a/Extensibility/Security/AuthorizationPolicy/Client/Client.csproj b/Extensibility/Security/AuthorizationPolicy/Client/Client.csproj new file mode 100644 index 0000000..001b3a1 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Client/Client.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/ConnectedService.json b/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/ConnectedService.json new file mode 100644 index 0000000..81ff117 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/ConnectedService.json @@ -0,0 +1,17 @@ +{ + "ExtendedData": { + "inputs": [ + "https://localhost:5001/CalculatorService?wsdl" + ], + "collectionTypes": [ + "System.Array", + "System.Collections.Generic.Dictionary`2" + ], + "namespaceMappings": [ + "*, CoreWcf.Samples.AuthorizationPolicy" + ], + "sync": true, + "targetFramework": "net6.0", + "typeReuseMode": "All" + } +} \ No newline at end of file diff --git a/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/Reference.cs b/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/Reference.cs new file mode 100644 index 0000000..40a2627 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Client/Connected Services/CoreWcf.Samples.AuthorizationPolicy/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.AuthorizationPolicy +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.1.0")] + [System.ServiceModel.ServiceContractAttribute(Namespace="http://CoreWcf.Samples.AuthorizationPolicy", ConfigurationName="CoreWcf.Samples.AuthorizationPolicy.ICalculatorService")] + public interface ICalculatorService + { + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Add", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/AddResponse")] + double Add(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Add", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/AddResponse")] + System.Threading.Tasks.Task AddAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Subtract", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/SubtractResponse")] + double Subtract(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Subtract", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/SubtractResponse")] + System.Threading.Tasks.Task SubtractAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Multiply", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/MultiplyResponse")] + double Multiply(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Multiply", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/MultiplyResponse")] + System.Threading.Tasks.Task MultiplyAsync(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Divide", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/DivideResponse")] + double Divide(double n1, double n2); + + [System.ServiceModel.OperationContractAttribute(Action="http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Divide", ReplyAction="http://CoreWcf.Samples.AuthorizationPolicy/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.AuthorizationPolicy.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.AuthorizationPolicy.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(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; + } + if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_ICalculatorService1)) + { + 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.Certificate; + 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/Username"); + } + if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_ICalculatorService1)) + { + return new System.ServiceModel.EndpointAddress("https://localhost:5001/CalculatorService/Certificate"); + } + throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration)); + } + + public enum EndpointConfiguration + { + + WSHttpBinding_ICalculatorService, + + WSHttpBinding_ICalculatorService1, + } + } +} diff --git a/Extensibility/Security/AuthorizationPolicy/Client/Program.cs b/Extensibility/Security/AuthorizationPolicy/Client/Program.cs new file mode 100644 index 0000000..1ed26ab --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Client/Program.cs @@ -0,0 +1,70 @@ +// 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 System.ServiceModel; +using System.ServiceModel.Security; + +//The service contract is defined using Connected Service "WCF Web Service", generated from the service by the dotnet svcutil tool. + +// Create a client with Username endpoint configuration +WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential); +binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; +var endpointAddress = new EndpointAddress("https://localhost:5001/CalculatorService/Username"); + +CalculatorServiceClient client = new CalculatorServiceClient(binding, endpointAddress); +client.ClientCredentials.UserName.UserName = "test1"; +client.ClientCredentials.UserName.Password = "1test"; + +CallServiceOperations(client); + +// Create a client with Certificate endpoint configuration +binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential); +binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate; +endpointAddress = new EndpointAddress("https://localhost:5001/CalculatorService/Certificate"); + +client = new CalculatorServiceClient(binding, endpointAddress); +client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "test1"); +client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust; + +CallServiceOperations(client); + +Console.WriteLine(); +Console.WriteLine("Press to terminate client."); +Console.ReadLine(); + + +void CallServiceOperations(CalculatorServiceClient client) +{ + 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 + client.CloseAsync(); +} diff --git a/Extensibility/Security/AuthorizationPolicy/Extensibility.Security.AuthorizationPolicy.sln b/Extensibility/Security/AuthorizationPolicy/Extensibility.Security.AuthorizationPolicy.sln new file mode 100644 index 0000000..721f7bd --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Extensibility.Security.AuthorizationPolicy.sln @@ -0,0 +1,37 @@ + +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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolicyLibrary", "PolicyLibrary\PolicyLibrary.csproj", "{7D922471-13A2-4C41-9113-914B475B903D}" +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 + {7D922471-13A2-4C41-9113-914B475B903D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D922471-13A2-4C41-9113-914B475B903D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D922471-13A2-4C41-9113-914B475B903D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D922471-13A2-4C41-9113-914B475B903D}.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/AuthorizationPolicy/GetComputerName.vbs b/Extensibility/Security/AuthorizationPolicy/GetComputerName.vbs new file mode 100644 index 0000000..9bdad3e --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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/AuthorizationPolicy/PolicyLibrary/CustomAuthorizationPolicy.cs b/Extensibility/Security/AuthorizationPolicy/PolicyLibrary/CustomAuthorizationPolicy.cs new file mode 100644 index 0000000..5b7e53a --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/PolicyLibrary/CustomAuthorizationPolicy.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CoreWCF.IdentityModel.Claims; +using CoreWCF.IdentityModel.Policy; + +namespace CoreWcf.Samples.AuthorizationPolicy +{ + public class CustomAuthorizationPolicy : IAuthorizationPolicy + { + string id; + + public CustomAuthorizationPolicy() + { + id = Guid.NewGuid().ToString(); + } + + public bool Evaluate(EvaluationContext evaluationContext, ref object state) + { + bool bRet = false; + CustomAuthState customstate = null; + + // If state is null, then we've not been called before so we need + // to set up our custom state + if (state == null) + { + customstate = new CustomAuthState(); + state = customstate; + } + else + customstate = (CustomAuthState)state; + + Console.WriteLine("Inside MyAuthorizationPolicy::Evaluate"); + + // If we've not added claims yet... + if (!customstate.ClaimsAdded) + { + // Create an empty list of Claims + IList claims = new List(); + + // Iterate through each of the claimsets in the evaluation context + foreach (ClaimSet cs in evaluationContext.ClaimSets) + // Look for Name claims in the current claimset... + foreach (Claim c in cs.FindClaims(ClaimTypes.Name, Rights.PossessProperty)) + // Get the list of operations the given username is allowed to call... + foreach (string s in GetAllowedOpList(c.Resource.ToString())) + { + // Check numbers aren't too large + + + // Add claims to the list + claims.Add(new Claim("http://example.org/claims/allowedoperation", s, Rights.PossessProperty)); + Console.WriteLine("Claim added {0}", s); + } + + // Add claims to the evaluation context + evaluationContext.AddClaimSet(this, new DefaultClaimSet(this.Issuer, claims)); + + // record that we've added claims + customstate.ClaimsAdded = true; + + // return true, indicating we do not need to be called again. + bRet = true; + } + else + { + // Should never get here, but just in case... + bRet = true; + } + + + return bRet; + } + + public ClaimSet Issuer + { + get { return ClaimSet.System; } + } + + public string Id + { + get { return id; } + } + + // This method returns a collection of action strings that indicate the + // operations the specified username is allowed to call. + private static IEnumerable GetAllowedOpList(string username) + { + IList ret = new List(); + + if (username == "test1") + { + ret.Add("http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Add"); + ret.Add("http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Multiply"); + ret.Add("http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Subtract"); + } + else if (username == "test2") + { + ret.Add("http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Add"); + ret.Add("http://CoreWcf.Samples.AuthorizationPolicy/ICalculatorService/Subtract"); + } + return ret; + } + + // internal class for state + class CustomAuthState + { + bool bClaimsAdded; + + public CustomAuthState() + { + } + + public bool ClaimsAdded + { + get { return bClaimsAdded; } + set { bClaimsAdded = value; } + } + } + } + +} + diff --git a/Extensibility/Security/AuthorizationPolicy/PolicyLibrary/PolicyLibrary.csproj b/Extensibility/Security/AuthorizationPolicy/PolicyLibrary/PolicyLibrary.csproj new file mode 100644 index 0000000..e9cbfd4 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/PolicyLibrary/PolicyLibrary.csproj @@ -0,0 +1,14 @@ + + + + Library + net6.0 + enable + enable + + + + + + + diff --git a/Extensibility/Security/AuthorizationPolicy/Service/CalculatorService.cs b/Extensibility/Security/AuthorizationPolicy/Service/CalculatorService.cs new file mode 100644 index 0000000..34ae206 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/CalculatorService.cs @@ -0,0 +1,29 @@ +// 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.AuthorizationPolicy +{ + // Service class which implements the service contract interface. + public class CalculatorService : ICalculatorService + { + public double Add(double n1, double n2) + { + return n1 + n2; + } + + public double Subtract(double n1, double n2) + { + return n1 - n2; + } + + public double Multiply(double n1, double n2) + { + return n1 * n2; + } + + public double Divide(double n1, double n2) + { + return n1 / n2; + } + } +} diff --git a/Extensibility/Security/AuthorizationPolicy/Service/CustomPolicyManager.cs b/Extensibility/Security/AuthorizationPolicy/Service/CustomPolicyManager.cs new file mode 100644 index 0000000..31d8a24 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/CustomPolicyManager.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CoreWCF.IdentityModel.Claims; +using CoreWCF.IdentityModel.Selectors; +using CoreWCF.IdentityModel.Tokens; + +namespace CoreWcf.Samples.AuthorizationPolicy +{ + public class CustomServiceAuthorizationManager : ServiceAuthorizationManager + { + protected override ValueTask CheckAccessCoreAsync(OperationContext operationContext) + { + // Extract the action URI from the OperationContext. We will use this to match against the claims + // in the AuthorizationContext + string action = operationContext.RequestContext.RequestMessage.Headers.Action; + Console.WriteLine("action: {0}", action); + + // Iterate through the various claimsets in the authorizationcontext + foreach (ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets) + { + // Only look at claimsets issued by System. + if (cs.Issuer == ClaimSet.System) + { + // Iterate through claims of type "http://example.org/claims/allowedoperation" + foreach (Claim c in cs.FindClaims("http://example.org/claims/allowedoperation", Rights.PossessProperty)) + { + // Dump the Claim resource to the console. + Console.WriteLine("resource: {0}", c.Resource.ToString()); + + // If the Claim resource matches the action URI then return true to allow access + if (action == c.Resource.ToString()) + return ValueTask.FromResult(true); + } + } + } + + // If we get here, return false, denying access. + return ValueTask.FromResult(false); + } + } + + public class CustomUserNameValidator : UserNamePasswordValidator + { + // This method validates users. It allows in two users, test1 and test2 + // with passwords 1test and 2test respectively. + // This code is for illustration purposes only and + // MUST NOT be used in a production environment because it is NOT secure. + public override ValueTask ValidateAsync(string userName, string password) + { + if (null == userName || null == password) + { + throw new ArgumentNullException(); + } + + if (!(userName == "test1" && password == "1test") && !(userName == "test2" && password == "2test")) + { + throw new SecurityTokenException("Unknown Username or Password"); + } + + return default; + } + } +} diff --git a/Extensibility/Security/AuthorizationPolicy/Service/ICalculatorService.cs b/Extensibility/Security/AuthorizationPolicy/Service/ICalculatorService.cs new file mode 100644 index 0000000..b05a1c5 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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.AuthorizationPolicy +{ + // Define a service contract. + [ServiceContract(Namespace = "http://CoreWcf.Samples.AuthorizationPolicy")] + 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/AuthorizationPolicy/Service/Program.cs b/Extensibility/Security/AuthorizationPolicy/Service/Program.cs new file mode 100644 index 0000000..964c5a7 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/Program.cs @@ -0,0 +1,60 @@ +// 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.Policy; + +var builder = WebApplication.CreateBuilder(); + +//Enable CoreWCF Services, with metadata (WSDL) support +builder.Services.AddServiceModelServices() + .AddServiceModelMetadata() + .AddSingleton() + .AddSingleton(); + +var app = builder.Build(); + +app.UseServiceModel(builder => +{ + // Add the Calculator Service + builder.AddService(serviceOptions => + { + serviceOptions.BaseAddresses.Clear(); + // Set the default host name:port in generated WSDL and the base path for the address + serviceOptions.BaseAddresses.Add(new Uri("https://localhost/CalculatorService")); + }) + // Add WSHttpBinding endpoint + .AddServiceEndpoint(ServiceWSHttpBinding(MessageCredentialType.UserName), "Username") + .AddServiceEndpoint(ServiceWSHttpBinding(MessageCredentialType.Certificate), "Certificate"); + + Action serviceHost = host => ChangeHostBehavior(host); + builder.ConfigureServiceHostBase(serviceHost); + + ServiceAuthorizationBehavior authBehavior = app.Services.GetRequiredService(); + var authPolicies = new List { new CustomAuthorizationPolicy() }; + var externalAuthPolicies = new System.Collections.ObjectModel.ReadOnlyCollection(authPolicies); + authBehavior.ExternalAuthorizationPolicies = externalAuthPolicies; + + // Configure WSDL to be available + var serviceMetadataBehavior = app.Services.GetRequiredService(); + serviceMetadataBehavior.HttpsGetEnabled = true; +}); + +app.Run(); + +static Binding ServiceWSHttpBinding(MessageCredentialType clientCredentialType) +{ + WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential); + binding.Security.Message.ClientCredentialType = clientCredentialType; + return binding; +} + +void ChangeHostBehavior(ServiceHostBase host) +{ + var srvCredentials = new ServiceCredentials(); + srvCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "localhost"); + srvCredentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust; + srvCredentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; + srvCredentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator(); + host.Description.Behaviors.Add(srvCredentials); +} diff --git a/Extensibility/Security/AuthorizationPolicy/Service/Properties/launchSettings.json b/Extensibility/Security/AuthorizationPolicy/Service/Properties/launchSettings.json new file mode 100644 index 0000000..941fac5 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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/AuthorizationPolicy/Service/Service.csproj b/Extensibility/Security/AuthorizationPolicy/Service/Service.csproj new file mode 100644 index 0000000..9f47aeb --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/Service.csproj @@ -0,0 +1,27 @@ + + + + net6.0 + enable + true + InProcess + + + + + + + + + + + + + + + + + + + + diff --git a/Extensibility/Security/AuthorizationPolicy/Service/appsettings.Development.json b/Extensibility/Security/AuthorizationPolicy/Service/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Extensibility/Security/AuthorizationPolicy/Service/appsettings.json b/Extensibility/Security/AuthorizationPolicy/Service/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/Service/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Extensibility/Security/AuthorizationPolicy/cleanup.bat b/Extensibility/Security/AuthorizationPolicy/cleanup.bat new file mode 100644 index 0000000..91e82a7 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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/AuthorizationPolicy/importclientcert.bat b/Extensibility/Security/AuthorizationPolicy/importclientcert.bat new file mode 100644 index 0000000..6106ab6 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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/AuthorizationPolicy/importservicecert.bat b/Extensibility/Security/AuthorizationPolicy/importservicecert.bat new file mode 100644 index 0000000..fd4bef4 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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/AuthorizationPolicy/setup.bat b/Extensibility/Security/AuthorizationPolicy/setup.bat new file mode 100644 index 0000000..0459365 --- /dev/null +++ b/Extensibility/Security/AuthorizationPolicy/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