From cf5d7ec1e975b6ba55dc0ac4d1c6a5620da1280b Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 31 May 2024 11:47:12 -0500 Subject: [PATCH] depend more heavily on Microsoft libraries for auth, split auth types to separate packages --- shared-package.props | 8 +- .../src/AutoConfiguration/BootstrapScanner.cs | 14 +- .../AutoConfiguration/PublicAPI.Unshipped.txt | 2 +- ...teeltoe.Bootstrap.AutoConfiguration.csproj | 6 +- .../SteeltoeAssemblyNames.cs | 2 +- .../HostBuilderExtensionsTest.cs | 3 +- ...oe.Bootstrap.AutoConfiguration.Test.csproj | 3 +- .../WebApplicationBuilderExtensionsTest.cs | 3 +- .../WebHostBuilderExtensionsTest.cs | 3 +- .../Options/CertificateOptions.cs | 2 +- .../Abstractions/Properties/AssemblyInfo.cs | 1 + .../CertificateConfigurationExtensions.cs | 109 +++ .../CertificateServiceCollectionExtensions.cs | 2 +- .../ConfigurationExtensions.cs | 50 -- .../Common.Security/LocalCertificateWriter.cs | 114 +-- .../Properties/AssemblyInfo.cs | 3 +- ...CertificateConfigurationExtensionsTest.cs} | 2 +- .../ConfigureCertificateOptionsTest.cs | 6 +- .../LocalCertificateWriterTest.cs | 6 +- .../ConfigurationBuilderExtensions.cs | 2 + .../CloudFoundryPostProcessor.cs | 17 +- .../IdentityCloudFoundryPostProcessor.cs | 48 ++ ...ServiceBindingConfigurationProviderTest.cs | 27 +- .../PostProcessorsTest.cs | 31 + .../CloudFoundryServiceInfoCreator.cs | 66 -- .../CloudFoundry/ConfigurationExtensions.cs | 117 ---- .../src/CloudFoundry/ConnectorException.cs | 22 - .../CloudFoundry/Properties/AssemblyInfo.cs | 13 - .../src/CloudFoundry/PublicAPI.Unshipped.txt | 4 - .../src/CloudFoundry/ServiceInfoCreator.cs | 198 ------ .../CloudFoundry/ServiceInfoCreatorFactory.cs | 55 -- .../Services/ServiceInfoFactory.cs | 252 ------- .../Services/SsoServiceInfoFactory.cs | 46 -- .../src/CloudFoundry/Services/Tags.cs | 58 -- .../Steeltoe.Connectors.CloudFoundry.csproj | 15 - .../CloudFoundryServiceInfoCreatorTest.cs | 67 -- .../ConnectorExceptionTest.cs | 28 - .../ServiceInfoCreatorFactoryTest.cs | 40 -- .../Services/ServiceInfoFactoryTest.cs | 416 ----------- .../Services/ServiceInfoTest.cs | 32 - .../Services/SsoServiceInfoFactoryTest.cs | 148 ---- .../Services/SsoServiceInfoTest.cs | 24 - .../CloudFoundry.Test/Services/TagsTest.cs | 84 --- .../Services/TestServiceInfo.cs | 21 - .../Services/TestServiceInfoFactory.cs | 27 - .../Services/TestUriServiceInfo.cs | 20 - .../CloudFoundry.Test/Services/UriInfoTest.cs | 69 -- .../Services/UriServiceInfoTest.cs | 52 -- ...eeltoe.Connectors.CloudFoundry.Test.csproj | 11 - .../test/CloudFoundry.Test/xunit.runner.json | 4 - .../Steeltoe.Connectors.Test.csproj | 1 - ...Connectors.EntityFrameworkCore.Test.csproj | 1 - .../Endpoint/Trace/TraceDiagnosticObserver.cs | 4 +- .../ApplicationBuilderExtensions.cs | 35 - .../ApplicationClaimTypes.cs | 13 - .../AuthServerOptions.cs | 58 -- .../AuthenticationBuilderExtensions.cs | 552 --------------- ...CertificateIdentityAuthorizationHandler.cs | 67 -- .../CloudFoundryClaimActionExtensions.cs | 19 - .../CloudFoundryDefaults.cs | 39 -- .../CloudFoundryHelper.cs | 106 --- .../CloudFoundryInstanceCertificate.cs | 67 -- .../CloudFoundryJwtBearerConfigurer.cs | 35 - .../CloudFoundryJwtBearerOptions.cs | 45 -- .../CloudFoundryOAuthConfigurer.cs | 30 - .../CloudFoundryOAuthHandler.cs | 184 ----- .../CloudFoundryOAuthOptions.cs | 78 --- .../CloudFoundryOpenIdConnectConfigurer.cs | 96 --- .../CloudFoundryOpenIdConnectOptions.cs | 59 -- .../CloudFoundryScopeClaimAction.cs | 30 - .../CloudFoundryTokenKeyResolver.cs | 106 --- .../CloudFoundryTokenValidator.cs | 114 --- .../ConfigurationBuilderExtensions.cs | 54 -- ...lTlsAuthenticationOptionsPostConfigurer.cs | 41 -- .../OpenIdTokenResponse.cs | 28 - .../src/Authentication.CloudFoundry/Readme.md | 12 - .../ServiceCollectionExtensions.cs | 123 ---- ...ecurity.Authentication.CloudFoundry.csproj | 22 - .../TokenExchanger.cs | 212 ------ .../JwtBearerServiceCollectionExtensions.cs | 37 + .../PostConfigureJwtBearerOptions.cs | 43 ++ .../Properties/AssemblyInfo.cs | 2 +- .../Authentication.Jwt/PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 4 + ...e.Security.Authentication.JwtBearer.csproj | 21 + ...tificateAuthenticationBuilderExtensions.cs | 68 -- .../Authentication.Mtls/LoggingExtensions.cs | 36 - .../MutualTlsAuthenticationHandler.cs | 292 -------- .../MutualTlsAuthenticationOptions.cs | 16 - ...penIdConnectServiceCollectionExtensions.cs | 37 + .../PostConfigureOpenIdConnectOptions.cs | 69 ++ .../Properties/AssemblyInfo.cs} | 8 +- .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 4 + ...curity.Authentication.OpenIdConnect.csproj | 21 + .../Properties/AssemblyInfo.cs | 11 + .../SharedServiceCollectionExtensions.cs | 20 + ...ltoe.Security.Authentication.Shared.csproj | 21 + .../SteeltoeSecurityDefaults.cs | 11 + .../Authentication.Shared/TokenKeyResolver.cs | 80 +++ .../ApplicationClaimTypes.cs | 13 + .../ApplicationInstanceCertificate.cs | 59 ++ ...CertificateApplicationBuilderExtensions.cs | 47 ++ .../CertificateAuthorizationDefaults.cs | 13 + .../CertificateAuthorizationHandler.cs | 71 ++ ...teAuthorizationPolicyBuilderExtensions.cs} | 10 +- .../CertificateServiceCollectionExtensions.cs | 79 +++ .../Authorization.Certificate/GlobalUsings.cs | 7 + ...nfigureCertificateAuthenticationOptions.cs | 75 ++ .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 25 + .../SameOrgRequirement.cs} | 5 +- .../SameSpaceRequirement.cs} | 6 +- ...Security.Authorization.Certificate.csproj} | 11 +- .../CloudFoundryClaimActionExtensionsTest.cs | 20 - .../CloudFoundryContainerIdentityMtlsTest.cs | 107 --- .../CloudFoundryExtensionsTest.cs | 176 ----- .../CloudFoundryHelperTest.cs | 61 -- .../CloudFoundryJwtBearerConfigurerTest.cs | 42 -- .../CloudFoundryJwtBearerOptionsTest.cs | 46 -- .../CloudFoundryOAuthBuilderTest.cs | 48 -- .../CloudFoundryOAuthConfigurerTest.cs | 58 -- .../CloudFoundryOAuthHandlerTest.cs | 216 ------ .../CloudFoundryOAuthOptionsTest.cs | 69 -- ...CloudFoundryOpenIdConnectConfigurerTest.cs | 55 -- .../CloudFoundryOpenIdConnectOptionsTest.cs | 28 - .../CloudFoundryScopeClaimActionTest.cs | 23 - .../CloudFoundryTokenValidatorTest.cs | 22 - .../MyTestCloudFoundryHandler.cs | 40 -- .../ServiceCollectionExtensionsTest.cs | 51 -- ...ty.Authentication.CloudFoundry.Test.csproj | 15 - .../TestApplicationFactory.cs | 39 -- .../TestClock.cs | 21 - .../TestHelpers.cs | 20 - .../TestResponse.cs | 45 -- .../TestServerCertificateStartup.cs | 47 -- .../TestServerJwtStartup.cs | 59 -- .../TestServerOpenIdStartup.cs | 58 -- .../TestServerStartup.cs | 64 -- .../TokenExchangerTest.cs | 139 ---- .../xunit.runner.json | 4 - .../GlobalUsings.cs | 5 + ...wtBearerServiceCollectionExtensionsTest.cs | 33 + .../PostConfigureJwtBearerOptionsTest.cs | 103 +++ ...urity.Authentication.JwtBearer.Test.csproj | 14 + .../ClientCertificateAuthenticationTests.cs | 660 ------------------ ...e.Security.Authentication.Mtls.Test.csproj | 15 - .../GlobalUsings.cs | 5 + ...dConnectServiceCollectionExtensionsTest.cs | 33 + .../PostConfigureOpenIdConnectOptionsTest.cs | 118 ++++ ...y.Authentication.OpenIdConnect.Test.csproj | 18 + .../GlobalUsings.cs | 5 + ...Security.Authentication.Shared.Test.csproj | 13 + .../TestMessageHandler.cs | 6 +- .../TokenKeyResolverTest.cs} | 97 +-- .../CertificateAuthorizationTest.cs | 135 ++++ .../ClientCertificatesFixture.cs | 2 +- .../GlobalUsings.cs | 5 + ...rity.Authorization.Certificate.Test.csproj | 25 + .../TestServerCertificateStartup.cs | 65 ++ .../instance.crt | 44 ++ .../instance.key | 27 + .../root_certificates/ca1.crt | 20 + src/Steeltoe.All.sln | 98 +-- src/Steeltoe.All.sln.DotSettings | 2 + versions.props | 2 + 166 files changed, 1872 insertions(+), 7059 deletions(-) create mode 100644 src/Common/src/Common.Security/CertificateConfigurationExtensions.cs delete mode 100644 src/Common/src/Common.Security/ConfigurationExtensions.cs rename src/Common/test/Common.Security.Test/{ConfigurationExtensionsTest.cs => CertificateConfigurationExtensionsTest.cs} (93%) create mode 100644 src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs delete mode 100644 src/Connectors/src/CloudFoundry/CloudFoundryServiceInfoCreator.cs delete mode 100644 src/Connectors/src/CloudFoundry/ConfigurationExtensions.cs delete mode 100644 src/Connectors/src/CloudFoundry/ConnectorException.cs delete mode 100644 src/Connectors/src/CloudFoundry/Properties/AssemblyInfo.cs delete mode 100644 src/Connectors/src/CloudFoundry/PublicAPI.Unshipped.txt delete mode 100644 src/Connectors/src/CloudFoundry/ServiceInfoCreator.cs delete mode 100644 src/Connectors/src/CloudFoundry/ServiceInfoCreatorFactory.cs delete mode 100644 src/Connectors/src/CloudFoundry/Services/ServiceInfoFactory.cs delete mode 100644 src/Connectors/src/CloudFoundry/Services/SsoServiceInfoFactory.cs delete mode 100644 src/Connectors/src/CloudFoundry/Services/Tags.cs delete mode 100644 src/Connectors/src/CloudFoundry/Steeltoe.Connectors.CloudFoundry.csproj delete mode 100644 src/Connectors/test/CloudFoundry.Test/CloudFoundryServiceInfoCreatorTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/ConnectorExceptionTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/ServiceInfoCreatorFactoryTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoFactoryTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoFactoryTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/TagsTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfo.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfoFactory.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/TestUriServiceInfo.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/UriInfoTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Services/UriServiceInfoTest.cs delete mode 100644 src/Connectors/test/CloudFoundry.Test/Steeltoe.Connectors.CloudFoundry.Test.csproj delete mode 100644 src/Connectors/test/CloudFoundry.Test/xunit.runner.json delete mode 100644 src/Security/src/Authentication.CloudFoundry/ApplicationBuilderExtensions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/ApplicationClaimTypes.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/AuthServerOptions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/AuthenticationBuilderExtensions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryCertificateIdentityAuthorizationHandler.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryClaimActionExtensions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryDefaults.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryHelper.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryInstanceCertificate.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerConfigurer.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerOptions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthConfigurer.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthHandler.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthOptions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectConfigurer.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectOptions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryScopeClaimAction.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenKeyResolver.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenValidator.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/ConfigurationBuilderExtensions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/MutualTlsAuthenticationOptionsPostConfigurer.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/OpenIdTokenResponse.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/Readme.md delete mode 100644 src/Security/src/Authentication.CloudFoundry/ServiceCollectionExtensions.cs delete mode 100644 src/Security/src/Authentication.CloudFoundry/Steeltoe.Security.Authentication.CloudFoundry.csproj delete mode 100644 src/Security/src/Authentication.CloudFoundry/TokenExchanger.cs create mode 100644 src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs create mode 100644 src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs rename src/Security/src/{Authentication.CloudFoundry => Authentication.Jwt}/Properties/AssemblyInfo.cs (93%) create mode 100644 src/Security/src/Authentication.Jwt/PublicAPI.Shipped.txt create mode 100644 src/Security/src/Authentication.Jwt/PublicAPI.Unshipped.txt create mode 100644 src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj delete mode 100644 src/Security/src/Authentication.Mtls/CertificateAuthenticationBuilderExtensions.cs delete mode 100644 src/Security/src/Authentication.Mtls/LoggingExtensions.cs delete mode 100644 src/Security/src/Authentication.Mtls/MutualTlsAuthenticationHandler.cs delete mode 100644 src/Security/src/Authentication.Mtls/MutualTlsAuthenticationOptions.cs create mode 100644 src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs create mode 100644 src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs rename src/Security/src/{Authentication.CloudFoundry/SameSpaceRequirement.cs => Authentication.OpenIdConnect/Properties/AssemblyInfo.cs} (56%) create mode 100644 src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt create mode 100644 src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt create mode 100644 src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj create mode 100644 src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs create mode 100644 src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs create mode 100644 src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj create mode 100644 src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs create mode 100644 src/Security/src/Authentication.Shared/TokenKeyResolver.cs create mode 100644 src/Security/src/Authorization.Certificate/ApplicationClaimTypes.cs create mode 100644 src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs create mode 100644 src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs create mode 100644 src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs create mode 100644 src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs rename src/Security/src/{Authentication.CloudFoundry/AuthorizationPolicyBuilderExtensions.cs => Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs} (72%) create mode 100644 src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs create mode 100644 src/Security/src/Authorization.Certificate/GlobalUsings.cs create mode 100644 src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs rename src/{Connectors/src/CloudFoundry => Security/src/Authorization.Certificate}/PublicAPI.Shipped.txt (100%) create mode 100644 src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt rename src/{Connectors/src/CloudFoundry/Services/ServiceInfoFactoryAttribute.cs => Security/src/Authorization.Certificate/SameOrgRequirement.cs} (58%) rename src/Security/src/{Authentication.CloudFoundry/SameOrgRequirement.cs => Authorization.Certificate/SameSpaceRequirement.cs} (57%) rename src/Security/src/{Authentication.Mtls/Steeltoe.Security.Authentication.Mtls.csproj => Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj} (63%) delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryClaimActionExtensionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryContainerIdentityMtlsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryExtensionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryHelperTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerConfigurerTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerOptionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthBuilderTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthConfigurerTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthHandlerTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthOptionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectConfigurerTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectOptionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryScopeClaimActionTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenValidatorTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/MyTestCloudFoundryHandler.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/ServiceCollectionExtensionsTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/Steeltoe.Security.Authentication.CloudFoundry.Test.csproj delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestApplicationFactory.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestClock.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestHelpers.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestResponse.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestServerCertificateStartup.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestServerJwtStartup.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestServerOpenIdStartup.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TestServerStartup.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/TokenExchangerTest.cs delete mode 100644 src/Security/test/Authentication.CloudFoundry.Test/xunit.runner.json create mode 100644 src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs create mode 100644 src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs create mode 100644 src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs create mode 100644 src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj delete mode 100644 src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs delete mode 100644 src/Security/test/Authentication.Mtls.Test/Steeltoe.Security.Authentication.Mtls.Test.csproj create mode 100644 src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs create mode 100644 src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs create mode 100644 src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs create mode 100644 src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj create mode 100644 src/Security/test/Authentication.Shared.Test/GlobalUsings.cs create mode 100644 src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj rename src/Security/test/{Authentication.CloudFoundry.Test => Authentication.Shared.Test}/TestMessageHandler.cs (74%) rename src/Security/test/{Authentication.CloudFoundry.Test/CloudFoundryTokenKeyResolverTest.cs => Authentication.Shared.Test/TokenKeyResolverTest.cs} (72%) create mode 100644 src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs rename src/Security/test/{Authentication.CloudFoundry.Test => Authorization.Certificate.Test}/ClientCertificatesFixture.cs (94%) create mode 100644 src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs create mode 100644 src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj create mode 100644 src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs create mode 100644 src/Security/test/Authorization.Certificate.Test/instance.crt create mode 100644 src/Security/test/Authorization.Certificate.Test/instance.key create mode 100644 src/Security/test/Authorization.Certificate.Test/root_certificates/ca1.crt diff --git a/shared-package.props b/shared-package.props index aab77638c3..53f776ec0b 100644 --- a/shared-package.props +++ b/shared-package.props @@ -54,14 +54,18 @@ + Condition="$(MSBuildProjectName.StartsWith('Steeltoe.Bootstrap')) Or $(MSBuildProjectName.StartsWith('Steeltoe.Configuration')) Or $(MSBuildProjectName.StartsWith('Steeltoe.Connectors')) Or + $(MSBuildProjectName.StartsWith('Steeltoe.Discovery')) Or $(MSBuildProjectName.StartsWith('Steeltoe.Logging')) Or $(MSBuildProjectName.StartsWith('Steeltoe.Management')) Or + $(MSBuildProjectName.StartsWith('Steeltoe.Security'))"> + Condition="!$(MSBuildProjectName.StartsWith('Steeltoe.Configuration')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Management')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Connectors')) And + !$(MSBuildProjectName.StartsWith('Steeltoe.Logging')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Bootstrap')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Discovery')) And + !$(MSBuildProjectName.StartsWith('Steeltoe.Security'))"> $(NoWarn);SA1401;S1168;S2360;S3900;S3956;S4004;S4023 diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index 4c429b9b49..7c3b7d76bb 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -2,13 +2,13 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Steeltoe.Common; using Steeltoe.Common.DynamicTypeAccess; using Steeltoe.Common.Hosting; using Steeltoe.Common.Logging; +using Steeltoe.Common.Security; using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.ConfigServer; using Steeltoe.Configuration.Placeholder; @@ -36,7 +36,6 @@ using Steeltoe.Management.Tracing; using Steeltoe.Management.Wavefront; using Steeltoe.Management.Wavefront.Exporters; -using Steeltoe.Security.Authentication.CloudFoundry; namespace Steeltoe.Bootstrap.AutoConfiguration; @@ -82,7 +81,7 @@ public void ConfigureSteeltoe() WireIfLoaded(WirePrometheus, SteeltoeAssemblyNames.ManagementPrometheus); WireIfLoaded(WireWavefrontMetrics, SteeltoeAssemblyNames.ManagementWavefront); WireIfLoaded(WireDistributedTracing, SteeltoeAssemblyNames.ManagementTracing); - WireIfLoaded(WireCloudFoundryContainerIdentity, SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + WireIfLoaded(WireContainerIdentity, SteeltoeAssemblyNames.CommonSecurity); } private void WireConfigServer() @@ -257,13 +256,12 @@ private void WireDistributedTracing() _logger.LogInformation("Configured distributed tracing"); } - private void WireCloudFoundryContainerIdentity() + private void WireContainerIdentity() { - _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddCloudFoundryContainerIdentity()); + _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddContainerIdentityCertificate()); + _wrapper.ConfigureServices((host, services) => services.ConfigureCertificateOptions(host.Configuration)); - _wrapper.ConfigureServices((host, services) => services.AddCloudFoundryCertificateAuth(host.Configuration)); - - _logger.LogInformation("Configured Cloud Foundry mTLS security"); + _logger.LogInformation("Configured container identity certificate"); } private bool WireIfLoaded(Action wireAction, string assemblyName) diff --git a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt index a5786e38b2..f0190c8154 100644 --- a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt +++ b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ #nullable enable +const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonSecurity = "Steeltoe.Common.Security" -> string! const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry" -> string! const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer" -> string! const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ConfigurationPlaceholder = "Steeltoe.Configuration.Placeholder" -> string! @@ -12,7 +13,6 @@ const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ManagementEndpo const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ManagementPrometheus = "Steeltoe.Management.Prometheus" -> string! const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ManagementTracing = "Steeltoe.Management.Tracing" -> string! const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.ManagementWavefront = "Steeltoe.Management.Wavefront" -> string! -const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry = "Steeltoe.Security.Authentication.CloudFoundry" -> string! static Steeltoe.Bootstrap.AutoConfiguration.HostBuilderExtensions.AddSteeltoe(this Microsoft.Extensions.Hosting.IHostBuilder! builder) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Bootstrap.AutoConfiguration.HostBuilderExtensions.AddSteeltoe(this Microsoft.Extensions.Hosting.IHostBuilder! builder, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> Microsoft.Extensions.Hosting.IHostBuilder! static Steeltoe.Bootstrap.AutoConfiguration.HostBuilderExtensions.AddSteeltoe(this Microsoft.Extensions.Hosting.IHostBuilder! builder, System.Collections.Generic.IReadOnlySet! assemblyNamesToExclude) -> Microsoft.Extensions.Hosting.IHostBuilder! diff --git a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj index 77e06b2703..b37aa865d6 100644 --- a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj +++ b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj @@ -14,8 +14,9 @@ - + + @@ -30,10 +31,7 @@ - - - diff --git a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs index d3055bf066..68cfa40f54 100644 --- a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs +++ b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs @@ -11,6 +11,7 @@ namespace Steeltoe.Bootstrap.AutoConfiguration; /// public static class SteeltoeAssemblyNames { + public const string CommonSecurity = "Steeltoe.Common.Security"; public const string ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry"; public const string ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer"; public const string ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue"; @@ -24,7 +25,6 @@ public static class SteeltoeAssemblyNames public const string ManagementPrometheus = "Steeltoe.Management.Prometheus"; public const string ManagementTracing = "Steeltoe.Management.Tracing"; public const string ManagementWavefront = "Steeltoe.Management.Wavefront"; - public const string SecurityAuthenticationCloudFoundry = "Steeltoe.Security.Authentication.CloudFoundry"; internal static readonly IReadOnlySet All = typeof(SteeltoeAssemblyNames).GetFields().Where(field => field.FieldType == typeof(string)) .Select(field => field.GetValue(null)).Cast().ToHashSet(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 6886543201..26f77170da 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -229,7 +229,7 @@ public void Tracing_IsAutowired() [Fact] public void CloudFoundryContainerSecurity_IsAutowired() { - using IHost host = GetHostForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:ContainerIdentity:CertificateFilePath"].Should().NotBeNull(); @@ -237,7 +237,6 @@ public void CloudFoundryContainerSecurity_IsAutowired() host.Services.GetService>().Should().NotBeNull(); host.Services.GetService>().Should().NotBeNull(); - host.Services.GetService().Should().NotBeNull(); } private static IHost GetHostForOnly(string assemblyNameToInclude) diff --git a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj index be99add6f2..f5e9ae3d09 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj @@ -7,15 +7,16 @@ + + - diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index f188f2676a..7348ed8a12 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -230,7 +230,7 @@ public void Tracing_IsAutowired() [Fact] public void CloudFoundryContainerSecurity_IsAutowired() { - using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:ContainerIdentity:CertificateFilePath"].Should().NotBeNull(); @@ -238,7 +238,6 @@ public void CloudFoundryContainerSecurity_IsAutowired() host.Services.GetService>().Should().NotBeNull(); host.Services.GetService>().Should().NotBeNull(); - host.Services.GetService().Should().NotBeNull(); } private static WebApplication GetWebApplicationForOnly(string assemblyNameToInclude) diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index 0e308073e8..6ac47c1b87 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -230,14 +230,13 @@ public void Tracing_IsAutowired() [Fact] public void CloudFoundryContainerSecurity_IsAutowired() { - using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:ContainerIdentity:CertificateFilePath"].Should().NotBeNull(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:ContainerIdentity:PrivateKeyFilePath"].Should().NotBeNull(); host.Services.GetService>()?.Get("ContainerIdentity").Certificate.Should().NotBeNull(); - host.Services.GetService().Should().NotBeNull(); } private static IWebHost GetWebHostForOnly(string assemblyNameToInclude) diff --git a/src/Common/src/Abstractions/Options/CertificateOptions.cs b/src/Common/src/Abstractions/Options/CertificateOptions.cs index 281f25b35a..65cc7bacba 100644 --- a/src/Common/src/Abstractions/Options/CertificateOptions.cs +++ b/src/Common/src/Abstractions/Options/CertificateOptions.cs @@ -18,5 +18,5 @@ public sealed class CertificateOptions public X509Certificate2? Certificate { get; set; } - public List IssuerChain { get; set; } = []; + public IList IssuerChain { get; internal set; } = []; } diff --git a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs index 49fec8c6b0..8c488412e0 100644 --- a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Security")] [assembly: InternalsVisibleTo("Steeltoe.Connectors")] [assembly: InternalsVisibleTo("Steeltoe.Connectors.EntityFrameworkCore")] [assembly: InternalsVisibleTo("Steeltoe.Logging.DynamicSerilog")] diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs new file mode 100644 index 0000000000..69abcb3982 --- /dev/null +++ b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Configuration; +using Steeltoe.Common.Options; + +namespace Steeltoe.Common.Security; + +public static class CertificateConfigurationExtensions +{ + /// + /// Adds file path information for a certificate and (optional) private key to configuration, for use with . + /// + /// + /// Your . + /// + /// + /// Name of the certificate. + /// + /// + /// The path on disk to locate a valid certificate file. + /// + /// + /// The path on disk to locate a valid pem-encoded RSA key. + /// + public static IConfigurationBuilder AddCertificate(this IConfigurationBuilder builder, string certificateName, string certificateFilePath, + string? privateKeyFilePath = null) + { + ArgumentGuard.NotNull(builder); + ArgumentGuard.NotNullOrEmpty(certificateFilePath); + + string keyPrefix = string.IsNullOrEmpty(certificateName) + ? $"{CertificateOptions.ConfigurationKeyPrefix}{ConfigurationPath.KeyDelimiter}" + : $"{CertificateOptions.ConfigurationKeyPrefix}{ConfigurationPath.KeyDelimiter}{certificateName}{ConfigurationPath.KeyDelimiter}"; + + var keys = new Dictionary + { + { $"{keyPrefix}CertificateFilePath", certificateFilePath } + }; + + if (!string.IsNullOrEmpty(privateKeyFilePath)) + { + keys[$"{keyPrefix}PrivateKeyFilePath"] = privateKeyFilePath; + } + + builder.AddInMemoryCollection(keys); + return builder; + } + + /// + /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, + /// will create certificates resembling those found on the platform. + /// + /// + /// Your . + /// + /// + /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can + /// be shared between different projects operating in the same solution context. + /// + public static IConfigurationBuilder AddContainerIdentityCertificate(this IConfigurationBuilder builder) + { + return builder.AddContainerIdentityCertificate(null, null); + } + + /// + /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, + /// will create certificates resembling those found on the platform. + /// + /// + /// Your . + /// + /// + /// (Optional) A GUID representing an organization, for use with Cloud Foundry certificate-based authorization + /// policy. + /// + /// + /// (Optional) A GUID representing a space, for use with Cloud Foundry certificate-based authorization policy. + /// + /// + /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can + /// be shared between different projects operating in the same solution context. + /// + public static IConfigurationBuilder AddContainerIdentityCertificate(this IConfigurationBuilder builder, Guid? organizationId, Guid? spaceId) + { + if (!Platform.IsCloudFoundry) + { + organizationId ??= Guid.NewGuid(); + spaceId ??= Guid.NewGuid(); + + var task = new LocalCertificateWriter(); + task.Write((Guid)organizationId, (Guid)spaceId); + + Environment.SetEnvironmentVariable("CF_SYSTEM_CERT_PATH", Path.Combine(Directory.GetParent(LocalCertificateWriter.ApplicationBasePath)!.ToString(), "GeneratedCertificates")); + + Environment.SetEnvironmentVariable("CF_INSTANCE_CERT", + Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem")); + + Environment.SetEnvironmentVariable("CF_INSTANCE_KEY", + Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem")); + } + + string? certificateFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); + string? keyFile = Environment.GetEnvironmentVariable("CF_INSTANCE_KEY"); + + return certificateFile == null || keyFile == null ? builder : builder.AddCertificate("ContainerIdentity", certificateFile, keyFile); + } +} diff --git a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs index 8e6c41b7c6..7004c5fbdc 100644 --- a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs @@ -25,7 +25,7 @@ public static class CertificateServiceCollectionExtensions /// /// Provides access to the file system. /// - public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, IConfiguration configuration, IFileProvider? fileProvider) + public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, IConfiguration configuration, IFileProvider? fileProvider = null) { fileProvider ??= new PhysicalFileProvider(Environment.CurrentDirectory); IConfigurationSection[] childSections = configuration.GetSection(CertificateOptions.ConfigurationKeyPrefix).GetChildren().ToArray(); diff --git a/src/Common/src/Common.Security/ConfigurationExtensions.cs b/src/Common/src/Common.Security/ConfigurationExtensions.cs deleted file mode 100644 index cb8b4cfe65..0000000000 --- a/src/Common/src/Common.Security/ConfigurationExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common.Options; - -namespace Steeltoe.Common.Security; - -public static class ConfigurationExtensions -{ - /// - /// Adds file path information for a certificate and (optional) private key to configuration, for use with . - /// - /// - /// Your . - /// - /// - /// Name of the certificate. - /// - /// - /// The path on disk to locate a valid certificate file. - /// - /// - /// The path on disk to locate a valid pem-encoded RSA key. - /// - public static IConfigurationBuilder AddCertificate(this IConfigurationBuilder builder, string certificateName, string certificateFilePath, - string? privateKeyFilePath = null) - { - ArgumentGuard.NotNull(builder); - ArgumentGuard.NotNullOrEmpty(certificateFilePath); - - string keyPrefix = string.IsNullOrEmpty(certificateName) - ? $"{CertificateOptions.ConfigurationKeyPrefix}{ConfigurationPath.KeyDelimiter}" - : $"{CertificateOptions.ConfigurationKeyPrefix}{ConfigurationPath.KeyDelimiter}{certificateName}{ConfigurationPath.KeyDelimiter}"; - - var keys = new Dictionary - { - { $"{keyPrefix}CertificateFilePath", certificateFilePath } - }; - - if (!string.IsNullOrEmpty(privateKeyFilePath)) - { - keys[$"{keyPrefix}PrivateKeyFilePath"] = privateKeyFilePath; - } - - builder.AddInMemoryCollection(keys); - return builder; - } -} diff --git a/src/Common/src/Common.Security/LocalCertificateWriter.cs b/src/Common/src/Common.Security/LocalCertificateWriter.cs index eecfe294ef..4f7603b818 100644 --- a/src/Common/src/Common.Security/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Security/LocalCertificateWriter.cs @@ -7,52 +7,54 @@ namespace Steeltoe.Common.Security; -public class LocalCertificateWriter +internal sealed class LocalCertificateWriter { - public static readonly string AppBasePath = + public static readonly string ApplicationBasePath = AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]; - internal static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); + internal static readonly string ParentPath = Directory.GetParent(ApplicationBasePath)!.ToString(); internal string CertificateFilenamePrefix { get; set; } = "SteeltoeInstance"; - public string RootCaPfxPath { get; set; } = Path.Combine(ParentPath, "GeneratedCertificates", "SteeltoeCA.pfx"); + public string RootCertificateAuthorityPfxPath { get; set; } = Path.Combine(Directory.GetParent(ApplicationBasePath)!.ToString(), "GeneratedCertificates", "SteeltoeCA.pfx"); - public string IntermediatePfxPath { get; set; } = Path.Combine(ParentPath, "GeneratedCertificates", "SteeltoeIntermediate.pfx"); + public string IntermediatePfxPath { get; set; } = + Path.Combine(Directory.GetParent(ApplicationBasePath)!.ToString(), "GeneratedCertificates", "SteeltoeIntermediate.pfx"); - public bool Write(Guid orgId, Guid spaceId) + public void Write(Guid orgId, Guid spaceId) { var appId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); - // Certificates provided by Diego will have a subject that doesn't comply with standards, but CertificateRequest would re-order these components anyway - // Diego subjects will look like this: "CN=, OU=organization: + OU=space: + OU=app:" + // Certificates provided by Diego will have a subject that doesn't comply with standards. + // System.Security.Cryptography.X509Certificates.CertificateRequest would re-order these components to comply if we tried to match. + // Non-compliant subject looks like this: "CN={instanceId}, OU=organization:{organizationId} + OU=space:{spaceId} + OU=app:{appId}" string subject = $"CN={instanceId}, OU=app:{appId} + OU=space:{spaceId} + OU=organization:{orgId}"; - X509Certificate2 caCertificate; + X509Certificate2 rootAuthorityCertificate; - // Create Root CA and intermediate cert PFX with private key (if not already there) + // Create a directory a level above the running project to contain the root and intermediate certificates if (!Directory.Exists(Path.Combine(ParentPath, "GeneratedCertificates"))) { Directory.CreateDirectory(Path.Combine(ParentPath, "GeneratedCertificates")); } - if (!File.Exists(RootCaPfxPath)) + // Create the root certificate if it doesn't already exist (can be shared by multiple applications) + if (!File.Exists(RootCertificateAuthorityPfxPath)) { - caCertificate = CreateRoot("CN=SteeltoeGeneratedCA"); - File.WriteAllBytes(RootCaPfxPath, caCertificate.Export(X509ContentType.Pfx)); + rootAuthorityCertificate = CreateRootCertificate("CN=SteeltoeGeneratedCA"); + File.WriteAllBytes(RootCertificateAuthorityPfxPath, rootAuthorityCertificate.Export(X509ContentType.Pfx)); } else { - caCertificate = new X509Certificate2(RootCaPfxPath); + rootAuthorityCertificate = new X509Certificate2(RootCertificateAuthorityPfxPath); } + // Create the intermediate certificate if it doesn't already exist (can be shared by multiple applications) X509Certificate2 intermediateCertificate; - - // Create intermediate cert PFX with private key (if not already there) if (!File.Exists(IntermediatePfxPath)) { - intermediateCertificate = CreateIntermediate("CN=SteeltoeGeneratedIntermediate", caCertificate); + intermediateCertificate = CreateIntermediateCertificate("CN=SteeltoeGeneratedIntermediate", rootAuthorityCertificate); File.WriteAllBytes(IntermediatePfxPath, intermediateCertificate.Export(X509ContentType.Pfx)); } else @@ -60,81 +62,91 @@ public bool Write(Guid orgId, Guid spaceId) intermediateCertificate = new X509Certificate2(IntermediatePfxPath); } - X509Certificate2 clientCertificate = CreateClient(subject, intermediateCertificate, new SubjectAlternativeNameBuilder()); + var subjectAlternativeNameBuilder = new SubjectAlternativeNameBuilder(); + + X509Certificate2 clientCertificate = CreateClientCertificate(subject, intermediateCertificate, subjectAlternativeNameBuilder); // Create a folder inside the project to store generated certificate files - if (!Directory.Exists(Path.Combine(AppBasePath, "GeneratedCertificates"))) + if (!Directory.Exists(Path.Combine(ApplicationBasePath, "GeneratedCertificates"))) { - Directory.CreateDirectory(Path.Combine(AppBasePath, "GeneratedCertificates")); + Directory.CreateDirectory(Path.Combine(ApplicationBasePath, "GeneratedCertificates")); } - string certContents = "-----BEGIN CERTIFICATE-----\r\n" + +#if NET8_0_OR_GREATER + string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + + intermediateCertificate.ExportCertificatePem() + "\r\n" + + rootAuthorityCertificate.ExportCertificatePem(); + string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); + +#else + string chainedCertificateContents = "-----BEGIN CERTIFICATE-----\r\n" + Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + + "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + + Convert.ToBase64String(rootAuthorityCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END CERTIFICATE-----\r\n"; string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END RSA PRIVATE KEY-----"; - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), certContents); - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Key.pem"), keyContents); +#endif - return true; + File.WriteAllText(Path.Combine(ApplicationBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), chainedCertificateContents); + File.WriteAllText(Path.Combine(ApplicationBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Key.pem"), keyContents); } - private static X509Certificate2 CreateRoot(string name) + private static X509Certificate2 CreateRootCertificate(string distinguishedName) { - using var key = RSA.Create(); - var request = new CertificateRequest(new X500DistinguishedName(name), key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using var privateKey = RSA.Create(); + var certificateRequest = new CertificateRequest(new X500DistinguishedName(distinguishedName), privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); + certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); - return request.CreateSelfSigned(DateTimeOffset.UtcNow, new DateTimeOffset(2039, 12, 31, 23, 59, 59, TimeSpan.Zero)); + return certificateRequest.CreateSelfSigned(DateTimeOffset.UtcNow, new DateTimeOffset(2039, 12, 31, 23, 59, 59, TimeSpan.Zero)); } - private static X509Certificate2 CreateIntermediate(string name, X509Certificate2 issuer) + private static X509Certificate2 CreateIntermediateCertificate(string subjectName, X509Certificate2 issuerCertificate) { - using var key = RSA.Create(); - var request = new CertificateRequest(name, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); + using var privateKey = RSA.Create(); + var certificateRequest = new CertificateRequest(subjectName, privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); byte[] serialNumber = new byte[8]; - using var rng = RandomNumberGenerator.Create(); - rng.GetBytes(serialNumber); + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(serialNumber); - return request.Create(issuer, DateTimeOffset.UtcNow, issuer.NotAfter, serialNumber).CopyWithPrivateKey(key); + return certificateRequest.Create(issuerCertificate, DateTimeOffset.UtcNow, issuerCertificate.NotAfter, serialNumber).CopyWithPrivateKey(privateKey); } - private static X509Certificate2 CreateClient(string name, X509Certificate2 issuer, SubjectAlternativeNameBuilder altNames, DateTimeOffset? notAfter = null) + private static X509Certificate2 CreateClientCertificate(string subjectName, X509Certificate2 issuerCertificate, SubjectAlternativeNameBuilder alternativeNames, DateTimeOffset? notAfter = null) { - using var key = RSA.Create(); - var request = new CertificateRequest(name, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using var privateKey = RSA.Create(); + var request = new CertificateRequest(subjectName, privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension( + [ + new Oid("1.3.6.1.5.5.7.3.1"), // serverAuth + new Oid("1.3.6.1.5.5.7.3.2") // clientAuth + ], false)); - request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection - { - new("1.3.6.1.5.5.7.3.1"), // serverAuth - new("1.3.6.1.5.5.7.3.2") // clientAuth - }, false)); - - if (altNames != null) + if (alternativeNames != null) { - request.CertificateExtensions.Add(altNames.Build()); + request.CertificateExtensions.Add(alternativeNames.Build()); } byte[] serialNumber = new byte[8]; - using (var rng = RandomNumberGenerator.Create()) + using (var randomNumberGenerator = RandomNumberGenerator.Create()) { - rng.GetBytes(serialNumber); + randomNumberGenerator.GetBytes(serialNumber); } - X509Certificate2 signedCert = request.Create(issuer, DateTimeOffset.UtcNow, notAfter ?? DateTimeOffset.UtcNow.AddDays(1), serialNumber); + X509Certificate2 signedCertificate = request.Create(issuerCertificate, DateTimeOffset.UtcNow, notAfter ?? DateTimeOffset.UtcNow.AddDays(1), serialNumber); - return signedCert.CopyWithPrivateKey(key); + return signedCertificate.CopyWithPrivateKey(privateKey); } } diff --git a/src/Common/src/Common.Security/Properties/AssemblyInfo.cs b/src/Common/src/Common.Security/Properties/AssemblyInfo.cs index 669968c456..bf9f9afcd7 100644 --- a/src/Common/src/Common.Security/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Security/Properties/AssemblyInfo.cs @@ -6,5 +6,4 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Security.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry")] -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate.Test")] diff --git a/src/Common/test/Common.Security.Test/ConfigurationExtensionsTest.cs b/src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs similarity index 93% rename from src/Common/test/Common.Security.Test/ConfigurationExtensionsTest.cs rename to src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs index 2207a6b34f..5c322d5e8d 100644 --- a/src/Common/test/Common.Security.Test/ConfigurationExtensionsTest.cs +++ b/src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs @@ -9,7 +9,7 @@ namespace Steeltoe.Common.Security.Test; -public sealed class ConfigurationExtensionsTest +public sealed class CertificateConfigurationExtensionsTest { private const string CertificateName = "test"; diff --git a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs index c14578f317..df3dd957a8 100644 --- a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs @@ -160,7 +160,7 @@ public async Task ServiceLoadsCertificate() collection.Should().NotBeNull(); - if (!File.Exists(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) + if (!File.Exists(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) { var orgId = Guid.NewGuid(); var spaceId = Guid.NewGuid(); @@ -170,8 +170,8 @@ public async Task ServiceLoadsCertificate() } X509Certificate2 certificate = - GetX509FromCertKeyPair(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"), - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem")); + GetX509FromCertKeyPair(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"), + Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem")); await File.WriteAllBytesAsync(filename, certificate.Export(X509ContentType.Pkcs12)); await Task.Delay(2000); diff --git a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs index ea88b20a45..398c248480 100644 --- a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs @@ -20,13 +20,13 @@ public void CertificatesIncludeParams() var rsa = RSA.Create(); certWriter.Write(orgId, spaceId); - var rootCertificate = new X509Certificate2(certWriter.RootCaPfxPath); + var rootCertificate = new X509Certificate2(certWriter.RootCertificateAuthorityPfxPath); var intermediateCert = new X509Certificate2(certWriter.IntermediatePfxPath); - rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem"))); + rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem"))); X509Certificate2 clientCert = - new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) + new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) .CopyWithPrivateKey(rsa); rootCertificate.Should().NotBeNull(); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/ConfigurationBuilderExtensions.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/ConfigurationBuilderExtensions.cs index b2e3aa0aa0..3df1fa4ea5 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/ConfigurationBuilderExtensions.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/ConfigurationBuilderExtensions.cs @@ -97,9 +97,11 @@ public static IConfigurationBuilder AddCloudFoundryServiceBindings(this IConfigu private static void RegisterPostProcessors(CloudFoundryServiceBindingConfigurationSource source, ILoggerFactory loggerFactory) { ILogger eurekaLogger = loggerFactory.CreateLogger(); + ILogger identityLogger = loggerFactory.CreateLogger(); source.RegisterPostProcessor(new CosmosDbCloudFoundryPostProcessor()); source.RegisterPostProcessor(new EurekaCloudFoundryPostProcessor(eurekaLogger)); + source.RegisterPostProcessor(new IdentityCloudFoundryPostProcessor(identityLogger)); source.RegisterPostProcessor(new MongoDbCloudFoundryPostProcessor()); source.RegisterPostProcessor(new MySqlCloudFoundryPostProcessor()); source.RegisterPostProcessor(new PostgreSqlCloudFoundryPostProcessor()); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs index 62dd7681b6..1e7b2e3e5e 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs @@ -9,18 +9,18 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBinding.PostProcessors; internal abstract class CloudFoundryPostProcessor : IConfigurationPostProcessor { - private static readonly Regex TagsConfigurationKeyRegex = - new("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + private static readonly Regex TagsConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex LabelConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.Compiled | RegexOptions.IgnoreCase); public abstract void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData); - protected IEnumerable FilterKeys(IDictionary configurationData, string tagValueToFind) + protected IEnumerable FilterKeys(IDictionary configurationData, string tagOrLabelValueToFind) { List keys = []; foreach ((string key, string? value) in configurationData) { - if (TagsConfigurationKeyRegex.IsMatch(key) && string.Equals(value, tagValueToFind, StringComparison.OrdinalIgnoreCase)) + if (TagsConfigurationKeyRegex.IsMatch(key) && string.Equals(value, tagOrLabelValueToFind, StringComparison.OrdinalIgnoreCase)) { string? parentKey = ConfigurationPath.GetParentPath(key); @@ -34,6 +34,15 @@ protected IEnumerable FilterKeys(IDictionary configurat } } } + else if (LabelConfigurationKeyRegex.IsMatch(key) && string.Equals(value, tagOrLabelValueToFind, StringComparison.OrdinalIgnoreCase)) + { + string? serviceBindingKey = ConfigurationPath.GetParentPath(key); + + if (serviceBindingKey != null) + { + keys.Add(serviceBindingKey); + } + } } return keys; diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs new file mode 100644 index 0000000000..cdb384648a --- /dev/null +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Logging; +using Steeltoe.Common; + +namespace Steeltoe.Configuration.CloudFoundry.ServiceBinding.PostProcessors; + +internal sealed class IdentityCloudFoundryPostProcessor : CloudFoundryPostProcessor +{ + internal const string BindingType = "p-identity"; + internal const string AuthenticationConfigurationKeyPrefix = "Authentication:Schemes"; + internal static readonly string[] AuthenticationSchemes = ["OpenIdConnect", "Bearer"]; + private readonly ILogger _logger; + + public IdentityCloudFoundryPostProcessor(ILogger logger) + { + ArgumentGuard.NotNull(logger); + + _logger = logger; + } + + public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) + { + bool hasMapped = false; + + foreach (string key in FilterKeys(configurationData, BindingType)) + { + if (hasMapped) + { + _logger.LogWarning("Multiple identity service bindings found, which is not supported. Using the first binding from VCAP_SERVICES."); + break; + } + + var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType, AuthenticationConfigurationKeyPrefix); + foreach (var scheme in AuthenticationSchemes) + { + mapper.MapFromTo("credentials:auth_domain", $"{scheme}:Authority"); + mapper.MapFromTo("credentials:client_id", $"{scheme}:ClientId"); + mapper.MapFromTo("credentials:client_secret", $"{scheme}:ClientSecret"); + } + + + hasMapped = true; + } + } +} diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/CloudFoundryServiceBindingConfigurationProviderTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/CloudFoundryServiceBindingConfigurationProviderTest.cs index bcc3b9de11..8a8da79790 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/CloudFoundryServiceBindingConfigurationProviderTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/CloudFoundryServiceBindingConfigurationProviderTest.cs @@ -76,7 +76,30 @@ public sealed class CloudFoundryServiceBindingConfigurationProviderTest ""uri"": ""mysql://gxXQb2pMbzFsZQW8:lvMkGf6oJQvKSOwn@192.168.0.97:3306/cf_b2d83697_5fa1_4a51_991b_975c9d7e5515?reconnect=true"", ""jdbcUrl"": ""jdbc:mysql://192.168.0.97:3306/cf_b2d83697_5fa1_4a51_991b_975c9d7e5515?user=gxXQb2pMbzFsZQW8&password=lvMkGf6oJQvKSOwn"" } - }] + }], + ""p-identity"": [{ + ""label"": ""p-identity"", + ""provider"": null, + ""plan"": ""uaa"", + ""name"": ""mySSOService"", + ""tags"": [], + ""instance_guid"": ""f0fd571f-4aaf-4807-b34f-3777b053de2f"", + ""instance_name"": ""mySSOService"", + ""binding_guid"": ""c2d303a8-8d4f-48ce-916a-74c0305e30b2"", + ""binding_name"": null, + ""credentials"": { + ""auth_domain"": ""https://login.system.testcloud.com"", + ""grant_types"": [ + ""authorization_code"", + ""client_credentials"" + ], + ""client_secret"": ""81f92f37-a38f-4b5e-b769-4c933c5c5aca"", + ""client_id"": ""c2d303a8-8d4f-48ce-916a-74c0305e30b2"" + }, + ""syslog_drain_url"": null, + ""volume_mounts"": [] + } + ] }"; [Fact] @@ -133,6 +156,8 @@ public void Build_LoadsServiceBindings() section.GetValue("p-service-registry:0:name").Should().Be("myServiceRegistry"); section.GetValue("p-service-registry:0:credentials:uri").Should().Be("https://eureka-f4b98d1c-3166-4741-b691-79abba5b2d51.apps.testcloud.com"); section.GetValue("p-mysql:1:name").Should().Be("mySql2"); + section.GetValue("p-identity:0:name").Should().Be("mySSOService"); + section.GetValue("p-identity:0:credentials:auth_domain").Should().Be("https://login.system.testcloud.com"); section.GetValue("p-mysql:1:credentials:uri").Should() .Be("mysql://gxXQb2pMbzFsZQW8:lvMkGf6oJQvKSOwn@192.168.0.97:3306/cf_b2d83697_5fa1_4a51_991b_975c9d7e5515?reconnect=true"); diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs index 0ce0c684c0..28b939c9bc 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs @@ -259,4 +259,35 @@ public void Processes_Eureka_configuration() configurationData[$"{keyPrefix}:AccessTokenUri"].Should().Be("test-access-token-uri"); configurationData[$"{keyPrefix}:Enabled"].Should().Be("true"); } + + [Fact] + public void Processes_Identity_configuration() + { + var postProcessor = new IdentityCloudFoundryPostProcessor(NullLogger.Instance); + + var secrets = new[] + { + Tuple.Create("credentials:auth_domain", "test-domain"), + Tuple.Create("credentials:client_id", "test-id"), + Tuple.Create("credentials:client_secret", "test-secret"), + + // these are included in bindings, but not currently mapped: + Tuple.Create("credentials:grant_types:0", "authorization_code"), + Tuple.Create("credentials:grant_types:1", "client_credentials") + }; + + Dictionary configurationData = + GetConfigurationData(IdentityCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + + PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); + + postProcessor.PostProcessConfiguration(provider, configurationData); + + foreach (string scheme in IdentityCloudFoundryPostProcessor.AuthenticationSchemes) + { + configurationData[$"{IdentityCloudFoundryPostProcessor.AuthenticationConfigurationKeyPrefix}:{scheme}:Authority"].Should().Be("test-domain"); + configurationData[$"{IdentityCloudFoundryPostProcessor.AuthenticationConfigurationKeyPrefix}:{scheme}:clientId"].Should().Be("test-id"); + configurationData[$"{IdentityCloudFoundryPostProcessor.AuthenticationConfigurationKeyPrefix}:{scheme}:clientSecret"].Should().Be("test-secret"); + } + } } diff --git a/src/Connectors/src/CloudFoundry/CloudFoundryServiceInfoCreator.cs b/src/Connectors/src/CloudFoundry/CloudFoundryServiceInfoCreator.cs deleted file mode 100644 index 7debc92f07..0000000000 --- a/src/Connectors/src/CloudFoundry/CloudFoundryServiceInfoCreator.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common; -using Steeltoe.Configuration; -using Steeltoe.Configuration.CloudFoundry; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry; - -internal sealed class CloudFoundryServiceInfoCreator : ServiceInfoCreator -{ - private static readonly object Lock = new(); - private static CloudFoundryServiceInfoCreator _me; - - public new static bool IsRelevant => Platform.IsCloudFoundry; - - private CloudFoundryServiceInfoCreator(IConfiguration configuration) - : base(configuration) - { - BuildServiceInfoFactories(); - BuildServiceInfos(); - } - - public new static CloudFoundryServiceInfoCreator Instance(IConfiguration configuration) - { - ArgumentGuard.NotNull(configuration); - - if (configuration != _me?.Configuration) - { - lock (Lock) - { - if (configuration != _me?.Configuration) - { - _me = new CloudFoundryServiceInfoCreator(configuration); - } - } - } - - return _me; - } - - private void BuildServiceInfos() - { - ServiceInfos.Clear(); - - var appInfo = new CloudFoundryApplicationOptions(Configuration); - var serviceOptions = new CloudFoundryServicesOptions(Configuration); - - foreach (KeyValuePair> serviceOption in serviceOptions.Services) - { - foreach (Service s in serviceOption.Value) - { - IServiceInfoFactory factory = FindFactory(s); - - if (factory != null && factory.Create(s) is ServiceInfo info) - { - info.ApplicationInfo = appInfo; - ServiceInfos.Add(info); - } - } - } - } -} diff --git a/src/Connectors/src/CloudFoundry/ConfigurationExtensions.cs b/src/Connectors/src/CloudFoundry/ConfigurationExtensions.cs deleted file mode 100644 index 47e0fc6693..0000000000 --- a/src/Connectors/src/CloudFoundry/ConfigurationExtensions.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry; - -internal static class ConfigurationExtensions -{ - /// - /// Get configuration info for all services of a given service type. - /// - /// - /// Service info type you're looking for. - /// - /// - /// Configuration to search. - /// - /// - /// List of service infos. - /// - public static IEnumerable GetServiceInfos(this IConfiguration configuration) - where TServiceInfo : class - { - return ServiceInfoCreatorFactory.GetServiceInfoCreator(configuration).GetServiceInfosOfType(); - } - - /// - /// Get configuration info for all services of a given service type. - /// - /// - /// Configuration to search. - /// - /// - /// Type to search for. - /// - /// - /// A list of relevant . - /// - public static IEnumerable GetServiceInfos(this IConfiguration configuration, Type infoType) - { - return ServiceInfoCreatorFactory.GetServiceInfoCreator(configuration).GetServiceInfosOfType(infoType); - } - - /// - /// Get service info when you know the ID. - /// - /// - /// Configuration to search. - /// - /// - /// Id of service. - /// - /// - /// Requested implementation of . - /// - public static IServiceInfo GetServiceInfo(this IConfiguration configuration, string id) - { - return ServiceInfoCreatorFactory.GetServiceInfoCreator(configuration).GetServiceInfo(id); - } - - /// - /// Get service info of a given type when you know the ID. - /// - /// - /// Service info type you're looking for. - /// - /// - /// Configuration to search. - /// - /// - /// Id of service. - /// - /// - /// Requested implementation of . - /// - public static TServiceInfo GetServiceInfo(this IConfiguration configuration, string id) - where TServiceInfo : class, IServiceInfo - { - return ServiceInfoCreatorFactory.GetServiceInfoCreator(configuration).GetServiceInfo(id); - } - - /// - /// Get Service Info from IConfiguration. - /// - /// - /// Type of Service Info to return. - /// - /// - /// Configuration to retrieve service info from. - /// - /// - /// Thrown when multiple matching services are found. - /// - /// - /// Information required to connect to provisioned service. - /// - public static TServiceInfo GetSingletonServiceInfo(this IConfiguration configuration) - where TServiceInfo : class - { - TServiceInfo[] results = GetServiceInfos(configuration).ToArray(); - - if (results.Length > 0) - { - if (results.Length != 1) - { - throw new ConnectorException($"Multiple services of type: {typeof(TServiceInfo)}, bound to application."); - } - - return results[0]; - } - - return null; - } -} diff --git a/src/Connectors/src/CloudFoundry/ConnectorException.cs b/src/Connectors/src/CloudFoundry/ConnectorException.cs deleted file mode 100644 index 105692218b..0000000000 --- a/src/Connectors/src/CloudFoundry/ConnectorException.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Connectors.CloudFoundry; - -public sealed class ConnectorException : Exception -{ - public ConnectorException() - { - } - - public ConnectorException(string message) - : base(message) - { - } - - public ConnectorException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/src/Connectors/src/CloudFoundry/Properties/AssemblyInfo.cs b/src/Connectors/src/CloudFoundry/Properties/AssemblyInfo.cs deleted file mode 100644 index 88e66b35fb..0000000000 --- a/src/Connectors/src/CloudFoundry/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; -using Steeltoe.Connectors; -using Steeltoe.Connectors.CloudFoundry; - -[assembly: InternalsVisibleTo("Steeltoe.Connectors.CloudFoundry.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry")] - -[assembly: ServiceInfoFactoryAssembly] -[assembly: ServiceInfoCreatorAssembly(typeof(CloudFoundryServiceInfoCreator))] diff --git a/src/Connectors/src/CloudFoundry/PublicAPI.Unshipped.txt b/src/Connectors/src/CloudFoundry/PublicAPI.Unshipped.txt deleted file mode 100644 index 3e60fb8bca..0000000000 --- a/src/Connectors/src/CloudFoundry/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,4 +0,0 @@ -Steeltoe.Connectors.CloudFoundry.ConnectorException -Steeltoe.Connectors.CloudFoundry.ConnectorException.ConnectorException() -> void -Steeltoe.Connectors.CloudFoundry.ConnectorException.ConnectorException(string message) -> void -Steeltoe.Connectors.CloudFoundry.ConnectorException.ConnectorException(string message, System.Exception innerException) -> void \ No newline at end of file diff --git a/src/Connectors/src/CloudFoundry/ServiceInfoCreator.cs b/src/Connectors/src/CloudFoundry/ServiceInfoCreator.cs deleted file mode 100644 index 3890a10a88..0000000000 --- a/src/Connectors/src/CloudFoundry/ServiceInfoCreator.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Reflection; -using Microsoft.Extensions.Configuration; -using Steeltoe.Common; -using Steeltoe.Common.Reflection; -using Steeltoe.Configuration; -using Steeltoe.Connectors.CloudFoundry.Services; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry; - -internal class ServiceInfoCreator -{ - private static readonly object Lock = new(); - private static ServiceInfoCreator _me; - - /// - /// Gets a value indicating whether this ServiceInfoCreator should be used. - /// - public static bool IsRelevant { get; } = true; - - protected internal IConfiguration Configuration { get; } - - /// - /// Gets a list of available for finding s. - /// - protected internal IList Factories { get; } = new List(); - - /// - /// Gets a list of that are configured in the application configuration. - /// - public IList ServiceInfos { get; } = new List(); - - protected ServiceInfoCreator(IConfiguration configuration) - { - Configuration = configuration; - } - - public static ServiceInfoCreator Instance(IConfiguration configuration) - { - ArgumentGuard.NotNull(configuration); - - if (configuration != _me?.Configuration) - { - lock (Lock) - { - if (configuration != _me?.Configuration) - { - _me = new ServiceInfoCreator(configuration); - _me.BuildServiceInfoFactories(); - _me.BuildServiceInfos(); - } - } - } - - return _me; - } - - /// - /// Get all Service Infos of type. - /// - /// - /// Service Info Type to retrieve. - /// - /// - /// List of matching Service Infos. - /// - public IEnumerable GetServiceInfosOfType() - where TServiceInfo : class - { - return ServiceInfos.Where(si => si is TServiceInfo).Cast(); - } - - /// - /// Get all Service Infos of type. - /// - /// - /// Service Info Type to retrieve. - /// - /// - /// List of matching Service Infos. - /// - public IEnumerable GetServiceInfosOfType(Type type) - { - return ServiceInfos.Where(info => info.GetType() == type); - } - - /// - /// Get a named service. - /// - /// - /// Service Info type. - /// - /// - /// Service name. - /// - /// - /// Service info or null. - /// - public TServiceInfo GetServiceInfo(string name) - where TServiceInfo : class, IServiceInfo - { - IEnumerable typed = GetServiceInfosOfType(); - - foreach (TServiceInfo si in typed) - { - if (si.Id == name) - { - return si; - } - } - - return null; - } - - /// - /// Get a named Service Info. - /// - /// - /// Name of service info. - /// - /// - /// Service info. - /// - public IServiceInfo GetServiceInfo(string name) - { - return ServiceInfos.FirstOrDefault(info => info.Id == name); - } - - internal IServiceInfoFactory CreateServiceInfoFactory(IEnumerable declaredConstructors) - { - IServiceInfoFactory result = null; - - foreach (ConstructorInfo ci in declaredConstructors) - { - if (ci.GetParameters().Length == 0 && ci.IsPublic && !ci.IsStatic) - { - result = ci.Invoke(null) as IServiceInfoFactory; - break; - } - } - - return result; - } - - protected virtual void BuildServiceInfoFactories() - { - Factories.Clear(); - - IEnumerable factories = - ReflectionHelpers.FindTypesWithAttributeFromAssemblyAttribute(); - - foreach (Type type in factories) - { - IServiceInfoFactory instance = CreateServiceInfoFactory(type.GetTypeInfo().DeclaredConstructors); - - if (instance != null) - { - Factories.Add(instance); - } - } - } - - protected IServiceInfoFactory FindFactory(Service s) - { - foreach (IServiceInfoFactory f in Factories) - { - if (f.Accepts(s)) - { - return f; - } - } - - return null; - } - - private void BuildServiceInfos() - { - ServiceInfos.Clear(); - - var appInfo = new ApplicationInstanceInfo(Configuration, true); - var serviceOpts = new ServicesOptions(Configuration); - - foreach (Service service in serviceOpts.Services.SelectMany(s => s.Value)) - { - IServiceInfoFactory factory = FindFactory(service); - - if (factory != null && factory.Create(service) is ServiceInfo info) - { - info.ApplicationInfo = appInfo; - ServiceInfos.Add(info); - } - } - } -} diff --git a/src/Connectors/src/CloudFoundry/ServiceInfoCreatorFactory.cs b/src/Connectors/src/CloudFoundry/ServiceInfoCreatorFactory.cs deleted file mode 100644 index c3c6123c95..0000000000 --- a/src/Connectors/src/CloudFoundry/ServiceInfoCreatorFactory.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common; -using Steeltoe.Common.Reflection; - -namespace Steeltoe.Connectors.CloudFoundry; - -internal static class ServiceInfoCreatorFactory -{ - private static readonly object Lock = new(); - private static ServiceInfoCreator _serviceInfoCreator; - - /// - /// Build or return the relevant . - /// - /// - /// Application . - /// - /// - /// Singleton . - /// - internal static ServiceInfoCreator GetServiceInfoCreator(IConfiguration configuration) - { - ArgumentGuard.NotNull(configuration); - - lock (Lock) - { - if (_serviceInfoCreator != null && configuration == _serviceInfoCreator.Configuration && - (bool)_serviceInfoCreator.GetType().GetProperty(nameof(ServiceInfoCreator.IsRelevant))!.GetValue(null)!) - { - return _serviceInfoCreator; - } - - IEnumerable alternateInfoCreators = ReflectionHelpers.FindTypeFromAssemblyAttribute(); - - foreach (Type alternateInfoCreator in alternateInfoCreators) - { - if ((bool)alternateInfoCreator.GetProperty(nameof(ServiceInfoCreator.IsRelevant))!.GetValue(null)!) - { - _serviceInfoCreator = - (ServiceInfoCreator)alternateInfoCreator.GetMethod(nameof(ServiceInfoCreator.Instance))!.Invoke(null, [configuration]); - - return _serviceInfoCreator; - } - } - - _serviceInfoCreator = ServiceInfoCreator.Instance(configuration); - } - - return _serviceInfoCreator; - } -} diff --git a/src/Connectors/src/CloudFoundry/Services/ServiceInfoFactory.cs b/src/Connectors/src/CloudFoundry/Services/ServiceInfoFactory.cs deleted file mode 100644 index 1f257ad235..0000000000 --- a/src/Connectors/src/CloudFoundry/Services/ServiceInfoFactory.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Globalization; -using Steeltoe.Common; -using Steeltoe.Configuration; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry.Services; - -[ServiceInfoFactory] -internal abstract class ServiceInfoFactory : IServiceInfoFactory -{ - private static readonly List UserList = new() - { - "user", - "username", - "uid" - }; - - private static readonly List PasswordList = new() - { - "password", - "pw" - }; - - private static readonly List HostList = new() - { - "hostname", - "host" - }; - - private readonly Tags _serviceInfoTags; - - private readonly List _uriKeys = new() - { - "uri", - "url" - }; - - private readonly IEnumerable _uriSchemes; - - protected ServiceInfoFactory(Tags tags, string scheme) - : this(tags, new List - { - scheme - }) - { - ArgumentGuard.NotNullOrEmpty(scheme); - } - - protected ServiceInfoFactory(Tags tags, IEnumerable schemes) - { - ArgumentGuard.NotNull(tags); - - _serviceInfoTags = tags; - _uriSchemes = schemes; - - if (_uriSchemes != null) - { - foreach (string s in _uriSchemes) - { - _uriKeys.Add($"{s}uri"); - _uriKeys.Add($"{s}url"); - } - } - } - - public virtual bool Accepts(Service binding) - { - return TagsMatch(binding) || LabelStartsWithTag(binding) || UriMatchesScheme(binding) || UriKeyMatchesScheme(binding); - } - - public abstract IServiceInfo Create(Service binding); - - protected internal bool TagsMatch(Service binding) - { - return _serviceInfoTags.ContainsOne(binding.Tags); - } - - protected internal bool LabelStartsWithTag(Service binding) - { - return _serviceInfoTags.StartsWith(binding.Label); - } - - protected internal bool UriMatchesScheme(Service binding) - { - if (_uriSchemes == null) - { - return false; - } - - IDictionary credentials = binding.Credentials; - - if (credentials == null) - { - return false; - } - - string uri = GetStringFromCredentials(binding.Credentials, _uriKeys); - - if (uri != null) - { - foreach (string uriScheme in _uriSchemes) - { - if (uri.StartsWith($"{uriScheme}://", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - - return false; - } - - private bool UriKeyMatchesScheme(Service binding) - { - if (_uriSchemes == null) - { - return false; - } - - IDictionary credentials = binding.Credentials; - - if (credentials == null) - { - return false; - } - - foreach (string uriScheme in _uriSchemes) - { - if (credentials.ContainsKey($"{uriScheme}Uri") || credentials.ContainsKey($"{uriScheme}uri") || credentials.ContainsKey($"{uriScheme}Url") || - credentials.ContainsKey($"{uriScheme}url")) - { - return true; - } - } - - return false; - } - - protected internal string GetUsernameFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, UserList); - } - - protected internal string GetPasswordFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, PasswordList); - } - - protected internal int GetPortFromCredentials(IDictionary credentials) - { - return GetIntFromCredentials(credentials, "port"); - } - - protected internal string GetHostFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, HostList); - } - - protected internal string GetUriFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, _uriKeys); - } - - protected string GetClientIdFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, "client_id"); - } - - protected string GetClientSecretFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, "client_secret"); - } - - protected string GetAccessTokenUriFromCredentials(IDictionary credentials) - { - return GetStringFromCredentials(credentials, "access_token_uri"); - } - - protected string GetStringFromCredentials(IDictionary credentials, string key) - { - return GetStringFromCredentials(credentials, new List - { - key - }); - } - - private string GetStringFromCredentials(IDictionary credentials, List keys) - { - if (credentials != null) - { - foreach (string key in keys) - { - if (credentials.TryGetValue(key, out Credential credential)) - { - return credential.Value; - } - } - } - - return null; - } - - protected internal int GetIntFromCredentials(IDictionary credentials, string key) - { - return GetIntFromCredentials(credentials, new List - { - key - }); - } - - private int GetIntFromCredentials(IDictionary credentials, List keys) - { - int result = 0; - - if (credentials != null) - { - foreach (string key in keys) - { - if (credentials.TryGetValue(key, out Credential credential)) - { - result = int.Parse(credential.Value, CultureInfo.InvariantCulture); - } - } - } - - return result; - } - - protected internal List GetListFromCredentials(IDictionary credentials, string key) - { - var result = new List(); - - if (credentials != null && credentials.TryGetValue(key, out Credential credential) && credential.Count > 0) - { - foreach (KeyValuePair kvp in credential) - { - if (kvp.Value.Count != 0 || string.IsNullOrEmpty(kvp.Value.Value)) - { - throw new ConnectorException($"Unable to extract list from credentials: key={key}, value={kvp.Key}/{kvp.Value}"); - } - - result.Add(kvp.Value.Value); - } - } - - return result; - } -} diff --git a/src/Connectors/src/CloudFoundry/Services/SsoServiceInfoFactory.cs b/src/Connectors/src/CloudFoundry/Services/SsoServiceInfoFactory.cs deleted file mode 100644 index 8688a115aa..0000000000 --- a/src/Connectors/src/CloudFoundry/Services/SsoServiceInfoFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Configuration; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry.Services; - -internal sealed class SsoServiceInfoFactory : ServiceInfoFactory -{ - public SsoServiceInfoFactory() - : base(new Tags("p-identity"), "uaa") - { - } - - public override IServiceInfo Create(Service binding) - { - string clientId = GetClientIdFromCredentials(binding.Credentials); - string clientSecret = GetClientSecretFromCredentials(binding.Credentials); - string authDomain = GetStringFromCredentials(binding.Credentials, "auth_domain"); - string uri = GetUriFromCredentials(binding.Credentials); - - if (!string.IsNullOrEmpty(authDomain)) - { - return new SsoServiceInfo(binding.Name, clientId, clientSecret, authDomain); - } - - if (!string.IsNullOrEmpty(uri)) - { - return new SsoServiceInfo(binding.Name, clientId, clientSecret, UpdateUaaScheme(uri)); - } - - return null; - } - - internal string UpdateUaaScheme(string uaaString) - { - if (uaaString.StartsWith("uaa:", StringComparison.Ordinal)) - { - return $"https:{uaaString.Substring(4)}"; - } - - return uaaString; - } -} diff --git a/src/Connectors/src/CloudFoundry/Services/Tags.cs b/src/Connectors/src/CloudFoundry/Services/Tags.cs deleted file mode 100644 index a13ec1b1e2..0000000000 --- a/src/Connectors/src/CloudFoundry/Services/Tags.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Connectors.CloudFoundry.Services; - -internal sealed class Tags -{ - public IEnumerable Values { get; } - - public Tags(string tag) - : this(new[] - { - tag - }) - { - } - - public Tags(string[] tags) - { - Values = tags ?? Array.Empty(); - } - - internal Tags() - { - } - - public bool ContainsOne(IEnumerable tags) - { - return tags != null && Values != null && tags.Intersect(Values).Any(); - } - - public bool Contains(string tag) - { - if (Values == null) - { - return false; - } - - return Values.Contains(tag); - } - - public bool StartsWith(string tag) - { - if (tag != null && Values != null) - { - foreach (string t in Values) - { - if (tag.StartsWith(t, StringComparison.Ordinal)) - { - return true; - } - } - } - - return false; - } -} diff --git a/src/Connectors/src/CloudFoundry/Steeltoe.Connectors.CloudFoundry.csproj b/src/Connectors/src/CloudFoundry/Steeltoe.Connectors.CloudFoundry.csproj deleted file mode 100644 index 5a39e3a1b8..0000000000 --- a/src/Connectors/src/CloudFoundry/Steeltoe.Connectors.CloudFoundry.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0;net6.0 - Package for enabling Steeltoe Connectors on Cloud Foundry - CloudFoundry;vcap;connectors - true - - - - - - - - - diff --git a/src/Connectors/test/CloudFoundry.Test/CloudFoundryServiceInfoCreatorTest.cs b/src/Connectors/test/CloudFoundry.Test/CloudFoundryServiceInfoCreatorTest.cs deleted file mode 100644 index 94dbb0d92a..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/CloudFoundryServiceInfoCreatorTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Configuration.CloudFoundry; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test; - -public sealed class CloudFoundryServiceInfoCreatorTest -{ - [Fact] - public void Constructor_ThrowsIfConfigNull() - { - const IConfiguration configuration = null; - - var ex = Assert.Throws(() => CloudFoundryServiceInfoCreator.Instance(configuration)); - Assert.Contains(nameof(configuration), ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void Constructor_ReturnsInstance() - { - IConfiguration configuration = new ConfigurationBuilder().Build(); - - var inst = CloudFoundryServiceInfoCreator.Instance(configuration); - Assert.NotNull(inst); - } - - [Fact] - public void Constructor_ReturnsNewInstance() - { - IConfiguration configuration1 = new ConfigurationBuilder().Build(); - IConfiguration configuration2 = new ConfigurationBuilder().Build(); - - var inst = CloudFoundryServiceInfoCreator.Instance(configuration1); - Assert.NotNull(inst); - - var inst2 = CloudFoundryServiceInfoCreator.Instance(configuration2); - Assert.NotSame(inst, inst2); - } - - [Fact] - public void BuildServiceInfoFactories_BuildsExpected() - { - IConfiguration configuration = new ConfigurationBuilder().Build(); - - var inst = CloudFoundryServiceInfoCreator.Instance(configuration); - Assert.NotNull(inst); - Assert.NotNull(inst.Factories); - Assert.NotEmpty(inst.Factories); - } - - [Fact] - public void BuildServiceInfos_NoCloudFoundryServices_BuildsExpected() - { - var builder = new ConfigurationBuilder(); - builder.AddCloudFoundry(); - IConfigurationRoot configurationRoot = builder.Build(); - - var creator = CloudFoundryServiceInfoCreator.Instance(configurationRoot); - - Assert.NotNull(creator.ServiceInfos); - Assert.Empty(creator.ServiceInfos); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/ConnectorExceptionTest.cs b/src/Connectors/test/CloudFoundry.Test/ConnectorExceptionTest.cs deleted file mode 100644 index afbe368718..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/ConnectorExceptionTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test; - -public sealed class ConnectorExceptionTest -{ - [Fact] - public void Constructor_CapturesMessage() - { - var ex = new ConnectorException("Test"); - Assert.Equal("Test", ex.Message); - - var ex2 = new ConnectorException("Test2", new Exception()); - Assert.Equal("Test2", ex2.Message); - } - - [Fact] - public void Constructor_CapturesNestedException() - { - var inner = new Exception(); - var ex = new ConnectorException("Test2", inner); - Assert.Equal(inner, ex.InnerException); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/ServiceInfoCreatorFactoryTest.cs b/src/Connectors/test/CloudFoundry.Test/ServiceInfoCreatorFactoryTest.cs deleted file mode 100644 index 47691696a5..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/ServiceInfoCreatorFactoryTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common.TestResources; -using Steeltoe.Configuration.CloudFoundry; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test; - -public sealed class ServiceInfoCreatorFactoryTest -{ - [Fact] - public void FactoryThrowsOnNullConfig() - { - var exception = Assert.Throws(() => ServiceInfoCreatorFactory.GetServiceInfoCreator(null)); - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void Factory_ReturnsSameInstance() - { - IConfigurationRoot configurationRoot = new ConfigurationBuilder().Build(); - - ServiceInfoCreator inst = ServiceInfoCreatorFactory.GetServiceInfoCreator(configurationRoot); - Assert.NotNull(inst); - ServiceInfoCreator inst2 = ServiceInfoCreatorFactory.GetServiceInfoCreator(configurationRoot); - Assert.Same(inst, inst2); - } - - [Fact] - public void FactoryReturnsCloudFoundryCreatorForCloudFoundry() - { - using var appScope = new EnvironmentVariableScope("VCAP_APPLICATION", TestHelpers.VcapApplication); - - ServiceInfoCreator serviceInfoCreator = ServiceInfoCreatorFactory.GetServiceInfoCreator(new ConfigurationBuilder().AddCloudFoundry().Build()); - Assert.IsType(serviceInfoCreator); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoFactoryTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoFactoryTest.cs deleted file mode 100644 index b2d7d9ccc6..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoFactoryTest.cs +++ /dev/null @@ -1,416 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Configuration; -using Steeltoe.Connectors.CloudFoundry.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class ServiceInfoFactoryTest -{ - [Fact] - public void Constructor_ThrowsIfSchemeNull() - { - const string scheme = null; - - var ex = Assert.Throws(() => new TestServiceInfoFactory(new Tags("foo"), scheme)); - Assert.Contains(nameof(scheme), ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void Constructor_ThrowsIfTagsNull() - { - const string scheme = "scheme"; - const Tags tags = null; - - var ex = Assert.Throws(() => new TestServiceInfoFactory(tags, scheme)); - Assert.Contains(nameof(tags), ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void TagsMatch_Matches() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "bar" - } - }; - - Assert.True(sif.TagsMatch(service1)); - } - - [Fact] - public void TagsMatch_DoesNotMatch() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "noMatch" - } - }; - - Assert.False(sif.TagsMatch(service1)); - } - - [Fact] - public void LabelStartsWithTag_Matches() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "noMatch" - }, - Label = "foobarfoo" - }; - - Assert.True(sif.LabelStartsWithTag(service1)); - } - - [Fact] - public void LabelStartsWithTag_DoesNotMatch() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "noMatch" - }, - Label = "baby" - }; - - Assert.False(sif.LabelStartsWithTag(service1)); - } - - [Fact] - public void UriMatchesScheme_Matches() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "noMatch" - }, - Label = "noMatch", - Credentials = - { - { "uri", new Credential("scheme://foo") } - } - }; - - Assert.True(sif.UriMatchesScheme(service1)); - } - - [Fact] - public void UriMatchesScheme_DoesNotMatch() - { - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - - var service1 = new Service - { - Tags = - { - "noMatch" - }, - Label = "noMatch", - Credentials = - { - { "uri", new Credential("nomatch://foo") } - } - }; - - Assert.False(sif.UriMatchesScheme(service1)); - } - - [Fact] - public void GetUsernameFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { "username", new Credential("username") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - string username = sif.GetUsernameFromCredentials(credentials); - Assert.Equal("username", username); - - credentials = new Dictionary - { - { "user", new Credential("username") } - }; - - username = sif.GetUsernameFromCredentials(credentials); - Assert.Equal("username", username); - - credentials = new Dictionary(); - username = sif.GetUsernameFromCredentials(credentials); - Assert.Null(username); - } - - [Fact] - public void GetPasswordFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { "password", new Credential("password") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - string pwd = sif.GetPasswordFromCredentials(credentials); - Assert.Equal("password", pwd); - credentials = new Dictionary(); - pwd = sif.GetPasswordFromCredentials(credentials); - Assert.Null(pwd); - } - - [Fact] - public void GetPortFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { "port", new Credential("123") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - int port = sif.GetPortFromCredentials(credentials); - Assert.Equal(123, port); - credentials = new Dictionary(); - port = sif.GetPortFromCredentials(credentials); - Assert.Equal(0, port); - } - - [Fact] - public void GetHostFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { "host", new Credential("host") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - string host = sif.GetHostFromCredentials(credentials); - Assert.Equal("host", host); - - credentials = new Dictionary - { - { "hostname", new Credential("hostname") } - }; - - host = sif.GetHostFromCredentials(credentials); - Assert.Equal("hostname", host); - - credentials = new Dictionary(); - host = sif.GetHostFromCredentials(credentials); - Assert.Null(host); - } - - [Fact] - public void GetUriFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { "uri", new Credential("https://boo:222") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - string uri = sif.GetUriFromCredentials(credentials); - Assert.Equal("https://boo:222", uri); - - credentials = new Dictionary - { - { "url", new Credential("https://boo:222") } - }; - - uri = sif.GetUriFromCredentials(credentials); - Assert.Equal("https://boo:222", uri); - - credentials = new Dictionary(); - uri = sif.GetUriFromCredentials(credentials); - Assert.Null(uri); - } - - [Fact] - public void GetListFromCredentials_ReturnsCorrect() - { - var credentials = new Dictionary - { - { - "uris", new Credential - { - { "0", new Credential("https://foo:11") }, - { "1", new Credential("https://bar:11") } - } - } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - List list = sif.GetListFromCredentials(credentials, "uris"); - Assert.NotNull(list); - Assert.Equal(2, list.Count); - Assert.True(list[0] == "https://foo:11" || list[0] == "https://bar:11"); - Assert.True(list[1] == "https://foo:11" || list[1] == "https://bar:11"); - } - - [Fact] - public void GetListFromCredentials_ThrowsWhenListNotPossible() - { - var credentials = new Dictionary - { - { - "foo", new Credential - { - { - "bar", new Credential - { - { "bang", new Credential("badabing") } - } - } - } - } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - var ex = Assert.Throws(() => sif.GetListFromCredentials(credentials, "foo")); - Assert.Contains("key=foo", ex.Message, StringComparison.Ordinal); - Assert.Contains("value=bar/", ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void GetIntFromCredentials_ThrowsFormatException() - { - var credentials = new Dictionary - { - { "key", new Credential("foobar") } - }; - - var tags = new Tags(new[] - { - "foo", - "bar" - }); - - const string scheme = "scheme"; - - var sif = new TestServiceInfoFactory(tags, scheme); - Assert.Throws(() => sif.GetIntFromCredentials(credentials, "key")); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoTest.cs deleted file mode 100644 index b493f97f8b..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/ServiceInfoTest.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class ServiceInfoTest -{ - [Fact] - public void Constructor_ThrowsIfIdNull() - { - const string id = null; - - var ex = Assert.Throws(() => new TestServiceInfo(id)); - Assert.Contains(nameof(id), ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void Constructor_InitializesValues() - { - IConfiguration configuration = new ConfigurationBuilder().Build(); - var info = new ApplicationInstanceInfo(configuration); - var si = new TestServiceInfo("id", info); - - Assert.Equal("id", si.Id); - Assert.Equal(info, si.ApplicationInfo); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoFactoryTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoFactoryTest.cs deleted file mode 100644 index 66fa12a6f0..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoFactoryTest.cs +++ /dev/null @@ -1,148 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Configuration; -using Steeltoe.Connectors.CloudFoundry.Services; -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class SsoServiceInfoFactoryTest -{ - [Fact] - public void Accept_AcceptsValidServiceBinding() - { - var s = new Service - { - Label = "p-identity", - Name = "mySSO", - Plan = "sso", - Credentials = - { - { "client_id", new Credential("clientId") }, - { "client_secret", new Credential("clientSecret") }, - { "auth_domain", new Credential("https://sso.login.system.testcloud.com") } - } - }; - - var factory = new SsoServiceInfoFactory(); - Assert.True(factory.Accepts(s)); - } - - [Fact] - public void Accept_AcceptsValidUAAServiceBinding() - { - var s = new Service - { - Label = "user-provided", - Name = "mySSO", - Credentials = - { - { "client_id", new Credential("clientId") }, - { "client_secret", new Credential("clientSecret") }, - { "uri", new Credential("uaa://sso.login.system.testcloud.com") } - } - }; - - var factory = new SsoServiceInfoFactory(); - Assert.True(factory.Accepts(s)); - } - - [Fact] - public void Accept_RejectsInvalidServiceBinding() - { - var s = new Service - { - Label = "p-mysql", - Tags = - { - "foobar", - "relational" - }, - Name = "mySqlService", - Plan = "100mb-dev", - Credentials = - { - { "hostname", new Credential("192.168.0.90") }, - { "port", new Credential("3306") }, - { "name", new Credential("cf_b4f8d2fa_a3ea_4e3a_a0e8_2cd040790355") }, - { "username", new Credential("Dd6O1BPXUHdrmzbP") }, - { "password", new Credential("7E1LxXnlH2hhlPVt") }, - { "uri", new Credential("mysql://Dd6O1BPXUHdrmzbP:7E1LxXnlH2hhlPVt@192.168.0.90:3306/cf_b4f8d2fa_a3ea_4e3a_a0e8_2cd040790355?reconnect=true") }, - { - "jdbcUrl", - new Credential("jdbc:mysql://192.168.0.90:3306/cf_b4f8d2fa_a3ea_4e3a_a0e8_2cd040790355?user=Dd6O1BPXUHdrmzbP&password=7E1LxXnlH2hhlPVt") - } - } - }; - - var factory = new SsoServiceInfoFactory(); - Assert.False(factory.Accepts(s)); - } - - [Fact] - public void Create_CreatesValidServiceBinding() - { - var s = new Service - { - Label = "p-identity", - Name = "mySSO", - Plan = "sso", - Credentials = - { - { "client_id", new Credential("clientId") }, - { "client_secret", new Credential("clientSecret") }, - { "auth_domain", new Credential("https://sso.login.system.testcloud.com") } - } - }; - - var factory = new SsoServiceInfoFactory(); - var info = factory.Create(s) as SsoServiceInfo; - Assert.NotNull(info); - Assert.Equal("mySSO", info.Id); - Assert.Equal("clientId", info.ClientId); - Assert.Equal("clientSecret", info.ClientSecret); - Assert.Equal("https://sso.login.system.testcloud.com", info.AuthDomain); - } - - [Fact] - public void CreateWithURI_CreatesValidServiceBinding() - { - var s = new Service - { - Label = "user-provided", - Name = "mySSO", - Credentials = - { - { "client_id", new Credential("clientId") }, - { "client_secret", new Credential("clientSecret") }, - { "uri", new Credential("uaa://sso.login.system.testcloud.com") } - } - }; - - var factory = new SsoServiceInfoFactory(); - var info = factory.Create(s) as SsoServiceInfo; - Assert.NotNull(info); - Assert.Equal("mySSO", info.Id); - Assert.Equal("clientId", info.ClientId); - Assert.Equal("clientSecret", info.ClientSecret); - Assert.Equal("https://sso.login.system.testcloud.com", info.AuthDomain); - } - - [Fact] - public void UpdateUaaScheme_UpdatesSchemeProperly() - { - const string uaa1 = "uaa://sso.login.system.testcloud.com"; - var factory = new SsoServiceInfoFactory(); - string result = factory.UpdateUaaScheme(uaa1); - Assert.Equal("https://sso.login.system.testcloud.com", result); - const string uaa2 = "uaa://uaa.system.testcloud.com"; - result = factory.UpdateUaaScheme(uaa2); - Assert.Equal("https://uaa.system.testcloud.com", result); - const string nonUaa = "https://uaa.system.testcloud.com"; - result = factory.UpdateUaaScheme(nonUaa); - Assert.Equal(nonUaa, result); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoTest.cs deleted file mode 100644 index bd09713798..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/SsoServiceInfoTest.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class SsoServiceInfoTest -{ - [Fact] - public void Constructor_CreatesExpected() - { - const string clientId = "clientId"; - const string clientSecret = "clientSecret"; - const string authDomain = "https://p-spring-cloud-services.uaa.my-cf.com/oauth/token"; - var r1 = new SsoServiceInfo("myId", clientId, clientSecret, authDomain); - Assert.Equal("myId", r1.Id); - Assert.Equal("clientId", r1.ClientId); - Assert.Equal("clientSecret", r1.ClientSecret); - Assert.Equal("https://p-spring-cloud-services.uaa.my-cf.com/oauth/token", r1.AuthDomain); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/TagsTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/TagsTest.cs deleted file mode 100644 index eae01daadf..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/TagsTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.CloudFoundry.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class TagsTest -{ - private static readonly Tags EmptyTags = new(); - - [Fact] - public void ContainsOne() - { - var tags = new Tags(new[] - { - "test1", - "test2" - }); - - Assert.True(tags.ContainsOne(new[] - { - "test1", - "testx" - })); - - Assert.True(tags.ContainsOne(new[] - { - "testx", - "test2" - })); - - Assert.False(tags.ContainsOne(new[] - { - "testx", - "testy" - })); - } - - [Fact] - public void ContainsOne_WithEmptyTags() - { - Assert.False(EmptyTags.ContainsOne(new[] - { - "test" - })); - } - - [Fact] - public void Contains() - { - var tags = new Tags(new[] - { - "test1", - "test2" - }); - - Assert.True(tags.Contains("test1")); - Assert.True(tags.Contains("test2")); - Assert.False(tags.Contains("testx")); - } - - [Fact] - public void Contains_WithEmptyTags() - { - Assert.False(EmptyTags.Contains("test")); - } - - [Fact] - public void StartsWith() - { - var tags = new Tags("test"); - Assert.True(tags.StartsWith("test-123")); - Assert.False(tags.StartsWith("abcd")); - } - - [Fact] - public void StartsWith_WithEmptyTags() - { - Assert.False(EmptyTags.StartsWith("test")); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfo.cs b/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfo.cs deleted file mode 100644 index 4276f2fda0..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Common; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -internal sealed class TestServiceInfo : ServiceInfo -{ - public TestServiceInfo(string id, IApplicationInstanceInfo info) - : base(id, info) - { - } - - public TestServiceInfo(string id) - : base(id, null) - { - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfoFactory.cs b/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfoFactory.cs deleted file mode 100644 index 76fa6c9804..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/TestServiceInfoFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Configuration; -using Steeltoe.Connectors.CloudFoundry.Services; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -internal sealed class TestServiceInfoFactory : ServiceInfoFactory -{ - public TestServiceInfoFactory(Tags tags, string scheme) - : base(tags, scheme) - { - } - - public TestServiceInfoFactory(Tags tags, string[] schemes) - : base(tags, schemes) - { - } - - public override IServiceInfo Create(Service binding) - { - throw new NotImplementedException(); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/TestUriServiceInfo.cs b/src/Connectors/test/CloudFoundry.Test/Services/TestUriServiceInfo.cs deleted file mode 100644 index 6ef78f6993..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/TestUriServiceInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -internal sealed class TestUriServiceInfo : UriServiceInfo -{ - public TestUriServiceInfo(string id, string uri) - : base(id, uri) - { - } - - public TestUriServiceInfo(string id, string scheme, string host, int port, string username, string password, string path) - : base(id, scheme, host, port, username, password, path) - { - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/UriInfoTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/UriInfoTest.cs deleted file mode 100644 index 7d7401a0f2..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/UriInfoTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class UriInfoTest -{ - [Fact] - public void Constructor_Uri() - { - const string uri = "mysql://joe:joes_password@localhost:1527/big_db"; - var result = new UriInfo(uri); - - AssertUriInfoEquals(result, "localhost", 1527, "joe", "joes_password", "big_db", null); - Assert.Equal(uri, result.UriString); - } - - [Fact] - public void Constructor_WithQuery() - { - const string uri = "mysql://joe:joes_password@localhost:1527/big_db?p1=v1&p2=v2"; - var result = new UriInfo(uri); - - AssertUriInfoEquals(result, "localhost", 1527, "joe", "joes_password", "big_db", "p1=v1&p2=v2"); - Assert.Equal(uri, result.UriString); - } - - [Fact] - public void Constructor_NoUsernamePassword() - { - const string uri = "mysql://localhost:1527/big_db"; - var result = new UriInfo(uri); - - AssertUriInfoEquals(result, "localhost", 1527, null, null, "big_db", null); - Assert.Equal(uri, result.UriString); - } - - [Fact] - public void Constructor_WithUsernameNoPassword() - { - const string uri = "mysql://joe@localhost:1527/big_db"; - var ex = Assert.Throws(() => new UriInfo(uri)); - Assert.Contains("joe", ex.Message, StringComparison.Ordinal); - } - - [Fact] - public void Constructor_WithExplicitParameters() - { - const string uri = "mysql://joe:joes_password@localhost:1527/big_db"; - var result = new UriInfo("mysql", "localhost", 1527, "joe", "joes_password", "big_db"); - - AssertUriInfoEquals(result, "localhost", 1527, "joe", "joes_password", "big_db", null); - Assert.Equal(uri, result.UriString); - } - - private void AssertUriInfoEquals(UriInfo result, string host, int port, string username, string password, string path, string query) - { - Assert.Equal(host, result.Host); - Assert.Equal(port, result.Port); - Assert.Equal(username, result.UserName); - Assert.Equal(password, result.Password); - Assert.Equal(path, result.Path); - Assert.Equal(query, result.Query); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Services/UriServiceInfoTest.cs b/src/Connectors/test/CloudFoundry.Test/Services/UriServiceInfoTest.cs deleted file mode 100644 index b6b2731c55..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Services/UriServiceInfoTest.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Connectors.CloudFoundry.Test.Services; - -public sealed class UriServiceInfoTest -{ - [Fact] - public void Constructor_CreatesExpected() - { - const string uri = "mysql://joe:joes_password@localhost:1527/big_db"; - UriServiceInfo r1 = new TestUriServiceInfo("myId", "mysql", "localhost", 1527, "joe", "joes_password", "big_db"); - UriServiceInfo r2 = new TestUriServiceInfo("myId", uri); - - Assert.Equal("myId", r1.Id); - Assert.Equal("mysql", r1.Scheme); - Assert.Equal("localhost", r1.Host); - Assert.Equal(1527, r1.Port); - Assert.Equal("joe", r1.UserName); - Assert.Equal("joes_password", r1.Password); - Assert.Equal("big_db", r1.Path); - Assert.Null(r1.Query); - - Assert.Equal("myId", r2.Id); - Assert.Equal("mysql", r2.Scheme); - Assert.Equal("localhost", r2.Host); - Assert.Equal(1527, r2.Port); - Assert.Equal("joe", r2.UserName); - Assert.Equal("joes_password", r2.Password); - Assert.Equal("big_db", r2.Path); - Assert.Null(r2.Query); - } - - [Fact] - public void UriEncodingIsApplied() - { - UriServiceInfo r1 = new TestUriServiceInfo("myId", "http", "foo.bar", 1337, "SomeUser", "P4SSW0RD#", "baz"); - - Assert.Equal("myId", r1.Id); - Assert.Equal("http", r1.Scheme); - Assert.Equal("foo.bar", r1.Host); - Assert.Equal(1337, r1.Port); - Assert.Equal("SomeUser", r1.UserName); - Assert.Equal("P4SSW0RD#", r1.Password); - Assert.Equal("baz", r1.Path); - Assert.Null(r1.Query); - } -} diff --git a/src/Connectors/test/CloudFoundry.Test/Steeltoe.Connectors.CloudFoundry.Test.csproj b/src/Connectors/test/CloudFoundry.Test/Steeltoe.Connectors.CloudFoundry.Test.csproj deleted file mode 100644 index 342fe4f65f..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/Steeltoe.Connectors.CloudFoundry.Test.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - net8.0;net6.0 - - - - - - - - diff --git a/src/Connectors/test/CloudFoundry.Test/xunit.runner.json b/src/Connectors/test/CloudFoundry.Test/xunit.runner.json deleted file mode 100644 index fdeefaa456..0000000000 --- a/src/Connectors/test/CloudFoundry.Test/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "maxParallelThreads": 1, - "parallelizeTestCollections": false -} diff --git a/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj b/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj index d171fef66e..aa2905dc08 100644 --- a/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj +++ b/src/Connectors/test/Connectors.Test/Steeltoe.Connectors.Test.csproj @@ -26,6 +26,5 @@ - diff --git a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj index c01e45ec8d..4709f37907 100644 --- a/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj +++ b/src/Connectors/test/EntityFrameworkCore.Test/Steeltoe.Connectors.EntityFrameworkCore.Test.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Management/src/Endpoint/Trace/TraceDiagnosticObserver.cs b/src/Management/src/Endpoint/Trace/TraceDiagnosticObserver.cs index 1629ba09ac..e409624c55 100644 --- a/src/Management/src/Endpoint/Trace/TraceDiagnosticObserver.cs +++ b/src/Management/src/Endpoint/Trace/TraceDiagnosticObserver.cs @@ -164,7 +164,9 @@ internal long GetJavaTime(long ticks) ArgumentGuard.NotNull(context); var sessionFeature = context.Features.Get(); - return sessionFeature?.Session.Id; + + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + return sessionFeature?.Session?.Id; } internal string GetTimeTaken(TimeSpan duration) diff --git a/src/Security/src/Authentication.CloudFoundry/ApplicationBuilderExtensions.cs b/src/Security/src/Authentication.CloudFoundry/ApplicationBuilderExtensions.cs deleted file mode 100644 index 40a50f5ac5..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.HttpOverrides; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class ApplicationBuilderExtensions -{ - /// - /// Enable identity certificate rotation, certificate and header forwarding, authentication and authorization Default ForwardedHeadersOptions only - /// includes . - /// - /// - /// The . - /// - /// - /// Custom header forwarding policy. - /// - public static IApplicationBuilder UseCloudFoundryCertificateAuth(this IApplicationBuilder app, ForwardedHeadersOptions forwardedHeaders = null) - { - app.UseForwardedHeaders(forwardedHeaders ?? new ForwardedHeadersOptions - { - ForwardedHeaders = ForwardedHeaders.XForwardedProto - }); - - app.UseCertificateForwarding(); - app.UseAuthentication(); - app.UseAuthorization(); - - return app; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/ApplicationClaimTypes.cs b/src/Security/src/Authentication.CloudFoundry/ApplicationClaimTypes.cs deleted file mode 100644 index 033b1105ef..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/ApplicationClaimTypes.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class ApplicationClaimTypes -{ - public const string CloudFoundryOrgId = "CloudFoundryOrgId"; - public const string CloudFoundrySpaceId = "CloudFoundrySpaceId"; - public const string CloudFoundryAppId = "CloudFoundryAppId"; - public const string CloudFoundryInstanceId = "CloudFoundryContainerInstanceId"; -} diff --git a/src/Security/src/Authentication.CloudFoundry/AuthServerOptions.cs b/src/Security/src/Authentication.CloudFoundry/AuthServerOptions.cs deleted file mode 100644 index 1279a2e913..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/AuthServerOptions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class AuthServerOptions -{ - /// - /// Gets or sets the location of the OAuth server. - /// - public string AuthorizationUrl { get; set; } - - /// - /// Gets or sets the location the user is sent to after authentication. - /// - public string CallbackUrl { get; set; } - - /// - /// Gets or sets the application's client id for interacting with the auth server. - /// - public string ClientId { get; set; } = CloudFoundryDefaults.ClientId; - - /// - /// Gets or sets the application's client secret for interacting with the auth server. - /// - public string ClientSecret { get; set; } = CloudFoundryDefaults.ClientSecret; - - /// - /// Gets or sets the name of the authentication type currently in use. - /// - public string SignInAsAuthenticationType { get; set; } - - /// - /// Gets or sets the timeout (in ms) for calls to the auth server. - /// - public int ClientTimeout { get; set; } = 3000; - - /// - /// Gets or sets additional scopes beyond 'openid' when requesting tokens. - /// - public string AdditionalTokenScopes { get; set; } = string.Empty; - - /// - /// Gets or sets a scopes to require. - /// - public string[] RequiredScopes { get; set; } - - /// - /// Gets or sets a list of additional audiences to use with token validation. - /// - public string[] AdditionalAudiences { get; set; } - - /// - /// Gets or sets a value indicating whether to validate SSO server certificate. - /// - public bool ValidateCertificates { get; set; } = true; -} diff --git a/src/Security/src/Authentication.CloudFoundry/AuthenticationBuilderExtensions.cs b/src/Security/src/Authentication.CloudFoundry/AuthenticationBuilderExtensions.cs deleted file mode 100644 index 674cb4bb8c..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/AuthenticationBuilderExtensions.cs +++ /dev/null @@ -1,552 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Steeltoe.Configuration.CloudFoundry; -using Steeltoe.Connectors.CloudFoundry; -using Steeltoe.Connectors.Services; -using Steeltoe.Security.Authentication.Mtls; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class AuthenticationBuilderExtensions -{ - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, IConfiguration configuration) - { - return builder.AddCloudFoundryOAuth(CloudFoundryDefaults.AuthenticationScheme, configuration); - } - - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, string authenticationScheme, IConfiguration configuration) - { - return builder.AddCloudFoundryOAuth(authenticationScheme, CloudFoundryDefaults.DisplayName, configuration); - } - - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration) - { - builder.AddOAuth(authenticationScheme, displayName, options => - { - IConfigurationSection securitySection = configuration.GetSection(CloudFoundryDefaults.SecurityClientSectionPrefix); - securitySection.Bind(options); - options.SetEndpoints(GetAuthDomain(securitySection)); - - var info = configuration.GetSingletonServiceInfo(); - CloudFoundryOAuthConfigurer.Configure(info, options); - }); - - return builder; - } - - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, IConfiguration configuration, - Action configurer) - { - return builder.AddCloudFoundryOAuth(CloudFoundryDefaults.AuthenticationScheme, configuration, configurer); - } - - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, string authenticationScheme, IConfiguration configuration, - Action configurer) - { - return builder.AddCloudFoundryOAuth(authenticationScheme, CloudFoundryDefaults.DisplayName, configuration, configurer); - } - - /// - /// Adds OAuth middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use OAuth with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOAuth(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration, Action configurer) - { - builder.AddOAuth(authenticationScheme, displayName, options => - { - configurer(options, configuration); - }); - - return builder; - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, IConfiguration configuration) - { - return builder.AddCloudFoundryOpenIdConnect(CloudFoundryDefaults.AuthenticationScheme, configuration); - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, - IConfiguration configuration) - { - return builder.AddCloudFoundryOpenIdConnect(authenticationScheme, CloudFoundryDefaults.DisplayName, configuration); - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration) - { - builder.AddOpenIdConnect(authenticationScheme, displayName, options => - { - var cloudFoundryOptions = new CloudFoundryOpenIdConnectOptions(); - IConfigurationSection securitySection = configuration.GetSection(CloudFoundryDefaults.SecurityClientSectionPrefix); - securitySection.Bind(cloudFoundryOptions); - - var info = configuration.GetSingletonServiceInfo(); - CloudFoundryOpenIdConnectConfigurer.Configure(info, options, cloudFoundryOptions); - }); - - return builder; - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Configure the after applying service bindings. - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, IConfiguration configuration, - Action configurer) - { - return builder.AddCloudFoundryOpenIdConnect(CloudFoundryDefaults.AuthenticationScheme, configuration, configurer); - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// (Optional) Your application configuration. Be sure to include the . - /// - /// - /// Configure the after applying service bindings. - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, - IConfiguration configuration, Action configurer) - { - return builder.AddCloudFoundryOpenIdConnect(authenticationScheme, CloudFoundryDefaults.DisplayName, configuration, configurer); - } - - /// - /// Adds OpenID Connect middleware and configuration for using UAA or Pivotal SSO for user authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Configure the after applying service bindings. - /// - /// - /// configured to use OpenID Connect with UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration, Action configurer) - { - builder.AddOpenIdConnect(authenticationScheme, displayName, options => - { - var cloudFoundryOptions = new CloudFoundryOpenIdConnectOptions(); - IConfigurationSection securitySection = configuration.GetSection(CloudFoundryDefaults.SecurityClientSectionPrefix); - securitySection.Bind(cloudFoundryOptions); - - var info = configuration.GetSingletonServiceInfo(); - CloudFoundryOpenIdConnectConfigurer.Configure(info, options, cloudFoundryOptions); - - configurer(options, configuration); - }); - - return builder; - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, IConfiguration configuration) - { - return builder.AddCloudFoundryJwtBearer(JwtBearerDefaults.AuthenticationScheme, configuration); - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, IConfiguration configuration) - { - return builder.AddCloudFoundryJwtBearer(authenticationScheme, JwtBearerDefaults.AuthenticationScheme, configuration); - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration) - { - builder.AddJwtBearer(authenticationScheme, displayName, options => - { - var cloudFoundryOptions = new CloudFoundryJwtBearerOptions(); - IConfigurationSection securitySection = configuration.GetSection(CloudFoundryDefaults.SecurityClientSectionPrefix); - securitySection.Bind(cloudFoundryOptions); - cloudFoundryOptions.SetEndpoints(GetAuthDomain(securitySection)); - - var info = configuration.GetSingletonServiceInfo(); - CloudFoundryJwtBearerConfigurer.Configure(info, options, cloudFoundryOptions); - }); - - return builder; - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, IConfiguration configuration, - Action configurer) - { - return builder.AddCloudFoundryJwtBearer(JwtBearerDefaults.AuthenticationScheme, configuration, configurer); - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, IConfiguration configuration, - Action configurer) - { - return builder.AddCloudFoundryJwtBearer(authenticationScheme, JwtBearerDefaults.AuthenticationScheme, configuration, configurer); - } - - /// - /// Adds JWT middleware and configuration for using UAA or Pivotal SSO for bearer token authentication. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Sets a display name for this auth scheme. Defaults to . - /// - /// - /// Your application configuration. Be sure to include the . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use JWT Bearer tokens from UAA or Pivotal SSO. - /// - public static AuthenticationBuilder AddCloudFoundryJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, - IConfiguration configuration, Action configurer) - { - builder.AddJwtBearer(authenticationScheme, displayName, jwtOptions => - { - configurer(jwtOptions, configuration); - }); - - return builder; - } - - /// - /// Adds Certificate authentication middleware and configuration to use platform identity certificates. - /// - /// - /// Your . - /// - /// - /// configured to use application identity certificates. - /// - public static AuthenticationBuilder AddCloudFoundryIdentityCertificate(this AuthenticationBuilder builder) - { - return builder.AddCloudFoundryIdentityCertificate(CertificateAuthenticationDefaults.AuthenticationScheme); - } - - /// - /// Adds Certificate authentication middleware and configuration to use platform identity certificates. - /// - /// - /// Your . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use application identity certificates. - /// - public static AuthenticationBuilder AddCloudFoundryIdentityCertificate(this AuthenticationBuilder builder, - Action configurer) - { - return builder.AddCloudFoundryIdentityCertificate(CertificateAuthenticationDefaults.AuthenticationScheme, configurer); - } - - /// - /// Adds Certificate authentication middleware and configuration to use platform identity certificates. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// configured to use application identity certificates. - /// - public static AuthenticationBuilder AddCloudFoundryIdentityCertificate(this AuthenticationBuilder builder, string authenticationScheme) - { - return builder.AddCloudFoundryIdentityCertificate(authenticationScheme, null); - } - - /// - /// Adds Certificate authentication middleware and configuration to use platform identity certificates. - /// - /// - /// Your . - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Used to configure the options. - /// - /// - /// configured to use application identity certificates. - /// - public static AuthenticationBuilder AddCloudFoundryIdentityCertificate(this AuthenticationBuilder builder, string authenticationScheme, - Action configurer) - { - builder.AddMutualTls(authenticationScheme, options => - { - configurer?.Invoke(options); - }); - - return builder; - } - - private static string GetAuthDomain(IConfigurationSection configurationSection) - { - return configurationSection.GetValue(nameof(SsoServiceInfo.AuthDomain)); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryCertificateIdentityAuthorizationHandler.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryCertificateIdentityAuthorizationHandler.cs deleted file mode 100644 index 4a03de4263..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryCertificateIdentityAuthorizationHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Common.Options; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryCertificateIdentityAuthorizationHandler : IAuthorizationHandler -{ - private readonly ILogger _logger; - private CloudFoundryInstanceCertificate _cloudFoundryCertificate; - - public CloudFoundryCertificateIdentityAuthorizationHandler(IOptionsMonitor identityCert, - ILogger logger) - { - _logger = logger; - identityCert.OnChange(OnCertRefresh); - OnCertRefresh(identityCert.Get("ContainerIdentity")); - } - - public Task HandleAsync(AuthorizationHandlerContext context) - { - HandleCertRequirement(context, ApplicationClaimTypes.CloudFoundryOrgId, _cloudFoundryCertificate?.OrgId); - HandleCertRequirement(context, ApplicationClaimTypes.CloudFoundrySpaceId, _cloudFoundryCertificate?.SpaceId); - return Task.CompletedTask; - } - - private void OnCertRefresh(CertificateOptions cert) - { - if (CloudFoundryInstanceCertificate.TryParse(cert.Certificate, out CloudFoundryInstanceCertificate cfCert, _logger)) - { - _cloudFoundryCertificate = cfCert; - } - } - - private void HandleCertRequirement(AuthorizationHandlerContext context, string claimType, string claimValue) - where T : class, IAuthorizationRequirement - { - T requirement = context.PendingRequirements.OfType().FirstOrDefault(); - - if (requirement == null) - { - return; - } - - if (claimValue == null) - { - context.Fail(); - return; - } - - if (context.User.HasClaim(claimType, claimValue)) - { - context.Succeed(requirement); - } - else - { - _logger?.LogDebug("User has the required claim, but the value doesn't match. Expected {0} but got {1}", claimValue, - context.User.FindFirstValue(claimType)); - } - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryClaimActionExtensions.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryClaimActionExtensions.cs deleted file mode 100644 index 3138c421fa..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryClaimActionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Steeltoe.Common; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class CloudFoundryClaimActionExtensions -{ - public static void MapScopes(this ClaimActionCollection collection, string claimType = "scope") - { - ArgumentGuard.NotNull(collection); - - collection.Add(new CloudFoundryScopeClaimAction(claimType, ClaimValueTypes.String)); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryDefaults.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryDefaults.cs deleted file mode 100644 index 24d22d016e..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryDefaults.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class CloudFoundryDefaults -{ - public const string SecurityClientSectionPrefix = "security:oauth2:client"; - public const string SecurityResourceSectionPrefix = "security:oauth2:resource"; - - public const string AuthenticationScheme = "CloudFoundry"; - public const string DisplayName = "CloudFoundry"; - - public const string AuthorizationUri = "/oauth/authorize"; - public const string AccessTokenUri = "/oauth/token"; - public const string UserInfoUri = "/userinfo"; - public const string CheckTokenUri = "/check_token"; - public const string JwtTokenUri = "/token_keys"; - - public const string OAuthServiceUrl = "Default_OAuthServiceUrl"; - public const string ClientId = "Default_ClientId"; - public const string ClientSecret = "Default_ClientSecret"; - public const string CallbackPath = "/signin-cloudfoundry"; - - public const bool ValidateCertificates = true; - - public const string ParamsClientId = "client_id"; - public const string ParamsClientSecret = "client_secret"; - public const string ParamsResponseType = "response_type"; - public const string ParamsScope = "scope"; - public const string ParamsRedirectUri = "redirect_uri"; - public const string ParamsGrantType = "grant_type"; - public const string ParamsTokenFormat = "token_format"; - public const string ParamsCode = "code"; - - public const string SameOrganizationAuthorizationPolicy = "sameorg"; - public const string SameSpaceAuthorizationPolicy = "samespace"; -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryHelper.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryHelper.cs deleted file mode 100644 index 0735a5d3d1..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryHelper.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json; -using Microsoft.IdentityModel.Tokens; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class CloudFoundryHelper -{ - private static readonly DateTime BaseTime = DateTime.UnixEpoch; - - public static List GetScopes(JsonElement user) - { - var result = new List(); - JsonElement scopes = user.GetProperty("scope"); - - if (scopes.ValueKind is JsonValueKind.Array) - { - foreach (JsonElement value in scopes.EnumerateArray()) - { - result.Add(value.GetString()); - } - - return result; - } - - return result; - } - - public static HttpMessageHandler GetBackChannelHandler(bool validateCertificates) - { - if (!validateCertificates) - { - var handler = new HttpClientHandler - { -#pragma warning disable S4830 // Server certificates should be verified during SSL/TLS connections - ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator -#pragma warning restore S4830 // Server certificates should be verified during SSL/TLS connections - }; - - return handler; - } - - return null; - } - - public static TokenValidationParameters GetTokenValidationParameters(TokenValidationParameters parameters, string keyUrl, HttpMessageHandler handler, - bool validateCertificates, AuthServerOptions options = null) - { - parameters ??= new TokenValidationParameters - { - ValidateAudience = false, - ValidateIssuer = true, - ValidateLifetime = true - }; - - var tokenValidator = new CloudFoundryTokenValidator(options ?? new AuthServerOptions()); - parameters.IssuerValidator = tokenValidator.ValidateIssuer; - parameters.AudienceValidator = tokenValidator.ValidateAudience; - - CloudFoundryTokenKeyResolver tkr = options is null - ? new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates) - : new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates, options.ClientTimeout); - - parameters.IssuerSigningKeyResolver = tkr.ResolveSigningKey; - - return parameters; - } - - /// - /// Retrieves the time at which a token was issued. - /// - /// - /// Contents of a JWT. - /// - /// - /// The representation of a token's issued-at time. - /// - public static DateTime GetIssueTime(JsonElement payload) - { - long time = payload.GetProperty("iat").GetInt64(); - return ToAbsoluteUtc(time); - } - - /// - /// Retrieves expiration time property (exp) in a . - /// - /// - /// Contents of a JWT. - /// - /// - /// The representation of a token's expiration. - /// - public static DateTime GetExpTime(JsonElement payload) - { - long time = payload.GetProperty("exp").GetInt64(); - return ToAbsoluteUtc(time); - } - - private static DateTime ToAbsoluteUtc(long secondsPastEpoch) - { - return BaseTime.AddSeconds(secondsPastEpoch); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryInstanceCertificate.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryInstanceCertificate.cs deleted file mode 100644 index d2d95ac8fa..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryInstanceCertificate.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Cryptography.X509Certificates; -using System.Text.RegularExpressions; -using Microsoft.Extensions.Logging; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryInstanceCertificate -{ - // This pattern is found on certificates issued by Diego - private const string CloudFoundryInstanceCertSubjectRegex = - @"^CN=(?[0-9a-f-]+),\sOU=organization:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=app:(?[0-9a-f-]+)$"; - - // This pattern is found on certificates created in .NET - private const string ValidInstanceCertSubjectRegex = - @"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$"; - - public string OrgId { get; private set; } - - public string SpaceId { get; private set; } - - public string AppId { get; private set; } - - public string InstanceId { get; private set; } - - public X509Certificate2 Certificate { get; private set; } - - public static bool TryParse(X509Certificate2 certificate, out CloudFoundryInstanceCertificate cloudFoundryInstanceCertificate, ILogger logger = null) - { - cloudFoundryInstanceCertificate = null; - - if (certificate == null) - { - return false; - } - - Match cfInstanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), CloudFoundryInstanceCertSubjectRegex, - RegexOptions.None, TimeSpan.FromSeconds(1)); - - if (!cfInstanceMatch.Success) - { - cfInstanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), ValidInstanceCertSubjectRegex, - RegexOptions.None, TimeSpan.FromSeconds(1)); - } - - if (cfInstanceMatch.Success) - { - cloudFoundryInstanceCertificate = new CloudFoundryInstanceCertificate - { - OrgId = cfInstanceMatch.Groups["org"].Value, - SpaceId = cfInstanceMatch.Groups["space"].Value, - AppId = cfInstanceMatch.Groups["app"].Value, - InstanceId = cfInstanceMatch.Groups["instance"].Value, - Certificate = certificate - }; - } - else - { - logger?.LogWarning("Identity certificate did not match an expected pattern! Subject was: {0}", certificate.Subject); - } - - return cfInstanceMatch.Success; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerConfigurer.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerConfigurer.cs deleted file mode 100644 index f94097ed15..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerConfigurer.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class CloudFoundryJwtBearerConfigurer -{ - internal static void Configure(SsoServiceInfo si, JwtBearerOptions jwtOptions, CloudFoundryJwtBearerOptions options) - { - if (jwtOptions == null || options == null) - { - return; - } - - if (si != null) - { - options.JwtKeyUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenUri; - } - - jwtOptions.ClaimsIssuer = options.ClaimsIssuer; - jwtOptions.BackchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates); - - jwtOptions.TokenValidationParameters = CloudFoundryHelper.GetTokenValidationParameters(options.TokenValidationParameters, options.JwtKeyUrl, - jwtOptions.BackchannelHttpHandler, options.ValidateCertificates, new AuthServerOptions - { - ClientTimeout = options.Timeout - }); - - jwtOptions.SaveToken = options.SaveToken; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerOptions.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerOptions.cs deleted file mode 100644 index 3ca91992c0..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryJwtBearerOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.JwtBearer; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryJwtBearerOptions : JwtBearerOptions -{ - public string JwtKeyUrl { get; set; } - - // ReSharper disable once InconsistentNaming - public bool Validate_Certificates { get; set; } = true; - - /// - /// Gets or sets a value indicating whether gets a value indicating whether to validate auth server certificate. - /// - public bool ValidateCertificates - { - get => Validate_Certificates; - set => Validate_Certificates = value; - } - - /// - /// Gets or sets the timeout (in ms) for calls to the auth server. - /// - public int Timeout { get; set; } = 100000; - - public CloudFoundryJwtBearerOptions() - { - ClaimsIssuer = CloudFoundryDefaults.AuthenticationScheme; - SaveToken = true; - TokenValidationParameters.ValidateAudience = false; - TokenValidationParameters.ValidateIssuer = true; - TokenValidationParameters.ValidateLifetime = true; - - SetEndpoints($"http://{CloudFoundryDefaults.OAuthServiceUrl}"); - } - - public void SetEndpoints(string authDomain) - { - JwtKeyUrl = !string.IsNullOrWhiteSpace(authDomain) ? authDomain + CloudFoundryDefaults.JwtTokenUri : JwtKeyUrl; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthConfigurer.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthConfigurer.cs deleted file mode 100644 index 43ec781036..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthConfigurer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class CloudFoundryOAuthConfigurer -{ - internal static void Configure(SsoServiceInfo si, CloudFoundryOAuthOptions options) - { - if (options == null) - { - return; - } - - if (si != null) - { - options.ClientId = si.ClientId; - options.ClientSecret = si.ClientSecret; - options.AuthorizationEndpoint = si.AuthDomain + CloudFoundryDefaults.AuthorizationUri; - options.TokenEndpoint = si.AuthDomain + CloudFoundryDefaults.AccessTokenUri; - options.UserInformationEndpoint = si.AuthDomain + CloudFoundryDefaults.UserInfoUri; - options.TokenInfoUrl = si.AuthDomain + CloudFoundryDefaults.CheckTokenUri; - } - - options.BackchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthHandler.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthHandler.cs deleted file mode 100644 index 06543aa7a0..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthHandler.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Text.Encodings.Web; -using System.Text.Json; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryOAuthHandler : OAuthHandler -{ - private readonly ILogger _logger; - -#if NET6_0 - public CloudFoundryOAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) - : base(options, logger, encoder, clock) -#else - public CloudFoundryOAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) - : base(options, logger, encoder) -#endif - { - _logger = logger.CreateLogger(); - } - - protected internal virtual Dictionary GetTokenInfoRequestParameters(OAuthTokenResponse tokens) - { - _logger?.LogDebug("GetTokenInfoRequestParameters() using token: {Token}", tokens.AccessToken); - - return new Dictionary - { - { "token", tokens.AccessToken } - }; - } - - protected internal virtual HttpRequestMessage GetTokenInfoRequestMessage(OAuthTokenResponse tokens) - { - _logger?.LogDebug("GetTokenInfoRequestMessage({Token}) with {ClientId}", tokens.AccessToken, Options.ClientId); - - Dictionary tokenRequestParameters = GetTokenInfoRequestParameters(tokens); - - var requestContent = new FormUrlEncodedContent(tokenRequestParameters); - var request = new HttpRequestMessage(HttpMethod.Post, new Uri(Options.TokenInfoUrl)); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Headers.Authorization = new AuthenticationHeaderValue("Basic", GetEncoded(Options.ClientId, Options.ClientSecret)); - request.Content = requestContent; - return request; - } - - protected internal string GetEncoded(string user, string password) - { - user ??= string.Empty; - password ??= string.Empty; - - return Convert.ToBase64String(Encoding.ASCII.GetBytes($"{user}:{password}")); - } - - protected internal virtual HttpClient GetHttpClient() - { - return Backchannel; - } - - protected override async Task ExchangeCodeAsync(OAuthCodeExchangeContext context) - { - _logger?.LogDebug("ExchangeCodeAsync({Code}, {RedirectUri})", context.Code, context.RedirectUri); - - AuthServerOptions options = Options.BaseOptions(); - options.CallbackUrl = context.RedirectUri; - - var tEx = new TokenExchanger(options, GetHttpClient()); - HttpResponseMessage response = await tEx.ExchangeCodeForTokenAsync(context.Code, Options.TokenEndpoint, Context.RequestAborted); - - if (response.IsSuccessStatusCode) - { - string result = await response.Content.ReadAsStringAsync(); - - _logger?.LogDebug("ExchangeCodeAsync() received json: {Json}", result); - JsonDocument payload = JsonDocument.Parse(result); - OAuthTokenResponse tokenResponse = OAuthTokenResponse.Success(payload); - - return tokenResponse; - } - - string error = $"OAuth token endpoint failure: {await DisplayAsync(response)}"; - return OAuthTokenResponse.Failed(new Exception(error)); - } - - protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, - OAuthTokenResponse tokens) - { - _logger?.LogDebug("CreateTicketAsync()"); - - HttpRequestMessage request = GetTokenInfoRequestMessage(tokens); - HttpClient client = GetHttpClient(); - - HttpResponseMessage response = await client.SendAsync(request, Context.RequestAborted); - - if (!response.IsSuccessStatusCode) - { - _logger?.LogDebug("CreateTicketAsync() failure getting token info from {RequestUri}", request.RequestUri); - throw new HttpRequestException($"An error occurred while retrieving token information ({response.StatusCode})."); - } - - string resp = await response.Content.ReadAsStringAsync(); - - _logger?.LogDebug("CreateTicketAsync() received json: {Json}", resp); - JsonElement payload = JsonDocument.Parse(resp).RootElement; - var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload); - context.RunClaimActions(); - await Events.CreatingTicket(context); - - if (Options.UseTokenLifetime) - { - properties.IssuedUtc = CloudFoundryHelper.GetIssueTime(payload); - properties.ExpiresUtc = CloudFoundryHelper.GetExpTime(payload); - } - - await Events.CreatingTicket(context); - var ticket = new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); - return ticket; - } - - protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) - { - _logger?.LogDebug("BuildChallengeUrl({RedirectUri}) with {ClientId}", redirectUri, Options.ClientId); - - string scope = FormatScope(); - - var queryStrings = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { CloudFoundryDefaults.ParamsResponseType, "code" }, - { CloudFoundryDefaults.ParamsClientId, Options.ClientId }, - { CloudFoundryDefaults.ParamsRedirectUri, redirectUri } - }; - - AddQueryString(queryStrings, properties, "scope", scope); - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - // https://github.com/dotnet/aspnetcore/issues/14250#issuecomment-538012394 - if (Options.StateDataFormat != null) - { - string state = Options.StateDataFormat.Protect(properties); - queryStrings.Add("state", state); - } - - string authorizationEndpoint = QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, queryStrings); - return authorizationEndpoint; - } - - private static void AddQueryString(IDictionary queryStrings, AuthenticationProperties properties, string name, string defaultValue = null) - { - if (!properties.Items.TryGetValue(name, out string value)) - { - value = defaultValue; - } - else - { - properties.Items.Remove(name); - } - - if (value == null) - { - return; - } - - queryStrings[name] = value; - } - - private static async Task DisplayAsync(HttpResponseMessage response) - { - var output = new StringBuilder(); - output.Append($"Status: {response.StatusCode};"); - output.Append($"Headers: {response.Headers};"); - output.Append($"Body: {await response.Content.ReadAsStringAsync()};"); - return output.ToString(); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthOptions.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthOptions.cs deleted file mode 100644 index caffbc26d8..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOAuthOptions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Http; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryOAuthOptions : OAuthOptions -{ - public string TokenInfoUrl { get; set; } - - /// - /// Gets or sets a value indicating whether to validate auth server certificate. - /// - // ReSharper disable once InconsistentNaming - public bool Validate_Certificates { get; set; } = true; - - /// - /// Gets or sets a value indicating whether gets a value indicating whether to validate auth server certificate. - /// - public bool ValidateCertificates - { - get => Validate_Certificates; - set => Validate_Certificates = value; - } - - /// - /// Gets or sets a value indicating whether token issue and expiration times will be set in the auth ticket. - /// - public bool UseTokenLifetime { get; set; } = true; - - public CloudFoundryOAuthOptions() - { - ClaimsIssuer = CloudFoundryDefaults.AuthenticationScheme; - ClientId = CloudFoundryDefaults.ClientId; - ClientSecret = CloudFoundryDefaults.ClientSecret; - CallbackPath = new PathString(CloudFoundryDefaults.CallbackPath); - SaveTokens = true; - - ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "user_id"); - ClaimActions.MapJsonKey(ClaimTypes.Name, "user_name"); - ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name"); - ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name"); - ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); - ClaimActions.MapScopes(); - - SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - - SetEndpoints($"http://{CloudFoundryDefaults.OAuthServiceUrl}"); - } - - public void SetEndpoints(string authDomain) - { - if (!string.IsNullOrWhiteSpace(authDomain)) - { - AuthorizationEndpoint = authDomain + CloudFoundryDefaults.AuthorizationUri; - TokenEndpoint = authDomain + CloudFoundryDefaults.AccessTokenUri; - UserInformationEndpoint = authDomain + CloudFoundryDefaults.UserInfoUri; - TokenInfoUrl = authDomain + CloudFoundryDefaults.CheckTokenUri; - } - } - - internal AuthServerOptions BaseOptions() - { - return new AuthServerOptions - { - ClientId = ClientId, - ClientSecret = ClientSecret, - ValidateCertificates = ValidateCertificates, - AuthorizationUrl = AuthorizationEndpoint - }; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectConfigurer.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectConfigurer.cs deleted file mode 100644 index bc0173d062..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectConfigurer.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -internal static class CloudFoundryOpenIdConnectConfigurer -{ - internal static readonly Func MapScopesToClaims = context => - { - // get claimsId - var claimsId = context.Principal.Identity as ClaimsIdentity; - - // get scopes - string scopes = context.TokenEndpointResponse.Scope; - - // make sure id has scopes - foreach (string scope in scopes.Split(' ')) - { - if (!claimsId.Claims.Any(c => c.Type == "scope" && c.Value == scope)) - { - claimsId.AddClaim(new Claim("scope", scope)); - } - } - - return Task.FromResult(0); - }; - - /// - /// Maps service info credentials and 'security:oauth2:client' info onto OpenIdConnectOptions. - /// - /// - /// Service info credentials parsed from VCAP_SERVICES. - /// - /// - /// OpenId Connect options to be configured. - /// - /// - /// Cloud Foundry-related OpenId Connect configuration options. - /// - internal static void Configure(SsoServiceInfo si, OpenIdConnectOptions openIdOptions, CloudFoundryOpenIdConnectOptions cloudFoundryOptions) - { - if (openIdOptions == null || cloudFoundryOptions == null) - { - return; - } - - if (si != null) - { - openIdOptions.Authority = si.AuthDomain; - openIdOptions.ClientId = si.ClientId; - openIdOptions.ClientSecret = si.ClientSecret; - } - else - { - openIdOptions.Authority = cloudFoundryOptions.Authority; - openIdOptions.ClientId = cloudFoundryOptions.ClientId; - openIdOptions.ClientSecret = cloudFoundryOptions.ClientSecret; - } - - openIdOptions.AuthenticationMethod = cloudFoundryOptions.AuthenticationMethod; - openIdOptions.BackchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(cloudFoundryOptions.ValidateCertificates); - openIdOptions.CallbackPath = cloudFoundryOptions.CallbackPath; - openIdOptions.ClaimsIssuer = cloudFoundryOptions.ClaimsIssuer; - openIdOptions.ResponseType = cloudFoundryOptions.ResponseType; - openIdOptions.SaveTokens = cloudFoundryOptions.SaveTokens; - openIdOptions.SignInScheme = cloudFoundryOptions.SignInScheme; - - // remove profile scope - openIdOptions.Scope.Clear(); - openIdOptions.Scope.Add("openid"); - - // add other scopes - if (!string.IsNullOrEmpty(cloudFoundryOptions.AdditionalScopes)) - { - foreach (string s in cloudFoundryOptions.AdditionalScopes.Split(' ')) - { - if (!openIdOptions.Scope.Contains(s)) - { - openIdOptions.Scope.Add(s); - } - } - } - - openIdOptions.TokenValidationParameters = CloudFoundryHelper.GetTokenValidationParameters(cloudFoundryOptions.TokenValidationParameters, - openIdOptions.Authority + CloudFoundryDefaults.JwtTokenUri, openIdOptions.BackchannelHttpHandler, cloudFoundryOptions.ValidateCertificates, - cloudFoundryOptions.BaseOptions(openIdOptions.ClientId)); - - // the ClaimsIdentity is built off the id_token, but scopes are returned in the access_token. Copy them as claims - openIdOptions.Events.OnTokenValidated = MapScopesToClaims; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectOptions.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectOptions.cs deleted file mode 100644 index f9283a3739..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryOpenIdConnectOptions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.Http; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryOpenIdConnectOptions : OpenIdConnectOptions -{ - /// - /// Gets or sets additional scopes beyond openid and profile when requesting tokens. - /// - public string AdditionalScopes { get; set; } - - /// - /// Gets or sets a value indicating whether to validate auth server certificate. - /// - public bool ValidateCertificates { get; set; } = true; - - /// - /// Gets or sets the timeout (in ms) for calls to the auth server. - /// - public int Timeout { get; set; } = 100000; - //// https://leastprivilege.com/2017/11/15/missing-claims-in-the-asp-net-core-2-openid-connect-handler/ - - public CloudFoundryOpenIdConnectOptions() - { - AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet; - Authority = $"https://{CloudFoundryDefaults.OAuthServiceUrl}"; - CallbackPath = new PathString(CloudFoundryDefaults.CallbackPath); - ClaimsIssuer = CloudFoundryDefaults.AuthenticationScheme; - ClientId = CloudFoundryDefaults.ClientId; - ClientSecret = CloudFoundryDefaults.ClientSecret; - ResponseType = OpenIdConnectResponseType.Code; - SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - - // http://irisclasson.com/2018/09/18/asp-net-core-openidconnect-why-is-the-claimsprincipal-name-null/ - TokenValidationParameters.NameClaimType = "user_name"; - TokenValidationParameters.ValidateAudience = true; - TokenValidationParameters.ValidateIssuer = true; - TokenValidationParameters.ValidateLifetime = true; - } - - internal AuthServerOptions BaseOptions(string updatedClientId) - { - return new AuthServerOptions - { - ClientId = updatedClientId ?? ClientId, - ClientSecret = ClientSecret, - ValidateCertificates = ValidateCertificates, - AuthorizationUrl = Authority, - ClientTimeout = Timeout - }; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryScopeClaimAction.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryScopeClaimAction.cs deleted file mode 100644 index b8b0ad51eb..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryScopeClaimAction.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using System.Text.Json; -using Microsoft.AspNetCore.Authentication.OAuth.Claims; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryScopeClaimAction : ClaimAction -{ - public CloudFoundryScopeClaimAction(string claimType, string valueType) - : base(claimType, valueType) - { - } - - public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) - { - List scopes = CloudFoundryHelper.GetScopes(userData); - - if (scopes != null) - { - foreach (string s in scopes) - { - identity.AddClaim(new Claim(ClaimType, s, ValueType, issuer)); - } - } - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenKeyResolver.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenKeyResolver.cs deleted file mode 100644 index 8183121265..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenKeyResolver.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Collections.Concurrent; -using System.Net.Http.Headers; -using Microsoft.IdentityModel.Tokens; -using Steeltoe.Common; -using Steeltoe.Common.Http; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryTokenKeyResolver -{ - private readonly string _jwtKeyUrl; - private readonly HttpMessageHandler _httpHandler; - private readonly bool _validateCertificates; - private readonly int _httpClientTimeoutMillis; - private HttpClient _httpClient; - - internal static ConcurrentDictionary Resolved { get; set; } = new(); - - public CloudFoundryTokenKeyResolver(string jwtKeyUrl, HttpMessageHandler httpHandler, bool validateCertificates) - { - ArgumentGuard.NotNullOrEmpty(jwtKeyUrl); - - _jwtKeyUrl = jwtKeyUrl; - _httpHandler = httpHandler; - _validateCertificates = validateCertificates; - _httpClientTimeoutMillis = 100000; - } - - public CloudFoundryTokenKeyResolver(string jwtKeyUrl, HttpMessageHandler httpHandler, bool validateCertificates, int httpClientTimeoutMs) - { - ArgumentGuard.NotNullOrEmpty(jwtKeyUrl); - - _jwtKeyUrl = jwtKeyUrl; - _httpHandler = httpHandler; - _validateCertificates = validateCertificates; - _httpClientTimeoutMillis = httpClientTimeoutMs; - } - - public virtual IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string kid, - TokenValidationParameters validationParameters) - { - if (Resolved.TryGetValue(kid, out SecurityKey resolved)) - { - return new List - { - resolved - }; - } - - JsonWebKeySet keySet = FetchKeySetAsync().GetAwaiter().GetResult(); - - if (keySet != null) - { - foreach (JsonWebKey key in keySet.Keys) - { - Resolved[key.Kid] = key; - } - } - - if (Resolved.TryGetValue(kid, out resolved)) - { - return new List - { - resolved - }; - } - - return null; - } - - public virtual async Task FetchKeySetAsync() - { - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(_jwtKeyUrl)); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - - HttpClient client = GetHttpClient(); - - HttpResponseMessage response = await client.SendAsync(requestMessage); - - if (response.IsSuccessStatusCode) - { - string result = await response.Content.ReadAsStringAsync(); - return GetJsonWebKeySet(result); - } - - return null; - } - - public virtual JsonWebKeySet GetJsonWebKeySet(string json) - { - return JsonWebKeySet.Create(json); - } - - public virtual HttpClient GetHttpClient() - { - _httpClient ??= _httpHandler is null - ? HttpClientHelper.GetHttpClient(_validateCertificates, _httpClientTimeoutMillis) - : HttpClientHelper.GetHttpClient(_httpHandler); - - return _httpClient; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenValidator.cs b/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenValidator.cs deleted file mode 100644 index 5be2bf0439..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/CloudFoundryTokenValidator.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class CloudFoundryTokenValidator -{ - private readonly AuthServerOptions _options; - - public CloudFoundryTokenValidator(AuthServerOptions options = null) - { - _options = options ?? new AuthServerOptions(); - } - - /// - /// Validate that a token was issued by UAA. - /// - /// - /// Token issuer. - /// - /// - /// [Not used] Token to validate. - /// - /// - /// [Not used]. - /// - /// - /// The issuer, if valid, else . - /// - public virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (issuer.Contains("uaa", StringComparison.Ordinal)) - { - return issuer; - } - - return null; - } - - /// - /// Validate that a token was meant for approved audience(s). - /// - /// - /// The list of audiences the token is valid for. - /// - /// - /// [Not used] The token being validated. - /// - /// - /// [Not used]. - /// - /// - /// if the audience matches the client id or any value in AdditionalAudiences. - /// - public virtual bool ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - foreach (string audience in audiences) - { - if (audience == _options.ClientId) - { - return true; - } - - if (_options.AdditionalAudiences != null) - { - bool found = Array.Exists(_options.AdditionalAudiences, x => x == audience); - - if (found) - { - return true; - } - } - } - - return false; - } - - /// - /// This method validates scopes provided in configuration, to perform scope based Authorization. - /// - /// - /// JSON Web token. - /// - /// - /// true if scopes validated. - /// - protected virtual bool ValidateScopes(JwtSecurityToken validJwt) - { - if (_options.RequiredScopes == null || !_options.RequiredScopes.Any()) - { - return true; // no check - } - - if (!validJwt.Claims.Any(x => x.Type == "scope" || x.Type == "authorities")) - { - return false; // no scopes at all - } - - foreach (Claim claim in validJwt.Claims) - { - if (claim.Type == "scope" || (claim.Type == "authorities" && Array.Exists(_options.RequiredScopes, x => x == claim.Value))) - { - return true; - } - } - - return false; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/ConfigurationBuilderExtensions.cs b/src/Security/src/Authentication.CloudFoundry/ConfigurationBuilderExtensions.cs deleted file mode 100644 index b50a752c7a..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/ConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Configuration; -using Steeltoe.Common; -using Steeltoe.Common.Security; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class ConfigurationBuilderExtensions -{ - /// - /// Adds PEM files representing application identity to application configuration. When running outside Pivotal Platform, will create certificates - /// resembling those found on the platform. - /// - /// - /// Your . - /// - /// - /// (Optional) A GUID representing an organization, for use with authorization - /// policy. - /// - /// - /// (Optional) A GUID representing a space, for use with authorization policy. - /// - public static IConfigurationBuilder AddCloudFoundryContainerIdentity(this IConfigurationBuilder builder, string orgId = null, string spaceId = null) - { - if (!Platform.IsCloudFoundry) - { - Guid orgGuid = orgId != null ? new Guid(orgId) : Guid.NewGuid(); - Guid spaceGuid = spaceId != null ? new Guid(spaceId) : Guid.NewGuid(); - - var task = new LocalCertificateWriter(); - task.Write(orgGuid, spaceGuid); - - Environment.SetEnvironmentVariable("CF_INSTANCE_CERT", - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem")); - - Environment.SetEnvironmentVariable("CF_INSTANCE_KEY", - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem")); - } - - string certFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); - string keyFile = Environment.GetEnvironmentVariable("CF_INSTANCE_KEY"); - - if (certFile == null || keyFile == null) - { - return builder; - } - - return builder.AddCertificate("ContainerIdentity", certFile, keyFile); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/MutualTlsAuthenticationOptionsPostConfigurer.cs b/src/Security/src/Authentication.CloudFoundry/MutualTlsAuthenticationOptionsPostConfigurer.cs deleted file mode 100644 index 817482a225..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/MutualTlsAuthenticationOptionsPostConfigurer.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public sealed class MutualTlsAuthenticationOptionsPostConfigurer(ILoggerFactory loggerFactory) : IPostConfigureOptions -{ - private readonly ILogger _cloudFoundryLogger = loggerFactory?.CreateLogger(); - - public void PostConfigure(string name, CertificateAuthenticationOptions options) - { - options.Events = new CertificateAuthenticationEvents - { - OnCertificateValidated = context => - { - var claims = new List(context.Principal.Claims); - - if (CloudFoundryInstanceCertificate.TryParse(context.ClientCertificate, out CloudFoundryInstanceCertificate cfCert, _cloudFoundryLogger)) - { - claims.Add(new Claim(ApplicationClaimTypes.CloudFoundryInstanceId, cfCert.InstanceId, ClaimValueTypes.String, - context.Options.ClaimsIssuer)); - - claims.Add(new Claim(ApplicationClaimTypes.CloudFoundryAppId, cfCert.AppId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); - claims.Add(new Claim(ApplicationClaimTypes.CloudFoundrySpaceId, cfCert.SpaceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); - claims.Add(new Claim(ApplicationClaimTypes.CloudFoundryOrgId, cfCert.OrgId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); - } - - var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); - context.Principal = new ClaimsPrincipal(identity); - context.Success(); - return Task.CompletedTask; - } - }; - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/OpenIdTokenResponse.cs b/src/Security/src/Authentication.CloudFoundry/OpenIdTokenResponse.cs deleted file mode 100644 index 4214bb8e29..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/OpenIdTokenResponse.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json.Serialization; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class OpenIdTokenResponse -{ - [JsonPropertyName("id_token")] - public string IdentityToken { get; set; } - - [JsonPropertyName("access_token")] - public string AccessToken { get; set; } - - [JsonPropertyName("refresh_token")] - public string RefreshToken { get; set; } - - [JsonPropertyName("token_type")] - public string TokenType { get; set; } - - [JsonPropertyName("expires_in")] - public int ExpiresIn { get; set; } - - [JsonPropertyName("scope")] - public string Scope { get; set; } -} diff --git a/src/Security/src/Authentication.CloudFoundry/Readme.md b/src/Security/src/Authentication.CloudFoundry/Readme.md deleted file mode 100644 index b9de3181bc..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/Readme.md +++ /dev/null @@ -1,12 +0,0 @@ -# ASP.NET Core Security Provider for CloudFoundry - -This project contains a [ASP.NET Core External Security Providers](https://github.com/aspnet/Security) for CloudFoundry. - -The providers simplify using CloudFoundry OAuth2 security services (e.g. [UAA Server](https://github.com/cloudfoundry/uaa) and/or [Pivotal Single Signon](https://docs.pivotal.io/p-identity/)) for Authentication and Authorization in an ASP.NET Core application. - -There are two providers to choose from in this package: - -* A provider that enables OAuth2 Single Signon with CloudFoundry Security services. Have a look at the Steeltoe [CloudFoundrySingleSignon](https://github.com/SteeltoeOSS/Samples/tree/dev/Security/src/CloudFoundrySingleSignon) for a sample app. -* A provider that enables using JWT tokens issued by CloudFoundry Security services for securing REST endpoints. Have a look at the Steeltoe [CloudFoundryJwtAuthentication](https://github.com/SteeltoeOSS/Samples/tree/dev/Security/src/CloudFoundryJwtAuthentication) for a sample app. - -For more information on how to use this component see the online [Steeltoe documentation](https://steeltoe.io/). \ No newline at end of file diff --git a/src/Security/src/Authentication.CloudFoundry/ServiceCollectionExtensions.cs b/src/Security/src/Authentication.CloudFoundry/ServiceCollectionExtensions.cs deleted file mode 100644 index a4516afb1c..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using Steeltoe.Common; -using Steeltoe.Common.Security; -using Steeltoe.Security.Authentication.Mtls; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public static class ServiceCollectionExtensions -{ - /// - /// Adds options and services to use Cloud Foundry container identity certificates. - /// - /// - /// Service collection. - /// - /// - /// The root to monitor for changes. - /// - /// - /// Provides access to the file system. - /// - public static void AddCloudFoundryContainerIdentity(this IServiceCollection services, IConfiguration configuration, IFileProvider fileProvider) - { - ArgumentGuard.NotNull(services); - - services.ConfigureCertificateOptions(configuration, fileProvider); - services.AddSingleton, MutualTlsAuthenticationOptionsPostConfigurer>(); - services.AddSingleton(); - services.AddCertificateForwarding(opt => opt.CertificateHeader = "X-Forwarded-Client-Cert"); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// The root to monitor for changes. - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, IConfiguration configuration) - { - AddCloudFoundryCertificateAuth(services, configuration, CertificateAuthenticationDefaults.AuthenticationScheme); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// The root to monitor for changes. - /// - /// - /// Used to configure the . - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, IConfiguration configuration, - Action configurer) - { - AddCloudFoundryCertificateAuth(services, configuration, CertificateAuthenticationDefaults.AuthenticationScheme, configurer, null); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// The root to monitor for changes. - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, IConfiguration configuration, string authenticationScheme) - { - AddCloudFoundryCertificateAuth(services, configuration, authenticationScheme, null, null); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// The root to monitor for changes. - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - /// - /// Used to configure the . - /// - /// - /// Provides access to the file system. - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, IConfiguration configuration, string authenticationScheme, - Action configurer, IFileProvider fileProvider) - { - ArgumentGuard.NotNull(services); - - services.AddCloudFoundryContainerIdentity(configuration, fileProvider); - - services.AddAuthentication(authenticationScheme).AddCloudFoundryIdentityCertificate(authenticationScheme, configurer); - - services.AddAuthorization(options => - { - options.AddPolicy(CloudFoundryDefaults.SameOrganizationAuthorizationPolicy, builder => builder.SameOrg()); - options.AddPolicy(CloudFoundryDefaults.SameSpaceAuthorizationPolicy, builder => builder.SameSpace()); - }); - } -} diff --git a/src/Security/src/Authentication.CloudFoundry/Steeltoe.Security.Authentication.CloudFoundry.csproj b/src/Security/src/Authentication.CloudFoundry/Steeltoe.Security.Authentication.CloudFoundry.csproj deleted file mode 100644 index 3bfc3b1770..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/Steeltoe.Security.Authentication.CloudFoundry.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net8.0;net6.0 - Base Security Provider for CloudFoundry - CloudFoundry;security;oauth2;sso;openid - true - - - - - - - - - - - - - - - - diff --git a/src/Security/src/Authentication.CloudFoundry/TokenExchanger.cs b/src/Security/src/Authentication.CloudFoundry/TokenExchanger.cs deleted file mode 100644 index 05788ff38b..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/TokenExchanger.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text.Json; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Steeltoe.Common.Http; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class TokenExchanger -{ - private readonly ILogger _logger; - private readonly AuthServerOptions _options; - private readonly HttpClient _httpClient; - - public TokenExchanger(AuthServerOptions options, HttpClient httpClient = null, ILogger logger = null) - { - _options = options; - _httpClient = httpClient ?? HttpClientHelper.GetHttpClient(options.ValidateCertificates, options.ClientTimeout); - _logger = logger; - } - - /// - /// Perform the HTTP call to exchange an authorization code for a token. - /// - /// - /// The auth code to exchange. - /// - /// - /// The full address of the token endpoint. - /// - /// - /// Your CancellationToken. - /// - /// - /// The response from the remote server. - /// - public async Task ExchangeCodeForTokenAsync(string code, string targetUrl, CancellationToken cancellationToken) - { - List> requestParameters = AuthCodeTokenRequestParameters(code); - HttpRequestMessage requestMessage = GetTokenRequestMessage(requestParameters, targetUrl); - _logger?.LogDebug("Exchanging code {Code} for token at {AccessTokenUrl}", code, targetUrl); - - return await _httpClient.SendAsync(requestMessage, cancellationToken); - } - - /// - /// Passes an authorization code to OAuth server, maps server's mapped to . - /// - /// - /// Auth code received after user logs in at remote server. - /// - /// - /// The user's ClaimsIdentity. - /// - public async Task ExchangeAuthCodeForClaimsIdentityAsync(string code) - { - HttpResponseMessage response = await ExchangeCodeForTokenAsync(code, _options.AuthorizationUrl, default); - - if (response.IsSuccessStatusCode) - { - _logger?.LogTrace("Successfully exchanged auth code for a token"); - var tokens = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); -#if DEBUG - _logger?.LogTrace("Identity token received: {IdentityToken}", tokens.IdentityToken); - _logger?.LogTrace("Access token received: {AccessToken}", tokens.AccessToken); -#endif - var securityToken = new JwtSecurityToken(tokens.IdentityToken); - - return BuildIdentityWithClaims(securityToken.Claims, tokens.Scope, tokens.AccessToken); - } - - _logger?.LogError("Failed call to exchange code for token: {Status}", response.StatusCode); - _logger?.LogWarning(response.ReasonPhrase); - _logger?.LogInformation(await response.Content.ReadAsStringAsync()); - - return null; - } - - /// - /// Get an access token using client_credentials grant. - /// - /// - /// full address of the token endpoint at the auth server. - /// - /// - /// HttpResponse from the auth server. - /// - public async Task GetAccessTokenWithClientCredentialsAsync(string targetUrl) - { - HttpRequestMessage requestMessage = GetTokenRequestMessage(ClientCredentialsTokenRequestParameters(), targetUrl); - - return await _httpClient.SendAsync(requestMessage); - } - - /// - /// Builds an that will POST with the params to the target. - /// - /// - /// Body of the request to send. - /// - /// - /// Location to send the request. - /// - /// - /// A request primed for receiving a token. - /// - internal HttpRequestMessage GetTokenRequestMessage(List> parameters, string targetUrl) - { - var requestContent = new FormUrlEncodedContent(parameters); - - var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(targetUrl, UriKind.RelativeOrAbsolute)); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - requestMessage.Content = requestContent; - return requestMessage; - } - - /// - /// Gets request parameters for authorization_code Token request. - /// - /// - /// Authorization code to be exchanged for token. - /// - /// - /// Content for HTTP request. - /// - internal List> AuthCodeTokenRequestParameters(string code) - { - List> parameters = CommonTokenRequestParams(); - parameters.Add(new KeyValuePair(CloudFoundryDefaults.ParamsRedirectUri, _options.CallbackUrl)); - parameters.Add(new KeyValuePair(CloudFoundryDefaults.ParamsCode, code)); - parameters.Add(new KeyValuePair(CloudFoundryDefaults.ParamsGrantType, OpenIdConnectGrantTypes.AuthorizationCode)); - - return parameters; - } - - internal List> ClientCredentialsTokenRequestParameters() - { - List> parameters = CommonTokenRequestParams(); - parameters.Add(new KeyValuePair(CloudFoundryDefaults.ParamsGrantType, OpenIdConnectGrantTypes.ClientCredentials)); - - return parameters; - } - - internal List> CommonTokenRequestParams() - { - string scopes = $"openid {_options.AdditionalTokenScopes}"; - - if (_options.RequiredScopes != null) - { - scopes = $"{scopes.Trim()} {string.Join(" ", _options.RequiredScopes)}"; - } - - return new List> - { - new(CloudFoundryDefaults.ParamsClientId, _options.ClientId), - new(CloudFoundryDefaults.ParamsClientSecret, _options.ClientSecret), - new(CloudFoundryDefaults.ParamsScope, scopes) - }; - } - - internal ClaimsIdentity BuildIdentityWithClaims(IEnumerable claims, string tokenScopes, string accessToken) - { - _logger?.LogTrace("Building identity with claims from token"); -#if DEBUG - foreach (Claim claim in claims) - { - _logger?.LogTrace("{Type} : {Value}", claim.Type, claim.Value); - } -#endif - string[] typedClaimNames = - { - "user_name", - "email", - "user_id" - }; - - IEnumerable typedClaims = claims.Where(t => !typedClaimNames.Contains(t.Type, StringComparer.OrdinalIgnoreCase)); - - // raw dump of claims, exclude mapped typedClaimNames - var claimsId = new ClaimsIdentity(typedClaims, _options.SignInAsAuthenticationType); - - string userName = claims.First(c => c.Type == "user_name").Value; - string email = claims.First(c => c.Type == "email").Value; - string userId = claims.First(c => c.Type == "user_id").Value; - - claimsId.AddClaims(new List - { - new(ClaimTypes.NameIdentifier, userId), - new(ClaimTypes.Name, userName), - new(ClaimTypes.Email, email) - }); - - _logger?.LogTrace("Adding scope claims from token"); - IEnumerable additionalScopes = tokenScopes.Split(' ').Where(s => s != "openid"); - - foreach (string scope in additionalScopes) - { - claimsId.AddClaim(new Claim("scope", scope)); - } - - claimsId.AddClaim(new Claim(ClaimTypes.Authentication, accessToken)); - _logger?.LogTrace("Finished building identity with claims from token"); - - return claimsId; - } -} diff --git a/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs new file mode 100644 index 0000000000..4007acba9a --- /dev/null +++ b/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.JwtBearer; + +public static class JwtBearerServiceCollectionExtensions +{ + /// + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// + /// The to add services to. + public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services) + { + return ConfigureJwtBearerForCloudFoundry(services, null); + } + + /// + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// + /// The to add services to. + /// Configure the HttpClient used to interact with the identity server. + public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) + { + ArgumentGuard.NotNull(services); + + services.AddSteeltoeSecurityHttpClient(configureHttpClient); + services.AddSingleton, PostConfigureJwtBearerOptions>(); + return services; + } +} diff --git a/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs b/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs new file mode 100644 index 0000000000..18898ca8c1 --- /dev/null +++ b/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.JwtBearer; + +internal sealed class PostConfigureJwtBearerOptions(IConfiguration configuration, IHttpClientFactory httpClientFactory) : IPostConfigureOptions +{ + private const string BearerConfigurationKeyPrefix = "Authentication:Schemes:Bearer"; + + public void PostConfigure(string? name, JwtBearerOptions options) + { + var clientId = configuration.GetValue($"{BearerConfigurationKeyPrefix}:ClientId"); + + if (!string.IsNullOrEmpty(clientId) && options.TokenValidationParameters.ValidAudiences?.Contains(clientId) != true) + { + var audiences = new List(options.TokenValidationParameters.ValidAudiences ?? []) { clientId }; + options.TokenValidationParameters.ValidAudiences = audiences; + } + + if (options.Authority?.Equals(SteeltoeSecurityDefaults.LocalUAAPath, StringComparison.InvariantCultureIgnoreCase) == true) + { + options.RequireHttpsMetadata = false; + options.TokenValidationParameters.ValidIssuer = $"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"; + } + else if (options.Authority != null) + { + options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + } + + if (options.Authority != null) + { + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel ?? httpClientFactory.CreateClient("SteeltoeSecurity")); + options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; + } + } +} diff --git a/src/Security/src/Authentication.CloudFoundry/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.Jwt/Properties/AssemblyInfo.cs similarity index 93% rename from src/Security/src/Authentication.CloudFoundry/Properties/AssemblyInfo.cs rename to src/Security/src/Authentication.Jwt/Properties/AssemblyInfo.cs index 1ba60e912c..5dbdb9307b 100644 --- a/src/Security/src/Authentication.CloudFoundry/Properties/AssemblyInfo.cs +++ b/src/Security/src/Authentication.Jwt/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.JwtBearer.Test")] diff --git a/src/Security/src/Authentication.Jwt/PublicAPI.Shipped.txt b/src/Security/src/Authentication.Jwt/PublicAPI.Shipped.txt new file mode 100644 index 0000000000..ab058de62d --- /dev/null +++ b/src/Security/src/Authentication.Jwt/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Security/src/Authentication.Jwt/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.Jwt/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..4e3942014e --- /dev/null +++ b/src/Security/src/Authentication.Jwt/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +#nullable enable +static Steeltoe.Security.Authentication.JwtBearer.JwtBearerServiceCollectionExtensions.ConfigureJwtBearerForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Security.Authentication.JwtBearer.JwtBearerServiceCollectionExtensions.ConfigureJwtBearerForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action? configureHttpClient) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Security.Authentication.JwtBearer.JwtBearerServiceCollectionExtensions diff --git a/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj new file mode 100644 index 0000000000..a23676d980 --- /dev/null +++ b/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -0,0 +1,21 @@ + + + net8.0 + Library for using JwtBearer with Tanzu + CloudFoundry;uaa;security;jwt;bearer + true + enable + + + + + + + + + + + + + + diff --git a/src/Security/src/Authentication.Mtls/CertificateAuthenticationBuilderExtensions.cs b/src/Security/src/Authentication.Mtls/CertificateAuthenticationBuilderExtensions.cs deleted file mode 100644 index b9da580b3a..0000000000 --- a/src/Security/src/Authentication.Mtls/CertificateAuthenticationBuilderExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Certificate; - -namespace Steeltoe.Security.Authentication.Mtls; - -public static class CertificateAuthenticationBuilderExtensions -{ - /// - /// Adds certificate authentication. - /// - /// - /// The . - /// - public static AuthenticationBuilder AddMutualTls(this AuthenticationBuilder builder) - { - return builder.AddMutualTls(CertificateAuthenticationDefaults.AuthenticationScheme); - } - - /// - /// Adds certificate authentication. - /// - /// - /// The . - /// - /// - /// Scheme identifier. - /// - public static AuthenticationBuilder AddMutualTls(this AuthenticationBuilder builder, string authenticationScheme) - { - return builder.AddMutualTls(authenticationScheme, null); - } - - /// - /// Adds certificate authentication. - /// - /// - /// The . - /// - /// - /// Additional options configuration. - /// - public static AuthenticationBuilder AddMutualTls(this AuthenticationBuilder builder, Action configureOptions) - { - return builder.AddMutualTls(CertificateAuthenticationDefaults.AuthenticationScheme, configureOptions); - } - - /// - /// Adds certificate authentication. - /// - /// - /// The . - /// - /// - /// Scheme identifier. - /// - /// - /// Additional options configuration. - /// - public static AuthenticationBuilder AddMutualTls(this AuthenticationBuilder builder, string authenticationScheme, - Action configureOptions) - { - return builder.AddScheme(authenticationScheme, configureOptions); - } -} diff --git a/src/Security/src/Authentication.Mtls/LoggingExtensions.cs b/src/Security/src/Authentication.Mtls/LoggingExtensions.cs deleted file mode 100644 index 031fe585a8..0000000000 --- a/src/Security/src/Authentication.Mtls/LoggingExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.Extensions.Logging; - -namespace Steeltoe.Security.Authentication.Mtls; - -internal static class LoggingExtensions -{ - private static readonly Action OnNoCertificate = LoggerMessage.Define(eventId: new EventId(0, "NoCertificate"), - logLevel: LogLevel.Debug, formatString: "No client certificate found."); - - private static readonly Action OnCertificateRejected = LoggerMessage.Define( - eventId: new EventId(1, "CertificateRejected"), logLevel: LogLevel.Warning, - formatString: "{CertificateType} certificate rejected, subject was {Subject}."); - - private static readonly Action OnCertificateFailedValidation = LoggerMessage.Define( - eventId: new EventId(2, "CertificateFailedValidation"), logLevel: LogLevel.Warning, - formatString: $"Certificate validation failed, subject was {{Subject}}.{Environment.NewLine}{{ChainErrors}}"); - - public static void NoCertificate(this ILogger logger) - { - OnNoCertificate(logger, null); - } - - public static void CertificateRejected(this ILogger logger, string certificateType, string subject) - { - OnCertificateRejected(logger, certificateType, subject, null); - } - - public static void CertificateFailedValidation(this ILogger logger, string subject, IEnumerable chainedErrors) - { - OnCertificateFailedValidation(logger, subject, string.Join(Environment.NewLine, chainedErrors), null); - } -} diff --git a/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationHandler.cs b/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationHandler.cs deleted file mode 100644 index 86b5617339..0000000000 --- a/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationHandler.cs +++ /dev/null @@ -1,292 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -// This file is a modified version of https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs - -using System.Security.Claims; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Common.Options; - -namespace Steeltoe.Security.Authentication.Mtls; - -/// -/// This class is based on , but allows side-loading a root CA. -/// -internal sealed class MutualTlsAuthenticationHandler : AuthenticationHandler -{ - private static readonly Oid ClientCertificateOid = new("1.3.6.1.5.5.7.3.2"); - private readonly IOptionsMonitor _certificateOptionsMonitor; - - /// - /// Gets the handler calls methods on the events which give the application control at certain points where processing is occurring. If it is not - /// provided a default instance is supplied which does nothing when the methods are called. - /// - private new CertificateAuthenticationEvents Events => (CertificateAuthenticationEvents)base.Events; - -#if NET6_0 - public MutualTlsAuthenticationHandler(IOptionsMonitor options, IOptionsMonitor certificateOptionsMonitor, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) - : base(options, logger, encoder, clock) -#else - public MutualTlsAuthenticationHandler(IOptionsMonitor options, - IOptionsMonitor certificateOptionsMonitor, ILoggerFactory logger, UrlEncoder encoder) - : base(options, logger, encoder) -#endif - { - _certificateOptionsMonitor = certificateOptionsMonitor; - } - - /// - /// Creates a new instance of the events instance. - /// - /// - /// A new instance of the events instance. - /// - protected override Task CreateEventsAsync() - { - return Task.FromResult(new CertificateAuthenticationEvents()); - } - - protected override async Task HandleAuthenticateAsync() - { - // You only get client certificates over HTTPS - if (!Context.Request.IsHttps) - { - return AuthenticateResult.NoResult(); - } - - try - { - X509Certificate2 clientCertificate = await Context.Connection.GetClientCertificateAsync(); - - // This should never be the case, as cert authentication happens long before ASP.NET kicks in. - if (clientCertificate == null) - { - Logger.NoCertificate(); - return AuthenticateResult.NoResult(); - } - - // If we have a self-signed cert, and they're not allowed, exit early and not bother with - // any other validations. - if (clientCertificate.IsSelfSigned() && !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.SelfSigned)) - { - Logger.LogWarning("Self signed certificate rejected, subject was {0}", clientCertificate.Subject); - return AuthenticateResult.Fail("Options do not allow self signed certificates."); - } - - // If we have a chained cert, and they're not allowed, exit early and not bother with - // any other validations. - if (!clientCertificate.IsSelfSigned() && !Options.AllowedCertificateTypes.HasFlag(CertificateTypes.Chained)) - { - Logger.CertificateRejected("Chained", clientCertificate.Subject); - return AuthenticateResult.Fail("Options do not allow chained certificates."); - } - - X509ChainPolicy chainPolicy = BuildChainPolicy(clientCertificate); - - var chain = new X509Chain - { - ChainPolicy = chainPolicy - }; - - //// - bool certificateIsValid = IsChainValid(chain, clientCertificate); - //// - - if (!certificateIsValid) - { - var chainErrors = new List(); - - foreach (X509ChainStatus validationFailure in chain.ChainStatus) - { - chainErrors.Add($"{validationFailure.Status} {validationFailure.StatusInformation}"); - } - - Logger.CertificateFailedValidation(clientCertificate.Subject, chainErrors); - return AuthenticateResult.Fail("Client certificate failed validation."); - } - - var certificateValidatedContext = new CertificateValidatedContext(Context, Scheme, Options) - { - ClientCertificate = clientCertificate, - Principal = CreatePrincipal(clientCertificate) - }; - - await Events.CertificateValidated(certificateValidatedContext); - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - // https://github.com/dotnet/aspnetcore/issues/45594 - if (certificateValidatedContext.Result != null) - { - return certificateValidatedContext.Result; - } - - // ReSharper disable once HeuristicUnreachableCode - // https://github.com/dotnet/aspnetcore/issues/45594 - certificateValidatedContext.Success(); - return certificateValidatedContext.Result; - } - catch (Exception ex) - { - var authenticationFailedContext = new CertificateAuthenticationFailedContext(Context, Scheme, Options) - { - Exception = ex - }; - - await Events.AuthenticationFailed(authenticationFailedContext); - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - // https://github.com/dotnet/aspnetcore/issues/45594 - if (authenticationFailedContext.Result != null) - { - return authenticationFailedContext.Result; - } - - throw; - } - } - - protected override Task HandleChallengeAsync(AuthenticationProperties properties) - { - if (Context.User.Claims.Any()) - { - Context.Response.StatusCode = 401; - return Task.CompletedTask; - } - - // Certificate authentication takes place at the connection level. We can't prompt once we're in - // user code, so the best thing to do is Forbid, not Challenge. - return HandleForbiddenAsync(properties); - } - - /// - /// Call chain.Build first, if !isValid then compares root with issuer chain known to this app. - /// - /// - /// Certificate chain to validate against. - /// - /// - /// Certificate to validate. - /// - /// - /// Indication of chain validity. - /// - private bool IsChainValid(X509Chain chain, X509Certificate2 certificate) - { - bool isValid = chain.Build(certificate); - - // allow root cert to be side loaded without installing into X509Store Root store - if (!isValid && Array.TrueForAll(chain.ChainStatus, - x => x.Status is X509ChainStatusFlags.UntrustedRoot or X509ChainStatusFlags.PartialChain or X509ChainStatusFlags.OfflineRevocation or - X509ChainStatusFlags.RevocationStatusUnknown)) - { - Logger.LogInformation("Certificate not valid by standard rules, trying custom validation"); - - isValid = _certificateOptionsMonitor.Get("ContainerIdentity").IssuerChain - .Intersect(ToGenericEnumerable(chain.ChainElements).Select(c => c.Certificate)).Any(); - } - - return isValid; - } - - private static IEnumerable ToGenericEnumerable(X509ChainElementCollection collection) - { - return collection; - } - - private X509ChainPolicy BuildChainPolicy(X509Certificate2 certificate) - { - // Now build the chain validation options. - X509RevocationFlag revocationFlag = Options.RevocationFlag; - X509RevocationMode revocationMode = Options.RevocationMode; - - if (certificate.IsSelfSigned()) - { - // Turn off chain validation, because we have a self signed certificate. - revocationFlag = X509RevocationFlag.EntireChain; - revocationMode = X509RevocationMode.NoCheck; - } - - var chainPolicy = new X509ChainPolicy - { - RevocationFlag = revocationFlag, - RevocationMode = revocationMode - }; - - //// - foreach (X509Certificate2 chainCert in _certificateOptionsMonitor.Get("ContainerIdentity").IssuerChain) - { - chainPolicy.ExtraStore.Add(chainCert); - } - //// - - if (Options.ValidateCertificateUse) - { - chainPolicy.ApplicationPolicy.Add(ClientCertificateOid); - } - - if (certificate.IsSelfSigned()) - { - chainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority; - chainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreEndRevocationUnknown; - chainPolicy.ExtraStore.Add(certificate); - } - - if (!Options.ValidateValidityPeriod) - { - chainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreNotTimeValid; - } - - return chainPolicy; - } - - private ClaimsPrincipal CreatePrincipal(X509Certificate2 certificate) - { - var claims = new List(); - - string issuer = certificate.Issuer; - claims.Add(new Claim("issuer", issuer, ClaimValueTypes.String, Options.ClaimsIssuer)); - - string thumbprint = certificate.Thumbprint; - claims.Add(new Claim(ClaimTypes.Thumbprint, thumbprint, ClaimValueTypes.Base64Binary, Options.ClaimsIssuer)); - - string value = certificate.SubjectName.Name; - - if (!string.IsNullOrWhiteSpace(value)) - { - claims.Add(new Claim(ClaimTypes.X500DistinguishedName, value, ClaimValueTypes.String, Options.ClaimsIssuer)); - } - - value = certificate.SerialNumber; - - if (!string.IsNullOrWhiteSpace(value)) - { - claims.Add(new Claim(ClaimTypes.SerialNumber, value, ClaimValueTypes.String, Options.ClaimsIssuer)); - } - - MapClaimIfFound(certificate, X509NameType.DnsName, claims, ClaimTypes.Dns); - MapClaimIfFound(certificate, X509NameType.SimpleName, claims, ClaimTypes.Name); - MapClaimIfFound(certificate, X509NameType.EmailName, claims, ClaimTypes.Email); - MapClaimIfFound(certificate, X509NameType.UpnName, claims, ClaimTypes.Upn); - MapClaimIfFound(certificate, X509NameType.UrlName, claims, ClaimTypes.Uri); - - var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); - return new ClaimsPrincipal(identity); - } - - private void MapClaimIfFound(X509Certificate2 certificate, X509NameType claimSource, List claims, string claimDestination) - { - string value = certificate.GetNameInfo(claimSource, false); - - if (!string.IsNullOrWhiteSpace(value)) - { - claims.Add(new Claim(claimDestination, value, ClaimValueTypes.String, Options.ClaimsIssuer)); - } - } -} diff --git a/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationOptions.cs b/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationOptions.cs deleted file mode 100644 index fad774b9f8..0000000000 --- a/src/Security/src/Authentication.Mtls/MutualTlsAuthenticationOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Cryptography.X509Certificates; -using Microsoft.AspNetCore.Authentication.Certificate; - -namespace Steeltoe.Security.Authentication.Mtls; - -public class MutualTlsAuthenticationOptions : CertificateAuthenticationOptions -{ - /// - /// Gets or sets partial or full certificate chain for validation. - /// - public List IssuerChain { get; set; } = new(); -} diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs new file mode 100644 index 0000000000..cdfceec619 --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.OpenIdConnect; + +public static class OpenIdConnectServiceCollectionExtensions +{ + /// + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application Service. + /// + /// The to add services to. + public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services) + { + return ConfigureOpenIdConnectForCloudFoundry(services, null); + } + + /// + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application Service. + /// + /// The to add services to. + /// Configure the HttpClient used to interact with the identity server. + public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) + { + ArgumentGuard.NotNull(services); + + services.AddSteeltoeSecurityHttpClient(configureHttpClient); + services.AddSingleton, PostConfigureOpenIdConnectOptions>(); + return services; + } +} diff --git a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs new file mode 100644 index 0000000000..b5aa481b01 --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Steeltoe.Common; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.OpenIdConnect; + +// ILogger logger +/// +internal sealed class PostConfigureOpenIdConnectOptions(IHttpClientFactory httpClientFactory) : IPostConfigureOptions +{ + /// + public void PostConfigure(string? name, OpenIdConnectOptions options) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentGuard.NotNull(options); + + options.Events.OnTokenValidated = MapScopesToClaims; + + options.ResponseType = OpenIdConnectResponseType.Code; + options.SignInScheme ??= CookieAuthenticationDefaults.AuthenticationScheme; + options.TokenValidationParameters.NameClaimType = "user_name"; + + if (options.Authority?.Contains(SteeltoeSecurityDefaults.LocalUAAPath, StringComparison.InvariantCultureIgnoreCase) == true) + { + options.RequireHttpsMetadata = false; + options.TokenValidationParameters.ValidIssuer = $"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"; + } + else if (options.Authority != null) + { + options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + } + + if (options.Authority != null) + { + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel ?? httpClientFactory.CreateClient("SteeltoeSecurity")); + options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; + } + } + + // The ClaimsIdentity is built off the id_token, but scopes are returned in the access_token. + // Identify scopes not already present as claims and add them to the ClaimsIdentity + private static readonly Func MapScopesToClaims = tokenValidatedContext => + { + if (tokenValidatedContext.Principal?.Identity is not ClaimsIdentity claimsIdentity) + { + return Task.FromResult(1); + } + + string scopes = tokenValidatedContext.TokenEndpointResponse?.Scope ?? string.Empty; + + IEnumerable claimsFromScopes = scopes + .Split(' ') + .Where(scope => !claimsIdentity.Claims.Any(claim => claim.Type == "scope" && claim.Value == scope)) + .Select(claimValue => new Claim("scope", claimValue)); + + claimsIdentity.AddClaims(claimsFromScopes); + + return Task.FromResult(0); + }; +} diff --git a/src/Security/src/Authentication.CloudFoundry/SameSpaceRequirement.cs b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs similarity index 56% rename from src/Security/src/Authentication.CloudFoundry/SameSpaceRequirement.cs rename to src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs index 654eabc7ce..c087fbbab4 100644 --- a/src/Security/src/Authentication.CloudFoundry/SameSpaceRequirement.cs +++ b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using Microsoft.AspNetCore.Authorization; +using System.Runtime.CompilerServices; -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class SameSpaceRequirement : IAuthorizationRequirement -{ -} +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.OpenIdConnect.Test")] diff --git a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt new file mode 100644 index 0000000000..ab058de62d --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..af6205b6dc --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +#nullable enable +static Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectServiceCollectionExtensions.ConfigureOpenIdConnectForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectServiceCollectionExtensions.ConfigureOpenIdConnectForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action? configureHttpClient) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectServiceCollectionExtensions diff --git a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj new file mode 100644 index 0000000000..f78d59263b --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -0,0 +1,21 @@ + + + net8.0 + Library for using OpenIdConnect with Tanzu + CloudFoundry;uaa;security;sso;openid;oidc + true + enable + + + + + + + + + + + + + + diff --git a/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2ded3f226b --- /dev/null +++ b/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.Shared.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.JwtBearer")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.JwtBearer.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.OpenIdConnect")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.OpenIdConnect.Test")] diff --git a/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs b/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs new file mode 100644 index 0000000000..b425da0e6c --- /dev/null +++ b/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.DependencyInjection; + +namespace Steeltoe.Security.Authentication.Shared; + +internal static class SharedServiceCollectionExtensions +{ + internal static IServiceCollection AddSteeltoeSecurityHttpClient(this IServiceCollection services, Action? configureHttpClient) + { + services.AddHttpClient(SteeltoeSecurityDefaults.HttpClientName, client => + { + client.Timeout = TimeSpan.FromSeconds(60); + configureHttpClient?.Invoke(client); + }); + return services; + } +} diff --git a/src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj b/src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj new file mode 100644 index 0000000000..29df2f13d2 --- /dev/null +++ b/src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj @@ -0,0 +1,21 @@ + + + net8.0 + Shared resources for application security with UAA/Cloud Foundry + CloudFoundry;uaa;security; + true + enable + + + + + + + + + + + + + + diff --git a/src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs b/src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs new file mode 100644 index 0000000000..f6fbf61256 --- /dev/null +++ b/src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Security.Authentication.Shared; + +internal static class SteeltoeSecurityDefaults +{ + internal const string HttpClientName = "SteeltoeSecurity"; + internal const string LocalUAAPath = "http://localhost:8080"; +} diff --git a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs new file mode 100644 index 0000000000..3a9b9ce9df --- /dev/null +++ b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Net.Http.Headers; +using Microsoft.IdentityModel.Tokens; +using Steeltoe.Common; + +namespace Steeltoe.Security.Authentication.Shared; + +internal sealed class TokenKeyResolver +{ + private string _authority; + private readonly HttpClient _httpClient; + + public TokenKeyResolver(string authority, HttpClient httpClient) + { + ArgumentGuard.NotNull(authority); + ArgumentGuard.NotNull(httpClient); + + _authority = authority; + _httpClient = httpClient; + } + + internal static ConcurrentDictionary Resolved { get; } = new(); + + internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string kid, + TokenValidationParameters validationParameters) + { + if (Resolved.TryGetValue(kid, out SecurityKey? resolved)) + { + return new List + { + resolved + }; + } + + JsonWebKeySet? keySet = FetchKeySetAsync().GetAwaiter().GetResult(); + + if (keySet != null) + { + foreach (JsonWebKey key in keySet.Keys) + { + Resolved[key.Kid] = key; + } + } + + if (Resolved.TryGetValue(kid, out resolved)) + { + return new List + { + resolved + }; + } + + return []; + } + + internal async Task FetchKeySetAsync() + { + if (!_authority.EndsWith("/", StringComparison.Ordinal)) + { + _authority += "/"; + } + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_authority}token_keys")); + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + HttpResponseMessage response = await _httpClient.SendAsync(requestMessage); + + if (!response.IsSuccessStatusCode) + { + return null; + } + + string result = await response.Content.ReadAsStringAsync(); + return JsonWebKeySet.Create(result); + } +} diff --git a/src/Security/src/Authorization.Certificate/ApplicationClaimTypes.cs b/src/Security/src/Authorization.Certificate/ApplicationClaimTypes.cs new file mode 100644 index 0000000000..bff0f53843 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/ApplicationClaimTypes.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Security.Authorization.Certificate; + +internal static class ApplicationClaimTypes +{ + internal const string OrganizationId = "OrganizationId"; + internal const string SpaceId = "SpaceId"; + internal const string ApplicationId = "ApplicationId"; + internal const string ApplicationInstanceId = "ApplicationInstanceId"; +} diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs new file mode 100644 index 0000000000..6786a5a28a --- /dev/null +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.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 Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Steeltoe.Security.Authorization.Certificate; + +internal sealed class ApplicationInstanceCertificate +{ + // This pattern is found on certificates issued by Diego + private const string CloudFoundryInstanceCertificateSubjectRegex = + @"^CN=(?[0-9a-f-]+),\sOU=organization:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=app:(?[0-9a-f-]+)$"; + + // This pattern is found on certificates created in Steeltoe + private const string SteeltoeInstanceCertificateSubjectRegex = + @"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$"; + + private ApplicationInstanceCertificate(string organizationId, string spaceId, string applicationId, string instanceId) + { + OrganizationId = organizationId; + SpaceId = spaceId; + ApplicationId = applicationId; + InstanceId = instanceId; + } + + public string OrganizationId { get; private set; } + + public string SpaceId { get; private set; } + + public string ApplicationId { get; private set; } + + public string InstanceId { get; private set; } + + public static bool TryParse(X509Certificate2 certificate, [MaybeNullWhen(false)] out ApplicationInstanceCertificate outInstanceCertificate, ILogger logger) + { + outInstanceCertificate = null; + + Match instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), CloudFoundryInstanceCertificateSubjectRegex); + + if (!instanceMatch.Success) + { + instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), SteeltoeInstanceCertificateSubjectRegex); + } + + if (instanceMatch.Success) + { + outInstanceCertificate = new ApplicationInstanceCertificate(instanceMatch.Groups["org"].Value,instanceMatch.Groups["space"].Value, instanceMatch.Groups["app"].Value, instanceMatch.Groups["instance"].Value); + logger.LogTrace("Successfully parsed an identity certificate with subject {CertificateSubject}", certificate.Subject); + } + else + { + logger.LogError("Identity certificate did not match an expected pattern. Subject was: {CertificateSubject}", certificate.Subject); + } + + return instanceMatch.Success; + } +} diff --git a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..a0a3364418 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; +using Steeltoe.Common; + +namespace Steeltoe.Security.Authorization.Certificate; + +public static class CertificateApplicationBuilderExtensions +{ + /// + /// Enable certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. + /// Sets ForwardedHeaders to . + /// + /// + /// The . + /// + public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder app) + { + return app.UseCertificateAuthorization(new ForwardedHeadersOptions()); + } + + /// + /// Enable certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. + /// + /// + /// The . + /// + /// + /// Custom header forwarding policy. is added to your . + /// + public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder app, ForwardedHeadersOptions forwardedHeaders) + { + ArgumentGuard.NotNull(forwardedHeaders); + + forwardedHeaders.ForwardedHeaders |= ForwardedHeaders.XForwardedProto; + + app.UseForwardedHeaders(forwardedHeaders); + app.UseCertificateForwarding(); + app.UseAuthentication(); + app.UseAuthorization(); + + return app; + } +} diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs new file mode 100644 index 0000000000..a5ba5a395e --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Security.Authorization.Certificate; + +public static class CertificateAuthorizationDefaults +{ + public const string SameOrganizationAuthorizationPolicy = "sameorg"; + public const string SameSpaceAuthorizationPolicy = "samespace"; + public const string ClientCertificateHeader = "X-Client-Cert"; // "somethingcompletelyunexpected" + public const string HttpClientName = "IncludeClientCertificate"; +} diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs new file mode 100644 index 0000000000..a6d6ed2fbb --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Security.Claims; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Options; + +namespace Steeltoe.Security.Authorization.Certificate; + +public sealed class CertificateAuthorizationHandler : IAuthorizationHandler +{ + private readonly ILogger _logger; + private ApplicationInstanceCertificate? _applicationInstanceCertificate; + + public CertificateAuthorizationHandler(IOptionsMonitor certificateOptionsMonitor, ILogger logger) + { + ArgumentGuard.NotNull(certificateOptionsMonitor); + + _logger = logger; + certificateOptionsMonitor.OnChange(OnCertificateRefresh); + OnCertificateRefresh(certificateOptionsMonitor.Get("ContainerIdentity")); + } + + public Task HandleAsync(AuthorizationHandlerContext context) + { + HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.OrganizationId, _applicationInstanceCertificate?.OrganizationId); + HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.SpaceId, _applicationInstanceCertificate?.SpaceId); + return Task.CompletedTask; + } + + private void OnCertificateRefresh(CertificateOptions certificateOptions) + { + if (certificateOptions.Certificate == null) + { + return; + } + + if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate, _logger)) + { + _applicationInstanceCertificate = applicationInstanceCertificate; + } + } + + private void HandleCertificateAuthorizationRequirement(AuthorizationHandlerContext context, string claimType, string? claimValue) + where T : class, IAuthorizationRequirement + { + T? requirement = context.PendingRequirements.OfType().FirstOrDefault(); + + if (requirement == null) + { + return; + } + + if (claimValue == null) + { + context.Fail(); + return; + } + + if (context.User.HasClaim(claimType, claimValue)) + { + context.Succeed(requirement); + } + else + { + _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ClaimValue} but got {ClaimType}", claimValue, context.User.FindFirstValue((claimType))); + } + } +} diff --git a/src/Security/src/Authentication.CloudFoundry/AuthorizationPolicyBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs similarity index 72% rename from src/Security/src/Authentication.CloudFoundry/AuthorizationPolicyBuilderExtensions.cs rename to src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs index 7867654e1e..14b38cdb09 100644 --- a/src/Security/src/Authentication.CloudFoundry/AuthorizationPolicyBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs @@ -2,20 +2,24 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using Microsoft.AspNetCore.Authorization; +using Steeltoe.Common; -namespace Steeltoe.Security.Authentication.CloudFoundry; +namespace Steeltoe.Security.Authorization.Certificate; -public static class AuthorizationPolicyBuilderExtensions +public static class CertificateAuthorizationPolicyBuilderExtensions { public static AuthorizationPolicyBuilder SameOrg(this AuthorizationPolicyBuilder builder) { + ArgumentGuard.NotNull(builder); + builder.Requirements.Add(new SameOrgRequirement()); return builder; } public static AuthorizationPolicyBuilder SameSpace(this AuthorizationPolicyBuilder builder) { + ArgumentGuard.NotNull(builder); + builder.Requirements.Add(new SameSpaceRequirement()); return builder; } diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs new file mode 100644 index 0000000000..52c5a1fdeb --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Microsoft.AspNetCore.Authentication.Certificate; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Steeltoe.Common.Options; +using Steeltoe.Common.Security; + +namespace Steeltoe.Security.Authorization.Certificate; + +public static class CertificateServiceCollectionExtensions +{ + /// + /// Add necessary components for server-side authorization of client certificates. + /// + /// + /// The to add services to. + /// + /// + /// The application configuration. + /// + public static IServiceCollection AddCertificateAuthorizationServer(this IServiceCollection services, IConfiguration configuration) + { + services.ConfigureCertificateOptions(configuration); + services.AddCertificateForwarding(opt => + { + opt.CertificateHeader = CertificateAuthorizationDefaults.ClientCertificateHeader; + opt.HeaderConverter = certificateData => + { + var certificate = new X509Certificate2(Encoding.ASCII.GetBytes(certificateData)); + return certificate; + }; + }); + + services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCertificateAuthenticationOptions>()); + services.AddSingleton(); + return services; + } + + /// + /// Add a named and necessary components for finding client certificates and attaching to outbound requests. + /// + /// + /// The to add services to. + /// + /// + /// The application configuration. + /// + public static IServiceCollection AddCertificateAuthorizationClient(this IServiceCollection services, IConfiguration configuration) + { + services.ConfigureCertificateOptions(configuration); + services.TryAddSingleton, PostConfigureCertificateAuthenticationOptions>(); + services.AddHttpClient(CertificateAuthorizationDefaults.HttpClientName, (serviceProvider, client) => + { + var loggerFactory = serviceProvider.GetRequiredService(); + var logger = loggerFactory.CreateLogger(("Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions")); + var optionsMonitor = serviceProvider.GetService>(); + var certificateOptions = optionsMonitor?.Get("ContainerIdentity"); + var certificate = certificateOptions?.Certificate; + if (certificate != null) + { + logger.LogDebug("Adding certificate with subject {CertificateSubject} to outbound requests in header {Header}", certificate.Subject, CertificateAuthorizationDefaults.ClientCertificateHeader); + string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); + client.DefaultRequestHeaders.Add(CertificateAuthorizationDefaults.ClientCertificateHeader, b64); + } + else + { + logger.LogError("Failed to find certificates"); + } + }); + + return services; + } +} diff --git a/src/Security/src/Authorization.Certificate/GlobalUsings.cs b/src/Security/src/Authorization.Certificate/GlobalUsings.cs new file mode 100644 index 0000000000..2b0224ece7 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/GlobalUsings.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +global using System.Security.Cryptography.X509Certificates; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.Extensions.Logging; diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs new file mode 100644 index 0000000000..805dad3a47 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Runtime.ConstrainedExecution; +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.Certificate; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Options; + +namespace Steeltoe.Security.Authorization.Certificate; + +public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions +{ + private readonly IOptionsMonitor _certificateOptions; + private readonly ILogger _logger; + + public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor certificateOptions, ILogger logger) + { + ArgumentGuard.NotNull(certificateOptions); + + _certificateOptions = certificateOptions; + _logger = logger; + } + + public void PostConfigure(string? name, CertificateAuthenticationOptions options) + { + ArgumentGuard.NotNull(options); + + CertificateOptions named = _certificateOptions.Get("ContainerIdentity"); + options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; + options.ClaimsIssuer = named.Certificate?.Issuer; + options.RevocationMode = X509RevocationMode.NoCheck; + + string? systemCertPath = Environment.GetEnvironmentVariable("CF_SYSTEM_CERT_PATH"); + if (!string.IsNullOrEmpty(systemCertPath)) + { + IEnumerable systemCertificates = Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)); + options.CustomTrustStore.AddRange(systemCertificates.ToArray()); + } + + if (named.IssuerChain.Any()) + { + options.AdditionalChainCertificates.AddRange(named.IssuerChain.ToArray()); + } + + options.Events = new CertificateAuthenticationEvents + { + OnCertificateValidated = context => + { + if (context.Principal == null) + { + return Task.CompletedTask; + } + + var claims = new List(context.Principal.Claims); + + if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate, out ApplicationInstanceCertificate? clientCertificate, _logger)) + { + claims.Add(new Claim(ApplicationClaimTypes.OrganizationId, clientCertificate.OrganizationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + claims.Add(new Claim(ApplicationClaimTypes.SpaceId, clientCertificate.SpaceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + claims.Add(new Claim(ApplicationClaimTypes.ApplicationId, clientCertificate.ApplicationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + claims.Add(new Claim(ApplicationClaimTypes.ApplicationInstanceId, clientCertificate.InstanceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + } + + var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); + context.Principal = new ClaimsPrincipal(identity); + context.Success(); + + return Task.CompletedTask; + } + }; + } +} diff --git a/src/Connectors/src/CloudFoundry/PublicAPI.Shipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Shipped.txt similarity index 100% rename from src/Connectors/src/CloudFoundry/PublicAPI.Shipped.txt rename to src/Security/src/Authorization.Certificate/PublicAPI.Shipped.txt diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..830ba6dec5 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -0,0 +1,25 @@ +#nullable enable +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.ClientCertificateHeader = "X-Client-Cert" -> string! +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.HttpClientName = "IncludeClientCertificate" -> string! +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy = "sameorg" -> string! +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy = "samespace" -> string! +static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! forwardedHeaders) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.SameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.SameSpace(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions.AddCertificateAuthorizationClient(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.Extensions.Configuration.IConfiguration! configuration) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions.AddCertificateAuthorizationServer(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.Extensions.Configuration.IConfiguration! configuration) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationHandler +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationHandler.CertificateAuthorizationHandler(Microsoft.Extensions.Options.IOptionsMonitor! certificateOptionsMonitor, Microsoft.Extensions.Logging.ILogger! logger) -> void +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationHandler.HandleAsync(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext! context) -> System.Threading.Tasks.Task! +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions +Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions +Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions +Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions.PostConfigure(string? name, Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions! options) -> void +Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions.PostConfigureCertificateAuthenticationOptions(Microsoft.Extensions.Options.IOptionsMonitor! certificateOptions, Microsoft.Extensions.Logging.ILogger! logger) -> void +Steeltoe.Security.Authorization.Certificate.SameOrgRequirement +Steeltoe.Security.Authorization.Certificate.SameOrgRequirement.SameOrgRequirement() -> void +Steeltoe.Security.Authorization.Certificate.SameSpaceRequirement +Steeltoe.Security.Authorization.Certificate.SameSpaceRequirement.SameSpaceRequirement() -> void diff --git a/src/Connectors/src/CloudFoundry/Services/ServiceInfoFactoryAttribute.cs b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs similarity index 58% rename from src/Connectors/src/CloudFoundry/Services/ServiceInfoFactoryAttribute.cs rename to src/Security/src/Authorization.Certificate/SameOrgRequirement.cs index b1b7ad0cc5..661d078a62 100644 --- a/src/Connectors/src/CloudFoundry/Services/ServiceInfoFactoryAttribute.cs +++ b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -namespace Steeltoe.Connectors.CloudFoundry.Services; +namespace Steeltoe.Security.Authorization.Certificate; -[AttributeUsage(AttributeTargets.Class)] -internal sealed class ServiceInfoFactoryAttribute : Attribute +public sealed class SameOrgRequirement : IAuthorizationRequirement { } diff --git a/src/Security/src/Authentication.CloudFoundry/SameOrgRequirement.cs b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs similarity index 57% rename from src/Security/src/Authentication.CloudFoundry/SameOrgRequirement.cs rename to src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs index a55b09ba29..fc6ddfec06 100644 --- a/src/Security/src/Authentication.CloudFoundry/SameOrgRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using Microsoft.AspNetCore.Authorization; +namespace Steeltoe.Security.Authorization.Certificate; -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public class SameOrgRequirement : IAuthorizationRequirement +public sealed class SameSpaceRequirement : IAuthorizationRequirement { } diff --git a/src/Security/src/Authentication.Mtls/Steeltoe.Security.Authentication.Mtls.csproj b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj similarity index 63% rename from src/Security/src/Authentication.Mtls/Steeltoe.Security.Authentication.Mtls.csproj rename to src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj index 1d3d38f43c..af9c75c4ce 100644 --- a/src/Security/src/Authentication.Mtls/Steeltoe.Security.Authentication.Mtls.csproj +++ b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj @@ -1,17 +1,14 @@ - net8.0;net6.0 - ASP.NET Core middleware that enables an application to support certificate authentication. - aspnetcore;authentication;security;x509;certificate;mtls + net8.0 + ASP.NET Core middleware that enables an application to support authorization via client certificates. + aspnetcore;authorization;security;x509;certificate;mutualtls true + enable - - - - diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryClaimActionExtensionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryClaimActionExtensionsTest.cs deleted file mode 100644 index b630761b17..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryClaimActionExtensionsTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.OAuth.Claims; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryClaimActionExtensionsTest -{ - [Fact] - public void MapScopes_AddsClaimAction() - { - var col = new ClaimActionCollection(); - col.MapScopes(); - Assert.Single(col); - Assert.IsType(col.FirstOrDefault()); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryContainerIdentityMtlsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryContainerIdentityMtlsTest.cs deleted file mode 100644 index 7ccfcbd840..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryContainerIdentityMtlsTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using System.Security.Cryptography.X509Certificates; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Hosting; -using Steeltoe.Common.Security; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryContainerIdentityMtlsTest : IClassFixture -{ - private readonly ClientCertificatesFixture _fixture; - - public CloudFoundryContainerIdentityMtlsTest(ClientCertificatesFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task CloudFoundryCertificateAuth_AcceptsSameSpace() - { - using IHost host = await GetHostBuilder().StartAsync(); - - var requestUri = new Uri($"https://localhost/{CloudFoundryDefaults.SameSpaceAuthorizationPolicy}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch).GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task CloudFoundryCertificateAuth_AcceptsSameOrg() - { - using IHost host = await GetHostBuilder().StartAsync(); - - var requestUri = new Uri($"https://localhost/{CloudFoundryDefaults.SameOrganizationAuthorizationPolicy}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch).GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task CloudFoundryCertificateAuth_RejectsOrgMismatch() - { - using IHost host = await GetHostBuilder().StartAsync(); - - var requestUri = new Uri($"https://localhost/{CloudFoundryDefaults.SameOrganizationAuthorizationPolicy}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.SpaceMatch).GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - [Fact] - public async Task CloudFoundryCertificateAuth_RejectsSpaceMismatch() - { - using IHost host = await GetHostBuilder().StartAsync(); - - var requestUri = new Uri($"https://localhost/{CloudFoundryDefaults.SameSpaceAuthorizationPolicy}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgMatch).GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - [Fact] - public async Task AddCloudFoundryCertificateAuth_ForbiddenWithoutCert() - { - using IHost host = await GetHostBuilder().StartAsync(); - - var requestUri = new Uri($"http://localhost/{CloudFoundryDefaults.SameSpaceAuthorizationPolicy}"); - HttpResponseMessage response = await host.GetTestClient().GetAsync(requestUri); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - private IHostBuilder GetHostBuilder() - { - return new HostBuilder() - .ConfigureAppConfiguration(builder => builder.AddCloudFoundryContainerIdentity(_fixture.ServerOrgId.ToString(), _fixture.ServerSpaceId.ToString())) - .ConfigureWebHostDefaults(webHost => webHost.UseStartup()).ConfigureWebHost(webBuilder => webBuilder.UseTestServer()); - } - - private HttpClient ClientWithCertificate(HttpClient httpClient, X509Certificate2 certificate) - { - byte[] bytes = certificate.GetRawCertData(); - string b64 = Convert.ToBase64String(bytes); - httpClient.DefaultRequestHeaders.Add("X-Forwarded-Client-Cert", b64); - return httpClient; - } - - private static class Certificates - { - private static readonly Func GetFilePath = fileName => - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", fileName); - - public static X509Certificate2 OrgAndSpaceMatch { get; } = - X509Certificate2.CreateFromPemFile(GetFilePath("OrgAndSpaceMatchCert.pem"), GetFilePath("OrgAndSpaceMatchKey.pem")); - - public static X509Certificate2 OrgMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("OrgMatchCert.pem"), GetFilePath("OrgMatchKey.pem")); - - public static X509Certificate2 SpaceMatch { get; } = - X509Certificate2.CreateFromPemFile(GetFilePath("SpaceMatchCert.pem"), GetFilePath("SpaceMatchKey.pem")); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryExtensionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryExtensionsTest.cs deleted file mode 100644 index 261a4d6134..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryExtensionsTest.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Steeltoe.Common.TestResources; -using Steeltoe.Configuration.CloudFoundry; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryExtensionsTest -{ - private const string VcapApplication = @" - { - ""cf_api"": ""https://api.system.testcloud.com"", - ""limits"": { ""fds"": 16384, ""mem"": 512, ""disk"": 1024 }, - ""application_name"": ""fortuneService"", - ""application_uris"": [ ""fortuneservice.apps.testcloud.com"" ], - ""name"": ""fortuneService"", - ""space_name"": ""test"", - ""space_id"": ""54af9d15-2f18-453b-a533-f0c9e6522c97"", - ""uris"": [ ""fortuneservice.apps.testcloud.com"" ], - ""users"": null, - ""application_id"": ""2ddeb650-187e-41db-a4a6-84fb60567908"", - ""version"": ""d2911a1c-c81a-47aa-be81-d820a6700d2b"", - ""application_version"": ""d2911a1c-c81a-47aa-be81-d820a6700d2b"" - }"; - - private const string VcapServices = @" - { - ""user-provided"": [{ - ""credentials"": { - ""client_id"": ""testApp"", - ""client_secret"": ""testApp"", - ""uri"": ""uaa://login.system.testcloud.com"" - }, - ""syslog_drain_url"": """", - ""volume_mounts"": [], - ""label"": ""user-provided"", - ""name"": ""myOAuthService"", - ""tags"": [] - }] - }"; - - private const string OpenIdConfigResponse = @" - { - ""issuer"":""https://default_oauthserviceurl/oauth/token"", - ""authorization_endpoint"":""https://default_oauthserviceurl/oauth/authorize"", - ""token_endpoint"":""https://default_oauthserviceurl/oauth/token"", - ""token_endpoint_auth_methods_supported"":[""client_secret_basic"",""client_secret_post""], - ""token_endpoint_auth_signing_alg_values_supported"":[""RS256"",""HS256""], - ""userinfo_endpoint"":""https://default_oauthserviceurl/userinfo"", - ""jwks_uri"":""https://default_oauthserviceurl/token_keys"", - ""scopes_supported"":[""openid"",""profile"",""email"",""phone"",""roles"",""user_attributes""], - ""response_types_supported"":[""code"",""code id_token"",""id_token"",""token id_token""], - ""subject_types_supported"":[""public""], - ""id_token_signing_alg_values_supported"":[""RS256"",""HS256""], - ""id_token_encryption_alg_values_supported"":[""none""], - ""claim_types_supported"":[""normal""], - ""claims_supported"":[""sub"",""user_name"",""origin"",""iss"",""auth_time"",""amr"",""acr"",""client_id"",""aud"",""zid"",""grant_type"",""user_id"",""azp"",""scope"",""exp"",""iat"",""jti"",""rev_sig"",""cid"",""given_name"",""family_name"",""phone_number"",""email""], - ""claims_parameter_supported"":false, - ""service_documentation"":""http://docs.cloudfoundry.org/api/uaa/"", - ""ui_locales_supported"":[""en-US""] - }"; - - private const string JwksResponse = @" - { - ""keys"":[{ - ""kty"":""RSA"", - ""e"":""AQAB"", - ""use"":""sig"", - ""kid"":""uaa-jwt-key-1"", - ""alg"":""RS256"", - ""value"":""-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnN\nIkggNOwOQVNbpO0WVHIivig5L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKI\npaaTWFQR5cTR1UFZJL/OF9vAfpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMo\nl6ZnTbSsFW6VZjFMjQIDAQAB\n-----END PUBLIC KEY-----"", - ""n"":""AMe0LmBRfEEqkSplMuQ28XA0ac0iSCA07A5BU1uk7RZUciK-KDkvf1apL27SGcD47swID8qWsBHhtdp5VWHB9Q9gEoilppNYVBHlxNHVQVkkv84X28B-k7DOegProMMKdBWlsKO0NhZf7HqKbGfwcJjGEyiXpmdNtKwVbpVmMUyN"" - }] - }"; - - [Fact] - public async Task AddCloudFoundryOAuthAuthentication_AddsIntoPipeline() - { - IWebHostBuilder builder = GetHostBuilder(); - using var server = new TestServer(builder); - HttpClient client = server.CreateClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - string location = result.Headers.Location.ToString(); - Assert.StartsWith("http://default_oauthserviceurl/oauth/authorize", location, StringComparison.Ordinal); - } - - [Fact] - public async Task AddCloudFoundryOAuthAuthentication_AddsIntoPipeline_UsesSSOInfo() - { - using var appScope = new EnvironmentVariableScope("VCAP_APPLICATION", VcapApplication); - using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", VcapServices); - - IWebHostBuilder builder = GetHostBuilder(); - - using var server = new TestServer(builder); - - HttpClient client = server.CreateClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - string location = result.Headers.Location.ToString(); - Assert.StartsWith("https://login.system.testcloud.com/oauth/authorize", location, StringComparison.Ordinal); - } - - [Fact] - public async Task AddCloudFoundryJwtBearerAuthentication_AddsIntoPipeline() - { - IWebHostBuilder builder = GetHostBuilder(); - using var server = new TestServer(builder); - HttpClient client = server.CreateClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode); - } - - [Fact] - public async Task AddCloudFoundryOpenId_AddsIntoPipeline() - { - using var configScope = new EnvironmentVariableScope("openIdConfigResponse", OpenIdConfigResponse); - using var jwksScope = new EnvironmentVariableScope("jwksResponse", JwksResponse); - - IWebHostBuilder builder = GetHostBuilder(new Dictionary - { - { "security:oauth2:client:Timeout", "9999" } - }); - - using var server = new TestServer(builder); - HttpClient client = server.CreateClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - string location = result.Headers.Location.ToString(); - Assert.StartsWith("https://default_oauthserviceurl/oauth/authorize", location, StringComparison.Ordinal); - } - - [Fact] - public async Task AddCloudFoundryOpenId_AddsIntoPipeline_UsesSSOInfo() - { - using var appScope = new EnvironmentVariableScope("VCAP_APPLICATION", VcapApplication); - using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", VcapServices); - - using var configScope = new EnvironmentVariableScope("openIdConfigResponse", - OpenIdConfigResponse.Replace("default_oauthserviceurl", "login.system.testcloud.com", StringComparison.Ordinal)); - - using var jwksScope = new EnvironmentVariableScope("jwksResponse", JwksResponse); - - IWebHostBuilder builder = GetHostBuilder(); - - using var server = new TestServer(builder); - - HttpClient client = server.CreateClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - string location = result.Headers.Location.ToString(); - Assert.StartsWith("https://login.system.testcloud.com/oauth/authorize", location, StringComparison.Ordinal); - } - - private IWebHostBuilder GetHostBuilder(Dictionary appsettings = null) - where T : class - { - return new WebHostBuilder().UseStartup().ConfigureAppConfiguration((_, builder) => - { - if (appsettings is not null) - { - builder.AddInMemoryCollection(appsettings); - } - - builder.AddCloudFoundry(); - }).UseEnvironment("development"); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryHelperTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryHelperTest.cs deleted file mode 100644 index c04b749d40..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryHelperTest.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text.Json; -using Microsoft.IdentityModel.Tokens; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryHelperTest -{ - [Fact] - public void GetBackChannelHandler_ReturnsExpected() - { - HttpMessageHandler result1 = CloudFoundryHelper.GetBackChannelHandler(false); - Assert.NotNull(result1); - - HttpMessageHandler result2 = CloudFoundryHelper.GetBackChannelHandler(true); - Assert.Null(result2); - } - - [Fact] - public void GetTokenValidationParameters_ReturnsExpected() - { - TokenValidationParameters parameters = CloudFoundryHelper.GetTokenValidationParameters(null, "https://foo.bar.com/keyurl", null, false); - Assert.False(parameters.ValidateAudience, "Audience validation should not be enabled by default"); - Assert.True(parameters.ValidateIssuer, "Issuer validation should be enabled by default"); - Assert.NotNull(parameters.IssuerValidator); - Assert.True(parameters.ValidateLifetime, "Token lifetime validation should be enabled by default"); - Assert.NotNull(parameters.IssuerSigningKeyResolver); - } - - [Fact] - public void GetExpTime_FindsTime() - { - string info = TestHelpers.GetValidTokenInfoRequestResponse(); - JsonElement payload = JsonDocument.Parse(info).RootElement; - DateTime dateTime = CloudFoundryHelper.GetExpTime(payload); - Assert.Equal(new DateTime(2016, 9, 2, 8, 04, 23, DateTimeKind.Utc), dateTime); - } - - [Fact] - public void GetIssueTime_FindsTime() - { - string info = TestHelpers.GetValidTokenInfoRequestResponse(); - JsonElement payload = JsonDocument.Parse(info).RootElement; - DateTime dateTime = CloudFoundryHelper.GetIssueTime(payload); - Assert.Equal(new DateTime(2016, 9, 1, 20, 04, 23, DateTimeKind.Utc), dateTime); - } - - [Fact] - public void GetScopes_FindsScopes() - { - string info = TestHelpers.GetValidTokenInfoRequestResponse(); - JsonElement payload = JsonDocument.Parse(info).RootElement; - List scopes = CloudFoundryHelper.GetScopes(payload); - Assert.Contains("openid", scopes); - Assert.Single(scopes); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerConfigurerTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerConfigurerTest.cs deleted file mode 100644 index 5fca7614be..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerConfigurerTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryJwtBearerConfigurerTest -{ - [Fact] - public void Configure_NoServiceInfo_ReturnsExpected() - { - var opts = new CloudFoundryJwtBearerOptions(); - var jwtOpts = new JwtBearerOptions(); - - CloudFoundryJwtBearerConfigurer.Configure(null, jwtOpts, opts); - Assert.True(opts.ValidateCertificates); - Assert.Equal(opts.ClaimsIssuer, jwtOpts.ClaimsIssuer); - Assert.Null(jwtOpts.BackchannelHttpHandler); - Assert.NotNull(jwtOpts.TokenValidationParameters); - Assert.Equal(opts.SaveToken, jwtOpts.SaveToken); - } - - [Fact] - public void Configure_WithServiceInfo_ReturnsExpected() - { - var opts = new CloudFoundryJwtBearerOptions(); - var info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain"); - var jwtOpts = new JwtBearerOptions(); - - CloudFoundryJwtBearerConfigurer.Configure(info, jwtOpts, opts); - Assert.Equal($"http://domain{CloudFoundryDefaults.JwtTokenUri}", opts.JwtKeyUrl); - Assert.True(opts.ValidateCertificates); - Assert.Equal(opts.ClaimsIssuer, jwtOpts.ClaimsIssuer); - Assert.Null(jwtOpts.BackchannelHttpHandler); - Assert.NotNull(jwtOpts.TokenValidationParameters); - Assert.Equal(opts.SaveToken, jwtOpts.SaveToken); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerOptionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerOptionsTest.cs deleted file mode 100644 index 0ea83ea74c..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryJwtBearerOptionsTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryJwtBearerOptionsTest -{ - private const string DefaultJwtTokenUrl = $"http://{CloudFoundryDefaults.OAuthServiceUrl}{CloudFoundryDefaults.JwtTokenUri}"; - - [Fact] - public void DefaultConstructor_SetsUpDefaultOptions() - { - var opts = new CloudFoundryJwtBearerOptions(); - - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer); - Assert.Equal(DefaultJwtTokenUrl, opts.JwtKeyUrl); - Assert.True(opts.SaveToken); - } - - [Theory] - [MemberData(nameof(SetEndpointsData))] - public void SetEndpoints_WithNewDomain_ReturnsExpected(string newDomain, string expectedUrl) - { - var options = new CloudFoundryJwtBearerOptions(); - - options.SetEndpoints(newDomain); - - Assert.Equal(expectedUrl, options.JwtKeyUrl); - } - - public static TheoryData SetEndpointsData() - { - var data = new TheoryData(); - const string newDomain = "http://not-the-original-domain"; - - data.Add(string.Empty, DefaultJwtTokenUrl); - data.Add(" ", DefaultJwtTokenUrl); - data.Add(default, DefaultJwtTokenUrl); - data.Add(newDomain, newDomain + CloudFoundryDefaults.JwtTokenUri); - - return data; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthBuilderTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthBuilderTest.cs deleted file mode 100644 index 0258de00a1..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthBuilderTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOAuthBuilderTest -{ - [Fact] - public async Task ShouldKeepDefaultServiceUrlsIfAuthDomainNotPresent() - { - const string expectedAuthorizationUrl = $"http://{CloudFoundryDefaults.OAuthServiceUrl}/oauth/authorize"; - using var webApplicationFactory = new TestApplicationFactory(); - HttpClient client = webApplicationFactory.CreateDefaultClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - string location = result.Headers.Location.ToString(); - - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - Assert.StartsWith(expectedAuthorizationUrl, location, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task ShouldAddAuthDomainToServiceUrlsIfPresent() - { - const string authDomain = "http://this-config-server-url"; - const string expectedAuthorizationUrl = $"{authDomain}/oauth/authorize"; - string expectedClientId = Guid.NewGuid().ToString(); - - var configuration = new Dictionary - { - { "security:oauth2:client:authDomain", authDomain }, - { "security:oauth2:client:clientId", expectedClientId }, - { "security:oauth2:client:clientSecret", Guid.NewGuid().ToString() } - }; - - using var webApplicationFactory = new TestApplicationFactory(configuration); - HttpClient client = webApplicationFactory.CreateDefaultClient(); - HttpResponseMessage result = await client.GetAsync(new Uri("http://localhost/")); - string location = result.Headers.Location.ToString(); - - Assert.Equal(HttpStatusCode.Redirect, result.StatusCode); - Assert.StartsWith(expectedAuthorizationUrl, location, StringComparison.OrdinalIgnoreCase); - Assert.Contains($"client_id={expectedClientId}", location, StringComparison.Ordinal); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthConfigurerTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthConfigurerTest.cs deleted file mode 100644 index 70006496b8..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthConfigurerTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOAuthConfigurerTest -{ - [Fact] - public void Configure_NoServiceInfo_ReturnsExpected() - { - var opts = new CloudFoundryOAuthOptions(); - CloudFoundryOAuthConfigurer.Configure(null, opts); - - const string authUrl = $"http://{CloudFoundryDefaults.OAuthServiceUrl}"; - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer); - Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId); - Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret); - Assert.Equal(new PathString("/signin-cloudfoundry"), opts.CallbackPath); - Assert.Equal(authUrl + CloudFoundryDefaults.AuthorizationUri, opts.AuthorizationEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.AccessTokenUri, opts.TokenEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.UserInfoUri, opts.UserInformationEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl); - Assert.True(opts.ValidateCertificates); - Assert.Equal(6, opts.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, opts.SignInScheme); - Assert.True(opts.SaveTokens); - Assert.Null(opts.BackchannelHttpHandler); - } - - [Fact] - public void Configure_WithServiceInfo_ReturnsExpected() - { - var opts = new CloudFoundryOAuthOptions(); - var info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain"); - CloudFoundryOAuthConfigurer.Configure(info, opts); - - const string authUrl = "http://domain"; - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer); - Assert.Equal("clientId", opts.ClientId); - Assert.Equal("secret", opts.ClientSecret); - Assert.Equal(new PathString("/signin-cloudfoundry"), opts.CallbackPath); - Assert.Equal(authUrl + CloudFoundryDefaults.AuthorizationUri, opts.AuthorizationEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.AccessTokenUri, opts.TokenEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.UserInfoUri, opts.UserInformationEndpoint); - Assert.Equal(authUrl + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl); - Assert.True(opts.ValidateCertificates); - Assert.Equal(6, opts.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, opts.SignInScheme); - Assert.True(opts.SaveTokens); - Assert.Null(opts.BackchannelHttpHandler); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthHandlerTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthHandlerTest.cs deleted file mode 100644 index ef25dae658..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthHandlerTest.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Text.Json; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Common.TestResources; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOAuthHandlerTest -{ - [Fact] - public async Task ExchangeCodeAsync_SendsTokenRequest_ReturnsValidTokenInfo() - { - var handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(TestHelpers.GetValidTokenRequestResponse()) - }; - - handler.Response = response; - - var client = new HttpClient(handler); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - OAuthTokenResponse resp = await testHandler.TestExchangeCodeAsync("code", "redirectUri"); - - Assert.NotNull(handler.LastRequest); - Assert.Equal(HttpMethod.Post, handler.LastRequest.Method); - Assert.Equal(opts.TokenEndpoint, handler.LastRequest.RequestUri.ToString(), StringComparer.OrdinalIgnoreCase); - - Assert.NotNull(resp); - Assert.NotNull(resp.Response); - Assert.Equal("bearer", resp.TokenType); - Assert.NotNull(resp.AccessToken); - Assert.NotNull(resp.RefreshToken); - } - - [Fact] - public async Task ExchangeCodeAsync_SendsTokenRequest_ReturnsErrorResponse() - { - var handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent(string.Empty) - }; - - handler.Response = response; - - var client = new HttpClient(handler); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - OAuthTokenResponse resp = await testHandler.TestExchangeCodeAsync("code", "http://redirectUri"); - - Assert.NotNull(handler.LastRequest); - Assert.Equal(HttpMethod.Post, handler.LastRequest.Method); - Assert.Equal(opts.TokenEndpoint, handler.LastRequest.RequestUri.ToString(), StringComparer.OrdinalIgnoreCase); - - Assert.NotNull(resp); - Assert.NotNull(resp.Error); - Assert.Contains("OAuth token endpoint failure", resp.Error.Message, StringComparison.Ordinal); - } - - [Fact] - public void BuildChallengeUrl_CreatesCorrectUrl() - { - var handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(TestHelpers.GetValidTokenRequestResponse()) - }; - - handler.Response = response; - - var client = new HttpClient(handler); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - - var props = new AuthenticationProperties(); - string result = testHandler.TestBuildChallengeUrl(props, "https://foo.bar/redirect"); - - Assert.Equal( - "http://Default_OAuthServiceUrl/oauth/authorize?response_type=code&client_id=Default_ClientId&redirect_uri=https%3A%2F%2Ffoo.bar%2Fredirect&scope=", - result); - } - - [Fact] - public void GetTokenInfoRequestParameters_ReturnsCorrectly() - { - var client = new HttpClient(new TestMessageHandler()); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - - JsonDocument payload = JsonDocument.Parse(TestHelpers.GetValidTokenInfoRequestResponse()); - OAuthTokenResponse tokens = OAuthTokenResponse.Success(payload); - Dictionary parameters = testHandler.GetTokenInfoRequestParameters(tokens); - Assert.NotNull(parameters); - - Assert.Equal(parameters["token"], tokens.AccessToken); - } - - [Fact] - public void GetTokenInfoRequestMessage_ReturnsCorrectly() - { - var client = new HttpClient(new TestMessageHandler()); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - - JsonDocument payload = JsonDocument.Parse(TestHelpers.GetValidTokenInfoRequestResponse()); - OAuthTokenResponse tokens = OAuthTokenResponse.Success(payload); - - HttpRequestMessage message = testHandler.GetTokenInfoRequestMessage(tokens); - Assert.NotNull(message); - var content = message.Content as FormUrlEncodedContent; - Assert.NotNull(content); - Assert.Equal(HttpMethod.Post, message.Method); - - Assert.Contains(new MediaTypeWithQualityHeaderValue("application/json"), message.Headers.Accept); - } - - [Fact] - public async Task CreateTicketAsync_SendsTokenInfoRequest_ReturnsValidTokenInfo() - { - var handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(TestHelpers.GetValidTokenInfoRequestResponse()) - }; - - handler.Response = response; - - var client = new HttpClient(handler); - - var opts = new CloudFoundryOAuthOptions - { - Backchannel = client - }; - - MyTestCloudFoundryHandler testHandler = GetTestHandler(opts); - - var identity = new ClaimsIdentity(); - - JsonDocument payload = JsonDocument.Parse(TestHelpers.GetValidTokenInfoRequestResponse()); - OAuthTokenResponse tokens = OAuthTokenResponse.Success(payload); - await testHandler.TestCreateTicketAsync(identity, new AuthenticationProperties(), tokens); - - Assert.NotNull(handler.LastRequest); - Assert.Equal(HttpMethod.Post, handler.LastRequest.Method); - Assert.Equal(opts.TokenInfoUrl, handler.LastRequest.RequestUri.ToString(), StringComparer.OrdinalIgnoreCase); - - Assert.Equal("testssouser", identity.Name); - Assert.Equal(4, identity.Claims.Count()); - identity.HasClaim(ClaimTypes.Email, "testssouser@testcloud.com"); - identity.HasClaim(ClaimTypes.NameIdentifier, "13bb6841-e4d6-4a9a-876c-9ef13aa61cc7"); - identity.HasClaim(ClaimTypes.Name, "testssouser"); - identity.HasClaim("openid", string.Empty); - } - - private MyTestCloudFoundryHandler GetTestHandler(CloudFoundryOAuthOptions options) - { - var loggerFactory = new LoggerFactory(); - IOptionsMonitor monitor = new TestOptionsMonitor(options); - var encoder = UrlEncoder.Default; - -#if NET6_0 - var testHandler = new MyTestCloudFoundryHandler(monitor, loggerFactory, encoder, new TestClock()); -#else - var testHandler = new MyTestCloudFoundryHandler(monitor, loggerFactory, encoder); -#endif - - testHandler.InitializeAsync( - new AuthenticationScheme(CloudFoundryDefaults.AuthenticationScheme, CloudFoundryDefaults.AuthenticationScheme, typeof(CloudFoundryOAuthHandler)), - new DefaultHttpContext()).GetAwaiter().GetResult(); - - return testHandler; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthOptionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthOptionsTest.cs deleted file mode 100644 index b324e182dc..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOAuthOptionsTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOAuthOptionsTest -{ - private const string DefaultOauthUrl = $"http://{CloudFoundryDefaults.OAuthServiceUrl}"; - private const string DefaultAccessTokenUrl = DefaultOauthUrl + CloudFoundryDefaults.AccessTokenUri; - private const string DefaultAuthorizationUrl = DefaultOauthUrl + CloudFoundryDefaults.AuthorizationUri; - private const string DefaultCheckTokenUrl = DefaultOauthUrl + CloudFoundryDefaults.CheckTokenUri; - private const string DefaultUserInfoUrl = DefaultOauthUrl + CloudFoundryDefaults.UserInfoUri; - - [Fact] - public void DefaultConstructor_SetsUpDefaultOptions() - { - var opts = new CloudFoundryOAuthOptions(); - - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer); - Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId); - Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret); - Assert.Equal(new PathString("/signin-cloudfoundry"), opts.CallbackPath); - Assert.Equal(DefaultAccessTokenUrl, opts.TokenEndpoint); - Assert.Equal(DefaultAuthorizationUrl, opts.AuthorizationEndpoint); - Assert.Equal(DefaultCheckTokenUrl, opts.TokenInfoUrl); - Assert.Equal(DefaultUserInfoUrl, opts.UserInformationEndpoint); - Assert.True(opts.ValidateCertificates); - Assert.Equal(6, opts.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, opts.SignInScheme); - Assert.True(opts.SaveTokens); - } - - [Theory] - [MemberData(nameof(SetEndpointsData))] - public void SetEndpoints_WithNewDomain_ReturnsExpected(string newDomain, string expectedAccessTokenUrl, string expectedAuthorizationUrl, - string expectedCheckTokenUrl, string expectedUserInfoUrl) - { - var options = new CloudFoundryOAuthOptions(); - - options.SetEndpoints(newDomain); - - Assert.Equal(expectedAccessTokenUrl ?? DefaultAccessTokenUrl, options.TokenEndpoint); - Assert.Equal(expectedAuthorizationUrl ?? DefaultAuthorizationUrl, options.AuthorizationEndpoint); - Assert.Equal(expectedCheckTokenUrl ?? DefaultCheckTokenUrl, options.TokenInfoUrl); - Assert.Equal(expectedUserInfoUrl ?? DefaultUserInfoUrl, options.UserInformationEndpoint); - } - - public static TheoryData SetEndpointsData() - { - var data = new TheoryData(); - const string newDomain = "http://not-the-original-domain"; - const string newAccessTokenUrl = newDomain + CloudFoundryDefaults.AccessTokenUri; - const string newAuthorizationUrl = newDomain + CloudFoundryDefaults.AuthorizationUri; - const string newCheckTokenUrl = newDomain + CloudFoundryDefaults.CheckTokenUri; - const string newUserInfoUrl = newDomain + CloudFoundryDefaults.UserInfoUri; - - data.Add(string.Empty, default, default, default, default); - data.Add(" ", default, default, default, default); - data.Add(default, default, default, default, default); - data.Add(newDomain, newAccessTokenUrl, newAuthorizationUrl, newCheckTokenUrl, newUserInfoUrl); - - return data; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectConfigurerTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectConfigurerTest.cs deleted file mode 100644 index 8c9cfe32a1..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectConfigurerTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.Http; -using Steeltoe.Connectors.Services; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOpenIdConnectConfigurerTest -{ - [Fact] - public void Configure_NoServiceInfo_ReturnsExpected() - { - var connectOptions = new OpenIdConnectOptions(); - - CloudFoundryOpenIdConnectConfigurer.Configure(null, connectOptions, new CloudFoundryOpenIdConnectOptions - { - ValidateCertificates = false - }); - - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, connectOptions.ClaimsIssuer); - Assert.Equal(CloudFoundryDefaults.ClientId, connectOptions.ClientId); - Assert.Equal(CloudFoundryDefaults.ClientSecret, connectOptions.ClientSecret); - Assert.Equal(new PathString(CloudFoundryDefaults.CallbackPath), connectOptions.CallbackPath); - Assert.Equal(19, connectOptions.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, connectOptions.SignInScheme); - Assert.False(connectOptions.SaveTokens); - Assert.NotNull(connectOptions.BackchannelHttpHandler); - } - - [Fact] - public void Configure_WithServiceInfo_ReturnsExpected() - { - const string authUrl = "https://domain"; - var connectOptions = new OpenIdConnectOptions(); - var info = new SsoServiceInfo("foobar", "clientId", "secret", authUrl); - - CloudFoundryOpenIdConnectConfigurer.Configure(info, connectOptions, new CloudFoundryOpenIdConnectOptions()); - - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, connectOptions.ClaimsIssuer); - Assert.Equal(authUrl, connectOptions.Authority); - Assert.Equal("clientId", connectOptions.ClientId); - Assert.Equal("secret", connectOptions.ClientSecret); - Assert.Equal(new PathString(CloudFoundryDefaults.CallbackPath), connectOptions.CallbackPath); - Assert.Null(connectOptions.BackchannelHttpHandler); - Assert.Equal(19, connectOptions.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, connectOptions.SignInScheme); - Assert.False(connectOptions.SaveTokens); - Assert.Null(connectOptions.BackchannelHttpHandler); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectOptionsTest.cs deleted file mode 100644 index f12799ba18..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryOpenIdConnectOptionsTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryOpenIdConnectOptionsTest -{ - [Fact] - public void DefaultConstructor_SetsDefaultOptions() - { - var opts = new CloudFoundryOpenIdConnectOptions(); - - Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer); - Assert.Equal($"https://{CloudFoundryDefaults.OAuthServiceUrl}", opts.Authority); - Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId); - Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret); - Assert.Equal(new PathString("/signin-cloudfoundry"), opts.CallbackPath); - Assert.True(opts.ValidateCertificates); - Assert.Equal(19, opts.ClaimActions.Count()); - Assert.Equal(CookieAuthenticationDefaults.AuthenticationScheme, opts.SignInScheme); - Assert.False(opts.SaveTokens); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryScopeClaimActionTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryScopeClaimActionTest.cs deleted file mode 100644 index 1a87b2cca2..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryScopeClaimActionTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using System.Text.Json; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryScopeClaimActionTest -{ - [Fact] - public void Run_AddsClaims() - { - string resp = TestHelpers.GetValidTokenInfoRequestResponse(); - JsonElement payload = JsonDocument.Parse(resp).RootElement; - var action = new CloudFoundryScopeClaimAction("scope", ClaimValueTypes.String); - var ident = new ClaimsIdentity(); - action.Run(payload, ident, "Issuer"); - Assert.NotEmpty(ident.Claims); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenValidatorTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenValidatorTest.cs deleted file mode 100644 index a1ad975cfd..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenValidatorTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class CloudFoundryTokenValidatorTest -{ - [Fact] - public void ValidateIssuer_ValidatesCorrectly() - { - var validator = new CloudFoundryTokenValidator(); - - string uaaResult = validator.ValidateIssuer("https://uaa.system.testcloud.com/", null, null); - string foobarResult = validator.ValidateIssuer("https://foobar.system.testcloud.com/", null, null); - - Assert.NotNull(uaaResult); - Assert.Null(foobarResult); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/MyTestCloudFoundryHandler.cs b/src/Security/test/Authentication.CloudFoundry.Test/MyTestCloudFoundryHandler.cs deleted file mode 100644 index 0f02a6e33f..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/MyTestCloudFoundryHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Security.Claims; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -internal sealed class MyTestCloudFoundryHandler : CloudFoundryOAuthHandler -{ -#if NET6_0 - public MyTestCloudFoundryHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) - : base(options, logger, encoder, clock) -#else - public MyTestCloudFoundryHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) - : base(options, logger, encoder) -#endif - { - } - - public async Task TestCreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) - { - return await CreateTicketAsync(identity, properties, tokens); - } - - public async Task TestExchangeCodeAsync(string code, string redirectUri) - { - return await ExchangeCodeAsync(new OAuthCodeExchangeContext(new AuthenticationProperties(), code, redirectUri)); - } - - public string TestBuildChallengeUrl(AuthenticationProperties properties, string redirectUri) - { - return BuildChallengeUrl(properties, redirectUri); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/ServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/ServiceCollectionExtensionsTest.cs deleted file mode 100644 index 3ad84c0629..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/ServiceCollectionExtensionsTest.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using Steeltoe.Common.Options; -using Steeltoe.Common.Security; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class ServiceCollectionExtensionsTest -{ - [Fact] - public void AddCloudFoundryCertificateAuth_AddsServices() - { - var services = new ServiceCollection(); - - IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary - { - { - $"{CertificateOptions.ConfigurationKeyPrefix}:ContainerIdentity:CertificateFilePath", - $"GeneratedCertificates{Path.DirectorySeparatorChar}SteeltoeInstanceCert.pem" - } - }).Build(); - - services.AddSingleton(configurationRoot); - services.AddLogging(); - - services.AddCloudFoundryCertificateAuth(configurationRoot, CertificateAuthenticationDefaults.AuthenticationScheme, null, - new PhysicalFileProvider(LocalCertificateWriter.AppBasePath)); - - ServiceProvider provider = services.BuildServiceProvider(true); - - Assert.NotNull(provider.GetRequiredService>()); - Assert.NotNull(provider.GetRequiredService>()); - Assert.NotNull(provider.GetRequiredService>()); - Assert.NotNull(provider.GetRequiredService()); - var mtlsOpts = provider.GetRequiredService>(); - Assert.NotNull(mtlsOpts); - - // confirm Events was set (in MutualTlsAuthenticationOptionsPostConfigurer.cs) vs being null by default - Assert.NotNull(mtlsOpts.Value.Events); - Assert.Null(new CertificateAuthenticationOptions().Events); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/Steeltoe.Security.Authentication.CloudFoundry.Test.csproj b/src/Security/test/Authentication.CloudFoundry.Test/Steeltoe.Security.Authentication.CloudFoundry.Test.csproj deleted file mode 100644 index dd68ae27e7..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/Steeltoe.Security.Authentication.CloudFoundry.Test.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0;net6.0 - - - - - - - - - - - - diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestApplicationFactory.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestApplicationFactory.cs deleted file mode 100644 index feeb44e72d..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestApplicationFactory.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestApplicationFactory : WebApplicationFactory - where TStartup : class -{ - private readonly IReadOnlyDictionary _configuration; - - internal TestApplicationFactory(IReadOnlyDictionary configuration = null) - { - _configuration = configuration ?? ImmutableDictionary.Empty; - } - - protected override IHost CreateHost(IHostBuilder builder) - { - builder.UseContentRoot(Directory.GetCurrentDirectory()); - - return base.CreateHost(builder); - } - - protected override IHostBuilder CreateHostBuilder() - { - IHostBuilder builder = Host.CreateDefaultBuilder().UseDefaultServiceProvider(options => options.ValidateScopes = true) - .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.UseStartup().UseTestServer()) - .ConfigureAppConfiguration(configuration => configuration.AddInMemoryCollection(_configuration)); - - return builder; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestClock.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestClock.cs deleted file mode 100644 index b526f50d57..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestClock.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -#if !NET6_0 -#pragma warning disable CS0618 // Type or member is obsolete -#endif - -public sealed class TestClock : ISystemClock -{ - public DateTimeOffset UtcNow { get; set; } = new(2013, 6, 11, 12, 34, 56, 789, TimeSpan.Zero); - - public void Add(TimeSpan timeSpan) - { - UtcNow += timeSpan; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestHelpers.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestHelpers.cs deleted file mode 100644 index 9a9bb05839..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestHelpers.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public static class TestHelpers -{ - public static string GetValidTokenRequestResponse() - { - return - @"{""access_token"":""eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJjNjUxZGI2NjllODM0NGNkOTMwMGUxYjJkYjUxOTgwZCIsInN1YiI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJjaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJhenAiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InRlc3Rzc291c2VyIiwiZW1haWwiOiJ0ZXN0c3NvdXNlckB0ZXN0Y2xvdWQuY29tIiwiYXV0aF90aW1lIjoxNDcyNzUxNDUyLCJyZXZfc2lnIjoiNTExMzU4NDIiLCJpYXQiOjE0NzI3NTE0NTIsImV4cCI6MTQ3Mjc5NDY1MiwiaXNzIjoiaHR0cHM6Ly90ZXN0c3NvLnVhYS5zeXN0ZW0udGVzdGNsb3VkLmNvbS9vYXV0aC90b2tlbiIsInppZCI6ImVmMzA0ZWQwLTNhNWEtNDQyZS05YWQ4LWU0Y2EzYWQ1MTIwZSIsImF1ZCI6WyI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJvcGVuaWQiXX0.f6CA7WrGrY__CNh - RvY1pl05 - 2PQjeyE7C_OH48ChfoxsL4hO8BJEofz934_Jox6dXJwp3wyeMPYwyS5fKGA3ASJT5KqdZ8QdHhOj7lbcejwTUUfG_t - K26W39BLT - cEbpTFyHzBUK79fhIfwSmmXJWU0vQaZ3F0dYKRm98rCEQo9s5jr2jrqHep52vL3Xo8mwDJO4GBc85p8zzVpUPOYMkKiWfEujIOvBL0yTqAL64NsYmFupbKyt5dgf - eF - 1GOuP7Rbgbk_llQuFOkwYr4dLzgw045wXeU3uzJzTrGNzIlJQPOijU1w2uBgYi7fT2wgJDKb4toxot2_6wSXpqBw"",""token_type"":""bearer"",""refresh_token"":""eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJjNjUxZGI2NjllODM0NGNkOTMwMGUxYjJkYjUxOTgwZC1yIiwic3ViIjoiMTNiYjY4NDEtZTRkNi00YTlhLTg3NmMtOWVmMTNhYTYxY2M3Iiwic2NvcGUiOlsib3BlbmlkIl0sImlhdCI6MTQ3Mjc1MTQ1MiwiZXhwIjoxNDc1MzQzNDUyLCJjaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJjbGllbnRfaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJpc3MiOiJodHRwczovL3Rlc3Rzc28udWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoiZWYzMDRlZDAtM2E1YS00NDJlLTlhZDgtZTRjYTNhZDUxMjBlIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsInVzZXJfbmFtZSI6InRlc3Rzc291c2VyIiwib3JpZ2luIjoidWFhIiwidXNlcl9pZCI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsInJldl9zaWciOiI1MTEzNTg0MiIsImF1ZCI6WyI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJvcGVuaWQiXX0.WMEZtwvoJXU8njysozaNglmpYouxoY65AirXC3ypdLsnSVE_OtePQQi3hb3YPQDbrc6JAETnCGcDBJVNUPsnlg4g_O_RPLejB4ZlMioRTZRNv9gzOlELqjgBLAVEkWOiBFuH7hwbR7X7DlmFTcDFSc - gQY5TaLn - wR6PVtQM3oRw_nx1f3 - xgDGcYx7Ktk9rov - xF_C0Pzsoet4dvG78YUlnXgR08KwJnDX_ElV - 0vTOtybqjs_XGSS9N39EWeeykCzciddhqVCfmMM5VMCVaP - 7pr - VDz0N8ArSWfOobI4nPqcj50wvPb595SgId - NlNP4fBX1ibnCeOxWwuzId0w"",""expires_in"":43199,""scope"":""openid"",""jti"":""c651db669e8344cd9300e1b2db51980d""}"; - } - - public static string GetValidTokenInfoRequestResponse() - { - return - @"{""user_id"":""13bb6841-e4d6-4a9a-876c-9ef13aa61cc7"",""user_name"":""testssouser"",""email"":""testssouser@testcloud.com"",""client_id"":""44f854fd-49fe-4102-b0d1-a526dbff93d9"",""exp"":1472803463,""scope"":[""openid""],""jti"":""cc956120c52a431fbbb8945ac42872fd"",""aud"":[""44f854fd-49fe-4102-b0d1-a526dbff93d9"",""openid""],""sub"":""13bb6841-e4d6-4a9a-876c-9ef13aa61cc7"",""iss"":""https://testsso.uaa.system.testcloud.com/oauth/token"",""iat"":1472760263,""cid"":""44f854fd-49fe-4102-b0d1-a526dbff93d9"",""grant_type"":""authorization_code"",""azp"":""44f854fd-49fe-4102-b0d1-a526dbff93d9"",""auth_time"":1472760254,""zid"":""ef304ed0-3a5a-442e-9ad8-e4ca3ad5120e"",""rev_sig"":""51135842"",""origin"":""uaa"",""revocable"":false}"; - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestResponse.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestResponse.cs deleted file mode 100644 index b098a7bcbc..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestResponse.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestResponse : IHttpResponseFeature -{ - public Stream Body - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public bool HasStarted => throw new NotImplementedException(); - - public IHeaderDictionary Headers - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public string ReasonPhrase - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public int StatusCode - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public void OnCompleted(Func callback, object state) - { - } - - public void OnStarting(Func callback, object state) - { - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestServerCertificateStartup.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestServerCertificateStartup.cs deleted file mode 100644 index 717c2969c6..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestServerCertificateStartup.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestServerCertificateStartup -{ - public static CloudFoundryJwtBearerOptions CloudFoundryOptions { get; set; } - - public IConfiguration Configuration { get; } - - public TestServerCertificateStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddCloudFoundryCertificateAuth(Configuration); - } - - public void Configure(IApplicationBuilder app, IAuthorizationService authorizationService) - { - app.UseCloudFoundryCertificateAuth(); - - app.Run(async context => - { - AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync(context.User, null, - context.Request.Path.Value.Replace("/", string.Empty, StringComparison.Ordinal)); - - if (!authorizationResult.Succeeded) - { - await context.ChallengeAsync(); - return; - } - - context.Response.StatusCode = 200; - }); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestServerJwtStartup.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestServerJwtStartup.cs deleted file mode 100644 index be9ec9fc12..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestServerJwtStartup.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestServerJwtStartup -{ - public static CloudFoundryJwtBearerOptions CloudFoundryOptions { get; set; } - - public IConfiguration Configuration { get; } - - public TestServerJwtStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddOptions(); - - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddCloudFoundryJwtBearer(Configuration); - } - - public void Configure(IApplicationBuilder app) - { - app.Use(async (context, next) => - { - try - { - await next(); - } - catch (Exception ex) - { - if (context.Response.HasStarted) - { - throw; - } - - context.Response.StatusCode = 500; - await context.Response.WriteAsync(ex.ToString()); - } - }); - - app.UseAuthentication(); - - app.Run(async context => - { - await context.ChallengeAsync(); - }); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestServerOpenIdStartup.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestServerOpenIdStartup.cs deleted file mode 100644 index f51fbd832f..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestServerOpenIdStartup.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using RichardSzalay.MockHttp; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestServerOpenIdStartup -{ - public IConfiguration Configuration { get; } - - public TestServerOpenIdStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - string openIdConfigResponse = Environment.GetEnvironmentVariable("openIdConfigResponse"); - string jwksResponse = Environment.GetEnvironmentVariable("jwksResponse"); - var mockHttpMessageHandler = new MockHttpMessageHandler(); - - services.AddOptions(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; - }).AddCookie(options => - { - options.AccessDeniedPath = new PathString("/Home/AccessDenied"); - }).AddCloudFoundryOpenIdConnect(Configuration, (options, _) => - { - mockHttpMessageHandler.Expect(HttpMethod.Get, $"{options.Authority}/.well-known/openid-configuration") - .Respond("application/json", openIdConfigResponse); - - mockHttpMessageHandler.Expect(HttpMethod.Get, $"{options.Authority}/token_keys").Respond("application/json", jwksResponse); - options.Backchannel = new HttpClient(mockHttpMessageHandler); - }); - } - - public void Configure(IApplicationBuilder app) - { - app.UseAuthentication(); - - app.Run(async context => - { - await context.ChallengeAsync(); - }); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestServerStartup.cs b/src/Security/test/Authentication.CloudFoundry.Test/TestServerStartup.cs deleted file mode 100644 index 7836ca086e..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestServerStartup.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TestServerStartup -{ - public IConfiguration Configuration { get; } - - public TestServerStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddOptions(); - - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = CloudFoundryDefaults.AuthenticationScheme; - }).AddCookie(options => - { - options.AccessDeniedPath = new PathString("/Home/AccessDenied"); - }).AddCloudFoundryOAuth(Configuration); - } - - public void Configure(IApplicationBuilder app) - { - app.Use(async (context, next) => - { - try - { - await next(); - } - catch (Exception ex) - { - if (context.Response.HasStarted) - { - throw; - } - - context.Response.StatusCode = 500; - await context.Response.WriteAsync(ex.ToString()); - } - }); - - app.UseAuthentication(); - - app.Run(async context => - { - await context.ChallengeAsync(); - }); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TokenExchangerTest.cs b/src/Security/test/Authentication.CloudFoundry.Test/TokenExchangerTest.cs deleted file mode 100644 index 55b1bbe178..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/TokenExchangerTest.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Net; -using System.Net.Http.Headers; -using System.Security.Claims; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using RichardSzalay.MockHttp; -using Xunit; - -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; - -public sealed class TokenExchangerTest -{ - private const string RealTokenResponse = - @"{""access_token"":""eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YTMzYzVhNjhjY2I0YjRiYmQ5N2I4MTRlZWExMTc3MiIsInN1YiI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsInNjb3BlIjpbInRlc3Rncm91cCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJhenAiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImRhdmUiLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsImF1dGhfdGltZSI6MTU0NjY0MjkzMywicmV2X3NpZyI6ImE1ZWY2ODg5IiwiaWF0IjoxNTQ2NjQyOTM1LCJleHAiOjE1NDY2ODYxMzUsImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsImF1ZCI6WyJvcGVuaWQiLCJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiXX0.tGTXZzuuUSObTwdPHSx-zvnld20DH5hlOZlYp5DhjwkMIsZB0uIvVwbVDkPp7H_AmmeJoo6vqa5hbbgfgnYpTrKlCGOypnHoa3yRIKrwcDmLLujaMz6ApZeaJ7sJN-0N1UnPZ9iGcqvt9hNb_198zRnMXGH72oI0e2iGUBV1olCFVdZTnMGT7sUieDFKy7n0ghZYq_gUI8rfvTwiC3lfxv0nDXz4oE9Z-UKhK6q1zkAtQrz61FQ_CHONejz1JnuxQFKMMvm8JLcRkn6OL-EcSi1hkmFw0efO1OqccQacxphlafyHloVPQ3IOtzLjCf8sJ5NgTdCTC3iddT_sYovdrg"",""token_type"":""bearer"",""id_token"":""eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJzdWIiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJhdWQiOlsiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il0sImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsImV4cCI6MTU0NjY4NjEzNSwiaWF0IjoxNTQ2NjQyOTM1LCJhbXIiOlsicHdkIl0sImF6cCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjdhMzNjNWE2OGNjYjRiNGJiZDk3YjgxNGVlYTExNzcyIiwicHJldmlvdXNfbG9nb25fdGltZSI6MTU0NjYzMzU1NjA0NCwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImNpZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX25hbWUiOiJkYXZlIiwicmV2X3NpZyI6ImE1ZWY2ODg5IiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsImF1dGhfdGltZSI6MTU0NjY0MjkzM30.KkTVOTg7Bhj1EWO63QmWzjnEAnKesoSLGfGL-2Y19PiK62KRd66dOcVcQEA_nWIE1mJQZsDByQYwcEuVRAiP-mXY0L2MrWUnRlW5yn1fqOc44iSDggMF5VfjGQok8fGfBPQX7va0evfaOaulRMuWsijYvzZtV-KncGUpxGwkzRs2AAEbkAv1_vAD2zSGJ-ji5L7s4a2-Qc_LxDlNANoYllzMTVxZ2DSvVPLfKPNGgSvNC7t053ExfGRXk-6cPxVznkngWDlYALeXsnrbvXdjuk1dw8dcXRhL4PUJDI7EVvTdqzd1fPYRgAQ3KJZOmvzBY7bxFtoq9odmKKHTI4CFUQ"",""refresh_token"":""eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI4NjdiNTcxNTBlNmM0ZDQ3OTM0NmE3ZjgwMTJmMGY2My1yIiwic3ViIjoiOTViYmIzNDYtYjY4Yy00ZjE1LWIzNDEtNzBkNjBmOWY0NmJhIiwic2NvcGUiOlsidGVzdGdyb3VwIiwib3BlbmlkIl0sImlhdCI6MTU0NjY0MjkzNSwiZXhwIjoxNTQ5MjM0OTM1LCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJpc3MiOiJodHRwczovL3N0ZWVsdG9lLnVhYS5jZi5iZWV0LnNwcmluZ2FwcHMuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIzYTNlYWRhZC01YjJmLTQ1MzAtYWY5NS1hNjliYzBhZmQxNWIiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9uYW1lIjoiZGF2ZSIsIm9yaWdpbiI6InVhYSIsInVzZXJfaWQiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJyZXZfc2lnIjoiYTVlZjY4ODkiLCJhdWQiOlsib3BlbmlkIiwiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il19.cOHCiZgmbtN1uyuvEou879du5XVvJqHEmkf6-2G0V9I1qYy0PJbGMHNL32d6L9LVsnXs4iTfi1qpKfdEGbfZbX8rNwNwcRoPndOZBepTTZHJPcU49VEF4t1hZ5icbt_4mSiQwpNy2n_mqwHizV8BAeOeQc_zUE-YFiuQC6V3ac0rTYO4NJGSioSG_y6HFp3refiPZVPT7En-dwhd-Yic1SB1OPxQ5bRNS7AAjGLeeCWOZQVxwQa5Atv9t791yUkH1zX8Psh_LRy2O8E6o7IBLI_yjz5N-YIHRa-BuMPDdKWarcOjy1KKJM27HynAQo1T4J0Ft8hSsIxFlObTd3-FVQ"",""expires_in"":43199,""scope"":""testgroup openid"",""jti"":""7a33c5a68ccb4b4bbd97b814eea11772""}"; - - [Fact] - public void GetTokenRequestMessage_ReturnsCorrectly() - { - var opts = new AuthServerOptions - { - ClientId = "clientId", - ClientSecret = "clientSecret" - }; - - var tEx = new TokenExchanger(opts); - - HttpRequestMessage message = tEx.GetTokenRequestMessage(new List>(), "redirectUri"); - - Assert.NotNull(message); - var content = message.Content as FormUrlEncodedContent; - Assert.NotNull(content); - Assert.Equal(HttpMethod.Post, message.Method); - - Assert.Contains(new MediaTypeWithQualityHeaderValue("application/json"), message.Headers.Accept); - } - - [Fact] - public void AuthCodeTokenRequestParameters_ReturnsCorrectly() - { - var opts = new AuthServerOptions - { - ClientId = "clientId", - ClientSecret = "clientSecret", - CallbackUrl = "redirect_uri" - }; - - var tEx = new TokenExchanger(opts); - - List> parameters = tEx.AuthCodeTokenRequestParameters("authcode"); - - Assert.NotNull(parameters); - Assert.Equal(opts.ClientId, parameters.First(i => i.Key == "client_id").Value); - Assert.Equal(opts.ClientSecret, parameters.First(i => i.Key == "client_secret").Value); - Assert.Equal("redirect_uri", parameters.First(i => i.Key == "redirect_uri").Value); - Assert.Equal("authcode", parameters.First(i => i.Key == "code").Value); - Assert.Equal(OpenIdConnectGrantTypes.AuthorizationCode, parameters.First(i => i.Key == "grant_type").Value); - } - - [Fact] - public void ClientCredentialsTokenRequestParameters_ReturnsCorrectly() - { - var opts = new AuthServerOptions - { - ClientId = "clientId", - ClientSecret = "clientSecret", - CallbackUrl = "redirect_uri" - }; - - var tEx = new TokenExchanger(opts); - - List> parameters = tEx.ClientCredentialsTokenRequestParameters(); - - Assert.NotNull(parameters); - Assert.Equal(opts.ClientId, parameters.First(i => i.Key == "client_id").Value); - Assert.Equal(opts.ClientSecret, parameters.First(i => i.Key == "client_secret").Value); - Assert.Equal(OpenIdConnectGrantTypes.ClientCredentials, parameters.First(i => i.Key == "grant_type").Value); - } - - [Fact] - public void CommonTokenRequestParamsHandlesScopes() - { - var opts = new AuthServerOptions - { - AdditionalTokenScopes = "onescope", - RequiredScopes = new[] - { - "twoscope" - } - }; - - var tEx = new TokenExchanger(opts); - - List> parameters = tEx.CommonTokenRequestParams(); - Assert.Equal("openid onescope twoscope", parameters.First(i => i.Key == CloudFoundryDefaults.ParamsScope).Value); - } - - [Fact] - public async Task ExchangeAuthCodeForClaimsIdentity_ExchangesCodeForIdentity() - { - var options = new AuthServerOptions - { - AuthorizationUrl = "http://localhost/tokenUrl" - }; - - var exchanger = new TokenExchanger(options, GetMockHttpClient()); - - ClaimsIdentity identity = await exchanger.ExchangeAuthCodeForClaimsIdentityAsync("goodCode"); - - Assert.IsType(identity); - } - - [Fact] - public async Task ExchangeAuthCodeForClaimsIdentity_ReturnsNullOnFailure() - { - var options = new AuthServerOptions - { - AuthorizationUrl = "http://localhost/tokenUrl" - }; - - var exchanger = new TokenExchanger(options, GetMockHttpClient()); - - ClaimsIdentity identity = await exchanger.ExchangeAuthCodeForClaimsIdentityAsync("badCode"); - - Assert.Null(identity); - } - - private HttpClient GetMockHttpClient() - { - var mockHttp = new MockHttpMessageHandler(); - - mockHttp.When("http://localhost/tokenUrl").WithFormData("code", "goodCode").Respond("application/json", RealTokenResponse); // Respond with JSON - mockHttp.When("http://localhost/tokenUrl").WithFormData("code", "badCode").Respond(HttpStatusCode.BadRequest); // Respond with JSON - - return mockHttp.ToHttpClient(); - } -} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/xunit.runner.json b/src/Security/test/Authentication.CloudFoundry.Test/xunit.runner.json deleted file mode 100644 index fdeefaa456..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "maxParallelThreads": 1, - "parallelizeTestCollections": false -} diff --git a/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs b/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs new file mode 100644 index 0000000000..e1326b3af2 --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +global using Xunit; diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..c0b418bb51 --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; + +namespace Steeltoe.Security.Authentication.JwtBearer.Test; + +public sealed class JwtBearerServiceCollectionExtensionsTest +{ + [Fact] + public void ConfigureJwtBearerForCloudFoundry_AddsExpectedRegistrations() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.ConfigureJwtBearerForCloudFoundry(); + + serviceCollection.Should().Contain(service => service.ServiceType == typeof(IHttpClientFactory)); + serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); + } + + [Fact] + public void ConfigureJwtBearerForCloudFoundry_ConfiguresHttpClient() + { + var serviceCollection = new ServiceCollection(); + + var serviceProvider = serviceCollection.ConfigureJwtBearerForCloudFoundry().BuildServiceProvider(); + var httpClient = serviceProvider.GetRequiredService().CreateClient("SteeltoeSecurity"); + + httpClient.Timeout.Should().Be(new TimeSpan(0, 0, 0, 60)); + } +} diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs new file mode 100644 index 0000000000..961fde9f7c --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using FluentAssertions; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Steeltoe.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry.ServiceBinding; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.JwtBearer.Test; + +public sealed class PostConfigureJwtBearerOptionsTest +{ + [Fact] + public void PostConfigure_AddsClientIdToValidAudiences() + { + var appSettings = new Dictionary + { + { "Authentication:Schemes:Bearer:ClientId", "testClient" } + }; + var jwtBearerOptions = new JwtBearerOptions + { + Backchannel = new HttpClient() + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); + var postConfigurer = new PostConfigureJwtBearerOptions(configuration, null!); + + postConfigurer.PostConfigure(null, jwtBearerOptions); + + jwtBearerOptions.TokenValidationParameters.ValidAudiences.Should().Contain("testClient"); + } + + [Fact] + public void PostConfigure_ConfiguresForLocalUAA() + { + var configuration = new ConfigurationBuilder().Build(); + var jwtBearerOptions = new JwtBearerOptions + { + Authority = SteeltoeSecurityDefaults.LocalUAAPath, + Backchannel = new HttpClient() + }; + jwtBearerOptions.RequireHttpsMetadata.Should().BeTrue(); + jwtBearerOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().BeNull(); + + var postConfigure = new PostConfigureJwtBearerOptions(configuration, null!); + + postConfigure.PostConfigure(null, jwtBearerOptions); + + jwtBearerOptions.RequireHttpsMetadata.Should().BeFalse(); + jwtBearerOptions.TokenValidationParameters.ValidIssuer.Should().Be($"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"); + jwtBearerOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + } + + [Fact] + public void PostConfigure_ConfiguresForCloudFoundry() + { + const string vcapServices = """ + { + "p-identity": [ + { + "label": "p-identity", + "provider": null, + "plan": "steeltoe", + "name": "mySSOService", + "tags": [], + "instance_guid": "ea8b8ac0-ce85-4726-8b39-d1b2eb55b45b", + "instance_name": "mySSOService", + "binding_guid": "be94e8e7-9246-49af-935f-5390ff10ac23", + "binding_name": null, + "credentials": { + "auth_domain": "https://steeltoe.login.sys.cf-app.com", + "grant_types": [ "client_credentials" ], + "client_secret": "dd2c82e1-aa99-4eaf-9871-2eb7412b79bb", + "client_id": "4e6f8e34-f42b-440e-a042-f2b13c1d5bed" + }, + "syslog_drain_url": null, + "volume_mounts": [] + }] + } + """; + using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); + var configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(configuration); + serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); + serviceCollection.ConfigureJwtBearerForCloudFoundry(); + + var jwtBearerOptions = serviceCollection + .BuildServiceProvider().GetRequiredService>() + .Get(JwtBearerDefaults.AuthenticationScheme); + + jwtBearerOptions.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); + jwtBearerOptions.MetadataAddress.Should().Be("https://steeltoe.login.sys.cf-app.com/.well-known/openid-configuration"); + jwtBearerOptions.RequireHttpsMetadata.Should().BeTrue(); + jwtBearerOptions.TokenValidationParameters.ValidIssuer.Should().Be("https://steeltoe.login.sys.cf-app.com/oauth/token"); + jwtBearerOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + jwtBearerOptions.TokenValidationParameters.ValidAudiences.Should().Contain("4e6f8e34-f42b-440e-a042-f2b13c1d5bed"); + } +} diff --git a/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj b/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj new file mode 100644 index 0000000000..c89eaaf654 --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/Steeltoe.Security.Authentication.JwtBearer.Test.csproj @@ -0,0 +1,14 @@ + + + net8.0 + enable + + + + + + + + + + diff --git a/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs b/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs deleted file mode 100644 index a7a217a7b8..0000000000 --- a/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs +++ /dev/null @@ -1,660 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -// This file is a modified version of https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/test/CertificateTests.cs - -using System.Net; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Xml.Linq; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Steeltoe.Security.Authentication.Mtls.Test; - -public sealed class ClientCertificateAuthenticationTests -{ - private readonly CertificateAuthenticationEvents _successfulValidationEvents = new() - { - OnCertificateValidated = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer), - new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer) - }; - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); - context.Success(); - return Task.CompletedTask; - } - }; - - private readonly CertificateAuthenticationEvents _failedValidationEvents = new() - { - OnCertificateValidated = context => - { - context.Fail("Not validated"); - return Task.CompletedTask; - } - }; - - private readonly CertificateAuthenticationEvents _unprocessedValidationEvents = new() - { - OnCertificateValidated = _ => Task.CompletedTask - }; - - [Fact] - public async Task VerifySchemeDefaults() - { - var services = new ServiceCollection(); - services.AddAuthentication().AddMutualTls(); - ServiceProvider sp = services.BuildServiceProvider(true); - var schemeProvider = sp.GetRequiredService(); - AuthenticationScheme scheme = await schemeProvider.GetSchemeAsync(CertificateAuthenticationDefaults.AuthenticationScheme); - Assert.NotNull(scheme); - Assert.Equal("MutualTlsAuthenticationHandler", scheme.HandlerType.Name); - Assert.Null(scheme.DisplayName); - } - - [Fact] - public void VerifyIsSelfSignedExtensionMethod() - { - Assert.True(Certificates.SelfSignedValidWithNoEku.IsSelfSigned()); - } - - [Fact] - public async Task NonHttpsIsForbidden() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions(), Certificates.SelfSignedValidWithClientEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("http://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithClientEkuAuthenticates() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithClientEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithNoEkuAuthenticates() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithNoEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithClientEkuFailsWhenSelfSignedCertsNotAllowed() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.Chained - }, Certificates.SelfSignedValidWithClientEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithNoEkuFailsWhenSelfSignedCertsNotAllowed() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.Chained, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithNoEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithServerFailsEvenIfSelfSignedCertsAreAllowed() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithServerEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithServerPassesWhenSelfSignedCertsAreAllowedAndPurposeValidationIsOff() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateCertificateUse = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithServerEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyValidSelfSignedWithServerFailsPurposeValidationIsOffButSelfSignedCertsAreNotAllowed() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.Chained, - ValidateCertificateUse = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedValidWithServerEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - [Trait("Category", "SkipOnLinux")] - public async Task VerifyExpiredSelfSignedFails() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateCertificateUse = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedExpired); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyExpiredSelfSignedPassesIfDateRangeValidationIsDisabled() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateValidityPeriod = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedExpired); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - // https://github.com/dotnet/aspnetcore/issues/32813 - [Fact] - [Trait("Category", "SkipOnLinux")] - public async Task VerifyNotYetValidSelfSignedFails() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateCertificateUse = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedNotYetValid); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyNotYetValidSelfSignedPassesIfDateRangeValidationIsDisabled() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateValidityPeriod = false, - Events = _successfulValidationEvents - }, Certificates.SelfSignedNotYetValid); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyFailingInTheValidationEventReturnsForbidden() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - ValidateCertificateUse = false, - Events = _failedValidationEvents - }, Certificates.SelfSignedValidWithServerEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task DoingNothingInTheValidationEventReturnsOk() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - ValidateCertificateUse = false, - Events = _unprocessedValidationEvents - }, Certificates.SelfSignedValidWithServerEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyNotSendingACertificateEndsUpInForbidden() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - Events = _successfulValidationEvents - }); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyUntrustedClientCertEndsUpInForbidden() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - Events = _successfulValidationEvents - }, Certificates.SignedClient); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifySideLoadedCaSignedCertReturnsOk() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, Certificates.SignedClient); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyHeaderIsUsedIfCertIsNotPresent() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, wireUpHeaderMiddleware: true); - - HttpClient client = server.CreateClient(); - client.DefaultRequestHeaders.Add("X-Client-Cert", Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData)); - HttpResponseMessage response = await client.GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyHeaderEncodedCertFailsOnBadEncoding() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - Events = _successfulValidationEvents - }, wireUpHeaderMiddleware: true); - - HttpClient client = server.CreateClient(); - client.DefaultRequestHeaders.Add("X-Client-Cert", $"OOPS{Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData)}"); - HttpResponseMessage response = await client.GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifySettingTheAzureHeaderOnTheForwarderOptionsWorks() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = _successfulValidationEvents - }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); - - HttpClient client = server.CreateClient(); - client.DefaultRequestHeaders.Add("X-ARR-ClientCert", Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData)); - HttpResponseMessage response = await client.GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task VerifyACustomHeaderFailsIfTheHeaderIsNotPresent() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - Events = _successfulValidationEvents - }, wireUpHeaderMiddleware: true, headerName: "X-ARR-ClientCert"); - - HttpClient client = server.CreateClient(); - client.DefaultRequestHeaders.Add("random-Weird-header", Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData)); - HttpResponseMessage response = await client.GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task VerifyNoEventWireUpWithAValidCertificateCreatesADefaultUser() - { - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned - }, Certificates.SelfSignedValidWithNoEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - XElement responseAsXml = null; - - if (response.Content.Headers.ContentType != null && response.Content.Headers.ContentType.MediaType == "text/xml") - { - string responseContent = await response.Content.ReadAsStringAsync(); - responseAsXml = XElement.Parse(responseContent); - } - - Assert.NotNull(responseAsXml); - - // There should always be an Issuer and a Thumbprint. - IEnumerable actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == "issuer"); - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.Issuer, actual.First().Value); - - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Thumbprint); - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.Thumbprint, actual.First().Value); - - // Now the optional ones - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.SubjectName.Name)) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.X500DistinguishedName); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.SubjectName.Name, actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.SerialNumber)) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.SerialNumber); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.SerialNumber, actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.DnsName, false))) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Dns); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.DnsName, false), actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.EmailName, false))) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Email); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.EmailName, false), actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.SimpleName, false))) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Name); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.SimpleName, false), actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.UpnName, false))) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Upn); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.UpnName, false), actual.First().Value); - } - } - - if (!string.IsNullOrEmpty(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.UrlName, false))) - { - actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == ClaimTypes.Uri); - - if (actual.Any()) - { - Assert.Single(actual); - Assert.Equal(Certificates.SelfSignedValidWithNoEku.GetNameInfo(X509NameType.UrlName, false), actual.First().Value); - } - } - } - - [Fact] - public async Task VerifyValidationEventPrincipalIsPropagated() - { - const string expected = "John Doe"; - - TestServer server = CreateServer(new CertificateAuthenticationOptions - { - AllowedCertificateTypes = CertificateTypes.SelfSigned, - Events = new CertificateAuthenticationEvents - { - OnCertificateValidated = context => - { - // Make sure we get the validated principal - Assert.NotNull(context.Principal); - - var claims = new[] - { - new Claim(ClaimTypes.Name, expected, ClaimValueTypes.String, context.Options.ClaimsIssuer) - }; - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); - context.Success(); - return Task.CompletedTask; - } - } - }, Certificates.SelfSignedValidWithNoEku); - - HttpResponseMessage response = await server.CreateClient().GetAsync(new Uri("https://example.com/")); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - XElement responseAsXml = null; - - if (response.Content.Headers.ContentType != null && response.Content.Headers.ContentType.MediaType == "text/xml") - { - string responseContent = await response.Content.ReadAsStringAsync(); - responseAsXml = XElement.Parse(responseContent); - } - - Assert.NotNull(responseAsXml); - IEnumerable actual = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type")!.Value == ClaimTypes.Name); - Assert.Single(actual); - Assert.Equal(expected, actual.First().Value); - Assert.Single(responseAsXml.Elements("claim")); - } - - private static TestServer CreateServer(CertificateAuthenticationOptions configureOptions, X509Certificate2 clientCertificate = null, Uri baseAddress = null, - bool wireUpHeaderMiddleware = false, string headerName = "") - { - IWebHostBuilder builder = new WebHostBuilder().Configure(app => - { - app.Use((context, next) => - { - if (clientCertificate != null) - { - context.Connection.ClientCertificate = clientCertificate; - } - - return next(); - }); - - if (wireUpHeaderMiddleware) - { - app.UseCertificateForwarding(); - } - - app.UseAuthentication(); - - app.Run(async context => - { - HttpResponse response = context.Response; - - AuthenticateResult authenticationResult = await context.AuthenticateAsync(); - - if (authenticationResult.Succeeded) - { - response.StatusCode = (int)HttpStatusCode.OK; - response.ContentType = "text/xml"; - - await response.WriteAsync(""); - - foreach (Claim claim in context.User.Claims) - { - await response.WriteAsync($"{claim.Value}"); - } - - await response.WriteAsync(""); - } - else - { - await context.ChallengeAsync(); - } - }); - }).ConfigureServices(services => - { - if (configureOptions != null) - { - services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddMutualTls(options => - { - options.AllowedCertificateTypes = configureOptions.AllowedCertificateTypes; - options.Events = configureOptions.Events; - options.ValidateCertificateUse = configureOptions.ValidateCertificateUse; - options.RevocationFlag = configureOptions.RevocationFlag; - options.RevocationMode = configureOptions.RevocationMode; - options.ValidateValidityPeriod = configureOptions.ValidateValidityPeriod; - }); - } - else - { - services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).AddCertificate(); - } - - if (wireUpHeaderMiddleware && !string.IsNullOrEmpty(headerName)) - { - services.AddCertificateForwarding(options => - { - options.CertificateHeader = headerName; - }); - } - }); - - var server = new TestServer(builder) - { - BaseAddress = baseAddress! - }; - - return server; - } - - private static class Certificates - { - private const string ServerEku = "1.3.6.1.5.5.7.3.1"; - private const string ClientEku = "1.3.6.1.5.5.7.3.2"; - - private static readonly X509KeyUsageExtension DigitalSignatureOnlyUsage = new(X509KeyUsageFlags.DigitalSignature, true); - - public static X509Certificate2 SelfSignedPrimaryRoot { get; } - - public static X509Certificate2 SignedSecondaryRoot { get; } - - public static X509Certificate2 SignedClient { get; } - - public static X509Certificate2 SelfSignedValidWithClientEku { get; } - - public static X509Certificate2 SelfSignedValidWithNoEku { get; } - - public static X509Certificate2 SelfSignedValidWithServerEku { get; } - - public static X509Certificate2 SelfSignedNotYetValid { get; } - - public static X509Certificate2 SelfSignedExpired { get; } - - static Certificates() - { - DateTimeOffset now = DateTimeOffset.UtcNow; - - SelfSignedPrimaryRoot = MakeCert("CN=Valid Self Signed Client EKU,OU=dev,DC=idunno-dev,DC=org", ClientEku, now); - - SignedSecondaryRoot = MakeCert("CN=Valid Signed Secondary Root EKU,OU=dev,DC=idunno-dev,DC=org", ClientEku, now); - - SelfSignedValidWithServerEku = MakeCert("CN=Valid Self Signed Server EKU,OU=dev,DC=idunno-dev,DC=org", ServerEku, now); - - SelfSignedValidWithClientEku = MakeCert("CN=Valid Self Signed Server EKU,OU=dev,DC=idunno-dev,DC=org", ClientEku, now); - - SelfSignedValidWithNoEku = MakeCert("CN=Valid Self Signed No EKU,OU=dev,DC=idunno-dev,DC=org", null, now); - - SelfSignedExpired = MakeCert("CN=Expired Self Signed,OU=dev,DC=idunno-dev,DC=org", null, now.AddYears(-2), now.AddYears(-1)); - - SelfSignedNotYetValid = MakeCert("CN=Not Valid Yet Self Signed,OU=dev,DC=idunno-dev,DC=org", null, now.AddYears(2), now.AddYears(3)); - - SignedClient = MakeCert("CN=Valid Signed Client,OU=dev,DC=idunno-dev,DC=org", ClientEku, now); - } - - private static X509Certificate2 MakeCert(string subjectName, string eku, DateTimeOffset now) - { - return MakeCert(subjectName, eku, now, now.AddYears(5)); - } - - private static X509Certificate2 MakeCert(string subjectName, string eku, DateTimeOffset notBefore, DateTimeOffset notAfter) - { - using var key = RSA.Create(2048); - var request = new CertificateRequest(subjectName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - - request.CertificateExtensions.Add(DigitalSignatureOnlyUsage); - - if (eku != null) - { - request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection - { - new(eku, null) - }, false)); - } - - return request.CreateSelfSigned(notBefore, notAfter); - } - } -} diff --git a/src/Security/test/Authentication.Mtls.Test/Steeltoe.Security.Authentication.Mtls.Test.csproj b/src/Security/test/Authentication.Mtls.Test/Steeltoe.Security.Authentication.Mtls.Test.csproj deleted file mode 100644 index fb6e2ff299..0000000000 --- a/src/Security/test/Authentication.Mtls.Test/Steeltoe.Security.Authentication.Mtls.Test.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - net8.0;net6.0 - - - - - - - - - - - - diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs b/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs new file mode 100644 index 0000000000..e1326b3af2 --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +global using Xunit; diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..d04a3e1a3c --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; + +public sealed class OpenIdConnectServiceCollectionExtensionsTest +{ + [Fact] + public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + + serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); + } + + [Fact] + public void ConfigureOpenIdConnectForCloudFoundry_ConfiguresHttpClient() + { + var serviceCollection = new ServiceCollection(); + + var serviceProvider = serviceCollection.ConfigureOpenIdConnectForCloudFoundry().BuildServiceProvider(); + var httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); + + httpClient.Timeout.Should().Be(new TimeSpan(0, 0, 0, 60)); + } +} diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs new file mode 100644 index 0000000000..623b03f699 --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Security.Claims; +using FluentAssertions; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Moq; +using Steeltoe.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry.ServiceBinding; +using Steeltoe.Security.Authentication.Shared; + +namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; + +public sealed class PostConfigureOpenIdConnectOptionsTest +{ + [Fact] + public void PostConfigure_AddsClientIdToValidAudiences() + { + var appSettings = new Dictionary + { + { "Authentication:Schemes:OpenIdConnect:Authority", "https://authority.com" }, + { "Authentication:Schemes:OpenIdConnect:ClientId", "testClient" }, + }; + IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(configuration); + serviceCollection.AddAuthentication().AddOpenIdConnect(); + OpenIdConnectOptions openIdConnectOptions = serviceCollection + .BuildServiceProvider().GetRequiredService>() + .Get(OpenIdConnectDefaults.AuthenticationScheme); + + var postConfigurer = new PostConfigureOpenIdConnectOptions(null!); + + postConfigurer.PostConfigure(OpenIdConnectDefaults.AuthenticationScheme, openIdConnectOptions); + + openIdConnectOptions.TokenValidationParameters.ValidAudience.Should().Be("testClient"); + } + + [Fact] + public void PostConfigure_ConfiguresForLocalUAA() + { + var openIdConnectOptions = new OpenIdConnectOptions + { + Authority = SteeltoeSecurityDefaults.LocalUAAPath, + Backchannel = new HttpClient() + }; + openIdConnectOptions.RequireHttpsMetadata.Should().BeTrue(); + openIdConnectOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().BeNull(); + + var postConfigure = new PostConfigureOpenIdConnectOptions(null!); + + postConfigure.PostConfigure(OpenIdConnectDefaults.AuthenticationScheme, openIdConnectOptions); + + openIdConnectOptions.RequireHttpsMetadata.Should().BeFalse(); + openIdConnectOptions.TokenValidationParameters.ValidIssuer.Should().Be($"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"); + openIdConnectOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + } + + [Fact] + public void PostConfigure_ConfiguresForCloudFoundry() + { + const string vcapServices = """ + { + "p-identity": [ + { + "label": "p-identity", + "provider": null, + "plan": "steeltoe", + "name": "mySSOService", + "tags": [], + "instance_guid": "ea8b8ac0-ce85-4726-8b39-d1b2eb55b45b", + "instance_name": "mySSOService", + "binding_guid": "be94e8e7-9246-49af-935f-5390ff10ac23", + "binding_name": null, + "credentials": { + "auth_domain": "https://steeltoe.login.sys.cf-app.com", + "grant_types": [ "authorization_code", "client_credentials" ], + "client_secret": "dd2c82e1-aa99-4eaf-9871-2eb7412b79bb", + "client_id": "4e6f8e34-f42b-440e-a042-f2b13c1d5bed" + }, + "syslog_drain_url": null, + "volume_mounts": [] + }] + } + """; + using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); + IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(configuration); + serviceCollection.AddHttpClient(); + serviceCollection.AddAuthentication().AddOpenIdConnect(); + serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + + OpenIdConnectOptions openIdConnectOptions = serviceCollection + .BuildServiceProvider().GetRequiredService>() + .Get(OpenIdConnectDefaults.AuthenticationScheme); + + openIdConnectOptions.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); + openIdConnectOptions.MetadataAddress.Should().Be("https://steeltoe.login.sys.cf-app.com/.well-known/openid-configuration"); + openIdConnectOptions.RequireHttpsMetadata.Should().BeTrue(); + openIdConnectOptions.TokenValidationParameters.ValidIssuer.Should().Be("https://steeltoe.login.sys.cf-app.com/oauth/token"); + openIdConnectOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + openIdConnectOptions.TokenValidationParameters.ValidAudience.Should().Be("4e6f8e34-f42b-440e-a042-f2b13c1d5bed"); + } + + ////[Fact] + ////public void MapScopesToClaims_MapsScopesToClaims() + ////{ + //// var claimsIdentity = new Mock(); + //// claimsIdentity. + //// var context = new Mock(); + //// context.Setup(ctx => ctx.Principal!.AddIdentity(claimsIdentity.Object)); + ////} +} diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj b/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj new file mode 100644 index 0000000000..77419105a7 --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + diff --git a/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs b/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs new file mode 100644 index 0000000000..e1326b3af2 --- /dev/null +++ b/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +global using Xunit; diff --git a/src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj b/src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj new file mode 100644 index 0000000000..3f73c8b45c --- /dev/null +++ b/src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj @@ -0,0 +1,13 @@ + + + net8.0 + enable + + + + + + + + + diff --git a/src/Security/test/Authentication.CloudFoundry.Test/TestMessageHandler.cs b/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs similarity index 74% rename from src/Security/test/Authentication.CloudFoundry.Test/TestMessageHandler.cs rename to src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs index fc328d9fa3..15e6e4a869 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestMessageHandler.cs +++ b/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs @@ -4,11 +4,11 @@ using System.Net; -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; +namespace Steeltoe.Security.Authentication.Shared.Test; -public sealed class TestMessageHandler : HttpMessageHandler +internal sealed class TestMessageHandler : HttpMessageHandler { - public HttpRequestMessage LastRequest { get; set; } + public HttpRequestMessage? LastRequest { get; private set; } public HttpResponseMessage Response { get; set; } = new(HttpStatusCode.OK); diff --git a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenKeyResolverTest.cs b/src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs similarity index 72% rename from src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenKeyResolverTest.cs rename to src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs index 88a30fcd1b..e3ae24505e 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs @@ -4,16 +4,16 @@ using System.Net; using Microsoft.IdentityModel.Tokens; -using Xunit; -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; +namespace Steeltoe.Security.Authentication.Shared.Test; -public sealed class CloudFoundryTokenKeyResolverTest +public sealed class TokenKeyResolverTest { [Fact] public void Constructor_ThrowsIfOptionsNull() { - Assert.Throws(() => new CloudFoundryTokenKeyResolver(null, null, false)); + Assert.Throws(() => new TokenKeyResolver(null!, new HttpClient())); + Assert.Throws(() => new TokenKeyResolver("someAuthority", null!)); } [Fact] @@ -39,12 +39,12 @@ public void ResolveSigningKey_FindsExistingKey() var keys = JsonWebKeySet.Create(keySet); JsonWebKey webKey = keys.Keys[0]; - CloudFoundryTokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.Resolved.Clear(); - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", null, false); - CloudFoundryTokenKeyResolver.Resolved["legacy-token-key"] = webKey; + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); + TokenKeyResolver.Resolved["legacy-token-key"] = webKey; - IEnumerable result = resolver.ResolveSigningKey(token, null, "legacy-token-key", null); + IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); Assert.True(result.First() == webKey); } @@ -77,12 +77,12 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() handler.Response = response; - CloudFoundryTokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.Resolved.Clear(); - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", handler, true); - IEnumerable result = resolver.ResolveSigningKey(token, null, "legacy-token-key", null); + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); Assert.NotNull(handler.LastRequest); - Assert.NotNull(CloudFoundryTokenKeyResolver.Resolved["legacy-token-key"]); + Assert.NotNull(TokenKeyResolver.Resolved["legacy-token-key"]); Assert.NotNull(result); } @@ -115,13 +115,13 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() handler.Response = response; - CloudFoundryTokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.Resolved.Clear(); - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", handler, true); - IEnumerable result = resolver.ResolveSigningKey(token, null, "legacy-token-key", null); + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); Assert.NotNull(handler.LastRequest); - Assert.False(CloudFoundryTokenKeyResolver.Resolved.ContainsKey("legacy-token-key")); - Assert.Null(result); + Assert.False(TokenKeyResolver.Resolved.ContainsKey("legacy-token-key")); + Assert.Empty(result); } [Fact] @@ -141,65 +141,18 @@ public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() ] }"; - var handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) + var handler = new TestMessageHandler { - Content = new StringContent(keySet) + Response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(keySet) + } }; - handler.Response = response; + TokenKeyResolver.Resolved.Clear(); - CloudFoundryTokenKeyResolver.Resolved.Clear(); - - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", handler, true); - JsonWebKeySet result = await resolver.FetchKeySetAsync(); + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + JsonWebKeySet? result = await resolver.FetchKeySetAsync(); Assert.NotNull(result); } - - [Fact] - public void GetJsonWebKey_DecodesValidJson() - { - CloudFoundryTokenKeyResolver.Resolved.Clear(); - - const string webKey = @"{ - ""keys"": [ - { - ""kid"": ""legacy-token-key"", - ""alg"": ""SHA256withRSA"", - ""value"": ""-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk+7xH35bYBppsn54cBW+\nFlrveTe+3L4xl7ix13XK8eBcCmNOyBhNzhks6toDiRjrgw5QW76cFirVRFIVQkiZ\nsUwDyGOax3q8NOJyBFXiplIUScrx8aI0jkY/Yd6ixAc5yBSBfXThy4EF9T0xCyt4\nxWLYNXMRwe88Y+i+MEoLNXWRbhjJm76LN7rsdIxALbS0vJNWUDALWjtE6FeYX6uU\nL9msAzlCQkdnSvwMmr8Ij2O3IVMxHDJXOZinFqt9zVfXwO11o7ZmiskZnRz1/V0f\nvbUQAadkcDEUt1gk9cbrAhiipg8VWDMsC7VUXuekJZjme5f8oWTwpsgP6cTUzwSS\n6wIDAQAB\n-----END PUBLIC KEY-----"", - ""kty"": ""RSA"", - ""use"": ""sig"", - ""n"": ""AJPu8R9+W2AaabJ+eHAVvhZa73k3vty+MZe4sdd1yvHgXApjTsgYTc4ZLOraA4kY64MOUFu+nBYq1URSFUJImbFMA8hjmsd6vDTicgRV4qZSFEnK8fGiNI5GP2HeosQHOcgUgX104cuBBfU9MQsreMVi2DVzEcHvPGPovjBKCzV1kW4YyZu+ize67HSMQC20tLyTVlAwC1o7ROhXmF+rlC/ZrAM5QkJHZ0r8DJq/CI9jtyFTMRwyVzmYpxarfc1X18DtdaO2ZorJGZ0c9f1dH721EAGnZHAxFLdYJPXG6wIYoqYPFVgzLAu1VF7npCWY5nuX/KFk8KbID+nE1M8Ekus="", - ""e"": ""AQAB"" - } - ] -}"; - - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", null, false); - JsonWebKeySet webKeySet = resolver.GetJsonWebKeySet(webKey); - Assert.NotNull(webKeySet); - Assert.NotNull(webKeySet.Keys); - Assert.Single(webKeySet.Keys); - } - - [Fact] - public void GetHttpClient_AddsHandler() - { - var handler = new TestMessageHandler(); - - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", handler, false); - HttpClient client = resolver.GetHttpClient(); - client.GetAsync(new Uri("http://localhost/")); - Assert.NotNull(handler.LastRequest); - } - - [Fact] - public void HttpClient_HasAtLeast_Default100secondsTimeout() - { - var resolver = new CloudFoundryTokenKeyResolver("https://foo.bar", null, false); - HttpClient client = resolver.GetHttpClient(); - - Assert.True(client.Timeout >= TimeSpan.FromSeconds(100)); - } } diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs new file mode 100644 index 0000000000..2ebbb2650d --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; +using Steeltoe.Common.Security; +using Steeltoe.Common.TestResources; + +namespace Steeltoe.Security.Authorization.Certificate.Test; + +public sealed class CertificateAuthorizationTest(ClientCertificatesFixture fixture) : IClassFixture +{ + [Fact] + public async Task CertificateAuth_AcceptsSameSpace() + { + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch).GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_AcceptsSameOrg() + { + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + var httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch); + HttpResponseMessage response = await httpClient.GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_RejectsOrgMismatch() + { + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.SpaceMatch).GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_RejectsSpaceMismatch() + { + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgMatch).GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_ForbiddenWithoutCert() + { + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"http://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + HttpResponseMessage response = await host.GetTestClient().GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_AcceptsSameSpace_DiegoCert() + { + using var appScope = new EnvironmentVariableScope("VCAP_APPLICATION", "not empty"); + using var certScope = new EnvironmentVariableScope("CF_INSTANCE_CERT", "instance.crt"); + using var keyScope = new EnvironmentVariableScope("CF_INSTANCE_KEY", "instance.key"); + using var caScope = new EnvironmentVariableScope("CF_SYSTEM_CERT_PATH", Path.Join(LocalCertificateWriter.ApplicationBasePath, "root_certificates")); + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego).GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() + { + using var appScope = new EnvironmentVariableScope("VCAP_APPLICATION", "not empty"); + using var certScope = new EnvironmentVariableScope("CF_INSTANCE_CERT", "instance.crt"); + using var keyScope = new EnvironmentVariableScope("CF_INSTANCE_KEY", "instance.key"); + using var caScope = new EnvironmentVariableScope("CF_SYSTEM_CERT_PATH", Path.Join(LocalCertificateWriter.ApplicationBasePath, "root_certificates")); + + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + var httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); + HttpResponseMessage response = await httpClient.GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + // MIIECDCCAvCgAwIBAgIRAKiNf1klu0/IdgHxirEAxJkwDQYJKoZIhvcNAQELBQAwMjEwMC4GA1UEAxMnRGllZ28gSW5zdGFuY2UgSWRlbnRpdHkgSW50ZXJtZWRpYXRlIENBMB4XDTI0MDUyODIwNTkyNloXDTI0MDUyOTIwNTkyNlowgcgxgZ4wLwYDVQQLEyhhcHA6ZmU5MjUzYzYtNTQ5Yi00Mzg1LWI2OWQtMmVjMDMyOGViMjk2MDEGA1UECxMqc3BhY2U6YWI2MGFhYzItZmI2NC00M2FiLWJhMjQtYzU3YTE1YTdlMTE0MDgGA1UECxMxb3JnYW5pemF0aW9uOjdmZTRkMDI3LTIwNTgtNDUzOS1hNDBjLTcwMmFjMTM3MzkwNTElMCMGA1UEAxMcOGE3YzE4ZmUtYWI1NC00MDlmLTU5YTctNzhmYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMj1sd7l3cRgxvxwlH/N3wW9SiRoT9fv0DZ0D1x5SM7bO4kHryOM4DyrF5YmJVICfgGJ5qI8hx8ohJD+MSam68gt4v1Isy5mcL7j4yMfRLN63TVM1w1vikTtxRa/cCWPl5ex+whKiGmu7j5fksntIzaW60wgx+TKOYV5Ukr40nKkmqytpqUIiG1QlvdptZjRP8WblaQ5zRyskIm33qTqjeaz/HgKmcsCLfN3+EgvIG0Oq8kqXW65BAzZJwXsy1cbyrG6tqKKL9foXKfkecnD9oIL46hZZk8lOdQO9UI0c8PA+FpMzBgftaT3MtxlTuxH8zRAAX0rRmSyxILiI0NWxBcCAwEAAaOBgTB/MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUSr6rDD3+zF8WowftQLoFo+ldyhswLQYDVR0RBCYwJIIcOGE3YzE4ZmUtYWI1NC00MDlmLTU5YTctNzhmYYcECv+J+DANBgkqhkiG9w0BAQsFAAOCAQEAFA1Px9XzccEWv1tvtFdJ8G6KHXOeOO5009RqdrUepHQLXIxUqs1VzYvmXv9FiG/Ahf4UyS/KrhX7gNgPN3562tgmiYRzUKSPY5qR6dFXydo+9GoDsx2r64JmX2OTdkHTFk2g0dnUWkMKmhlSHnYI+X9lvvAkKbQDwo+1qVCvjdX55POmHVWQxwDZnnO381F0OAg4Wq89hIfXodPCK9cmBUd0fjpJikAq4064wxVpt/IQrs9RLtN3nhLhHvzK5UkTuI22gyrZHF+4mnB0NfpxQRxnDApHGKDOWrN5g1FltXLiS8rX1znYFIZ0XbRXqC9jS0SJHVZt9QIdVxwTugNLHQ== + + + private IHostBuilder GetHostBuilder() + { + return new HostBuilder() + .ConfigureAppConfiguration(builder => builder.AddContainerIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)) + .ConfigureWebHostDefaults(webHost => webHost.UseStartup()).ConfigureWebHost(webBuilder => webBuilder.UseTestServer()); + } + + private static HttpClient ClientWithCertificate(HttpClient httpClient, X509Certificate certificate) + { + byte[] bytes = certificate.GetRawCertData(); + string b64 = Convert.ToBase64String(bytes); + httpClient.DefaultRequestHeaders.Add(CertificateAuthorizationDefaults.ClientCertificateHeader, b64); + return httpClient; + } + + private static class Certificates + { + private static readonly Func GetFilePath = fileName => Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", fileName); + + public static X509Certificate2 OrgAndSpaceMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("OrgAndSpaceMatchCert.pem"), GetFilePath("OrgAndSpaceMatchKey.pem")); + + public static X509Certificate2 OrgMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("OrgMatchCert.pem"), GetFilePath("OrgMatchKey.pem")); + + public static X509Certificate2 SpaceMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("SpaceMatchCert.pem"), GetFilePath("SpaceMatchKey.pem")); + + public static X509Certificate2 FromDiego { get; } = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); + } +} diff --git a/src/Security/test/Authentication.CloudFoundry.Test/ClientCertificatesFixture.cs b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs similarity index 94% rename from src/Security/test/Authentication.CloudFoundry.Test/ClientCertificatesFixture.cs rename to src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs index f54d2b8333..39d85f5bd8 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/ClientCertificatesFixture.cs +++ b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs @@ -4,7 +4,7 @@ using Steeltoe.Common.Security; -namespace Steeltoe.Security.Authentication.CloudFoundry.Test; +namespace Steeltoe.Security.Authorization.Certificate.Test; public sealed class ClientCertificatesFixture : IDisposable { diff --git a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs new file mode 100644 index 0000000000..e1326b3af2 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +global using Xunit; diff --git a/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj b/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj new file mode 100644 index 0000000000..d76ca79564 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/Steeltoe.Security.Authorization.Certificate.Test.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs new file mode 100644 index 0000000000..e2ea1d2825 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Steeltoe.Security.Authorization.Certificate.Test; + +public sealed class TestServerCertificateStartup(IConfiguration configuration) +{ + internal IConfiguration Configuration { get; } = configuration; + + public void ConfigureServices(IServiceCollection services) + { + services.AddCertificateAuthorizationServer(Configuration); + services + .AddAuthentication() + .AddCertificate(options => + { + options.ValidateValidityPeriod = false; + options.Events = new() + { + OnCertificateValidated = context => + { + return Task.CompletedTask; + } + }; + }); + services.AddAuthorization(options => + { + options.AddPolicy(CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy, authorizationPolicyBuilder => + { + authorizationPolicyBuilder.SameOrg(); + }); + + options.AddPolicy(CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy, authorizationPolicyBuilder => + { + authorizationPolicyBuilder.SameSpace(); + }); + }); + } + + public void Configure(IApplicationBuilder app, IAuthorizationService authorizationService) + { + app.UseCertificateAuthorization(); + + app.Run(async context => + { + AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync(context.User, null, + context.Request.Path.Value!.Replace("/", string.Empty, StringComparison.Ordinal)); + + if (!authorizationResult.Succeeded) + { + await context.ChallengeAsync(); + return; + } + + context.Response.StatusCode = 200; + }); + } +} diff --git a/src/Security/test/Authorization.Certificate.Test/instance.crt b/src/Security/test/Authorization.Certificate.Test/instance.crt new file mode 100644 index 0000000000..4f1a46ce9e --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/instance.crt @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIQawagEk2HSdppbry1fi5NbjANBgkqhkiG9w0BAQsFADAy +MTAwLgYDVQQDEydEaWVnbyBJbnN0YW5jZSBJZGVudGl0eSBJbnRlcm1lZGlhdGUg +Q0EwHhcNMjQwNjAzMTQwMjA4WhcNMjQwNjA0MTQwMjA4WjCByDGBnjAvBgNVBAsT +KGFwcDplNTk1MDI3NS1hZmNlLTQ4MGEtYTAxNy1iYWJhZmQzZDU3OTgwMQYDVQQL +EypzcGFjZTphYjYwYWFjMi1mYjY0LTQzYWItYmEyNC1jNTdhMTVhN2UxMTQwOAYD +VQQLEzFvcmdhbml6YXRpb246N2ZlNGQwMjctMjA1OC00NTM5LWE0MGMtNzAyYWMx +MzczOTA1MSUwIwYDVQQDExwzNGM5NzY1ZC1iNWRhLTQ5YTctNGE1My0xYzU4MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtCN0/EzldYnbFytKtmTxyxzk +AlhDYgGw+zX7IqoKMp1R6tfRok8WkqGUQAOUK3x81P+3xhdcIgfFgqGMQNp/FM4L +9lhYn6deJUtS9py7Rnv6jJNwSSbKHWrZPg7Raera3Dxn6hyCUi+Li5+4KviwAZFO ++WS9oO017U1TIRLk+VayAPxDZZz0FA7wt60m2KR77m6tSpayFxB81F1cVMbJa3Ln +mGSjsW3KZrW3Dx7uVEGBFRZxtJ3oMLRQtQnc7Oyv6eeOa1B4Z7/dYx5SgxdL5AhL +zKVqfgC6CP5BJL9PcPRjG/umNEHcMTR3dg+NoA8Neoc4RVDcWdUCJXbbMUN+WwID +AQABo4GBMH8wDgYDVR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr +BgEFBQcDATAfBgNVHSMEGDAWgBRKvqsMPf7MXxajB+1AugWj6V3KGzAtBgNVHREE +JjAkghwzNGM5NzY1ZC1iNWRhLTQ5YTctNGE1My0xYzU4hwQK/4leMA0GCSqGSIb3 +DQEBCwUAA4IBAQCyrjn/AnffIabvQfxE/bETvPfxXAGi88VCPWJv+kSeN4PV5LfZ +EvMd/oSKrJKx4ET8d9kJjQ/LekApDlP1JPYa1WlHvSOIaCEtWLuVZTf53WsRwHD6 +fhFs/x1w5xc74aGsb1raktEAzWKx6lVVxT2sKOyL/CxO9geCWz3606L8CI9P0qq9 +bMZ1Dohw2oYNIDj0lS7QnVOGd05QG+0Qrw2RHwcNRnKCtmqnGuz6nD4heZFj9htV +2RTDoc9jxl8AniXIKbSaARDUGSTpTMeImW/Et7wmcE/RM0gbu8y7LexekOjIvcYl +4bAnm1ngfLBbOSuJdF1vyqDAqrd3f1h/uNgV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIUbA3Xig7xIhr8m4UeSX3XnKThjCUwDQYJKoZIhvcNAQEL +BQAwKjEoMCYGA1UEAxMfRGllZ28gSW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTAe +Fw0yMTEyMDIwMTA0MTRaFw0zMTExMzAwMTA0MTRaMDIxMDAuBgNVBAMTJ0RpZWdv +IEluc3RhbmNlIElkZW50aXR5IEludGVybWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALuUA0zHNxm+JNam6HS7kp1qkVQKh6zFHSYUGhVS +Qet7suQ3Rlh1ETqZYJODOs2eGdSLRlOg+YBHesu45aTiItDVjFlW4yvobyEL2R/n +1p+wt3LonkbephnPfa7r/898fuybtMJOjAt3dtoHVlATCAP/5MNWTBpRoPAXk/RF +3Kuv6L0dWdXVqoD10dMNs06imvHS0ISBa2qNiX5gDgJqBeJx7VKtFg4htoczJPns +twqyiL5qPH5NlSPt9DTXPAOMzec3h0pA0V5lOggJhZ7qc+x2W0r4Ck1VnmTgY5ME +1mLBB2fq/F9bFBgHLqkh1dzMyXDA2T+H6sNmFt0FI7szQZsCAwEAAaNjMGEwHQYD +VR0OBBYEFEq+qww9/sxfFqMH7UC6BaPpXcobMA4GA1UdDwEB/wQEAwICBDAfBgNV +HSMEGDAWgBTeujdE0yXCEMTEq2vYQYQAD+ejKDAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQAeRE8W7T4uLI5LFHMc1/sKMdiownKVmMqeTD4IkmoP +T4C9yGf5HaVpArTLiW5Hw8Jo4Sed71PkjOgYm4qUZHW6CORtLGExO26qtL3YuPyn +i1VGI3jFG1t49HPFbF9G/QyICrLvRxvqLjhQ2i29sHSDXrbXDggcdcF0stzzz99o +QSM1zAYRdvw7f+PjluTvHLyXTgLRfl8J8UGCTMX/59LvSP2kgiQAiitlYDvfj/W6 +u9JvrYURQSprpcZqTBKE4rSzYPU93oS4Vlqemq5pylaL7lpClnA75n2IgTmnZla4 +l033+vtgSaCFQR4La8n8GBKZBHVwqJcpVp8W2qXyz4G/ +-----END CERTIFICATE----- diff --git a/src/Security/test/Authorization.Certificate.Test/instance.key b/src/Security/test/Authorization.Certificate.Test/instance.key new file mode 100644 index 0000000000..84ada49bad --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/instance.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtCN0/EzldYnbFytKtmTxyxzkAlhDYgGw+zX7IqoKMp1R6tfR +ok8WkqGUQAOUK3x81P+3xhdcIgfFgqGMQNp/FM4L9lhYn6deJUtS9py7Rnv6jJNw +SSbKHWrZPg7Raera3Dxn6hyCUi+Li5+4KviwAZFO+WS9oO017U1TIRLk+VayAPxD +ZZz0FA7wt60m2KR77m6tSpayFxB81F1cVMbJa3LnmGSjsW3KZrW3Dx7uVEGBFRZx +tJ3oMLRQtQnc7Oyv6eeOa1B4Z7/dYx5SgxdL5AhLzKVqfgC6CP5BJL9PcPRjG/um +NEHcMTR3dg+NoA8Neoc4RVDcWdUCJXbbMUN+WwIDAQABAoIBAFHBhea8P6gI2Ra6 +3z3IsrSseA9YX7yrZ9ebuE8TPW2bWQJs0QgJPQVvsAkdlrHl3gINzSpqj7YiMNII +387PW73lhihYEGDlZSUn+o9SsuxaeGw0vlF0+WZAiDhSIVkg7hPmloL6TWtfiMXl +x4A5Pih3+o/V2Pqgrkj1PzVTjfhbTVXzbuUA+xlKjYOZWDv7B174JItnt7sjQp3F +6iWYqdRnWT4XZT5Nt0lEUNtyKref2zHNZCs29nYG/DDXa6z3YYrxeyIZEHw+wyNj +PbjmmT0n6NZ+InlUVqAiZ2wpcCEW+BZ1tT/HEAWG6rkG5I2MwqN+lQt+7N6S+C2y +8A7JvIECgYEA4txS1WpxeU+N6/gLSUuv9vCmB3yT/Sdgrx0leuGxMhhVBJwQY/x4 +sR5G0x3tebAt5y9GLgUvSaWKZgWZaFswBR8d3KTNuma45gyt6fnChE+CHv26TMWC +ESVoXskxxijisy0/aAZGujHthawqWj/CDYcC1W4koAB3vgd4fLS+35UCgYEAy0bO +kbZ0U5HqlRU6WDP3qG8fPG1fFQg98CJIMyY4Be4HUp7Rz2zpEeyelo1UGuiQenxk +hmZZz5f+GzqhpbGFdXqdk0+h15J0LJzA0g3oPzdsXzkrWUjBLFyw8p6cBw5yJFF9 +wpug73zOuQ2iS5MamM4wCPfGiO++StvvhBDiKi8CgYEArMUslHn/N2sd46LBPa3V +shPt7e+zaO2vVU22OJJfd45OWTddyDgD1qf/OlMlgzJokxNOuEecjtLyxuXmwjII +LS5YeKxOPXJzyEfJv1JGEKvYpi+HzWXxu4sopF9Hd+m1VM3V5yw4ex8BDHdkvdym +tdSnlNHQNMaGawOXgCnK380CgYAPOe2NMpqCDAMDScTGCJZ7cl9nmlWt8KEbFKZN +1oYJw6uev7C9lc3bftreMhXjshnoYtrwykfd6eepyHmFPMffZeDZwPRBfoHfOZqj +4VDNM2yVsWWS2YVGumaytbjAOo/IMqZ15kxmw/WEHQZUHN+4JHGqqkyfwz2Aw/qr +7MvUFwKBgQCD0FardMW57DC3PNJDxAjh+Y+ZJsk8kfxj/r2aqW9xOxg5dMzBbuzr +B3onLFqCZpRDZPg5PfhlliZBtdCjpYWwfbNXrGHlJ96htNmNi0bMq6YWSjvKndPW ++UMRq5hvnQmUBjlsRolsac51KC3HZICLMue6f3mbPxoUpYQwC8LQwg== +-----END RSA PRIVATE KEY----- diff --git a/src/Security/test/Authorization.Certificate.Test/root_certificates/ca1.crt b/src/Security/test/Authorization.Certificate.Test/root_certificates/ca1.crt new file mode 100644 index 0000000000..7bd8195a75 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/root_certificates/ca1.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUJKHWyFnKK9GQoDGrdfC8vpkwKRYwDQYJKoZIhvcNAQEL +BQAwKjEoMCYGA1UEAxMfRGllZ28gSW5zdGFuY2UgSWRlbnRpdHkgUm9vdCBDQTAe +Fw0yMTEyMDIwMTA0MTRaFw0zMTExMzAwMTA0MTRaMCoxKDAmBgNVBAMTH0RpZWdv +IEluc3RhbmNlIElkZW50aXR5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCxvE0Qhido2Hqtq/CmLXlP8t77/Ze2mk7JByRupX2sHua7RiPl +KLKMxA8vAJllTmajEGG1gYhowi2ifaJkwut5XRWPQElBApJqBCU19FPxJD2Po0CV +8UcADxhTiHok46zTi5Qzj6yijKfkIjD1heWhZwEXz9cB+bVP5KRMbJ2GUgiVyPhk +wCX4LYvE5+V6YT7ou5O7syxHczz74LSO247xEh04jC9EReEz0zKtFjcqsIat7Y3P +ZJyhLpAFiJbU5ik2hiFKc1dZtNPiZV9KuRC/WmkV7QVBLTfMqT0PWxeTQetYo5Tn +32UjPaeGyXs3vZ/CZq1NHencYahPPjZkumwXAgMBAAGjUzBRMB0GA1UdDgQWBBTe +ujdE0yXCEMTEq2vYQYQAD+ejKDAfBgNVHSMEGDAWgBTeujdE0yXCEMTEq2vYQYQA +D+ejKDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAIhkFc2Gcq +GWx3JmAl61ZZtzXhYivNwdGEJU/zh+qtsf4N7Ix6PxDYmr6l1dxBkBgBMzvfXfEN +Aa92ozaJuBHtbUn4xn/hbeSl0O0+lgwHoG0QhTPf8pb0nufWT+KpOapJ/Tvl+zJT +Zp8lL9fDgC8KJu19LPaNkKkuL4cfqEG+WMWtcPtE3uKE1yzu16MvxBpVhbX6Dyvy +D1fLIwY3r9dUj/EtT97JJzJVfHhBBwVaOfRp+94o7RYmR8zpcYzCS5xHtvzR8hLA +3bhmrVvAfiuzIuYGMZ8oQNM+/8+HeokAPfaUmkpqJ3KLaycEXsM4MknkP/BQtV/E +prRbjAssnUdV +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index ff761882da..80f8ada424 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -121,12 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Tracing EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Management.Task.Test", "Management\test\Task.Test\Steeltoe.Management.Task.Test.csproj", "{A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.CloudFoundry", "Security\src\Authentication.CloudFoundry\Steeltoe.Security.Authentication.CloudFoundry.csproj", "{7E904751-A3CF-47EA-AB81-4FF0B6377244}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProtection.Redis", "Security\src\DataProtection.Redis\Steeltoe.Security.DataProtection.Redis.csproj", "{31E52250-3422-49E9-9605-EBE786CC1BE3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.CloudFoundry.Test", "Security\test\Authentication.CloudFoundry.Test\Steeltoe.Security.Authentication.CloudFoundry.Test.csproj", "{3F235016-1E47-4BB7-AB68-F1900E8CF951}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProtection.Redis.Test", "Security\test\DataProtection.Redis.Test\Steeltoe.Security.DataProtection.Redis.Test.csproj", "{6D09B0D3-AF28-4FC4-A67D-424E7746682B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.TestResources", "Common\test\Common.TestResources\Steeltoe.Common.TestResources.csproj", "{65CF716D-9215-475C-B37F-CA943F5CD6A3}" @@ -145,14 +141,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.Abstractio EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.Abstractions", "Connectors\src\Abstractions\Steeltoe.Connectors.Abstractions.csproj", "{0FEEB397-0A2B-4E24-A5AE-B91A56303C98}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.Mtls", "Security\src\Authentication.Mtls\Steeltoe.Security.Authentication.Mtls.csproj", "{BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.Mtls.Test", "Security\test\Authentication.Mtls.Test\Steeltoe.Security.Authentication.Mtls.Test.csproj", "{0134F5C0-7595-497E-91BE-CCCD2290CF8E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.CloudFoundry", "Connectors\src\CloudFoundry\Steeltoe.Connectors.CloudFoundry.csproj", "{AFD6152E-A432-4CD4-AB73-A5DC2411EAB1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.CloudFoundry.Test", "Connectors\test\CloudFoundry.Test\Steeltoe.Connectors.CloudFoundry.Test.csproj", "{7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Connectors.Test", "Connectors\test\Connectors.Test\Steeltoe.Connectors.Test.csproj", "{C3232459-EE86-430B-B310-EB32C991A11A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.DynamicSerilog", "Logging\src\DynamicSerilog\Steeltoe.Logging.DynamicSerilog.csproj", "{E0B1415D-8D97-4BA5-9315-82E7D33E3933}" @@ -225,6 +213,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Discovery.Configur EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.ConfigServer.Discovery.Test", "Configuration\test\ConfigServer.Discovery.Test\Steeltoe.Configuration.ConfigServer.Discovery.Test.csproj", "{10EC7705-BE9E-4E2A-B174-E74D1CC9852F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.OpenIdConnect", "Security\src\Authentication.OpenIdConnect\Steeltoe.Security.Authentication.OpenIdConnect.csproj", "{AF26B4AC-35B5-4954-ADE8-97BA2DC31250}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.OpenIdConnect.Test", "Security\test\Authentication.OpenIdConnect.Test\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", "{DAC8173E-F37E-4895-8BCD-441BDA001A3A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.JwtBearer", "Security\src\Authentication.Jwt\Steeltoe.Security.Authentication.JwtBearer.csproj", "{5EBE664D-D071-43A9-9B53-346BE3DCCB41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.JwtBearer.Test", "Security\test\Authentication.JwtBearer.Test\Steeltoe.Security.Authentication.JwtBearer.Test.csproj", "{196A7EF1-56CE-481E-BB49-97BD0FF096BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authorization.Certificate", "Security\src\Authorization.Certificate\Steeltoe.Security.Authorization.Certificate.csproj", "{B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authorization.Certificate.Test", "Security\test\Authorization.Certificate.Test\Steeltoe.Security.Authorization.Certificate.Test.csproj", "{AAA51F15-E5BE-4E77-92DF-1992434D557A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.Shared", "Security\src\Authentication.Shared\Steeltoe.Security.Authentication.Shared.csproj", "{A389FBD9-618E-4180-AB3B-64E584DAB078}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.Shared.Test", "Security\test\Authentication.Shared.Test\Steeltoe.Security.Authentication.Shared.Test.csproj", "{14205F79-098F-4372-8336-4434F2B2433C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -367,18 +371,10 @@ Global {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C}.Release|Any CPU.Build.0 = Release|Any CPU - {7E904751-A3CF-47EA-AB81-4FF0B6377244}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E904751-A3CF-47EA-AB81-4FF0B6377244}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E904751-A3CF-47EA-AB81-4FF0B6377244}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E904751-A3CF-47EA-AB81-4FF0B6377244}.Release|Any CPU.Build.0 = Release|Any CPU {31E52250-3422-49E9-9605-EBE786CC1BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31E52250-3422-49E9-9605-EBE786CC1BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {31E52250-3422-49E9-9605-EBE786CC1BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {31E52250-3422-49E9-9605-EBE786CC1BE3}.Release|Any CPU.Build.0 = Release|Any CPU - {3F235016-1E47-4BB7-AB68-F1900E8CF951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F235016-1E47-4BB7-AB68-F1900E8CF951}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F235016-1E47-4BB7-AB68-F1900E8CF951}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F235016-1E47-4BB7-AB68-F1900E8CF951}.Release|Any CPU.Build.0 = Release|Any CPU {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Debug|Any CPU.Build.0 = Debug|Any CPU {6D09B0D3-AF28-4FC4-A67D-424E7746682B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -415,22 +411,6 @@ Global {0FEEB397-0A2B-4E24-A5AE-B91A56303C98}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FEEB397-0A2B-4E24-A5AE-B91A56303C98}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FEEB397-0A2B-4E24-A5AE-B91A56303C98}.Release|Any CPU.Build.0 = Release|Any CPU - {BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC}.Release|Any CPU.Build.0 = Release|Any CPU - {0134F5C0-7595-497E-91BE-CCCD2290CF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0134F5C0-7595-497E-91BE-CCCD2290CF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0134F5C0-7595-497E-91BE-CCCD2290CF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0134F5C0-7595-497E-91BE-CCCD2290CF8E}.Release|Any CPU.Build.0 = Release|Any CPU - {AFD6152E-A432-4CD4-AB73-A5DC2411EAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AFD6152E-A432-4CD4-AB73-A5DC2411EAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AFD6152E-A432-4CD4-AB73-A5DC2411EAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AFD6152E-A432-4CD4-AB73-A5DC2411EAB1}.Release|Any CPU.Build.0 = Release|Any CPU - {7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E}.Release|Any CPU.Build.0 = Release|Any CPU {C3232459-EE86-430B-B310-EB32C991A11A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3232459-EE86-430B-B310-EB32C991A11A}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3232459-EE86-430B-B310-EB32C991A11A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -523,6 +503,38 @@ Global {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Debug|Any CPU.Build.0 = Debug|Any CPU {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Release|Any CPU.ActiveCfg = Release|Any CPU {10EC7705-BE9E-4E2A-B174-E74D1CC9852F}.Release|Any CPU.Build.0 = Release|Any CPU + {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF26B4AC-35B5-4954-ADE8-97BA2DC31250}.Release|Any CPU.Build.0 = Release|Any CPU + {DAC8173E-F37E-4895-8BCD-441BDA001A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAC8173E-F37E-4895-8BCD-441BDA001A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAC8173E-F37E-4895-8BCD-441BDA001A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAC8173E-F37E-4895-8BCD-441BDA001A3A}.Release|Any CPU.Build.0 = Release|Any CPU + {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EBE664D-D071-43A9-9B53-346BE3DCCB41}.Release|Any CPU.Build.0 = Release|Any CPU + {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {196A7EF1-56CE-481E-BB49-97BD0FF096BE}.Release|Any CPU.Build.0 = Release|Any CPU + {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4}.Release|Any CPU.Build.0 = Release|Any CPU + {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAA51F15-E5BE-4E77-92DF-1992434D557A}.Release|Any CPU.Build.0 = Release|Any CPU + {A389FBD9-618E-4180-AB3B-64E584DAB078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A389FBD9-618E-4180-AB3B-64E584DAB078}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A389FBD9-618E-4180-AB3B-64E584DAB078}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A389FBD9-618E-4180-AB3B-64E584DAB078}.Release|Any CPU.Build.0 = Release|Any CPU + {14205F79-098F-4372-8336-4434F2B2433C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14205F79-098F-4372-8336-4434F2B2433C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14205F79-098F-4372-8336-4434F2B2433C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14205F79-098F-4372-8336-4434F2B2433C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -562,9 +574,7 @@ Global {C2EFCD80-C96D-4A09-BFD6-02CB4603961C} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} {C437628B-43EE-4AAD-83ED-DDE7499E705B} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} {A4D6D08A-BC0C-4CAD-97E6-F4BAAA03928C} = {8BD7D5E5-D887-4BD7-9F42-725A8714F7BC} - {7E904751-A3CF-47EA-AB81-4FF0B6377244} = {5128206A-242E-4069-AD30-910EDC40B165} {31E52250-3422-49E9-9605-EBE786CC1BE3} = {5128206A-242E-4069-AD30-910EDC40B165} - {3F235016-1E47-4BB7-AB68-F1900E8CF951} = {5128206A-242E-4069-AD30-910EDC40B165} {6D09B0D3-AF28-4FC4-A67D-424E7746682B} = {5128206A-242E-4069-AD30-910EDC40B165} {65CF716D-9215-475C-B37F-CA943F5CD6A3} = {59874241-E276-4035-B31D-14924889A1C9} {BEB156E9-4E06-403A-B778-E7B092B79962} = {59874241-E276-4035-B31D-14924889A1C9} @@ -574,10 +584,6 @@ Global {5CC834D7-C501-4FEC-9244-E829584192FB} = {59874241-E276-4035-B31D-14924889A1C9} {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D} = {DDB99068-891F-4937-98B0-E72CC3F4964B} {0FEEB397-0A2B-4E24-A5AE-B91A56303C98} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {BC84AEA9-31BB-4D19-BC55-1B6EF6AD53FC} = {5128206A-242E-4069-AD30-910EDC40B165} - {0134F5C0-7595-497E-91BE-CCCD2290CF8E} = {5128206A-242E-4069-AD30-910EDC40B165} - {AFD6152E-A432-4CD4-AB73-A5DC2411EAB1} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} - {7B4E0A71-F3AE-4F7A-AA01-519DE4AFD08E} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} {C3232459-EE86-430B-B310-EB32C991A11A} = {3CBE0336-C3C8-4DC2-AE45-86FC1D1B3C25} {E0B1415D-8D97-4BA5-9315-82E7D33E3933} = {DDB99068-891F-4937-98B0-E72CC3F4964B} {6C116C3E-177A-44C0-A149-8DDB62812DC3} = {DDB99068-891F-4937-98B0-E72CC3F4964B} @@ -602,6 +608,14 @@ Global {EA3AFC13-2399-411E-AD20-C6677505615A} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} {5031A608-281E-4FAF-9501-1DBDAB645907} = {31BAEBB1-696E-44A1-B1EF-0D150E6D2559} {10EC7705-BE9E-4E2A-B174-E74D1CC9852F} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} + {AF26B4AC-35B5-4954-ADE8-97BA2DC31250} = {5128206A-242E-4069-AD30-910EDC40B165} + {DAC8173E-F37E-4895-8BCD-441BDA001A3A} = {5128206A-242E-4069-AD30-910EDC40B165} + {5EBE664D-D071-43A9-9B53-346BE3DCCB41} = {5128206A-242E-4069-AD30-910EDC40B165} + {196A7EF1-56CE-481E-BB49-97BD0FF096BE} = {5128206A-242E-4069-AD30-910EDC40B165} + {B0AC88EC-EB6C-4E73-B9D5-51DD213931B4} = {5128206A-242E-4069-AD30-910EDC40B165} + {AAA51F15-E5BE-4E77-92DF-1992434D557A} = {5128206A-242E-4069-AD30-910EDC40B165} + {A389FBD9-618E-4180-AB3B-64E584DAB078} = {5128206A-242E-4069-AD30-910EDC40B165} + {14205F79-098F-4372-8336-4434F2B2433C} = {5128206A-242E-4069-AD30-910EDC40B165} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB0245B9-2464-47F8-BE15-D80A7A2FA965} diff --git a/src/Steeltoe.All.sln.DotSettings b/src/Steeltoe.All.sln.DotSettings index 22172473f8..af86338c81 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -603,6 +603,7 @@ See the LICENSE file in the project root for more information. IP MQ OSX + UAA False <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> @@ -736,6 +737,7 @@ $left$ = $right$; True True True + True True True True diff --git a/versions.props b/versions.props index 56eee59e9e..072dbe47f1 100644 --- a/versions.props +++ b/versions.props @@ -68,6 +68,8 @@ --> 8.0.* + 8.0.* + 7.5.* 1.6.*-* 1.8.*-* 1.8.*