From 6e36ed135ba3586f93fd9b18496629285244dc48 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 14:27:02 -0500 Subject: [PATCH 01/28] depend more heavily on Microsoft libraries for auth - split auth types to separate packages to align with the Microsoft libraries they configure - delete Connectors.Abstractions and Connectors.CloudFoundry --- shared-package.props | 8 +- .../src/AutoConfiguration/BootstrapScanner.cs | 12 +- .../AutoConfiguration/PublicAPI.Unshipped.txt | 2 +- ...teeltoe.Bootstrap.AutoConfiguration.csproj | 6 +- .../SteeltoeAssemblyNames.cs | 2 +- .../HostBuilderExtensionsTest.cs | 6 +- ...oe.Bootstrap.AutoConfiguration.Test.csproj | 3 +- .../WebApplicationBuilderExtensionsTest.cs | 6 +- .../WebHostBuilderExtensionsTest.cs | 6 +- .../CertificateConfigurationExtensions.cs | 109 +++ .../ConfigurationExtensions.cs | 50 -- .../Common.Security/LocalCertificateWriter.cs | 96 +-- .../Properties/AssemblyInfo.cs | 6 +- .../Common.Security}/PublicAPI.Shipped.txt | 0 .../Common.Security/PublicAPI.Unshipped.txt | 8 + ...CertificateConfigurationExtensionsTest.cs} | 2 +- .../ConfigurationBuilderExtensions.cs | 2 + .../CloudFoundryPostProcessor.cs | 16 +- .../IdentityCloudFoundryPostProcessor.cs | 54 ++ ...ServiceBindingConfigurationProviderTest.cs | 27 +- .../PostProcessorsTest.cs | 31 + .../src/Abstractions/PublicAPI.Unshipped.txt | 7 - .../ServiceInfoCreatorAssemblyAttribute.cs | 25 - .../ServiceInfoFactoryAssemblyAttribute.cs | 20 - .../src/Abstractions/Services/IServiceInfo.cs | 14 - .../Services/IServiceInfoFactory.cs | 32 - .../src/Abstractions/Services/ServiceInfo.cs | 27 - .../Abstractions/Services/SsoServiceInfo.cs | 22 - .../src/Abstractions/Services/UriInfo.cs | 269 ------- .../Abstractions/Services/UriServiceInfo.cs | 42 -- .../Steeltoe.Connectors.Abstractions.csproj | 20 - .../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 - .../src/Connectors/Steeltoe.Connectors.csproj | 1 - .../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 +- src/Security/README.md | 2 +- .../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 | 42 -- .../OpenIdTokenResponse.cs | 28 - .../src/Authentication.CloudFoundry/Readme.md | 12 - .../ServiceCollectionExtensions.cs | 106 --- ...ecurity.Authentication.CloudFoundry.csproj | 22 - .../TokenExchanger.cs | 212 ------ .../JwtBearerServiceCollectionExtensions.cs | 43 ++ .../PostConfigureJwtBearerOptions.cs | 48 ++ .../Properties/AssemblyInfo.cs | 2 +- .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 4 + ...e.Security.Authentication.JwtBearer.csproj | 20 + ...tificateAuthenticationBuilderExtensions.cs | 68 -- .../Authentication.Mtls/LoggingExtensions.cs | 36 - .../MutualTlsAuthenticationHandler.cs | 292 -------- .../MutualTlsAuthenticationOptions.cs | 16 - ...penIdConnectServiceCollectionExtensions.cs | 45 ++ .../PostConfigureOpenIdConnectOptions.cs | 65 ++ .../Properties/AssemblyInfo.cs | 7 + .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 4 + ...curity.Authentication.OpenIdConnect.csproj | 20 + .../Properties/AssemblyInfo.cs | 9 +- .../SharedServiceCollectionExtensions.cs | 21 + ...ltoe.Security.Authentication.Shared.csproj | 21 + .../SteeltoeSecurityDefaults.cs | 11 + .../Authentication.Shared/TokenKeyResolver.cs | 79 +++ .../ApplicationClaimTypes.cs | 13 + .../ApplicationInstanceCertificate.cs | 62 ++ ...CertificateApplicationBuilderExtensions.cs | 47 ++ .../CertificateAuthorizationDefaults.cs | 12 + .../CertificateAuthorizationHandler.cs | 74 ++ ...teAuthorizationPolicyBuilderExtensions.cs} | 10 +- .../CertificateServiceCollectionExtensions.cs | 81 +++ .../Authorization.Certificate/GlobalUsings.cs | 9 + ...nfigureCertificateAuthenticationOptions.cs | 83 +++ .../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 | 53 -- ...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} | 11 +- ...wtBearerServiceCollectionExtensionsTest.cs | 30 + .../PostConfigureJwtBearerOptionsTest.cs | 104 +++ ...urity.Authentication.JwtBearer.Test.csproj | 14 + .../ClientCertificateAuthenticationTests.cs | 660 ------------------ ...e.Security.Authentication.Mtls.Test.csproj | 15 - .../GlobalUsings.cs | 8 + ...dConnectServiceCollectionExtensionsTest.cs | 30 + .../PostConfigureOpenIdConnectOptionsTest.cs | 106 +++ ...y.Authentication.OpenIdConnect.Test.csproj | 19 + .../GlobalUsings.cs | 6 + ...Security.Authentication.Shared.Test.csproj | 13 + .../TestMessageHandler.cs | 8 +- .../TokenKeyResolverTest.cs} | 98 +-- .../CertificateAuthorizationTest.cs | 133 ++++ .../ClientCertificatesFixture.cs | 4 +- .../GlobalUsings.cs | 6 + ...rity.Authorization.Certificate.Test.csproj | 25 + .../TestServerCertificateStartup.cs | 58 ++ .../instance.crt | 44 ++ .../instance.key | 27 + .../root_certificates/ca1.crt | 20 + src/Steeltoe.All.sln | 105 +-- src/Steeltoe.All.sln.DotSettings | 2 + src/Steeltoe.Connectors.slnf | 3 - src/Steeltoe.Security.slnf | 14 +- versions.props | 2 + 177 files changed, 1888 insertions(+), 7540 deletions(-) create mode 100644 src/Common/src/Common.Security/CertificateConfigurationExtensions.cs delete mode 100644 src/Common/src/Common.Security/ConfigurationExtensions.cs rename src/{Connectors/src/Abstractions => Common/src/Common.Security}/PublicAPI.Shipped.txt (100%) create mode 100644 src/Common/src/Common.Security/PublicAPI.Unshipped.txt 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/Abstractions/PublicAPI.Unshipped.txt delete mode 100644 src/Connectors/src/Abstractions/ServiceInfoCreatorAssemblyAttribute.cs delete mode 100644 src/Connectors/src/Abstractions/ServiceInfoFactoryAssemblyAttribute.cs delete mode 100644 src/Connectors/src/Abstractions/Services/IServiceInfo.cs delete mode 100644 src/Connectors/src/Abstractions/Services/IServiceInfoFactory.cs delete mode 100644 src/Connectors/src/Abstractions/Services/ServiceInfo.cs delete mode 100644 src/Connectors/src/Abstractions/Services/SsoServiceInfo.cs delete mode 100644 src/Connectors/src/Abstractions/Services/UriInfo.cs delete mode 100644 src/Connectors/src/Abstractions/Services/UriServiceInfo.cs delete mode 100644 src/Connectors/src/Abstractions/Steeltoe.Connectors.Abstractions.csproj 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.JwtBearer/JwtBearerServiceCollectionExtensions.cs create mode 100644 src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs rename src/Security/src/{Authentication.CloudFoundry => Authentication.JwtBearer}/Properties/AssemblyInfo.cs (93%) create mode 100644 src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt create mode 100644 src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt create mode 100644 src/Security/src/Authentication.JwtBearer/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 create mode 100644 src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs 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 rename src/{Connectors/src/Abstractions => Security/src/Authentication.Shared}/Properties/AssemblyInfo.cs (57%) 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 rename src/Security/{src/Authentication.CloudFoundry/SameSpaceRequirement.cs => test/Authentication.JwtBearer.Test/GlobalUsings.cs} (55%) 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 (72%) 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 (91%) 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..d20e1d1bf1 100644 --- a/shared-package.props +++ b/shared-package.props @@ -54,14 +54,18 @@ + Condition="$(MSBuildProjectName.StartsWith('Steeltoe.Bootstrap')) Or $(MSBuildProjectName.StartsWith('Steeltoe.Common.Security')) 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.Common.Security')) And !$(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 a572daaf8e..c601dbd731 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -8,6 +8,7 @@ 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; @@ -35,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; @@ -81,7 +81,7 @@ public void ConfigureSteeltoe() WireIfLoaded(WirePrometheus, SteeltoeAssemblyNames.ManagementPrometheus); WireIfLoaded(WireWavefrontMetrics, SteeltoeAssemblyNames.ManagementWavefront); WireIfLoaded(WireDistributedTracing, SteeltoeAssemblyNames.ManagementTracing); - WireIfLoaded(WireCloudFoundryContainerIdentity, SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonSecurity); } private void WireConfigServer() @@ -256,12 +256,12 @@ private void WireDistributedTracing() _logger.LogInformation("Configured distributed tracing"); } - private void WireCloudFoundryContainerIdentity() + private void WireAppInstanceIdentity() { - _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddCloudFoundryContainerIdentity()); - _wrapper.ConfigureServices(services => services.AddCloudFoundryCertificateAuth()); + _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddAppInstanceIdentityCertificate()); + _wrapper.ConfigureServices(services => services.ConfigureCertificateOptions("AppInstanceIdentity", null)); - _logger.LogInformation("Configured Cloud Foundry mTLS security"); + _logger.LogInformation("Configured application instance 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 8ef78ca959..04cd7ba3ee 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -4,7 +4,6 @@ using System.Reflection; using FluentAssertions; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -227,9 +226,9 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { - using IHost host = GetHostForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); @@ -237,7 +236,6 @@ public void CloudFoundryContainerSecurity_IsAutowired() host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.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 1a1b97096d..c2c164a17a 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -5,7 +5,6 @@ using System.Net; using System.Reflection; using FluentAssertions; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -228,9 +227,9 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { - using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); @@ -238,7 +237,6 @@ public void CloudFoundryContainerSecurity_IsAutowired() host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.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 cd6f9cf741..a36357eb6a 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -5,7 +5,6 @@ using System.Reflection; using FluentAssertions; using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -228,9 +227,9 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { - using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.SecurityAuthenticationCloudFoundry); + using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); @@ -238,7 +237,6 @@ public void CloudFoundryContainerSecurity_IsAutowired() host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.Should().NotBeNull(); host.Services.GetService>().Should().NotBeNull(); - host.Services.GetService().Should().NotBeNull(); } private static IWebHost GetWebHostForOnly(string assemblyNameToInclude) diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs new file mode 100644 index 0000000000..ddba8f3419 --- /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.Configuration; + +namespace Steeltoe.Common.Security; + +internal static class CertificateConfigurationExtensions +{ + /// + /// Adds file path information for a certificate and (optional) private key to configuration, for use with . + /// + /// + /// Your . + /// + /// + /// Name of the certificate, or for an unnamed certificate. + /// + /// + /// The path on disk to locate a valid certificate file. + /// + /// + /// The path on disk to locate a valid PEM-encoded RSA key file. + /// + 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 AddAppInstanceIdentityCertificate(this IConfigurationBuilder builder) + { + return builder.AddAppInstanceIdentityCertificate(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 AddAppInstanceIdentityCertificate(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.AppBasePath)!.ToString(), "GeneratedCertificates")); + + 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? certificateFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); + string? keyFile = Environment.GetEnvironmentVariable("CF_INSTANCE_KEY"); + + return certificateFile == null || keyFile == null ? builder : builder.AddCertificate("AppInstanceIdentity", certificateFile, keyFile); + } +} diff --git a/src/Common/src/Common.Security/ConfigurationExtensions.cs b/src/Common/src/Common.Security/ConfigurationExtensions.cs deleted file mode 100644 index aa33e343be..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.Configuration; - -namespace Steeltoe.Common.Security; - -internal static class ConfigurationExtensions -{ - /// - /// Adds file path information for a certificate and (optional) private key to configuration, for use with . - /// - /// - /// Your . - /// - /// - /// Name of the certificate, or for an unnamed certificate. - /// - /// - /// The path on disk to locate a valid certificate file. - /// - /// - /// The path on disk to locate a valid PEM-encoded RSA key file. - /// - internal 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 834a11eaf9..cebbe79ef5 100644 --- a/src/Common/src/Common.Security/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Security/LocalCertificateWriter.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Common.Security; -public sealed class LocalCertificateWriter +internal sealed class LocalCertificateWriter { internal static readonly string AppBasePath = AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]; @@ -20,39 +20,41 @@ public sealed class LocalCertificateWriter internal string IntermediatePfxPath { get; set; } = Path.Combine(ParentPath, "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 Cloud Foundry 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")); } + // Create the root certificate if it doesn't already exist (can be shared by multiple applications) if (!File.Exists(RootCaPfxPath)) { - caCertificate = CreateRoot("CN=SteeltoeGeneratedCA"); - File.WriteAllBytes(RootCaPfxPath, caCertificate.Export(X509ContentType.Pfx)); + rootAuthorityCertificate = CreateRootCertificate("CN=SteeltoeGeneratedCA"); + File.WriteAllBytes(RootCaPfxPath, rootAuthorityCertificate.Export(X509ContentType.Pfx)); } else { - caCertificate = new X509Certificate2(RootCaPfxPath); + rootAuthorityCertificate = new X509Certificate2(RootCaPfxPath); } + // 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,7 +62,9 @@ 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"))) @@ -68,70 +72,74 @@ public bool Write(Guid orgId, Guid spaceId) Directory.CreateDirectory(Path.Combine(AppBasePath, "GeneratedCertificates")); } - string certContents = "-----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"; +#if NET8_0_OR_GREATER + string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + intermediateCertificate.ExportCertificatePem() + "\r\n" + + rootAuthorityCertificate.ExportCertificatePem(); - string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + - Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + - "\r\n-----END RSA PRIVATE KEY-----"; + string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), certContents); - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Key.pem"), keyContents); +#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-----"; + +#endif - return true; + File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), chainedCertificateContents); + File.WriteAllText(Path.Combine(AppBasePath, "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(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 12718f69a1..eae52bc83a 100644 --- a/src/Common/src/Common.Security/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Security/Properties/AssemblyInfo.cs @@ -4,9 +4,7 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Security.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Discovery.HttpClients.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/Connectors/src/Abstractions/PublicAPI.Shipped.txt b/src/Common/src/Common.Security/PublicAPI.Shipped.txt similarity index 100% rename from src/Connectors/src/Abstractions/PublicAPI.Shipped.txt rename to src/Common/src/Common.Security/PublicAPI.Shipped.txt diff --git a/src/Common/src/Common.Security/PublicAPI.Unshipped.txt b/src/Common/src/Common.Security/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..54ca7ae367 --- /dev/null +++ b/src/Common/src/Common.Security/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Common.Security.CertificateServiceCollectionExtensions +Steeltoe.Common.Security.ConfigureCertificateOptions +Steeltoe.Common.Security.ConfigureCertificateOptions.Configure(Steeltoe.Common.Configuration.CertificateOptions! options) -> void +Steeltoe.Common.Security.ConfigureCertificateOptions.Configure(string? name, Steeltoe.Common.Configuration.CertificateOptions! options) -> void +Steeltoe.Common.Security.ConfigureCertificateOptions.ConfigureCertificateOptions(Microsoft.Extensions.Configuration.IConfiguration! configuration) -> void 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 f64ce6f401..baa8a23fae 100644 --- a/src/Common/test/Common.Security.Test/ConfigurationExtensionsTest.cs +++ b/src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs @@ -8,7 +8,7 @@ namespace Steeltoe.Common.Security.Test; -public sealed class ConfigurationExtensionsTest +public sealed class CertificateConfigurationExtensionsTest { private const string CertificateName = "test"; 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..dbffa88db9 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs @@ -12,15 +12,18 @@ 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 LabelConfigurationKeyRegex = + new("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + 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 +37,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..815437fc46 --- /dev/null +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs @@ -0,0 +1,54 @@ +// 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 (string 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/Abstractions/PublicAPI.Unshipped.txt b/src/Connectors/src/Abstractions/PublicAPI.Unshipped.txt deleted file mode 100644 index a950743ebf..0000000000 --- a/src/Connectors/src/Abstractions/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,7 +0,0 @@ -Steeltoe.Connectors.ServiceInfoCreatorAssemblyAttribute -Steeltoe.Connectors.ServiceInfoCreatorAssemblyAttribute.ServiceInfoCreatorAssemblyAttribute(System.Type creatorType) -> void -Steeltoe.Connectors.ServiceInfoFactoryAssemblyAttribute -Steeltoe.Connectors.ServiceInfoFactoryAssemblyAttribute.ServiceInfoFactoryAssemblyAttribute() -> void -Steeltoe.Connectors.Services.IServiceInfo -Steeltoe.Connectors.Services.IServiceInfo.ApplicationInfo.get -> Steeltoe.Common.IApplicationInstanceInfo -Steeltoe.Connectors.Services.IServiceInfo.Id.get -> string \ No newline at end of file diff --git a/src/Connectors/src/Abstractions/ServiceInfoCreatorAssemblyAttribute.cs b/src/Connectors/src/Abstractions/ServiceInfoCreatorAssemblyAttribute.cs deleted file mode 100644 index 7a591acbe4..0000000000 --- a/src/Connectors/src/Abstractions/ServiceInfoCreatorAssemblyAttribute.cs +++ /dev/null @@ -1,25 +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.Attributes; - -namespace Steeltoe.Connectors; - -/// -/// Identify assemblies containing ServiceInfoCreators. -/// -[AttributeUsage(AttributeTargets.Assembly)] -public sealed class ServiceInfoCreatorAssemblyAttribute : AssemblyContainsTypeAttribute -{ - /// - /// Initializes a new instance of the class. Used to override the default ServiceInfoCreator. - /// - /// - /// The type of your info creator that inherits from Steeltoe.Connectors.ServiceInfoCreator. - /// - public ServiceInfoCreatorAssemblyAttribute(Type creatorType) - : base(creatorType) - { - } -} diff --git a/src/Connectors/src/Abstractions/ServiceInfoFactoryAssemblyAttribute.cs b/src/Connectors/src/Abstractions/ServiceInfoFactoryAssemblyAttribute.cs deleted file mode 100644 index 78da0fc328..0000000000 --- a/src/Connectors/src/Abstractions/ServiceInfoFactoryAssemblyAttribute.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.Common.Attributes; -using Steeltoe.Connectors.Services; - -namespace Steeltoe.Connectors; - -/// -/// Identifies an assembly that contains one or more . -/// -[AttributeUsage(AttributeTargets.Assembly)] -public sealed class ServiceInfoFactoryAssemblyAttribute : AssemblyContainsTypeAttribute -{ - public ServiceInfoFactoryAssemblyAttribute() - : base(typeof(IServiceInfoFactory)) - { - } -} diff --git a/src/Connectors/src/Abstractions/Services/IServiceInfo.cs b/src/Connectors/src/Abstractions/Services/IServiceInfo.cs deleted file mode 100644 index 24d853a936..0000000000 --- a/src/Connectors/src/Abstractions/Services/IServiceInfo.cs +++ /dev/null @@ -1,14 +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; - -namespace Steeltoe.Connectors.Services; - -public interface IServiceInfo -{ - string Id { get; } - - IApplicationInstanceInfo ApplicationInfo { get; } -} diff --git a/src/Connectors/src/Abstractions/Services/IServiceInfoFactory.cs b/src/Connectors/src/Abstractions/Services/IServiceInfoFactory.cs deleted file mode 100644 index fd58ae00ab..0000000000 --- a/src/Connectors/src/Abstractions/Services/IServiceInfoFactory.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 Steeltoe.Configuration; - -namespace Steeltoe.Connectors.Services; - -internal interface IServiceInfoFactory -{ - /// - /// Check if this factory can create from the given binding. - /// - /// - /// A service binding to evaluate. - /// - /// - /// Gets a value indicating whether or not the binding is compatible with this factory. - /// - bool Accepts(Service binding); - - /// - /// Return service information from a service binding. - /// - /// - /// A service binding. - /// - /// - /// Relevant . - /// - IServiceInfo Create(Service binding); -} diff --git a/src/Connectors/src/Abstractions/Services/ServiceInfo.cs b/src/Connectors/src/Abstractions/Services/ServiceInfo.cs deleted file mode 100644 index d890b0c405..0000000000 --- a/src/Connectors/src/Abstractions/Services/ServiceInfo.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.Common; - -namespace Steeltoe.Connectors.Services; - -internal abstract class ServiceInfo : IServiceInfo -{ - public string Id { get; } - - public IApplicationInstanceInfo ApplicationInfo { get; set; } - - protected ServiceInfo(string id) - : this(id, null) - { - } - - protected ServiceInfo(string id, IApplicationInstanceInfo info) - { - ArgumentGuard.NotNullOrEmpty(id); - - Id = id; - ApplicationInfo = info; - } -} diff --git a/src/Connectors/src/Abstractions/Services/SsoServiceInfo.cs b/src/Connectors/src/Abstractions/Services/SsoServiceInfo.cs deleted file mode 100644 index 7bd1948ed9..0000000000 --- a/src/Connectors/src/Abstractions/Services/SsoServiceInfo.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.Services; - -internal sealed class SsoServiceInfo : ServiceInfo -{ - public string ClientId { get; } - - public string ClientSecret { get; } - - public string AuthDomain { get; } - - public SsoServiceInfo(string id, string clientId, string clientSecret, string domain) - : base(id) - { - ClientId = clientId; - ClientSecret = clientSecret; - AuthDomain = domain; - } -} diff --git a/src/Connectors/src/Abstractions/Services/UriInfo.cs b/src/Connectors/src/Abstractions/Services/UriInfo.cs deleted file mode 100644 index aa7caaacb3..0000000000 --- a/src/Connectors/src/Abstractions/Services/UriInfo.cs +++ /dev/null @@ -1,269 +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; - -namespace Steeltoe.Connectors.Services; - -internal sealed class UriInfo -{ - private readonly char[] _questionMark = - { - '?' - }; - - private readonly char[] _colon = - { - ':' - }; - - public string Scheme { get; } - - public string Host { get; private set; } - - public string[] Hosts { get; private set; } - - public int Port { get; } - - public string UserName { get; } - - public string Password { get; } - - public string Path { get; } - - public string Query { get; } - - public string UriString { get; } - - public Uri Uri => MakeUri(UriString); - - public UriInfo(string scheme, string host, int port, string username, string password, string path = null, string query = null) - { - Scheme = scheme; - Host = host; - Port = port; - UserName = WebUtility.UrlEncode(username); - Password = WebUtility.UrlEncode(password); - Path = path; - Query = query; - - UriString = MakeUri(scheme, host, port, username, password, path, query).ToString(); - } - - public UriInfo(string uriString) - { - Uri uri = MakeUri(uriString); - - if (uri != null) - { - Scheme = uri.Scheme; - Host ??= uri.Host; - - Port = uri.Port; - Path = GetPath(uri.PathAndQuery); - Query = GetQuery(uri.PathAndQuery); - - string[] userInfo = GetUserInfo(uri.UserInfo); - UserName = userInfo[0]; - Password = userInfo[1]; - } - - UriString = uriString; - } - - public UriInfo(string uriString, string username, string password) - { - Uri uri = MakeUri(uriString); - - if (uri != null) - { - Scheme = uri.Scheme; - Host ??= uri.Host; - - Port = uri.Port; - Path = GetPath(uri.PathAndQuery); - Query = GetQuery(uri.PathAndQuery); - } - - UserName = WebUtility.UrlEncode(username); - Password = WebUtility.UrlEncode(password); - UriString = uriString; - } - - public override string ToString() - { - return UriString; - } - - private Uri MakeUri(string scheme, string host, int port, string username, string password, string path, string query) - { - string cleanedPath = path == null || path.StartsWith('/') ? path : $"/{path}"; - cleanedPath = query != null ? $"{cleanedPath}?{query}" : cleanedPath; - - if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) - { - var builder = new UriBuilder - { - Scheme = scheme, - Host = host, - Port = port, - UserName = WebUtility.UrlEncode(username), - Password = WebUtility.UrlEncode(password), - Path = cleanedPath - }; - - return builder.Uri; - } - else - { - var builder = new UriBuilder - { - Scheme = scheme, - Host = host, - Port = port, - Path = cleanedPath - }; - - return builder.Uri; - } - } - - private Uri MakeUri(string uriString) - { - if (uriString.StartsWith("jdbc", StringComparison.Ordinal) || uriString.Contains(';')) - { - uriString = ConvertJdbcToUri(uriString); - } - - uriString = EscapeFirstAtSignIfMultipleOccurrences(uriString); - - try - { - return new Uri(uriString); - } - catch (UriFormatException) - { - // URI parsing will fail if multiple (comma separated) hosts were provided... - if (uriString.Contains(',')) - { - // Slide past the protocol - string[] splitUri = uriString.Split('/'); - - // get the host list (and maybe credentials) - // -- pre-emptively set it as the Host property rather than a local variable - // since the connector is likely to expect this format here anyway - string credentialAndHost = splitUri[2]; - - // skip over credentials if they're present - Host = credentialAndHost.Contains('@') ? credentialAndHost.Split('@')[1] : credentialAndHost; - - // add the hosts to a separate property for reconstruction later - Hosts = Host.Split(','); - - // swap all the hosts out with a placeholder so we can parse the rest of the info - return new Uri(uriString.Replace(Host, "multipleHostsDetected", StringComparison.Ordinal)); - } - - return null; - } - } - - private static string EscapeFirstAtSignIfMultipleOccurrences(string uriString) - { - // Workaround for CloudFoundry bug: it forgets to uri-escape the '@' character in username field when using Azure, resulting in an invalid Uri. - // For example: postgresql://@:@:5432/vsbdb -> postgresql://%40:@:5432/vsbdb - - string[] parts = uriString.Split('@', 3); - return parts.Length == 3 ? $"{parts[0]}%40{parts[1]}@{parts[2]}" : uriString; - } - - private string ConvertJdbcToUri(string uriString) - { - uriString = uriString.Replace("jdbc:", string.Empty, StringComparison.Ordinal).Replace(';', '&'); - - if (!uriString.Contains('?')) - { - int firstAmp = uriString.IndexOf('&'); - - // If there is an equals sign before any ampersands, it is likely a key was included for the db name. - // Make the database name part of the path rather than query string if possible - int firstEquals = uriString.IndexOf('='); - - if (firstEquals > 0 && (firstEquals < firstAmp || firstAmp == -1)) - { - int dbNameIndex = uriString.IndexOf("databasename=", StringComparison.OrdinalIgnoreCase); - - if (dbNameIndex > 0) - { - uriString = uriString.Remove(dbNameIndex, 13); - - // recalculate the location of the first '&' - firstAmp = uriString.IndexOf('&'); - } - } - - if (firstAmp > 0) - { - uriString = uriString.Substring(0, firstAmp) + _questionMark[0] + uriString.Substring(firstAmp + 1, uriString.Length - firstAmp - 1); - } - } - - return uriString; - } - - private string GetPath(string pathAndQuery) - { - if (string.IsNullOrEmpty(pathAndQuery)) - { - return null; - } - - string[] split = pathAndQuery.Split(_questionMark); - - if (split.Length == 0) - { - return null; - } - - return split[0].Substring(1); - } - - private string GetQuery(string pathAndQuery) - { - if (string.IsNullOrEmpty(pathAndQuery)) - { - return null; - } - - string[] split = pathAndQuery.Split(_questionMark); - - if (split.Length <= 1) - { - return null; - } - - return split[1]; - } - - private string[] GetUserInfo(string userPass) - { - if (string.IsNullOrEmpty(userPass)) - { - return new string[] - { - null, - null - }; - } - - string[] split = userPass.Split(_colon); - - if (split.Length != 2) - { - throw new ArgumentException($"Bad user/password in URI: {userPass}", nameof(userPass)); - } - - return split; - } -} diff --git a/src/Connectors/src/Abstractions/Services/UriServiceInfo.cs b/src/Connectors/src/Abstractions/Services/UriServiceInfo.cs deleted file mode 100644 index 4184447828..0000000000 --- a/src/Connectors/src/Abstractions/Services/UriServiceInfo.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 System.Net; - -namespace Steeltoe.Connectors.Services; - -internal abstract class UriServiceInfo : ServiceInfo -{ - public UriInfo Info { get; } - - public string Uri => Info.UriString; - - public string UserName => WebUtility.UrlDecode(Info.UserName); - - public string Password => WebUtility.UrlDecode(Info.Password); - - public string Host => Info.Host; - - public string[] Hosts => Info.Hosts; - - public int Port => Info.Port; - - public string Path => Info.Path; - - public string Query => Info.Query; - - public string Scheme => Info.Scheme; - - protected UriServiceInfo(string id, string scheme, string host, int port, string username, string password, string path) - : base(id) - { - Info = new UriInfo(scheme, host, port, username, password, path); - } - - protected UriServiceInfo(string id, string uriString) - : base(id) - { - Info = new UriInfo(uriString); - } -} diff --git a/src/Connectors/src/Abstractions/Steeltoe.Connectors.Abstractions.csproj b/src/Connectors/src/Abstractions/Steeltoe.Connectors.Abstractions.csproj deleted file mode 100644 index 6b53f49040..0000000000 --- a/src/Connectors/src/Abstractions/Steeltoe.Connectors.Abstractions.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - net8.0;net6.0 - Steeltoe.Connectors - Abstractions for working with backing services - abstractions;connectors;services - true - - - - - - - - - - - - - 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/src/Connectors/Steeltoe.Connectors.csproj b/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj index d4b0c86eb7..540bfa4ffd 100644 --- a/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj +++ b/src/Connectors/src/Connectors/Steeltoe.Connectors.csproj @@ -16,7 +16,6 @@ - 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/README.md b/src/Security/README.md index 06a1bc9179..e3d7180149 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -1,6 +1,6 @@ # Steeltoe Security -Authentication and DataProtection libraries which simplify interacting with CredHub and using security services on CloudFoundry. +Authentication and DataProtection libraries which simplify interacting with security services on Tanzu Platforms. For more information on how to use these components see the [Steeltoe documentation](https://steeltoe.io/). 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 23ac686083..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 ddfd4ce283..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.Configuration; - -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("AppInstanceIdentity")); - } - - 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 bfea994924..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("AppInstanceIdentity", 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 e9b3725121..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/MutualTlsAuthenticationOptionsPostConfigurer.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 System.Security.Claims; -using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Security.Authentication.Mtls; - -namespace Steeltoe.Security.Authentication.CloudFoundry; - -public sealed class MutualTlsAuthenticationOptionsPostConfigurer(ILoggerFactory loggerFactory) : IPostConfigureOptions -{ - private readonly ILogger _cloudFoundryLogger = loggerFactory?.CreateLogger(); - - public void PostConfigure(string name, MutualTlsAuthenticationOptions 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 bc7dcb7134..0000000000 --- a/src/Security/src/Authentication.CloudFoundry/ServiceCollectionExtensions.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 Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.AspNetCore.Authorization; -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. - /// - /// - /// Provides access to the file system. - /// - public static void AddCloudFoundryContainerIdentity(this IServiceCollection services, IFileProvider fileProvider) - { - ArgumentGuard.NotNull(services); - - services.ConfigureCertificateOptions("AppInstanceIdentity", 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. - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services) - { - AddCloudFoundryCertificateAuth(services, CertificateAuthenticationDefaults.AuthenticationScheme); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// Used to configure the . - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, Action configurer) - { - AddCloudFoundryCertificateAuth(services, CertificateAuthenticationDefaults.AuthenticationScheme, configurer, null); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// An identifier for this authentication mechanism. Default value is . - /// - public static void AddCloudFoundryCertificateAuth(this IServiceCollection services, string authenticationScheme) - { - AddCloudFoundryCertificateAuth(services, authenticationScheme, null, null); - } - - /// - /// Adds options and services for Cloud Foundry container identity certificates along with certificate-based authentication and authorization. - /// - /// - /// Service collection. - /// - /// - /// 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, string authenticationScheme, - Action configurer, IFileProvider fileProvider) - { - ArgumentGuard.NotNull(services); - - services.AddCloudFoundryContainerIdentity(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.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs new file mode 100644 index 0000000000..80803a7fda --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.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.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.JwtBearer/PostConfigureJwtBearerOptions.cs b/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs new file mode 100644 index 0000000000..9f42c0fd13 --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.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.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) + { + string? 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.JwtBearer/Properties/AssemblyInfo.cs similarity index 93% rename from src/Security/src/Authentication.CloudFoundry/Properties/AssemblyInfo.cs rename to src/Security/src/Authentication.JwtBearer/Properties/AssemblyInfo.cs index 1ba60e912c..5dbdb9307b 100644 --- a/src/Security/src/Authentication.CloudFoundry/Properties/AssemblyInfo.cs +++ b/src/Security/src/Authentication.JwtBearer/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.JwtBearer/PublicAPI.Shipped.txt b/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt new file mode 100644 index 0000000000..ab058de62d --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..4e3942014e --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/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.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj new file mode 100644 index 0000000000..b0d462760a --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -0,0 +1,20 @@ + + + net8.0 + Library for using JWT Bearer tokens 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 e5ce09b744..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 2fbcb6e081..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.Configuration; - -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("AppInstanceIdentity").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("AppInstanceIdentity").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..6d59de088d --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -0,0 +1,45 @@ +// 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..a8e1d5bf0f --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.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 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; + +internal sealed class PostConfigureOpenIdConnectOptions(IHttpClientFactory httpClientFactory) : IPostConfigureOptions +{ + // 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); + }; + + 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; + } + } +} diff --git a/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c087fbbab4 --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/Properties/AssemblyInfo.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. + +using System.Runtime.CompilerServices; + +[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..69c4173a01 --- /dev/null +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -0,0 +1,20 @@ + + + net8.0 + Library for using OpenID Connect with Tanzu + CloudFoundry;uaa;security;sso;openid;oidc + true + enable + + + + + + + + + + + + + diff --git a/src/Connectors/src/Abstractions/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs similarity index 57% rename from src/Connectors/src/Abstractions/Properties/AssemblyInfo.cs rename to src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs index 0b3360eb15..2ded3f226b 100644 --- a/src/Connectors/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs @@ -4,7 +4,8 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Steeltoe.Connectors.CloudFoundry")] -[assembly: InternalsVisibleTo("Steeltoe.Connectors.CloudFoundry.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry")] -[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundry.Test")] +[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..b0edd671ff --- /dev/null +++ b/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +// 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..70c0f7fde3 --- /dev/null +++ b/src/Security/src/Authentication.Shared/TokenKeyResolver.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.Collections.Concurrent; +using System.Net.Http.Headers; +using Microsoft.IdentityModel.Tokens; +using Steeltoe.Common; + +namespace Steeltoe.Security.Authentication.Shared; + +internal sealed class TokenKeyResolver +{ + private readonly HttpClient _httpClient; + private string _authority; + + internal static ConcurrentDictionary Resolved { get; } = new(); + + public TokenKeyResolver(string authority, HttpClient httpClient) + { + ArgumentGuard.NotNull(authority); + ArgumentGuard.NotNull(httpClient); + + _authority = authority; + _httpClient = httpClient; + } + + 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..aabeb95355 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the 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-]+)$"; + + public string OrganizationId { get; private set; } + + public string SpaceId { get; private set; } + + public string ApplicationId { get; private set; } + + public string InstanceId { get; private set; } + + private ApplicationInstanceCertificate(string organizationId, string spaceId, string applicationId, string instanceId) + { + OrganizationId = organizationId; + SpaceId = spaceId; + ApplicationId = applicationId; + InstanceId = instanceId; + } + + 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..5f9a62d2ad --- /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..f0572026b9 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs @@ -0,0 +1,12 @@ +// 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 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..d513a4ac42 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -0,0 +1,74 @@ +// 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 Steeltoe.Common; +using Steeltoe.Common.Configuration; + +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("AppInstanceIdentity")); + } + + 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..f1e3c59ac6 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -0,0 +1,81 @@ +// 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 Steeltoe.Common.Configuration; +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("AppInstanceIdentity"); + + services.AddCertificateForwarding(opt => + { + 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("AppInstanceIdentity"); + services.AddHttpClient(CertificateAuthorizationDefaults.HttpClientName, (serviceProvider, client) => + { + var loggerFactory = serviceProvider.GetRequiredService(); + ILogger logger = loggerFactory.CreateLogger("Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions"); + var optionsMonitor = serviceProvider.GetService>(); + CertificateOptions? certificateOptions = optionsMonitor?.Get("AppInstanceIdentity"); + X509Certificate2? certificate = certificateOptions?.Certificate; + + if (certificate != null) + { + logger.LogDebug("Adding certificate with subject {CertificateSubject} to outbound requests in header X-Client-Cert", certificate.Subject); + + string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); + client.DefaultRequestHeaders.Add("X-Client-Cert", 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..e76bd93454 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/GlobalUsings.cs @@ -0,0 +1,9 @@ +// 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; +global using Microsoft.Extensions.Options; +global using Steeltoe.Common.Options; diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs new file mode 100644 index 0000000000..ffb8b2231f --- /dev/null +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -0,0 +1,83 @@ +// 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 Steeltoe.Common; +using Steeltoe.Common.Configuration; + +namespace Steeltoe.Security.Authorization.Certificate; + +public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions +{ + private readonly IOptionsMonitor _certificateOptionsMonitor; + private readonly ILogger _logger; + + public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor certificateOptionsMonitor, + ILogger logger) + { + ArgumentGuard.NotNull(certificateOptionsMonitor); + + _certificateOptionsMonitor = certificateOptionsMonitor; + _logger = logger; + } + + public void PostConfigure(string? name, CertificateAuthenticationOptions options) + { + ArgumentGuard.NotNull(options); + + CertificateOptions containerIdentityOptions = _certificateOptionsMonitor.Get("AppInstanceIdentity"); + options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; + options.ClaimsIssuer = containerIdentityOptions.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 (containerIdentityOptions.IssuerChain.Any()) + { + options.AdditionalChainCertificates.AddRange(containerIdentityOptions.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..b9f23ee86f --- /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! certificateOptionsMonitor, 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 09901a3ca0..0000000000 --- a/src/Security/test/Authentication.CloudFoundry.Test/ServiceCollectionExtensionsTest.cs +++ /dev/null @@ -1,53 +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 FluentAssertions; -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.Configuration; -using Steeltoe.Common.Security; -using Steeltoe.Security.Authentication.Mtls; -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}:AppInstanceIdentity:CertificateFilePath", - $"GeneratedCertificates{Path.DirectorySeparatorChar}SteeltoeInstanceCert.pem" - } - }).Build(); - - services.AddSingleton(configurationRoot); - services.AddLogging(); - - services.AddCloudFoundryCertificateAuth(CertificateAuthenticationDefaults.AuthenticationScheme, null, - new PhysicalFileProvider(LocalCertificateWriter.AppBasePath)); - - ServiceProvider provider = services.BuildServiceProvider(true); - - provider.GetRequiredService>().Should().NotBeNull(); - provider.GetRequiredService>().Should().NotBeNull(); - provider.GetRequiredService>().Should().NotBeNull(); - provider.GetRequiredService().Should().NotBeNull(); - var mtlsOptions = provider.GetRequiredService>(); - mtlsOptions.Should().NotBeNull(); - - // confirm Events was set (in MutualTlsAuthenticationOptionsPostConfigurer.cs) vs being null by default - mtlsOptions.Value.Events.Should().NotBeNull(); - new MutualTlsAuthenticationOptions().Events.Should().BeNull(); - } -} 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 2183101043..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(); - } - - 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/src/Authentication.CloudFoundry/SameSpaceRequirement.cs b/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs similarity index 55% rename from src/Security/src/Authentication.CloudFoundry/SameSpaceRequirement.cs rename to src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs index 654eabc7ce..67ac867baa 100644 --- a/src/Security/src/Authentication.CloudFoundry/SameSpaceRequirement.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs @@ -2,10 +2,7 @@ // 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.Authentication.CloudFoundry; - -public class SameSpaceRequirement : IAuthorizationRequirement -{ -} +global using FluentAssertions; +global using Microsoft.Extensions.DependencyInjection; +global using Steeltoe.Security.Authentication.Shared; +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..bf4a132d8f --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs @@ -0,0 +1,30 @@ +// 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.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(); + + ServiceProvider serviceProvider = serviceCollection.ConfigureJwtBearerForCloudFoundry().BuildServiceProvider(); + HttpClient httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); + + 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..193518205d --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -0,0 +1,104 @@ +// 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.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry.ServiceBinding; + +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() + }; + + IConfigurationRoot 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() + { + IConfigurationRoot 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); + IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(configuration); + serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); + serviceCollection.ConfigureJwtBearerForCloudFoundry(); + + JwtBearerOptions 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..cf2e7fc9ad --- /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 20b4572330..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 MutualTlsAuthenticationOptions(), 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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 MutualTlsAuthenticationOptions - { - 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(MutualTlsAuthenticationOptions 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..67ac867baa --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs @@ -0,0 +1,8 @@ +// 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 FluentAssertions; +global using Microsoft.Extensions.DependencyInjection; +global using Steeltoe.Security.Authentication.Shared; +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..9a36e78969 --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs @@ -0,0 +1,30 @@ +// 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.OpenIdConnect.Test; + +public sealed class OpenIdConnectServiceCollectionExtensionsTest +{ + [Fact] + public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + + serviceCollection.Should().Contain(service => service.ServiceType == typeof(IHttpClientFactory)); + serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); + } + + [Fact] + public void ConfigureOpenIdConnectForCloudFoundry_ConfiguresHttpClient() + { + var serviceCollection = new ServiceCollection(); + + ServiceProvider serviceProvider = serviceCollection.ConfigureOpenIdConnectForCloudFoundry().BuildServiceProvider(); + HttpClient 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..36fbdac94c --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -0,0 +1,106 @@ +// 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.Configuration; +using Microsoft.Extensions.Options; +using Steeltoe.Common.TestResources; +using Steeltoe.Configuration.CloudFoundry.ServiceBinding; + +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"); + } +} 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..e571c4b17f --- /dev/null +++ b/src/Security/test/Authentication.OpenIdConnect.Test/Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj @@ -0,0 +1,19 @@ + + + + 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..ca564f4b61 --- /dev/null +++ b/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs @@ -0,0 +1,6 @@ +// 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.Net; +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 72% rename from src/Security/test/Authentication.CloudFoundry.Test/TestMessageHandler.cs rename to src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs index fc328d9fa3..ac46fe60eb 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/TestMessageHandler.cs +++ b/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs @@ -2,13 +2,11 @@ // 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; +namespace Steeltoe.Security.Authentication.Shared.Test; -namespace Steeltoe.Security.Authentication.CloudFoundry.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..41ce20dd09 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/CloudFoundryTokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs @@ -2,18 +2,17 @@ // 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.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 +38,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 +76,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 +114,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 +140,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..9064b62eb9 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -0,0 +1,133 @@ +// 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.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}"); + HttpClient 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.AppBasePath, "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.AppBasePath, "root_certificates")); + + using IHost host = await GetHostBuilder().StartAsync(); + + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); + HttpResponseMessage response = await httpClient.GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + private IHostBuilder GetHostBuilder() + { + return new HostBuilder().ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate(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("X-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")); + + 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 91% rename from src/Security/test/Authentication.CloudFoundry.Test/ClientCertificatesFixture.cs rename to src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs index f54d2b8333..214758c017 100644 --- a/src/Security/test/Authentication.CloudFoundry.Test/ClientCertificatesFixture.cs +++ b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs @@ -2,9 +2,7 @@ // 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.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..a9f9918319 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs @@ -0,0 +1,6 @@ +// 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 Steeltoe.Common.Security; +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..5997d0ec6c --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -0,0 +1,58 @@ +// 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; + }); + + 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..61c54b3a45 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}" @@ -143,16 +139,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Abstraction EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Logging.Abstractions", "Logging\src\Abstractions\Steeltoe.Logging.Abstractions.csproj", "{86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}" 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 +211,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.JwtBearer\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 +369,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 @@ -411,26 +405,6 @@ Global {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Debug|Any CPU.Build.0 = Debug|Any CPU {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Release|Any CPU.ActiveCfg = Release|Any CPU {86FBBCC7-97F5-4795-A61E-FA7E0CE0514D}.Release|Any CPU.Build.0 = Release|Any CPU - {0FEEB397-0A2B-4E24-A5AE-B91A56303C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {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 +497,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 +568,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} @@ -573,11 +577,6 @@ Global {920BDA8F-094B-4D81-A03C-664C0F808DC7} = {4AB95F47-0C93-4C88-B87F-231262CD0E89} {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 +601,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/src/Steeltoe.Connectors.slnf b/src/Steeltoe.Connectors.slnf index f0bb12eafe..517ba8c9b1 100644 --- a/src/Steeltoe.Connectors.slnf +++ b/src/Steeltoe.Connectors.slnf @@ -11,11 +11,8 @@ "Configuration\\src\\CloudFoundry.ServiceBinding\\Steeltoe.Configuration.CloudFoundry.ServiceBinding.csproj", "Configuration\\src\\CloudFoundry\\Steeltoe.Configuration.CloudFoundry.csproj", "Configuration\\src\\Kubernetes.ServiceBinding\\Steeltoe.Configuration.Kubernetes.ServiceBinding.csproj", - "Connectors\\src\\Abstractions\\Steeltoe.Connectors.Abstractions.csproj", - "Connectors\\src\\CloudFoundry\\Steeltoe.Connectors.CloudFoundry.csproj", "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", "Connectors\\src\\EntityFrameworkCore\\Steeltoe.Connectors.EntityFrameworkCore.csproj", - "Connectors\\test\\CloudFoundry.Test\\Steeltoe.Connectors.CloudFoundry.Test.csproj", "Connectors\\test\\Connectors.Test\\Steeltoe.Connectors.Test.csproj", "Connectors\\test\\EntityFrameworkCore.Test\\Steeltoe.Connectors.EntityFrameworkCore.Test.csproj" ] diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index 0fcdd2e030..0054a1889b 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -12,14 +12,16 @@ "Configuration\\src\\CloudFoundry.ServiceBinding\\Steeltoe.Configuration.CloudFoundry.ServiceBinding.csproj", "Configuration\\src\\CloudFoundry\\Steeltoe.Configuration.CloudFoundry.csproj", "Configuration\\src\\Kubernetes.ServiceBinding\\Steeltoe.Configuration.Kubernetes.ServiceBinding.csproj", - "Connectors\\src\\Abstractions\\Steeltoe.Connectors.Abstractions.csproj", - "Connectors\\src\\CloudFoundry\\Steeltoe.Connectors.CloudFoundry.csproj", "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", - "Security\\src\\Authentication.CloudFoundry\\Steeltoe.Security.Authentication.CloudFoundry.csproj", - "Security\\src\\Authentication.Mtls\\Steeltoe.Security.Authentication.Mtls.csproj", + "Security\\src\\Authentication.JwtBearer\\Steeltoe.Security.Authentication.JwtBearer.csproj", + "Security\\src\\Authentication.OpenIdConnect\\Steeltoe.Security.Authentication.OpenIdConnect.csproj", + "Security\\src\\Authentication.Shared\\Steeltoe.Security.Authentication.Shared.csproj", + "Security\\src\\Authorization.Certificate\\Steeltoe.Security.Authorization.Certificate.csproj", "Security\\src\\DataProtection.Redis\\Steeltoe.Security.DataProtection.Redis.csproj", - "Security\\test\\Authentication.CloudFoundry.Test\\Steeltoe.Security.Authentication.CloudFoundry.Test.csproj", - "Security\\test\\Authentication.Mtls.Test\\Steeltoe.Security.Authentication.Mtls.Test.csproj", + "Security\\test\\Authentication.JwtBearer.Test\\Steeltoe.Security.Authentication.JwtBearer.Test.csproj", + "Security\\test\\Authentication.OpenIdConnect.Test\\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", + "Security\\test\\Authentication.Shared.Test\\Steeltoe.Security.Authentication.Shared.Test.csproj", + "Security\\test\\Authorization.Certificate.Test\\Steeltoe.Security.Authorization.Certificate.Test.csproj", "Security\\test\\DataProtection.Redis.Test\\Steeltoe.Security.DataProtection.Redis.Test.csproj" ] } 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.* From 9b6c3b7fa3e2629873771bb410727ce17973e218 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 16:47:44 -0500 Subject: [PATCH 02/28] cleanup, more validation and tests --- .../HostBuilderExtensionsTest.cs | 2 +- .../WebApplicationBuilderExtensionsTest.cs | 2 +- .../WebHostBuilderExtensionsTest.cs | 2 +- .../CertificateConfigurationExtensions.cs | 4 +- .../Common.Security/LocalCertificateWriter.cs | 18 ++++---- .../Common.Security/PublicAPI.Unshipped.txt | 3 ++ .../JwtBearerServiceCollectionExtensions.cs | 6 +++ ...penIdConnectServiceCollectionExtensions.cs | 6 +++ ...CertificateApplicationBuilderExtensions.cs | 1 - .../CertificateAuthorizationHandler.cs | 4 -- ...ateAuthorizationPolicyBuilderExtensions.cs | 6 +-- .../CertificateServiceCollectionExtensions.cs | 16 ++------ .../Authorization.Certificate/GlobalUsings.cs | 5 ++- ...nfigureCertificateAuthenticationOptions.cs | 13 ++---- .../PublicAPI.Unshipped.txt | 9 ++-- ...wtBearerServiceCollectionExtensionsTest.cs | 9 ++++ ...dConnectServiceCollectionExtensionsTest.cs | 9 ++++ .../CertificateAuthorizationTest.cs | 3 -- ...tificateServiceCollectionExtensionsTest.cs | 41 +++++++++++++++++++ .../GlobalUsings.cs | 5 +++ .../TestServerCertificateStartup.cs | 12 ++---- 21 files changed, 113 insertions(+), 63 deletions(-) create mode 100644 src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 04cd7ba3ee..78db56a5ae 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -226,7 +226,7 @@ public void Tracing_IsAutowired() } [Fact] - public void ContainerIdentityCertificate_IsAutowired() + public void AppInstanceIdentityCertificate_IsAutowired() { using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index c2c164a17a..537dbaa0c4 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -227,7 +227,7 @@ public void Tracing_IsAutowired() } [Fact] - public void ContainerIdentityCertificate_IsAutowired() + public void AppInstanceIdentityCertificate_IsAutowired() { using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index a36357eb6a..706e217aeb 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -227,7 +227,7 @@ public void Tracing_IsAutowired() } [Fact] - public void ContainerIdentityCertificate_IsAutowired() + public void AppInstanceIdentityCertificate_IsAutowired() { using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs index ddba8f3419..42a23e61ac 100644 --- a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Common.Security; -internal static class CertificateConfigurationExtensions +public static class CertificateConfigurationExtensions { /// /// Adds file path information for a certificate and (optional) private key to configuration, for use with . @@ -24,7 +24,7 @@ internal static class CertificateConfigurationExtensions /// /// The path on disk to locate a valid PEM-encoded RSA key file. /// - public static IConfigurationBuilder AddCertificate(this IConfigurationBuilder builder, string certificateName, string certificateFilePath, + internal static IConfigurationBuilder AddCertificate(this IConfigurationBuilder builder, string certificateName, string certificateFilePath, string? privateKeyFilePath = null) { ArgumentGuard.NotNull(builder); diff --git a/src/Common/src/Common.Security/LocalCertificateWriter.cs b/src/Common/src/Common.Security/LocalCertificateWriter.cs index cebbe79ef5..4ff2731396 100644 --- a/src/Common/src/Common.Security/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Security/LocalCertificateWriter.cs @@ -30,7 +30,7 @@ public void Write(Guid orgId, Guid spaceId) // 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 rootAuthorityCertificate; + X509Certificate2 caCertificate; // Create a directory a level above the running project to contain the root and intermediate certificates if (!Directory.Exists(Path.Combine(ParentPath, "GeneratedCertificates"))) @@ -41,12 +41,12 @@ public void Write(Guid orgId, Guid spaceId) // Create the root certificate if it doesn't already exist (can be shared by multiple applications) if (!File.Exists(RootCaPfxPath)) { - rootAuthorityCertificate = CreateRootCertificate("CN=SteeltoeGeneratedCA"); - File.WriteAllBytes(RootCaPfxPath, rootAuthorityCertificate.Export(X509ContentType.Pfx)); + caCertificate = CreateRootCertificate("CN=SteeltoeGeneratedCA"); + File.WriteAllBytes(RootCaPfxPath, caCertificate.Export(X509ContentType.Pfx)); } else { - rootAuthorityCertificate = new X509Certificate2(RootCaPfxPath); + caCertificate = new X509Certificate2(RootCaPfxPath); } // Create the intermediate certificate if it doesn't already exist (can be shared by multiple applications) @@ -54,7 +54,7 @@ public void Write(Guid orgId, Guid spaceId) if (!File.Exists(IntermediatePfxPath)) { - intermediateCertificate = CreateIntermediateCertificate("CN=SteeltoeGeneratedIntermediate", rootAuthorityCertificate); + intermediateCertificate = CreateIntermediateCertificate("CN=SteeltoeGeneratedIntermediate", caCertificate); File.WriteAllBytes(IntermediatePfxPath, intermediateCertificate.Export(X509ContentType.Pfx)); } else @@ -73,16 +73,12 @@ public void Write(Guid orgId, Guid spaceId) } #if NET8_0_OR_GREATER - string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + intermediateCertificate.ExportCertificatePem() + "\r\n" + - rootAuthorityCertificate.ExportCertificatePem(); - + string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + intermediateCertificate.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 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"; string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END RSA PRIVATE KEY-----"; - #endif File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), chainedCertificateContents); diff --git a/src/Common/src/Common.Security/PublicAPI.Unshipped.txt b/src/Common/src/Common.Security/PublicAPI.Unshipped.txt index 54ca7ae367..9c5413fa95 100644 --- a/src/Common/src/Common.Security/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common.Security/PublicAPI.Unshipped.txt @@ -1,6 +1,9 @@ #nullable enable +static Steeltoe.Common.Security.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Common.Security.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Common.Security.CertificateConfigurationExtensions Steeltoe.Common.Security.CertificateServiceCollectionExtensions Steeltoe.Common.Security.ConfigureCertificateOptions Steeltoe.Common.Security.ConfigureCertificateOptions.Configure(Steeltoe.Common.Configuration.CertificateOptions! options) -> void diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs index 80803a7fda..76b51381d2 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs @@ -36,6 +36,12 @@ public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IService { ArgumentGuard.NotNull(services); + if (services.Any(descriptor => descriptor.ServiceType.IsAssignableFrom(typeof(IPostConfigureOptions)))) + { + throw new InvalidOperationException( + $"{nameof(ConfigureJwtBearerForCloudFoundry)} must be called before {nameof(JwtBearerExtensions.AddJwtBearer)}."); + } + services.AddSteeltoeSecurityHttpClient(configureHttpClient); services.AddSingleton, PostConfigureJwtBearerOptions>(); return services; diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs index 6d59de088d..71c2874ea4 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -38,6 +38,12 @@ public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this ISer { ArgumentGuard.NotNull(services); + if (services.Any(descriptor => descriptor.ServiceType.IsAssignableFrom(typeof(IPostConfigureOptions)))) + { + throw new InvalidOperationException( + $"{nameof(ConfigureOpenIdConnectForCloudFoundry)} must be called before {nameof(OpenIdConnectExtensions.AddOpenIdConnect)}."); + } + services.AddSteeltoeSecurityHttpClient(configureHttpClient); services.AddSingleton, PostConfigureOpenIdConnectOptions>(); return services; diff --git a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs index 5f9a62d2ad..8bd60f4635 100644 --- a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; -using Steeltoe.Common; namespace Steeltoe.Security.Authorization.Certificate; diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index d513a4ac42..2bce199e9e 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.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 System.Security.Claims; -using Steeltoe.Common; -using Steeltoe.Common.Configuration; - namespace Steeltoe.Security.Authorization.Certificate; public sealed class CertificateAuthorizationHandler : IAuthorizationHandler diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs index 14b38cdb09..b6127c4181 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs @@ -2,13 +2,11 @@ // 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; - namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateAuthorizationPolicyBuilderExtensions { - public static AuthorizationPolicyBuilder SameOrg(this AuthorizationPolicyBuilder builder) + public static AuthorizationPolicyBuilder RequireSameOrg(this AuthorizationPolicyBuilder builder) { ArgumentGuard.NotNull(builder); @@ -16,7 +14,7 @@ public static AuthorizationPolicyBuilder SameOrg(this AuthorizationPolicyBuilder return builder; } - public static AuthorizationPolicyBuilder SameSpace(this AuthorizationPolicyBuilder builder) + public static AuthorizationPolicyBuilder RequireSameSpace(this AuthorizationPolicyBuilder builder) { ArgumentGuard.NotNull(builder); diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index f1e3c59ac6..eb3150a6c4 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -3,11 +3,8 @@ // 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 Steeltoe.Common.Configuration; using Steeltoe.Common.Security; namespace Steeltoe.Security.Authorization.Certificate; @@ -20,10 +17,7 @@ public static class CertificateServiceCollectionExtensions /// /// The to add services to. /// - /// - /// The application configuration. - /// - public static IServiceCollection AddCertificateAuthorizationServer(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddCertificateAuthorizationServer(this IServiceCollection services) { services.ConfigureCertificateOptions("AppInstanceIdentity"); @@ -44,17 +38,15 @@ public static IServiceCollection AddCertificateAuthorizationServer(this IService } /// - /// Add a named and necessary components for finding client certificates and attaching to outbound requests. + /// 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) + public static IServiceCollection AddCertificateAuthorizationClient(this IServiceCollection services) { services.ConfigureCertificateOptions("AppInstanceIdentity"); + services.AddHttpClient(CertificateAuthorizationDefaults.HttpClientName, (serviceProvider, client) => { var loggerFactory = serviceProvider.GetRequiredService(); diff --git a/src/Security/src/Authorization.Certificate/GlobalUsings.cs b/src/Security/src/Authorization.Certificate/GlobalUsings.cs index e76bd93454..92a4e3da3f 100644 --- a/src/Security/src/Authorization.Certificate/GlobalUsings.cs +++ b/src/Security/src/Authorization.Certificate/GlobalUsings.cs @@ -2,8 +2,11 @@ // 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.Claims; global using System.Security.Cryptography.X509Certificates; +global using Microsoft.AspNetCore.Authentication.Certificate; global using Microsoft.AspNetCore.Authorization; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; -global using Steeltoe.Common.Options; +global using Steeltoe.Common; +global using Steeltoe.Common.Configuration; diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index ffb8b2231f..8faebd75d4 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -2,11 +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 System.Security.Claims; -using Microsoft.AspNetCore.Authentication.Certificate; -using Steeltoe.Common; -using Steeltoe.Common.Configuration; - namespace Steeltoe.Security.Authorization.Certificate; public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions @@ -27,9 +22,9 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options { ArgumentGuard.NotNull(options); - CertificateOptions containerIdentityOptions = _certificateOptionsMonitor.Get("AppInstanceIdentity"); + CertificateOptions appInstanceIdentityOptions = _certificateOptionsMonitor.Get("AppInstanceIdentity"); options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; - options.ClaimsIssuer = containerIdentityOptions.Certificate?.Issuer; + options.ClaimsIssuer = appInstanceIdentityOptions.Certificate?.Issuer; options.RevocationMode = X509RevocationMode.NoCheck; string? systemCertPath = Environment.GetEnvironmentVariable("CF_SYSTEM_CERT_PATH"); @@ -42,9 +37,9 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options options.CustomTrustStore.AddRange(systemCertificates.ToArray()); } - if (containerIdentityOptions.IssuerChain.Any()) + if (appInstanceIdentityOptions.IssuerChain.Any()) { - options.AdditionalChainCertificates.AddRange(containerIdentityOptions.IssuerChain.ToArray()); + options.AdditionalChainCertificates.AddRange(appInstanceIdentityOptions.IssuerChain.ToArray()); } options.Events = new CertificateAuthenticationEvents diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index b9f23ee86f..61d07484bf 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -1,14 +1,13 @@ #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! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameSpace(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.DependencyInjection.IServiceCollection! +static Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions.AddCertificateAuthorizationServer(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationHandler diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs index bf4a132d8f..2a25973487 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs @@ -17,6 +17,15 @@ public void ConfigureJwtBearerForCloudFoundry_AddsExpectedRegistrations() serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); } + [Fact] + public void ConfigureJwtBearerForCloudFoundry_RequiredBeforeAddJwtBearer() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAuthentication().AddJwtBearer(); + var exception = Assert.Throws(() => serviceCollection.ConfigureJwtBearerForCloudFoundry()); + exception.Message.Should().Contain($"{nameof(JwtBearerServiceCollectionExtensions.ConfigureJwtBearerForCloudFoundry)} must be called before"); + } + [Fact] public void ConfigureJwtBearerForCloudFoundry_ConfiguresHttpClient() { diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs index 9a36e78969..7bec80dd66 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs @@ -17,6 +17,15 @@ public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); } + [Fact] + public void ConfigureOpenIdConnectForCloudFoundry_RequiredBeforeAddJwtBearer() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAuthentication().AddOpenIdConnect(); + var exception = Assert.Throws(() => serviceCollection.ConfigureOpenIdConnectForCloudFoundry()); + exception.Message.Should().Contain($"{nameof(OpenIdConnectServiceCollectionExtensions.ConfigureOpenIdConnectForCloudFoundry)} must be called before"); + } + [Fact] public void ConfigureOpenIdConnectForCloudFoundry_ConfiguresHttpClient() { diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 9064b62eb9..20f41a69ad 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -5,9 +5,6 @@ using System.Net; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Hosting; -using Steeltoe.Common.TestResources; namespace Steeltoe.Security.Authorization.Certificate.Test; diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..ed7ed01758 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs @@ -0,0 +1,41 @@ +// 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.Hosting; + +namespace Steeltoe.Security.Authorization.Certificate.Test; + +public sealed class CertificateServiceCollectionExtensionsTest +{ + [Fact] + public async Task AddCertificateAuthorizationClient_AddsNamedHttpClientWithCertificate() + { + byte[] certificateBytes = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key").Export(X509ContentType.Cert); + 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 IHost host = await GetHostBuilder().StartAsync(); + var factory = host.Services.GetRequiredService(); + HttpClient client = factory.CreateClient(CertificateAuthorizationDefaults.HttpClientName); + + client.Should().NotBeNull(); + client.DefaultRequestHeaders.Contains("X-Client-Cert").Should().BeTrue(); + string certificateHeader = client.DefaultRequestHeaders.GetValues("X-Client-Cert").First(); + certificateHeader.Should().BeEquivalentTo(Convert.ToBase64String(certificateBytes)); + } + + private static IHostBuilder GetHostBuilder() + { + return new HostBuilder().ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate()) + .ConfigureServices(services => services.AddCertificateAuthorizationClient()).ConfigureWebHost(webBuilder => + { + webBuilder.Configure(_ => + { + }); + + webBuilder.UseTestServer(); + }); + } +} diff --git a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs index a9f9918319..12bd59c9d9 100644 --- a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs +++ b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs @@ -2,5 +2,10 @@ // 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 FluentAssertions; +global using Microsoft.AspNetCore.TestHost; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; global using Steeltoe.Common.Security; +global using Steeltoe.Common.TestResources; global using Xunit; diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs index 5997d0ec6c..20953d5915 100644 --- a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -5,18 +5,14 @@ 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) +public sealed class TestServerCertificateStartup { - internal IConfiguration Configuration { get; } = configuration; - public void ConfigureServices(IServiceCollection services) { - services.AddCertificateAuthorizationServer(Configuration); + services.AddCertificateAuthorizationServer(); services.AddAuthentication().AddCertificate(options => { @@ -27,12 +23,12 @@ public void ConfigureServices(IServiceCollection services) { options.AddPolicy(CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy, authorizationPolicyBuilder => { - authorizationPolicyBuilder.SameOrg(); + authorizationPolicyBuilder.RequireSameOrg(); }); options.AddPolicy(CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy, authorizationPolicyBuilder => { - authorizationPolicyBuilder.SameSpace(); + authorizationPolicyBuilder.RequireSameSpace(); }); }); } From faab6132a0b8abff3d03938a691616c1d1539e0a Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 17:06:45 -0500 Subject: [PATCH 03/28] adjust tests to call Steeltoe configure methods before AddAuth --- .../PostConfigureJwtBearerOptionsTest.cs | 2 +- .../PostConfigureOpenIdConnectOptionsTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs index 193518205d..4267d95880 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -88,8 +88,8 @@ public void PostConfigure_ConfiguresForCloudFoundry() IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); - serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); serviceCollection.ConfigureJwtBearerForCloudFoundry(); + serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); JwtBearerOptions jwtBearerOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(JwtBearerDefaults.AuthenticationScheme); diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs index 36fbdac94c..e138738c7a 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -90,8 +90,8 @@ public void PostConfigure_ConfiguresForCloudFoundry() var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); serviceCollection.AddHttpClient(); - serviceCollection.AddAuthentication().AddOpenIdConnect(); serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + serviceCollection.AddAuthentication().AddOpenIdConnect(); OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(OpenIdConnectDefaults.AuthenticationScheme); From 363aabd71f3a8c68e0e46b14b680fdebace4224f Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 17:24:19 -0500 Subject: [PATCH 04/28] skip .NET 6 for app security component build --- build/security.yml | 2 ++ build/templates/component-build.yaml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/build/security.yml b/build/security.yml index 96a99fd73e..69a613b15b 100644 --- a/build/security.yml +++ b/build/security.yml @@ -18,7 +18,9 @@ jobs: parameters: component: Security skipFilter: --filter "Category!=SkipOnLinux" + skipNET6: true - template: templates/component-build.yaml parameters: component: Security OS: windows + skipNET6: true diff --git a/build/templates/component-build.yaml b/build/templates/component-build.yaml index 91f29c72fa..b5a9587e89 100644 --- a/build/templates/component-build.yaml +++ b/build/templates/component-build.yaml @@ -2,6 +2,7 @@ parameters: component: '' runConfigServer: false runRabbitMQ: false + skipNET6: false skipFilter: '' OS: ubuntu @@ -27,6 +28,7 @@ jobs: - checkout: self fetchDepth: 0 - task: UseDotNet@2 + condition: ne(${{parameters.skipNET6}}, 'true') displayName: Install .NET 6 inputs: version: 6.0.x @@ -68,6 +70,7 @@ jobs: condition: eq(${{parameters.runConfigServer}}, 'true') displayName: Start Config Server - task: DotNetCoreCLI@2 + condition: ne(${{parameters.skipNET6}}, 'true') displayName: dotnet test 6.0 inputs: command: test From 9a4b4f956a9d87bbc79328d7b0bf6e10c066ae2d Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 18:00:14 -0500 Subject: [PATCH 05/28] use default HeaderConverter for X-Client-Cert processing --- .../CertificateServiceCollectionExtensions.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index eb3150a6c4..52dad180f8 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -21,14 +21,7 @@ public static IServiceCollection AddCertificateAuthorizationServer(this IService { services.ConfigureCertificateOptions("AppInstanceIdentity"); - services.AddCertificateForwarding(opt => - { - opt.HeaderConverter = certificateData => - { - var certificate = new X509Certificate2(Encoding.ASCII.GetBytes(certificateData)); - return certificate; - }; - }); + services.AddCertificateForwarding(_ => {}); services.TryAddEnumerable(ServiceDescriptor .Singleton, PostConfigureCertificateAuthenticationOptions>()); From 681cbcb822f3b7549991887264585efaf3a001e4 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 11 Jun 2024 18:24:05 -0500 Subject: [PATCH 06/28] formatting --- .../CertificateServiceCollectionExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index 52dad180f8..72ad2b7869 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -2,7 +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 System.Text; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Steeltoe.Common.Security; @@ -21,7 +20,9 @@ public static IServiceCollection AddCertificateAuthorizationServer(this IService { services.ConfigureCertificateOptions("AppInstanceIdentity"); - services.AddCertificateForwarding(_ => {}); + services.AddCertificateForwarding(_ => + { + }); services.TryAddEnumerable(ServiceDescriptor .Singleton, PostConfigureCertificateAuthenticationOptions>()); From 21545438fc583567c59a09d73ae2adbc967e1175 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 12 Jun 2024 08:24:42 -0500 Subject: [PATCH 07/28] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- .../CertificateConfigurationExtensions.cs | 27 +++++++++++-------- .../JwtBearerServiceCollectionExtensions.cs | 2 +- ...penIdConnectServiceCollectionExtensions.cs | 2 +- .../Authentication.Shared/TokenKeyResolver.cs | 14 +++------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs index 42a23e61ac..80b460e267 100644 --- a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs @@ -49,15 +49,15 @@ internal static IConfigurationBuilder AddCertificate(this IConfigurationBuilder } /// - /// 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. + /// Adds PEM certificate files representing application identity to the application configuration. When running outside of Cloud Foundry-based platforms, + /// this method 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. + /// When running outside of Cloud Foundry, the CA and Intermediate certificates will be created in a directory above the current project, so that they + /// can be shared between different projects in the same solution. /// public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConfigurationBuilder builder) { @@ -65,8 +65,8 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf } /// - /// 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. + /// Adds PEM certificate files representing application identity to the application configuration. When running outside of Cloud Foundry-based platforms, + /// this method will create certificates resembling those found on the platform. /// /// /// Your . @@ -78,8 +78,8 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf /// (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. + /// When running outside of Cloud Foundry, the CA and Intermediate certificates will be created in a directory above the current project, so that they + /// can be shared between different projects in the same solution. /// public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConfigurationBuilder builder, Guid? organizationId, Guid? spaceId) { @@ -88,8 +88,8 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf organizationId ??= Guid.NewGuid(); spaceId ??= Guid.NewGuid(); - var task = new LocalCertificateWriter(); - task.Write((Guid)organizationId, (Guid)spaceId); + var writer = new LocalCertificateWriter(); + writer.Write(organizationId.Value, spaceId.Value); Environment.SetEnvironmentVariable("CF_SYSTEM_CERT_PATH", Path.Combine(Directory.GetParent(LocalCertificateWriter.AppBasePath)!.ToString(), "GeneratedCertificates")); @@ -104,6 +104,11 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf string? certificateFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); string? keyFile = Environment.GetEnvironmentVariable("CF_INSTANCE_KEY"); - return certificateFile == null || keyFile == null ? builder : builder.AddCertificate("AppInstanceIdentity", certificateFile, keyFile); + if (certificateFile != null && keyFile != null) + { + builder.AddCertificate("AppInstanceIdentity", certificateFile, keyFile); + } + + return builder; } } diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs index 76b51381d2..9ee9033ced 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs @@ -30,7 +30,7 @@ public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IService /// The to add services to. /// /// - /// Configure the HttpClient used to interact with the identity server. + /// Configures the used to interact with the identity server. /// public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs index 71c2874ea4..a78bd81d50 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -32,7 +32,7 @@ public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this ISer /// The to add services to. /// /// - /// Configure the HttpClient used to interact with the identity server. + /// Configures the used to interact with the identity server. /// public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { diff --git a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs index 70c0f7fde3..0fea772251 100644 --- a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs @@ -29,10 +29,7 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken { if (Resolved.TryGetValue(kid, out SecurityKey? resolved)) { - return new List - { - resolved - }; + return [resolved]; } JsonWebKeySet? keySet = FetchKeySetAsync().GetAwaiter().GetResult(); @@ -47,10 +44,7 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken if (Resolved.TryGetValue(kid, out resolved)) { - return new List - { - resolved - }; + return [resolved]; } return []; @@ -58,12 +52,12 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken internal async Task FetchKeySetAsync() { - if (!_authority.EndsWith("/", StringComparison.Ordinal)) + if (!_authority.EndsWith('/')) { _authority += "/"; } - var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_authority}token_keys")); + using 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); From de03386640dee20ae4f156a2a3f3d7bbd59c55e9 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 12 Jun 2024 13:55:51 -0500 Subject: [PATCH 08/28] Rename Steeltoe.Common.Security to Steeltoe.Common.Certificate --- shared-package.props | 8 +++---- .../src/AutoConfiguration/BootstrapScanner.cs | 4 ++-- .../AutoConfiguration/PublicAPI.Unshipped.txt | 2 +- ...teeltoe.Bootstrap.AutoConfiguration.csproj | 2 +- .../SteeltoeAssemblyNames.cs | 2 +- .../HostBuilderExtensionsTest.cs | 2 +- ...oe.Bootstrap.AutoConfiguration.Test.csproj | 2 +- .../WebApplicationBuilderExtensionsTest.cs | 2 +- .../WebHostBuilderExtensionsTest.cs | 2 +- .../Abstractions/Properties/AssemblyInfo.cs | 4 ++-- .../CertificateConfigurationExtensions.cs | 2 +- .../CertificateServiceCollectionExtensions.cs | 2 +- .../ConfigureCertificateOptions.cs | 4 ++-- .../FilePathInOptionsChangeTokenSource.cs | 2 +- .../LocalCertificateWriter.cs | 22 +++++++++++------- .../Properties/AssemblyInfo.cs | 3 ++- .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 7 ++++++ .../ServiceCollectionExtensions.cs | 2 +- .../Steeltoe.Common.Certificate.csproj} | 0 .../Common.Security/PublicAPI.Unshipped.txt | 11 --------- .../src/Common/Properties/AssemblyInfo.cs | 4 ++-- .../CertificateConfigurationExtensionsTest.cs | 2 +- .../ConfigureCertificateOptionsTest.cs | 2 +- .../LocalCertificateWriterTest.cs | 2 +- .../Steeltoe.Common.Certificate.Test.csproj} | 5 ++-- .../empty.crt | 0 .../instance.crt | 0 .../instance.key | 0 .../instance.p12 | Bin .../instance2.crt | 0 .../instance2.key | 0 .../invalid.key | 0 .../xunit.runner.json | 0 .../ConfigServerConfigurationSource.cs | 2 +- ...Steeltoe.Configuration.ConfigServer.csproj | 2 +- ...toe.Configuration.ConfigServer.Test.csproj | 2 +- .../EurekaServiceCollectionExtensions.cs | 2 +- .../Eureka/Steeltoe.Discovery.Eureka.csproj | 2 +- ...Steeltoe.Discovery.HttpClients.Test.csproj | 2 +- .../CertificateServiceCollectionExtensions.cs | 2 +- ....Security.Authorization.Certificate.csproj | 2 +- .../GlobalUsings.cs | 2 +- src/Steeltoe.All.sln | 4 ++-- src/Steeltoe.Common.slnf | 6 ++--- src/Steeltoe.Configuration.slnf | 4 ++-- src/Steeltoe.Discovery.slnf | 4 ++-- src/Steeltoe.Security.slnf | 7 ++---- 48 files changed, 72 insertions(+), 71 deletions(-) rename src/Common/src/{Common.Security => Common.Certificate}/CertificateConfigurationExtensions.cs (99%) rename src/Common/src/{Common.Security => Common.Certificate}/CertificateServiceCollectionExtensions.cs (98%) rename src/Common/src/{Common.Security => Common.Certificate}/ConfigureCertificateOptions.cs (94%) rename src/Common/src/{Common.Security => Common.Certificate}/FilePathInOptionsChangeTokenSource.cs (97%) rename src/Common/src/{Common.Security => Common.Certificate}/LocalCertificateWriter.cs (86%) rename src/Common/src/{Common.Security => Common.Certificate}/Properties/AssemblyInfo.cs (77%) rename src/Common/src/{Common.Security => Common.Certificate}/PublicAPI.Shipped.txt (100%) create mode 100644 src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt rename src/Common/src/{Common.Security => Common.Certificate}/ServiceCollectionExtensions.cs (98%) rename src/Common/src/{Common.Security/Steeltoe.Common.Security.csproj => Common.Certificate/Steeltoe.Common.Certificate.csproj} (100%) delete mode 100644 src/Common/src/Common.Security/PublicAPI.Unshipped.txt rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/CertificateConfigurationExtensionsTest.cs (95%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/ConfigureCertificateOptionsTest.cs (99%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/LocalCertificateWriterTest.cs (97%) rename src/Common/test/{Common.Security.Test/Steeltoe.Common.Security.Test.csproj => Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj} (85%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/empty.crt (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/instance.crt (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/instance.key (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/instance.p12 (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/instance2.crt (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/instance2.key (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/invalid.key (100%) rename src/Common/test/{Common.Security.Test => Common.Certificate.Test}/xunit.runner.json (100%) diff --git a/shared-package.props b/shared-package.props index d20e1d1bf1..4f258e19c6 100644 --- a/shared-package.props +++ b/shared-package.props @@ -54,7 +54,7 @@ @@ -63,9 +63,9 @@ + Condition="!$(MSBuildProjectName.StartsWith('Steeltoe.Bootstrap')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Common.Certificates')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Configuration')) And + !$(MSBuildProjectName.StartsWith('Steeltoe.Connectors')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Discovery')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Logging')) And + !$(MSBuildProjectName.StartsWith('Steeltoe.Management')) 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 c601dbd731..b4f73f4cc5 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -8,7 +8,7 @@ using Steeltoe.Common.DynamicTypeAccess; using Steeltoe.Common.Hosting; using Steeltoe.Common.Logging; -using Steeltoe.Common.Security; +using Steeltoe.Common.Certificate; using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.ConfigServer; using Steeltoe.Configuration.Placeholder; @@ -81,7 +81,7 @@ public void ConfigureSteeltoe() WireIfLoaded(WirePrometheus, SteeltoeAssemblyNames.ManagementPrometheus); WireIfLoaded(WireWavefrontMetrics, SteeltoeAssemblyNames.ManagementWavefront); WireIfLoaded(WireDistributedTracing, SteeltoeAssemblyNames.ManagementTracing); - WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonSecurity); + WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonCertificate); } private void WireConfigServer() diff --git a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt index f0190c8154..799e1e5900 100644 --- a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt +++ b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt @@ -1,5 +1,5 @@ #nullable enable -const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonSecurity = "Steeltoe.Common.Security" -> string! +const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonCertificate = "Steeltoe.Common.Certificate" -> 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! diff --git a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj index b37aa865d6..70b34102c0 100644 --- a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj +++ b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs index 68cfa40f54..b40c07fbaf 100644 --- a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs +++ b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Bootstrap.AutoConfiguration; /// public static class SteeltoeAssemblyNames { - public const string CommonSecurity = "Steeltoe.Common.Security"; + public const string CommonCertificate = "Steeltoe.Common.Certificate"; public const string ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry"; public const string ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer"; public const string ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue"; diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 78db56a5ae..6a2882e9eb 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -228,7 +228,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonSecurity); + using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonCertificate); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); 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 f5e9ae3d09..822975fe75 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index 537dbaa0c4..930132134a 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -229,7 +229,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonSecurity); + using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonCertificate); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index 706e217aeb..bc9540da92 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -229,7 +229,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonSecurity); + using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonCertificate); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); diff --git a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs index 3ea8179152..9e09e116c3 100644 --- a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs @@ -6,8 +6,8 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Security")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Security.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Connectors")] [assembly: InternalsVisibleTo("Steeltoe.Connectors.EntityFrameworkCore")] diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs similarity index 99% rename from src/Common/src/Common.Security/CertificateConfigurationExtensions.cs rename to src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs index 80b460e267..56bc185887 100644 --- a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; public static class CertificateConfigurationExtensions { diff --git a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs similarity index 98% rename from src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs rename to src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs index 3b59d91561..af0bccf83a 100644 --- a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; public static class CertificateServiceCollectionExtensions { diff --git a/src/Common/src/Common.Security/ConfigureCertificateOptions.cs b/src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs similarity index 94% rename from src/Common/src/Common.Security/ConfigureCertificateOptions.cs rename to src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs index 19d2dba564..849724c24f 100644 --- a/src/Common/src/Common.Security/ConfigureCertificateOptions.cs +++ b/src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs @@ -9,9 +9,9 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; -public sealed class ConfigureCertificateOptions : IConfigureNamedOptions +internal sealed class ConfigureCertificateOptions : IConfigureNamedOptions { private static readonly Regex CertificateRegex = new("-+BEGIN CERTIFICATE-+.+?-+END CERTIFICATE-+", RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); diff --git a/src/Common/src/Common.Security/FilePathInOptionsChangeTokenSource.cs b/src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs similarity index 97% rename from src/Common/src/Common.Security/FilePathInOptionsChangeTokenSource.cs rename to src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs index 0570e81156..eb92c5b685 100644 --- a/src/Common/src/Common.Security/FilePathInOptionsChangeTokenSource.cs +++ b/src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; internal sealed class FilePathInOptionsChangeTokenSource : IOptionsChangeTokenSource { diff --git a/src/Common/src/Common.Security/LocalCertificateWriter.cs b/src/Common/src/Common.Certificate/LocalCertificateWriter.cs similarity index 86% rename from src/Common/src/Common.Security/LocalCertificateWriter.cs rename to src/Common/src/Common.Certificate/LocalCertificateWriter.cs index 4ff2731396..4bc33736ca 100644 --- a/src/Common/src/Common.Security/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificate/LocalCertificateWriter.cs @@ -5,14 +5,14 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; internal sealed class LocalCertificateWriter { internal static readonly string AppBasePath = AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]; - internal static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); + private static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); internal string CertificateFilenamePrefix { get; set; } = "SteeltoeInstance"; @@ -72,13 +72,19 @@ public void Write(Guid orgId, Guid spaceId) Directory.CreateDirectory(Path.Combine(AppBasePath, "GeneratedCertificates")); } -#if NET8_0_OR_GREATER - string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + intermediateCertificate.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"; +#if NET6_0 + string chainedCertificateContents = "-----BEGIN CERTIFICATE-----" + Environment.NewLine + + Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + + "-----END CERTIFICATE-----" + Environment.NewLine + "-----BEGIN CERTIFICATE-----" + Environment.NewLine + + Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + + "-----END CERTIFICATE-----" + Environment.NewLine; - string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END RSA PRIVATE KEY-----"; + string keyContents = "-----BEGIN RSA PRIVATE KEY-----" + Environment.NewLine + + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + + Environment.NewLine + "-----END RSA PRIVATE KEY-----"; +#else + string chainedCertificateContents = clientCertificate.ExportCertificatePem() + Environment.NewLine + intermediateCertificate.ExportCertificatePem(); + string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); #endif File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), chainedCertificateContents); diff --git a/src/Common/src/Common.Security/Properties/AssemblyInfo.cs b/src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs similarity index 77% rename from src/Common/src/Common.Security/Properties/AssemblyInfo.cs rename to src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs index eae52bc83a..fc025a7a32 100644 --- a/src/Common/src/Common.Security/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs @@ -6,5 +6,6 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Security.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate.Test")] diff --git a/src/Common/src/Common.Security/PublicAPI.Shipped.txt b/src/Common/src/Common.Certificate/PublicAPI.Shipped.txt similarity index 100% rename from src/Common/src/Common.Security/PublicAPI.Shipped.txt rename to src/Common/src/Common.Certificate/PublicAPI.Shipped.txt diff --git a/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt b/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..b58c792977 --- /dev/null +++ b/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt @@ -0,0 +1,7 @@ +#nullable enable +static Steeltoe.Common.Certificate.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Common.Certificate.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Common.Certificate.CertificateConfigurationExtensions +Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions diff --git a/src/Common/src/Common.Security/ServiceCollectionExtensions.cs b/src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs similarity index 98% rename from src/Common/src/Common.Security/ServiceCollectionExtensions.cs rename to src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs index 012ac2be6e..44ced30011 100644 --- a/src/Common/src/Common.Security/ServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -namespace Steeltoe.Common.Security; +namespace Steeltoe.Common.Certificate; internal static class ServiceCollectionExtensions { diff --git a/src/Common/src/Common.Security/Steeltoe.Common.Security.csproj b/src/Common/src/Common.Certificate/Steeltoe.Common.Certificate.csproj similarity index 100% rename from src/Common/src/Common.Security/Steeltoe.Common.Security.csproj rename to src/Common/src/Common.Certificate/Steeltoe.Common.Certificate.csproj diff --git a/src/Common/src/Common.Security/PublicAPI.Unshipped.txt b/src/Common/src/Common.Security/PublicAPI.Unshipped.txt deleted file mode 100644 index 9c5413fa95..0000000000 --- a/src/Common/src/Common.Security/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,11 +0,0 @@ -#nullable enable -static Steeltoe.Common.Security.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! -static Steeltoe.Common.Security.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! -static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Steeltoe.Common.Security.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -Steeltoe.Common.Security.CertificateConfigurationExtensions -Steeltoe.Common.Security.CertificateServiceCollectionExtensions -Steeltoe.Common.Security.ConfigureCertificateOptions -Steeltoe.Common.Security.ConfigureCertificateOptions.Configure(Steeltoe.Common.Configuration.CertificateOptions! options) -> void -Steeltoe.Common.Security.ConfigureCertificateOptions.Configure(string? name, Steeltoe.Common.Configuration.CertificateOptions! options) -> void -Steeltoe.Common.Security.ConfigureCertificateOptions.ConfigureCertificateOptions(Microsoft.Extensions.Configuration.IConfiguration! configuration) -> void diff --git a/src/Common/src/Common/Properties/AssemblyInfo.cs b/src/Common/src/Common/Properties/AssemblyInfo.cs index 503950a39a..b0ac7661b6 100644 --- a/src/Common/src/Common/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common/Properties/AssemblyInfo.cs @@ -4,8 +4,8 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Steeltoe.Common.Security")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Security.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting.Test")] diff --git a/src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs b/src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs similarity index 95% rename from src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs rename to src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs index baa8a23fae..eebe9d3585 100644 --- a/src/Common/test/Common.Security.Test/CertificateConfigurationExtensionsTest.cs +++ b/src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Configuration; using Xunit; -namespace Steeltoe.Common.Security.Test; +namespace Steeltoe.Common.Certificate.Test; public sealed class CertificateConfigurationExtensionsTest { diff --git a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs similarity index 99% rename from src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs rename to src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs index 0bea22db38..99e06f93f7 100644 --- a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs @@ -13,7 +13,7 @@ using Steeltoe.Common.Utils.IO; using Xunit; -namespace Steeltoe.Common.Security.Test; +namespace Steeltoe.Common.Certificate.Test; public sealed class ConfigureCertificateOptionsTest { diff --git a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs similarity index 97% rename from src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs rename to src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs index 9deed749a8..fb7da8c1c6 100644 --- a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs @@ -7,7 +7,7 @@ using FluentAssertions; using Xunit; -namespace Steeltoe.Common.Security.Test; +namespace Steeltoe.Common.Certificate.Test; public sealed class LocalCertificateWriterTest { diff --git a/src/Common/test/Common.Security.Test/Steeltoe.Common.Security.Test.csproj b/src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj similarity index 85% rename from src/Common/test/Common.Security.Test/Steeltoe.Common.Security.Test.csproj rename to src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj index b4899c06aa..fe427b9630 100644 --- a/src/Common/test/Common.Security.Test/Steeltoe.Common.Security.Test.csproj +++ b/src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj @@ -1,7 +1,8 @@ net8.0;net6.0 - enable + enable + Steeltoe.Common.Certificate.Test @@ -13,7 +14,7 @@ - + diff --git a/src/Common/test/Common.Security.Test/empty.crt b/src/Common/test/Common.Certificate.Test/empty.crt similarity index 100% rename from src/Common/test/Common.Security.Test/empty.crt rename to src/Common/test/Common.Certificate.Test/empty.crt diff --git a/src/Common/test/Common.Security.Test/instance.crt b/src/Common/test/Common.Certificate.Test/instance.crt similarity index 100% rename from src/Common/test/Common.Security.Test/instance.crt rename to src/Common/test/Common.Certificate.Test/instance.crt diff --git a/src/Common/test/Common.Security.Test/instance.key b/src/Common/test/Common.Certificate.Test/instance.key similarity index 100% rename from src/Common/test/Common.Security.Test/instance.key rename to src/Common/test/Common.Certificate.Test/instance.key diff --git a/src/Common/test/Common.Security.Test/instance.p12 b/src/Common/test/Common.Certificate.Test/instance.p12 similarity index 100% rename from src/Common/test/Common.Security.Test/instance.p12 rename to src/Common/test/Common.Certificate.Test/instance.p12 diff --git a/src/Common/test/Common.Security.Test/instance2.crt b/src/Common/test/Common.Certificate.Test/instance2.crt similarity index 100% rename from src/Common/test/Common.Security.Test/instance2.crt rename to src/Common/test/Common.Certificate.Test/instance2.crt diff --git a/src/Common/test/Common.Security.Test/instance2.key b/src/Common/test/Common.Certificate.Test/instance2.key similarity index 100% rename from src/Common/test/Common.Security.Test/instance2.key rename to src/Common/test/Common.Certificate.Test/instance2.key diff --git a/src/Common/test/Common.Security.Test/invalid.key b/src/Common/test/Common.Certificate.Test/invalid.key similarity index 100% rename from src/Common/test/Common.Security.Test/invalid.key rename to src/Common/test/Common.Certificate.Test/invalid.key diff --git a/src/Common/test/Common.Security.Test/xunit.runner.json b/src/Common/test/Common.Certificate.Test/xunit.runner.json similarity index 100% rename from src/Common/test/Common.Security.Test/xunit.runner.json rename to src/Common/test/Common.Certificate.Test/xunit.runner.json diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs index 6a750cb795..5efca7e74c 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Steeltoe.Common; using Steeltoe.Common.Configuration; -using Steeltoe.Common.Security; +using Steeltoe.Common.Certificate; namespace Steeltoe.Configuration.ConfigServer; diff --git a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj index 043085d1a1..c23edef10a 100644 --- a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj +++ b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj index ee73f92087..e7fa74e56e 100644 --- a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj +++ b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index fd8f9e7eb8..fbad42a883 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -13,7 +13,7 @@ using Steeltoe.Common.HealthChecks; using Steeltoe.Common.Http.HttpClientPooling; using Steeltoe.Common.Net; -using Steeltoe.Common.Security; +using Steeltoe.Common.Certificate; using Steeltoe.Discovery.Eureka.Configuration; namespace Steeltoe.Discovery.Eureka; diff --git a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj index 4649ebb2b8..1fa9ef2caf 100644 --- a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj +++ b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj index 28a81b098a..38bccb3dbe 100644 --- a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj +++ b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index 72ad2b7869..742dd78302 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Steeltoe.Common.Security; +using Steeltoe.Common.Certificate; namespace Steeltoe.Security.Authorization.Certificate; diff --git a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj index af9c75c4ce..c5a7a24c97 100644 --- a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj +++ b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj @@ -14,6 +14,6 @@ - + diff --git a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs index 12bd59c9d9..1273eaa307 100644 --- a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs +++ b/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs @@ -6,6 +6,6 @@ global using Microsoft.AspNetCore.TestHost; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; -global using Steeltoe.Common.Security; +global using Steeltoe.Common.Certificate; global using Steeltoe.Common.TestResources; global using Xunit; diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index 61c54b3a45..685e8ec204 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -14,7 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http", "Com EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net", "Common\src\Common.Net\Steeltoe.Common.Net.csproj", "{8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Security", "Common\src\Common.Security\Steeltoe.Common.Security.csproj", "{DFE642E6-1CD0-4485-AC86-43CEBC451484}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificate", "Common\src\Common.Certificate\Steeltoe.Common.Certificate.csproj", "{DFE642E6-1CD0-4485-AC86-43CEBC451484}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{59874241-E276-4035-B31D-14924889A1C9}" ProjectSection(SolutionItems) = preProject @@ -25,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net.Test", "Common\test\Common.Net.Test\Steeltoe.Common.Net.Test.csproj", "{1ACC6991-7D4C-48B2-A41C-4B179B19A85C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Security.Test", "Common\test\Common.Security.Test\Steeltoe.Common.Security.Test.csproj", "{880208DF-05C6-4763-A447-744D6C8DBDEA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificate.Test", "Common\test\Common.Certificate.Test\Steeltoe.Common.Certificate.Test.csproj", "{880208DF-05C6-4763-A447-744D6C8DBDEA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_shared", "_shared", "{DC1BC61A-E0FA-4CF9-9F24-D4C564A07836}" ProjectSection(SolutionItems) = preProject diff --git a/src/Steeltoe.Common.slnf b/src/Steeltoe.Common.slnf index 9c1373c668..38221f71de 100644 --- a/src/Steeltoe.Common.slnf +++ b/src/Steeltoe.Common.slnf @@ -6,16 +6,16 @@ "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", "Common\\src\\Common.Net\\Steeltoe.Common.Net.csproj", - "Common\\src\\Common.Security\\Steeltoe.Common.Security.csproj", + "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.Hosting.Test\\Steeltoe.Common.Hosting.Test.csproj", "Common\\test\\Common.Http.Test\\Steeltoe.Common.Http.Test.csproj", "Common\\test\\Common.Net.Test\\Steeltoe.Common.Net.Test.csproj", - "Common\\test\\Common.Security.Test\\Steeltoe.Common.Security.Test.csproj", + "Common\\test\\Common.Certificate.Test\\Steeltoe.Common.Certificate.Test.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", "Common\\test\\Common.Test\\Steeltoe.Common.Test.csproj", "Common\\test\\Common.Utils.Test\\Steeltoe.Common.Utils.Test.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Steeltoe.Configuration.slnf b/src/Steeltoe.Configuration.slnf index 8c96c23155..55119d692a 100644 --- a/src/Steeltoe.Configuration.slnf +++ b/src/Steeltoe.Configuration.slnf @@ -5,7 +5,7 @@ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", - "Common\\src\\Common.Security\\Steeltoe.Common.Security.csproj", + "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", @@ -37,4 +37,4 @@ "Management\\src\\Endpoint\\Steeltoe.Management.Endpoint.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Steeltoe.Discovery.slnf b/src/Steeltoe.Discovery.slnf index 27ddb302a6..b56102e638 100644 --- a/src/Steeltoe.Discovery.slnf +++ b/src/Steeltoe.Discovery.slnf @@ -5,7 +5,7 @@ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", - "Common\\src\\Common.Security\\Steeltoe.Common.Security.csproj", + "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", @@ -26,4 +26,4 @@ "Management\\src\\Endpoint\\Steeltoe.Management.Endpoint.csproj" ] } -} \ No newline at end of file +} diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index 0054a1889b..750f55f910 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -3,14 +3,11 @@ "path": "Steeltoe.All.sln", "projects": [ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", - "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", - "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", - "Common\\src\\Common.Security\\Steeltoe.Common.Security.csproj", + "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", "Configuration\\src\\Abstractions\\Steeltoe.Configuration.Abstractions.csproj", "Configuration\\src\\CloudFoundry.ServiceBinding\\Steeltoe.Configuration.CloudFoundry.ServiceBinding.csproj", - "Configuration\\src\\CloudFoundry\\Steeltoe.Configuration.CloudFoundry.csproj", "Configuration\\src\\Kubernetes.ServiceBinding\\Steeltoe.Configuration.Kubernetes.ServiceBinding.csproj", "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", "Security\\src\\Authentication.JwtBearer\\Steeltoe.Security.Authentication.JwtBearer.csproj", @@ -25,4 +22,4 @@ "Security\\test\\DataProtection.Redis.Test\\Steeltoe.Security.DataProtection.Redis.Test.csproj" ] } -} \ No newline at end of file +} From 9427171d23232ff4a6c246cfbd8f11d213e3d237 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 12 Jun 2024 16:28:46 -0500 Subject: [PATCH 09/28] PR feedback, more simplification remove special handling for local uaa remove shared named http client code (Microsoft code can handle backchannel creation) remove globalusings --- .../src/AutoConfiguration/BootstrapScanner.cs | 2 +- .../LocalCertificateWriter.cs | 12 +- .../ConfigServerConfigurationSource.cs | 2 +- .../EurekaServiceCollectionExtensions.cs | 2 +- .../JwtBearerServiceCollectionExtensions.cs | 22 --- .../PostConfigureJwtBearerOptions.cs | 36 ++-- .../PublicAPI.Shipped.txt | 2 +- .../PublicAPI.Unshipped.txt | 1 - ...e.Security.Authentication.JwtBearer.csproj | 3 +- .../TokenKeyResolver.cs | 74 +++++++ ...penIdConnectServiceCollectionExtensions.cs | 23 --- .../PostConfigureOpenIdConnectOptions.cs | 23 +-- .../PublicAPI.Shipped.txt | 2 +- .../PublicAPI.Unshipped.txt | 1 - ...curity.Authentication.OpenIdConnect.csproj | 3 +- .../TokenKeyResolver.cs | 37 ++-- .../Properties/AssemblyInfo.cs | 11 -- .../SharedServiceCollectionExtensions.cs | 21 -- ...ltoe.Security.Authentication.Shared.csproj | 21 -- .../SteeltoeSecurityDefaults.cs | 11 -- .../ApplicationInstanceCertificate.cs | 26 ++- ...CertificateApplicationBuilderExtensions.cs | 22 ++- .../CertificateAuthorizationHandler.cs | 15 +- ...ateAuthorizationPolicyBuilderExtensions.cs | 3 + .../CertificateServiceCollectionExtensions.cs | 6 + .../Authorization.Certificate/GlobalUsings.cs | 12 -- ...nfigureCertificateAuthenticationOptions.cs | 16 +- .../PublicAPI.Unshipped.txt | 4 +- .../SameOrgRequirement.cs | 2 + .../SameSpaceRequirement.cs | 2 + .../GlobalUsings.cs | 8 - ...wtBearerServiceCollectionExtensionsTest.cs | 25 +-- .../PostConfigureJwtBearerOptionsTest.cs | 29 +-- .../TokenKeyResolverTest.cs | 181 ++++++++++++++++++ .../GlobalUsings.cs | 8 - ...dConnectServiceCollectionExtensionsTest.cs | 25 +-- .../PostConfigureOpenIdConnectOptionsTest.cs | 29 +-- .../TokenKeyResolverTest.cs | 144 ++++++++------ .../GlobalUsings.cs | 6 - ...Security.Authentication.Shared.Test.csproj | 13 -- .../TestMessageHandler.cs | 18 -- .../CertificateAuthorizationTest.cs | 5 + ...tificateServiceCollectionExtensionsTest.cs | 7 + .../ClientCertificatesFixture.cs | 2 + .../GlobalUsings.cs | 11 -- .../TestServerCertificateStartup.cs | 1 + src/Steeltoe.All.sln | 26 +-- src/Steeltoe.All.sln.DotSettings | 2 +- 48 files changed, 503 insertions(+), 454 deletions(-) create mode 100644 src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs rename src/Security/src/{Authentication.Shared => Authentication.OpenIdConnect}/TokenKeyResolver.cs (60%) delete mode 100644 src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs delete mode 100644 src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs delete mode 100644 src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj delete mode 100644 src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs delete mode 100644 src/Security/src/Authorization.Certificate/GlobalUsings.cs delete mode 100644 src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs create mode 100644 src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs delete mode 100644 src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs rename src/Security/test/{Authentication.Shared.Test => Authentication.OpenIdConnect.Test}/TokenKeyResolverTest.cs (52%) delete mode 100644 src/Security/test/Authentication.Shared.Test/GlobalUsings.cs delete mode 100644 src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj delete mode 100644 src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs delete mode 100644 src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index b4f73f4cc5..e0777b95d1 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -5,10 +5,10 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Steeltoe.Common; +using Steeltoe.Common.Certificate; using Steeltoe.Common.DynamicTypeAccess; using Steeltoe.Common.Hosting; using Steeltoe.Common.Logging; -using Steeltoe.Common.Certificate; using Steeltoe.Configuration.CloudFoundry; using Steeltoe.Configuration.ConfigServer; using Steeltoe.Configuration.Placeholder; diff --git a/src/Common/src/Common.Certificate/LocalCertificateWriter.cs b/src/Common/src/Common.Certificate/LocalCertificateWriter.cs index 4bc33736ca..a7162a2ad5 100644 --- a/src/Common/src/Common.Certificate/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificate/LocalCertificateWriter.cs @@ -73,15 +73,9 @@ public void Write(Guid orgId, Guid spaceId) } #if NET6_0 - string chainedCertificateContents = "-----BEGIN CERTIFICATE-----" + Environment.NewLine + - Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + - "-----END CERTIFICATE-----" + Environment.NewLine + "-----BEGIN CERTIFICATE-----" + Environment.NewLine + - Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + - "-----END CERTIFICATE-----" + Environment.NewLine; - - string keyContents = "-----BEGIN RSA PRIVATE KEY-----" + Environment.NewLine + - Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + - Environment.NewLine + "-----END RSA PRIVATE KEY-----"; + string chainedCertificateContents = "-----BEGIN CERTIFICATE-----" + Environment.NewLine + Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END CERTIFICATE-----" + Environment.NewLine + "-----BEGIN CERTIFICATE-----" + Environment.NewLine + Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END CERTIFICATE-----" + Environment.NewLine; + + string keyContents = "-----BEGIN RSA PRIVATE KEY-----" + Environment.NewLine + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END RSA PRIVATE KEY-----"; #else string chainedCertificateContents = clientCertificate.ExportCertificatePem() + Environment.NewLine + intermediateCertificate.ExportCertificatePem(); string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs index 5efca7e74c..f32727cbcd 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs @@ -6,8 +6,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Steeltoe.Common; -using Steeltoe.Common.Configuration; using Steeltoe.Common.Certificate; +using Steeltoe.Common.Configuration; namespace Steeltoe.Configuration.ConfigServer; diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index fbad42a883..b4dbec43e3 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -8,12 +8,12 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Steeltoe.Common; +using Steeltoe.Common.Certificate; using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.HealthChecks; using Steeltoe.Common.Http.HttpClientPooling; using Steeltoe.Common.Net; -using Steeltoe.Common.Certificate; using Steeltoe.Discovery.Eureka.Configuration; namespace Steeltoe.Discovery.Eureka; diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs index 9ee9033ced..429e392fbb 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Steeltoe.Common; -using Steeltoe.Security.Authentication.Shared; namespace Steeltoe.Security.Authentication.JwtBearer; @@ -19,30 +18,9 @@ public static class JwtBearerServiceCollectionExtensions /// 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. - /// - /// - /// Configures the used to interact with the identity server. - /// - public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { ArgumentGuard.NotNull(services); - if (services.Any(descriptor => descriptor.ServiceType.IsAssignableFrom(typeof(IPostConfigureOptions)))) - { - throw new InvalidOperationException( - $"{nameof(ConfigureJwtBearerForCloudFoundry)} must be called before {nameof(JwtBearerExtensions.AddJwtBearer)}."); - } - - services.AddSteeltoeSecurityHttpClient(configureHttpClient); services.AddSingleton, PostConfigureJwtBearerOptions>(); return services; } diff --git a/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs b/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs index 9f42c0fd13..ad8cba32a8 100644 --- a/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs +++ b/src/Security/src/Authentication.JwtBearer/PostConfigureJwtBearerOptions.cs @@ -5,18 +5,27 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; -using Steeltoe.Security.Authentication.Shared; +using Steeltoe.Common; namespace Steeltoe.Security.Authentication.JwtBearer; -internal sealed class PostConfigureJwtBearerOptions(IConfiguration configuration, IHttpClientFactory httpClientFactory) - : IPostConfigureOptions +internal sealed class PostConfigureJwtBearerOptions : IPostConfigureOptions { private const string BearerConfigurationKeyPrefix = "Authentication:Schemes:Bearer"; + private readonly IConfiguration _configuration; + + public PostConfigureJwtBearerOptions(IConfiguration configuration) + { + ArgumentGuard.NotNull(configuration); + + _configuration = configuration; + } public void PostConfigure(string? name, JwtBearerOptions options) { - string? clientId = configuration.GetValue($"{BearerConfigurationKeyPrefix}:ClientId"); + ArgumentGuard.NotNull(options); + + string? clientId = _configuration.GetValue($"{BearerConfigurationKeyPrefix}:ClientId"); if (!string.IsNullOrEmpty(clientId) && options.TokenValidationParameters.ValidAudiences?.Contains(clientId) != true) { @@ -28,21 +37,14 @@ public void PostConfigure(string? name, JwtBearerOptions options) options.TokenValidationParameters.ValidAudiences = audiences; } - if (options.Authority?.Equals(SteeltoeSecurityDefaults.LocalUAAPath, StringComparison.InvariantCultureIgnoreCase) == true) + if (options.Authority == null) { - options.RequireHttpsMetadata = false; - options.TokenValidationParameters.ValidIssuer = $"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"; - } - else if (options.Authority != null) - { - options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + return; } - if (options.Authority != null) - { - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract - var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel ?? httpClientFactory.CreateClient("SteeltoeSecurity")); - options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; - } + options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + + var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel); + options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; } } diff --git a/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt b/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt index ab058de62d..5f282702bb 100644 --- a/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt +++ b/src/Security/src/Authentication.JwtBearer/PublicAPI.Shipped.txt @@ -1 +1 @@ -#nullable enable + \ No newline at end of file diff --git a/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt index 4e3942014e..6f9cffa424 100644 --- a/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ #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.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj index b0d462760a..070d115306 100644 --- a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj +++ b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -12,9 +12,10 @@ + - + diff --git a/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs new file mode 100644 index 0000000000..b88b8a210f --- /dev/null +++ b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs @@ -0,0 +1,74 @@ +// 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.JwtBearer; + +internal sealed class TokenKeyResolver +{ + private readonly HttpClient _httpClient; + private readonly Uri _authority; + private readonly MediaTypeWithQualityHeaderValue _acceptHeader = new("application/json"); + + internal static ConcurrentDictionary ResolvedSecurityKeysById { get; } = new(); + + public TokenKeyResolver(string authority, HttpClient httpClient) + { + ArgumentGuard.NotNull(authority); + ArgumentGuard.NotNull(httpClient); + + if (!authority.EndsWith('/')) + { + authority += '/'; + } + + _authority = new Uri($"{authority}token_keys"); + _httpClient = httpClient; + } + + internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) + { + if (ResolvedSecurityKeysById.TryGetValue(keyId, out SecurityKey? resolved)) + { + return [resolved]; + } + + JsonWebKeySet? keySet = FetchKeySetAsync(default).GetAwaiter().GetResult(); + + if (keySet != null) + { + foreach (JsonWebKey key in keySet.Keys) + { + ResolvedSecurityKeysById[key.Kid] = key; + } + } + + if (ResolvedSecurityKeysById.TryGetValue(keyId, out resolved)) + { + return [resolved]; + } + + return []; + } + + internal async Task FetchKeySetAsync(CancellationToken cancellationToken) + { + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authority); + requestMessage.Headers.Accept.Add(_acceptHeader); + + HttpResponseMessage response = await _httpClient.SendAsync(requestMessage, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + return null; + } + + string result = await response.Content.ReadAsStringAsync(cancellationToken); + return JsonWebKeySet.Create(result); + } +} diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs index a78bd81d50..d5b0a198d4 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Steeltoe.Common; -using Steeltoe.Security.Authentication.Shared; namespace Steeltoe.Security.Authentication.OpenIdConnect; @@ -20,31 +19,9 @@ public static class OpenIdConnectServiceCollectionExtensions /// 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. - /// - /// - /// Configures the used to interact with the identity server. - /// - public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { ArgumentGuard.NotNull(services); - if (services.Any(descriptor => descriptor.ServiceType.IsAssignableFrom(typeof(IPostConfigureOptions)))) - { - throw new InvalidOperationException( - $"{nameof(ConfigureOpenIdConnectForCloudFoundry)} must be called before {nameof(OpenIdConnectExtensions.AddOpenIdConnect)}."); - } - - 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 index a8e1d5bf0f..9f0c5981c8 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect; -internal sealed class PostConfigureOpenIdConnectOptions(IHttpClientFactory httpClientFactory) : IPostConfigureOptions +internal sealed class PostConfigureOpenIdConnectOptions : IPostConfigureOptions { // 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 @@ -36,7 +36,7 @@ internal sealed class PostConfigureOpenIdConnectOptions(IHttpClientFactory httpC public void PostConfigure(string? name, OpenIdConnectOptions options) { - ArgumentNullException.ThrowIfNull(name); + ArgumentGuard.NotNull(name); ArgumentGuard.NotNull(options); options.Events.OnTokenValidated = MapScopesToClaims; @@ -45,21 +45,14 @@ public void PostConfigure(string? name, OpenIdConnectOptions options) options.SignInScheme ??= CookieAuthenticationDefaults.AuthenticationScheme; options.TokenValidationParameters.NameClaimType = "user_name"; - if (options.Authority?.Contains(SteeltoeSecurityDefaults.LocalUAAPath, StringComparison.InvariantCultureIgnoreCase) == true) + if (options.Authority == null) { - options.RequireHttpsMetadata = false; - options.TokenValidationParameters.ValidIssuer = $"{SteeltoeSecurityDefaults.LocalUAAPath}/uaa/oauth/token"; - } - else if (options.Authority != null) - { - options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + return; } - if (options.Authority != null) - { - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract - var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel ?? httpClientFactory.CreateClient("SteeltoeSecurity")); - options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; - } + options.TokenValidationParameters.ValidIssuer = $"{options.Authority}/oauth/token"; + + var keyResolver = new TokenKeyResolver(options.Authority, options.Backchannel); + options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; } } diff --git a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt index ab058de62d..5f282702bb 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt +++ b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Shipped.txt @@ -1 +1 @@ -#nullable enable + \ No newline at end of file diff --git a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt index af6205b6dc..bad187262b 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ #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 index 69c4173a01..3633be8a88 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -12,9 +12,10 @@ + - + diff --git a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs similarity index 60% rename from src/Security/src/Authentication.Shared/TokenKeyResolver.cs rename to src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs index 0fea772251..ca492c286a 100644 --- a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs @@ -12,37 +12,43 @@ namespace Steeltoe.Security.Authentication.Shared; internal sealed class TokenKeyResolver { private readonly HttpClient _httpClient; - private string _authority; + private readonly Uri _authority; + private readonly MediaTypeWithQualityHeaderValue _acceptHeader = new("application/json"); - internal static ConcurrentDictionary Resolved { get; } = new(); + internal static ConcurrentDictionary ResolvedSecurityKeysById { get; } = new(); public TokenKeyResolver(string authority, HttpClient httpClient) { ArgumentGuard.NotNull(authority); ArgumentGuard.NotNull(httpClient); - _authority = authority; + if (!authority.EndsWith('/')) + { + authority += '/'; + } + + _authority = new Uri($"{authority}token_keys"); _httpClient = httpClient; } - internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) + internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) { - if (Resolved.TryGetValue(kid, out SecurityKey? resolved)) + if (ResolvedSecurityKeysById.TryGetValue(keyId, out SecurityKey? resolved)) { return [resolved]; } - JsonWebKeySet? keySet = FetchKeySetAsync().GetAwaiter().GetResult(); + JsonWebKeySet? keySet = FetchKeySetAsync(default).GetAwaiter().GetResult(); if (keySet != null) { foreach (JsonWebKey key in keySet.Keys) { - Resolved[key.Kid] = key; + ResolvedSecurityKeysById[key.Kid] = key; } } - if (Resolved.TryGetValue(kid, out resolved)) + if (ResolvedSecurityKeysById.TryGetValue(keyId, out resolved)) { return [resolved]; } @@ -50,24 +56,19 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken return []; } - internal async Task FetchKeySetAsync() + internal async Task FetchKeySetAsync(CancellationToken cancellationToken) { - if (!_authority.EndsWith('/')) - { - _authority += "/"; - } - - using var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri($"{_authority}token_keys")); - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authority); + requestMessage.Headers.Accept.Add(_acceptHeader); - HttpResponseMessage response = await _httpClient.SendAsync(requestMessage); + HttpResponseMessage response = await _httpClient.SendAsync(requestMessage, cancellationToken); if (!response.IsSuccessStatusCode) { return null; } - string result = await response.Content.ReadAsStringAsync(); + string result = await response.Content.ReadAsStringAsync(cancellationToken); return JsonWebKeySet.Create(result); } } diff --git a/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs b/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs deleted file mode 100644 index 2ded3f226b..0000000000 --- a/src/Security/src/Authentication.Shared/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +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; - -[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 deleted file mode 100644 index b0edd671ff..0000000000 --- a/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.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.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 deleted file mode 100644 index 29df2f13d2..0000000000 --- a/src/Security/src/Authentication.Shared/Steeltoe.Security.Authentication.Shared.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - 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 deleted file mode 100644 index f6fbf61256..0000000000 --- a/src/Security/src/Authentication.Shared/SteeltoeSecurityDefaults.cs +++ /dev/null @@ -1,11 +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.Shared; - -internal static class SteeltoeSecurityDefaults -{ - internal const string HttpClientName = "SteeltoeSecurity"; - internal const string LocalUAAPath = "http://localhost:8080"; -} diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs index aabeb95355..f7fb3e9dba 100644 --- a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; namespace Steeltoe.Security.Authorization.Certificate; @@ -10,12 +11,14 @@ 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-]+)$"; + private static readonly Regex CloudFoundryInstanceCertificateSubjectRegex = + new(@"^CN=(?[0-9a-f-]+),\sOU=organization:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=app:(?[0-9a-f-]+)$", + RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); - // 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-]+)$"; + // This pattern is found on certificates created by Steeltoe + private static readonly Regex SteeltoeInstanceCertificateSubjectRegex = + new(@"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$", + RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); public string OrganizationId { get; private set; } @@ -33,28 +36,21 @@ private ApplicationInstanceCertificate(string organizationId, string spaceId, st InstanceId = instanceId; } - public static bool TryParse(X509Certificate2 certificate, [MaybeNullWhen(false)] out ApplicationInstanceCertificate outInstanceCertificate, ILogger logger) + public static bool TryParse(X509Certificate2 certificate, [NotNullWhen(true)] out ApplicationInstanceCertificate? outInstanceCertificate) { outInstanceCertificate = null; - Match instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), - CloudFoundryInstanceCertificateSubjectRegex); + Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex.Match(certificate.Subject); if (!instanceMatch.Success) { - instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), SteeltoeInstanceCertificateSubjectRegex); + instanceMatch = SteeltoeInstanceCertificateSubjectRegex.Match(certificate.Subject); } 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 index 8bd60f4635..ec7ce4dfc9 100644 --- a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; +using Steeltoe.Common; namespace Steeltoe.Security.Authorization.Certificate; @@ -13,34 +14,35 @@ 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) + public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder applicationBuilder) { - return app.UseCertificateAuthorization(new ForwardedHeadersOptions()); + return applicationBuilder.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) + public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder applicationBuilder, ForwardedHeadersOptions forwardedHeaders) { + ArgumentGuard.NotNull(applicationBuilder); ArgumentGuard.NotNull(forwardedHeaders); forwardedHeaders.ForwardedHeaders |= ForwardedHeaders.XForwardedProto; - app.UseForwardedHeaders(forwardedHeaders); - app.UseCertificateForwarding(); - app.UseAuthentication(); - app.UseAuthorization(); + applicationBuilder.UseForwardedHeaders(forwardedHeaders); + applicationBuilder.UseCertificateForwarding(); + applicationBuilder.UseAuthentication(); + applicationBuilder.UseAuthorization(); - return app; + return applicationBuilder; } } diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index 2bce199e9e..355d6e8f80 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -2,6 +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 System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Configuration; + namespace Steeltoe.Security.Authorization.Certificate; public sealed class CertificateAuthorizationHandler : IAuthorizationHandler @@ -34,11 +41,15 @@ private void OnCertificateRefresh(CertificateOptions certificateOptions) return; } - if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate, - _logger)) + if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate)) { _applicationInstanceCertificate = applicationInstanceCertificate; } + else + { + _logger.LogError("Identity certificate did not match an expected pattern. Subject was: {CertificateSubject}", + certificateOptions.Certificate.Subject); + } } private void HandleCertificateAuthorizationRequirement(AuthorizationHandlerContext context, string claimType, string? claimValue) diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs index b6127c4181..351bb99d0d 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs @@ -2,6 +2,9 @@ // 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.Authorization.Certificate; public static class CertificateAuthorizationPolicyBuilderExtensions diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index 742dd78302..13ed693197 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -2,9 +2,15 @@ // 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; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Steeltoe.Common.Certificate; +using Steeltoe.Common.Configuration; namespace Steeltoe.Security.Authorization.Certificate; diff --git a/src/Security/src/Authorization.Certificate/GlobalUsings.cs b/src/Security/src/Authorization.Certificate/GlobalUsings.cs deleted file mode 100644 index 92a4e3da3f..0000000000 --- a/src/Security/src/Authorization.Certificate/GlobalUsings.cs +++ /dev/null @@ -1,12 +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. - -global using System.Security.Claims; -global using System.Security.Cryptography.X509Certificates; -global using Microsoft.AspNetCore.Authentication.Certificate; -global using Microsoft.AspNetCore.Authorization; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using Steeltoe.Common; -global using Steeltoe.Common.Configuration; diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 8faebd75d4..5fff0af5ba 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -2,6 +2,14 @@ // 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.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Authentication.Certificate; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Configuration; + namespace Steeltoe.Security.Authorization.Certificate; public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions @@ -20,6 +28,7 @@ public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor(context.Principal.Claims); - if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate, out ApplicationInstanceCertificate? clientCertificate, _logger)) + if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate, out ApplicationInstanceCertificate? clientCertificate)) { claims.Add(new Claim(ApplicationClaimTypes.OrganizationId, clientCertificate.OrganizationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); @@ -66,6 +75,11 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options claims.Add(new Claim(ApplicationClaimTypes.ApplicationInstanceId, clientCertificate.InstanceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); } + else + { + _logger.LogError("Identity certificate did not match an expected pattern. Subject was: {CertificateSubject}", + context.ClientCertificate.Subject); + } var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); context.Principal = new ClaimsPrincipal(identity); diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index 61d07484bf..6914f9d7f6 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -2,8 +2,8 @@ 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.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! forwardedHeaders) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameSpace(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.DependencyInjection.IServiceCollection! diff --git a/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs index 661d078a62..2d874b5557 100644 --- a/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs @@ -2,6 +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; public sealed class SameOrgRequirement : IAuthorizationRequirement diff --git a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs index fc6ddfec06..1568f1026f 100644 --- a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs @@ -2,6 +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; public sealed class SameSpaceRequirement : IAuthorizationRequirement diff --git a/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs b/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs deleted file mode 100644 index 67ac867baa..0000000000 --- a/src/Security/test/Authentication.JwtBearer.Test/GlobalUsings.cs +++ /dev/null @@ -1,8 +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. - -global using FluentAssertions; -global using Microsoft.Extensions.DependencyInjection; -global using Steeltoe.Security.Authentication.Shared; -global using Xunit; diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs index 2a25973487..27a3d4e6b7 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs @@ -2,6 +2,10 @@ // 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 Xunit; + namespace Steeltoe.Security.Authentication.JwtBearer.Test; public sealed class JwtBearerServiceCollectionExtensionsTest @@ -13,27 +17,6 @@ public void ConfigureJwtBearerForCloudFoundry_AddsExpectedRegistrations() serviceCollection.ConfigureJwtBearerForCloudFoundry(); - serviceCollection.Should().Contain(service => service.ServiceType == typeof(IHttpClientFactory)); serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); } - - [Fact] - public void ConfigureJwtBearerForCloudFoundry_RequiredBeforeAddJwtBearer() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAuthentication().AddJwtBearer(); - var exception = Assert.Throws(() => serviceCollection.ConfigureJwtBearerForCloudFoundry()); - exception.Message.Should().Contain($"{nameof(JwtBearerServiceCollectionExtensions.ConfigureJwtBearerForCloudFoundry)} must be called before"); - } - - [Fact] - public void ConfigureJwtBearerForCloudFoundry_ConfiguresHttpClient() - { - var serviceCollection = new ServiceCollection(); - - ServiceProvider serviceProvider = serviceCollection.ConfigureJwtBearerForCloudFoundry().BuildServiceProvider(); - HttpClient httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); - - 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 index 4267d95880..1d47c6e1c3 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -2,11 +2,14 @@ // 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 Xunit; namespace Steeltoe.Security.Authentication.JwtBearer.Test; @@ -26,36 +29,13 @@ public void PostConfigure_AddsClientIdToValidAudiences() }; IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); - var postConfigurer = new PostConfigureJwtBearerOptions(configuration, null!); + var postConfigurer = new PostConfigureJwtBearerOptions(configuration); postConfigurer.PostConfigure(null, jwtBearerOptions); jwtBearerOptions.TokenValidationParameters.ValidAudiences.Should().Contain("testClient"); } - [Fact] - public void PostConfigure_ConfiguresForLocalUAA() - { - IConfigurationRoot 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() { @@ -88,6 +68,7 @@ public void PostConfigure_ConfiguresForCloudFoundry() IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); + serviceCollection.AddAuthentication().AddJwtBearer(); serviceCollection.ConfigureJwtBearerForCloudFoundry(); serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs new file mode 100644 index 0000000000..ed773ea151 --- /dev/null +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -0,0 +1,181 @@ +// 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.IdentityModel.Tokens; +using Xunit; + +namespace Steeltoe.Security.Authentication.JwtBearer.Test; + +public sealed class TokenKeyResolverTest +{ + [Fact] + public void Constructor_ThrowsIfOptionsNull() + { + Assert.Throws(() => new TokenKeyResolver(null!, new HttpClient())); + Assert.Throws(() => new TokenKeyResolver("someAuthority", null!)); + } + + [Fact] + public void ResolveSigningKey_FindsExistingKey() + { + // ReSharper disable StringLiteralTypo + const string token = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + + const string keySet = """ + { + "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 keys = JsonWebKeySet.Create(keySet); + JsonWebKey webKey = keys.Keys[0]; + + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); + + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); + TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; + + IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); + Assert.True(result.First() == webKey); + } + + [Fact] + public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() + { + const string token = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + + const string keySet = """ + { + "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 handler = new TestMessageHandler(); + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(keySet) + }; + + handler.Response = response; + + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); + + 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(TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"]); + Assert.NotNull(result); + } + + [Fact] + public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() + { + const string token = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + + const string keySet = """ + { + "keys": [ + { + "kid": "foobar", + "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 handler = new TestMessageHandler(); + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(keySet) + }; + + handler.Response = response; + + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); + + 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(TokenKeyResolver.ResolvedSecurityKeysById.ContainsKey("legacy-token-key")); + Assert.Empty(result); + } + + [Fact] + public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() + { + const string keySet = """ + { + "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 handler = new TestMessageHandler + { + Response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(keySet) + } + }; + + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); + + var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); + Assert.NotNull(result); + } + + private sealed class TestMessageHandler : HttpMessageHandler + { + public HttpRequestMessage? LastRequest { get; private set; } + + public HttpResponseMessage Response { get; set; } = new(HttpStatusCode.OK); + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + LastRequest = request; + return Task.FromResult(Response); + } + } +} diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs b/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs deleted file mode 100644 index 67ac867baa..0000000000 --- a/src/Security/test/Authentication.OpenIdConnect.Test/GlobalUsings.cs +++ /dev/null @@ -1,8 +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. - -global using FluentAssertions; -global using Microsoft.Extensions.DependencyInjection; -global using Steeltoe.Security.Authentication.Shared; -global using Xunit; diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs index 7bec80dd66..26a874f0e3 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs @@ -2,6 +2,10 @@ // 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 Xunit; + namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; public sealed class OpenIdConnectServiceCollectionExtensionsTest @@ -13,27 +17,6 @@ public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); - serviceCollection.Should().Contain(service => service.ServiceType == typeof(IHttpClientFactory)); serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); } - - [Fact] - public void ConfigureOpenIdConnectForCloudFoundry_RequiredBeforeAddJwtBearer() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAuthentication().AddOpenIdConnect(); - var exception = Assert.Throws(() => serviceCollection.ConfigureOpenIdConnectForCloudFoundry()); - exception.Message.Should().Contain($"{nameof(OpenIdConnectServiceCollectionExtensions.ConfigureOpenIdConnectForCloudFoundry)} must be called before"); - } - - [Fact] - public void ConfigureOpenIdConnectForCloudFoundry_ConfiguresHttpClient() - { - var serviceCollection = new ServiceCollection(); - - ServiceProvider serviceProvider = serviceCollection.ConfigureOpenIdConnectForCloudFoundry().BuildServiceProvider(); - HttpClient 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 index e138738c7a..ac2ba766df 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -2,11 +2,14 @@ // 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.OpenIdConnect; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Steeltoe.Common.TestResources; using Steeltoe.Configuration.CloudFoundry.ServiceBinding; +using Xunit; namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; @@ -29,34 +32,13 @@ public void PostConfigure_AddsClientIdToValidAudiences() OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(OpenIdConnectDefaults.AuthenticationScheme); - var postConfigurer = new PostConfigureOpenIdConnectOptions(null!); + var postConfigurer = new PostConfigureOpenIdConnectOptions(); 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() { @@ -89,9 +71,8 @@ public void PostConfigure_ConfiguresForCloudFoundry() IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); - serviceCollection.AddHttpClient(); - serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); serviceCollection.AddAuthentication().AddOpenIdConnect(); + serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(OpenIdConnectDefaults.AuthenticationScheme); diff --git a/src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs similarity index 52% rename from src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs rename to src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index 41ce20dd09..879fc22f76 100644 --- a/src/Security/test/Authentication.Shared.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -2,7 +2,9 @@ // 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.IdentityModel.Tokens; +using Xunit; namespace Steeltoe.Security.Authentication.Shared.Test; @@ -18,30 +20,33 @@ public void Constructor_ThrowsIfOptionsNull() [Fact] public void ResolveSigningKey_FindsExistingKey() { + // ReSharper disable StringLiteralTypo const string token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; - const string keySet = @"{ - ""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"" - } - ] -}"; + const string keySet = """ + { + "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 keys = JsonWebKeySet.Create(keySet); JsonWebKey webKey = keys.Keys[0]; - TokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); - TokenKeyResolver.Resolved["legacy-token-key"] = webKey; + TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); Assert.True(result.First() == webKey); @@ -53,19 +58,21 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() const string token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; - const string keySet = @"{ - ""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"" - } - ] -}"; + const string keySet = """ + { + "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 handler = new TestMessageHandler(); @@ -76,12 +83,12 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() handler.Response = response; - TokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); 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(TokenKeyResolver.Resolved["legacy-token-key"]); + Assert.NotNull(TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"]); Assert.NotNull(result); } @@ -91,19 +98,21 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() const string token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; - const string keySet = @"{ - ""keys"": [ - { - ""kid"": ""foobar"", - ""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"" - } - ] -}"; + const string keySet = """ + { + "keys": [ + { + "kid": "foobar", + "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 handler = new TestMessageHandler(); @@ -114,31 +123,33 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() handler.Response = response; - TokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); 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(TokenKeyResolver.Resolved.ContainsKey("legacy-token-key")); + Assert.False(TokenKeyResolver.ResolvedSecurityKeysById.ContainsKey("legacy-token-key")); Assert.Empty(result); } [Fact] public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() { - const string keySet = @"{ - ""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"" - } - ] -}"; + const string keySet = """ + { + "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 handler = new TestMessageHandler { @@ -148,10 +159,23 @@ public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() } }; - TokenKeyResolver.Resolved.Clear(); + TokenKeyResolver.ResolvedSecurityKeysById.Clear(); var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); - JsonWebKeySet? result = await resolver.FetchKeySetAsync(); + JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); Assert.NotNull(result); } + + private sealed class TestMessageHandler : HttpMessageHandler + { + public HttpRequestMessage? LastRequest { get; private set; } + + public HttpResponseMessage Response { get; set; } = new(HttpStatusCode.OK); + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + LastRequest = request; + return Task.FromResult(Response); + } + } } diff --git a/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs b/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs deleted file mode 100644 index ca564f4b61..0000000000 --- a/src/Security/test/Authentication.Shared.Test/GlobalUsings.cs +++ /dev/null @@ -1,6 +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. - -global using System.Net; -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 deleted file mode 100644 index 3f73c8b45c..0000000000 --- a/src/Security/test/Authentication.Shared.Test/Steeltoe.Security.Authentication.Shared.Test.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - net8.0 - enable - - - - - - - - - diff --git a/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs b/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs deleted file mode 100644 index ac46fe60eb..0000000000 --- a/src/Security/test/Authentication.Shared.Test/TestMessageHandler.cs +++ /dev/null @@ -1,18 +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.Shared.Test; - -internal sealed class TestMessageHandler : HttpMessageHandler -{ - public HttpRequestMessage? LastRequest { get; private set; } - - public HttpResponseMessage Response { get; set; } = new(HttpStatusCode.OK); - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - LastRequest = request; - return Task.FromResult(Response); - } -} diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 20f41a69ad..2127d357e2 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -5,6 +5,11 @@ using System.Net; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; +using Steeltoe.Common.Certificate; +using Steeltoe.Common.TestResources; +using Xunit; namespace Steeltoe.Security.Authorization.Certificate.Test; diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs index ed7ed01758..ce03be0a7b 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs @@ -3,7 +3,14 @@ // See the LICENSE file in the project root for more information. using System.Security.Cryptography.X509Certificates; +using FluentAssertions; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Steeltoe.Common.Certificate; +using Steeltoe.Common.TestResources; +using Xunit; namespace Steeltoe.Security.Authorization.Certificate.Test; diff --git a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs index 214758c017..c1a566293b 100644 --- a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs +++ b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs @@ -2,6 +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 Steeltoe.Common.Certificate; + 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 deleted file mode 100644 index 1273eaa307..0000000000 --- a/src/Security/test/Authorization.Certificate.Test/GlobalUsings.cs +++ /dev/null @@ -1,11 +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. - -global using FluentAssertions; -global using Microsoft.AspNetCore.TestHost; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Hosting; -global using Steeltoe.Common.Certificate; -global using Steeltoe.Common.TestResources; -global using Xunit; diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs index 20953d5915..f3556a570b 100644 --- a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; namespace Steeltoe.Security.Authorization.Certificate.Test; diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index 685e8ec204..823b1df1d3 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -213,8 +213,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Configuration.Conf 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.JwtBearer\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}" @@ -223,9 +221,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authoriza 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.OpenIdConnect.Test", "Security\test\Authentication.OpenIdConnect.Test\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", "{9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -501,10 +497,6 @@ Global {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 @@ -521,14 +513,10 @@ Global {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 + {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -602,13 +590,11 @@ Global {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} + {9E83FDF7-D838-4E7E-A767-6FEAC6BC1C5D} = {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 af86338c81..c12922ea89 100644 --- a/src/Steeltoe.All.sln.DotSettings +++ b/src/Steeltoe.All.sln.DotSettings @@ -737,7 +737,7 @@ $left$ = $right$; True True True - True + True True True True From 573d476a9a7bf6d37ac4796cef74c32e9bfa9ff9 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 13 Jun 2024 08:27:54 -0500 Subject: [PATCH 10/28] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- .../JwtBearerServiceCollectionExtensions.cs | 2 +- .../CertificateApplicationBuilderExtensions.cs | 4 ++-- .../CertificateServiceCollectionExtensions.cs | 4 ++-- ...onfigureCertificateAuthenticationOptions.cs | 8 ++++---- .../CertificateAuthorizationTest.cs | 10 ++++++++-- ...rtificateServiceCollectionExtensionsTest.cs | 18 ++++++++++-------- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs index 429e392fbb..7584c03186 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Security.Authentication.JwtBearer; public static class JwtBearerServiceCollectionExtensions { /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// Configures for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. /// /// /// The to add services to. diff --git a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs index ec7ce4dfc9..065a287a1e 100644 --- a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -11,7 +11,7 @@ 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 + /// Enables certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. Sets ForwardedHeaders to /// . /// /// @@ -23,7 +23,7 @@ public static IApplicationBuilder UseCertificateAuthorization(this IApplicationB } /// - /// Enable certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. + /// Enables certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. /// /// /// The . diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index 13ed693197..8a19ed75c2 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateServiceCollectionExtensions { /// - /// Add necessary components for server-side authorization of client certificates. + /// Adds the necessary components for server-side authorization of client certificates. /// /// /// The to add services to. @@ -38,7 +38,7 @@ public static IServiceCollection AddCertificateAuthorizationServer(this IService } /// - /// Add a named and necessary components for finding client certificates and attaching to outbound requests. + /// Adds a named and the necessary components for finding client certificates and attaching them to outbound requests. /// /// /// The to add services to. diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 5fff0af5ba..fef9bac392 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -40,13 +40,13 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options if (!string.IsNullOrEmpty(systemCertPath)) { - IEnumerable systemCertificates = - Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)); + X509Certificate2[] systemCertificates = + Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)).ToArray(); - options.CustomTrustStore.AddRange(systemCertificates.ToArray()); + options.CustomTrustStore.AddRange(systemCertificates); } - if (appInstanceIdentityOptions.IssuerChain.Any()) + if (appInstanceIdentityOptions.IssuerChain.Count > 0) { options.AdditionalChainCertificates.AddRange(appInstanceIdentityOptions.IssuerChain.ToArray()); } diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 2127d357e2..d778d2b8ca 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -105,8 +105,14 @@ public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() private IHostBuilder GetHostBuilder() { - return new HostBuilder().ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)) - .ConfigureWebHostDefaults(webHost => webHost.UseStartup()).ConfigureWebHost(webBuilder => webBuilder.UseTestServer()); + private IHostBuilder GetHostBuilder() + { + var hostBuilder = new HostBuilder(); + hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)); + hostBuilder.ConfigureWebHostDefaults(builder => builder.UseStartup()); + hostBuilder.ConfigureWebHost(builder => builder.UseTestServer()); + return hostBuilder; + } } private static HttpClient ClientWithCertificate(HttpClient httpClient, X509Certificate certificate) diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs index ce03be0a7b..58f4c24be0 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs @@ -35,14 +35,16 @@ public async Task AddCertificateAuthorizationClient_AddsNamedHttpClientWithCerti private static IHostBuilder GetHostBuilder() { - return new HostBuilder().ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate()) - .ConfigureServices(services => services.AddCertificateAuthorizationClient()).ConfigureWebHost(webBuilder => - { - webBuilder.Configure(_ => - { - }); + var hostBuilder = new HostBuilder(); + hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate()); + hostBuilder.ConfigureServices(services => services.AddCertificateAuthorizationClient()); - webBuilder.UseTestServer(); - }); + hostBuilder.ConfigureWebHost(webBuilder => + { + webBuilder.Configure(HostingHelpers.EmptyAction); + webBuilder.UseTestServer(); + }); + + return hostBuilder; } } From 3a81fa9c83276ec70991a3b3b5f21ef66b609a72 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Thu, 13 Jun 2024 17:57:31 -0500 Subject: [PATCH 11/28] PR feedback pluralize Common.Certificate enforce S3900 in code but not tests (remove unused classes) --- shared-package.props | 2 +- shared-test.props | 2 +- .../src/AutoConfiguration/BootstrapScanner.cs | 4 +- ...teeltoe.Bootstrap.AutoConfiguration.csproj | 2 +- .../SteeltoeAssemblyNames.cs | 2 +- .../HostBuilderExtensionsTest.cs | 2 +- ...oe.Bootstrap.AutoConfiguration.Test.csproj | 2 +- .../WebApplicationBuilderExtensionsTest.cs | 2 +- .../WebHostBuilderExtensionsTest.cs | 2 +- .../Abstractions/Properties/AssemblyInfo.cs | 4 +- ...SnakeCaseAllCapsEnumMemberJsonConverter.cs | 4 +- ...SnakeCaseAllCapsEnumMemberTypeConverter.cs | 62 --------------- .../SnakeCaseNoCapsEnumMemberJsonConverter.cs | 32 -------- .../PublicAPI.Unshipped.txt | 7 -- .../CertificateConfigurationExtensions.cs | 10 ++- .../CertificateServiceCollectionExtensions.cs | 2 +- .../ConfigureCertificateOptions.cs | 2 +- .../FilePathInOptionsChangeTokenSource.cs | 2 +- .../LocalCertificateWriter.cs | 39 ++++++---- .../Properties/AssemblyInfo.cs | 2 +- .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 7 ++ .../ServiceCollectionExtensions.cs | 2 +- .../Steeltoe.Common.Certificates.csproj} | 2 +- .../Serialization/BoolStringJsonConverter.cs | 2 + .../Serialization/LongStringJsonConverter.cs | 2 + .../src/Common.Net/WindowsNetworkFileShare.cs | 6 ++ .../ConfigurationValuesHelper.cs | 4 + .../src/Common/Properties/AssemblyInfo.cs | 4 +- .../Common/Reflection/ReflectionHelpers.cs | 15 ++++ .../CertificateConfigurationExtensionsTest.cs | 2 +- .../ConfigureCertificateOptionsTest.cs | 2 +- .../LocalCertificateWriterTest.cs | 6 +- .../Steeltoe.Common.Certificates.Test.csproj} | 3 +- .../empty.crt | 0 .../instance.crt | 0 .../instance.key | 0 .../instance.p12 | Bin .../instance2.crt | 0 .../instance2.key | 0 .../invalid.key | 0 .../xunit.runner.json | 0 .../ConfigServerConfigurationSource.cs | 2 +- ...Steeltoe.Configuration.ConfigServer.csproj | 2 +- .../PostProcessorsTest.cs | 13 ++-- ...toe.Configuration.ConfigServer.Test.csproj | 2 +- .../EurekaServiceCollectionExtensions.cs | 2 +- .../Eureka/Steeltoe.Discovery.Eureka.csproj | 2 +- ...Steeltoe.Discovery.HttpClients.Test.csproj | 2 +- .../TokenKeyResolver.cs | 12 +-- .../PostConfigureOpenIdConnectOptions.cs | 21 ++--- .../TokenKeyResolver.cs | 14 ++-- .../ApplicationInstanceCertificate.cs | 6 +- ...CertificateApplicationBuilderExtensions.cs | 12 +-- ...rtificateAuthorizationBuilderExtensions.cs | 48 ++++++++++++ .../CertificateAuthorizationDefaults.cs | 12 --- .../CertificateAuthorizationHandler.cs | 5 +- .../CertificateAuthorizationPolicies.cs | 29 +++++++ ...ateAuthorizationPolicyBuilderExtensions.cs | 2 +- .../CertificateHttpClientBuilderExtensions.cs | 67 ++++++++++++++++ .../CertificateServiceCollectionExtensions.cs | 73 ------------------ ...nfigureCertificateAuthenticationOptions.cs | 4 +- .../PublicAPI.Unshipped.txt | 30 +++---- .../SameOrgRequirement.cs | 2 +- .../SameSpaceRequirement.cs | 2 +- ....Security.Authorization.Certificate.csproj | 4 +- .../PostConfigureJwtBearerOptionsTest.cs | 17 ++-- ...urity.Authentication.JwtBearer.Test.csproj | 1 - .../TokenKeyResolverTest.cs | 7 -- .../PostConfigureOpenIdConnectOptionsTest.cs | 26 ++++--- ...y.Authentication.OpenIdConnect.Test.csproj | 6 -- .../TokenKeyResolverTest.cs | 9 +-- .../CertificateAuthorizationTest.cs | 19 ++--- ...> CertificateHttpBuilderExtensionsTest.cs} | 8 +- .../ClientCertificatesFixture.cs | 8 +- ...rity.Authorization.Certificate.Test.csproj | 1 - .../TestServerCertificateStartup.cs | 15 +--- src/Steeltoe.All.sln | 4 +- src/Steeltoe.Common.slnf | 2 +- src/Steeltoe.Configuration.slnf | 2 +- src/Steeltoe.Discovery.slnf | 2 +- src/Steeltoe.Security.slnf | 2 +- versions.props | 1 - 83 files changed, 352 insertions(+), 389 deletions(-) delete mode 100644 src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberTypeConverter.cs delete mode 100644 src/Common/src/Abstractions/Util/SnakeCaseNoCapsEnumMemberJsonConverter.cs delete mode 100644 src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt rename src/Common/src/{Common.Certificate => Common.Certificates}/CertificateConfigurationExtensions.cs (93%) rename src/Common/src/{Common.Certificate => Common.Certificates}/CertificateServiceCollectionExtensions.cs (98%) rename src/Common/src/{Common.Certificate => Common.Certificates}/ConfigureCertificateOptions.cs (98%) rename src/Common/src/{Common.Certificate => Common.Certificates}/FilePathInOptionsChangeTokenSource.cs (97%) rename src/Common/src/{Common.Certificate => Common.Certificates}/LocalCertificateWriter.cs (77%) rename src/Common/src/{Common.Certificate => Common.Certificates}/Properties/AssemblyInfo.cs (89%) rename src/Common/src/{Common.Certificate => Common.Certificates}/PublicAPI.Shipped.txt (100%) create mode 100644 src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt rename src/Common/src/{Common.Certificate => Common.Certificates}/ServiceCollectionExtensions.cs (98%) rename src/Common/src/{Common.Certificate/Steeltoe.Common.Certificate.csproj => Common.Certificates/Steeltoe.Common.Certificates.csproj} (87%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/CertificateConfigurationExtensionsTest.cs (94%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/ConfigureCertificateOptionsTest.cs (99%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/LocalCertificateWriterTest.cs (89%) rename src/Common/test/{Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj => Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj} (87%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/empty.crt (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/instance.crt (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/instance.key (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/instance.p12 (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/instance2.crt (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/instance2.key (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/invalid.key (100%) rename src/Common/test/{Common.Certificate.Test => Common.Certificates.Test}/xunit.runner.json (100%) create mode 100644 src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs delete mode 100644 src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs create mode 100644 src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs create mode 100644 src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs delete mode 100644 src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs rename src/Security/test/Authorization.Certificate.Test/{CertificateServiceCollectionExtensionsTest.cs => CertificateHttpBuilderExtensionsTest.cs} (86%) diff --git a/shared-package.props b/shared-package.props index 4f258e19c6..cbed6bde28 100644 --- a/shared-package.props +++ b/shared-package.props @@ -68,6 +68,6 @@ !$(MSBuildProjectName.StartsWith('Steeltoe.Management')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Security'))"> - $(NoWarn);SA1401;S1168;S2360;S3900;S3956;S4004;S4023 + $(NoWarn);SA1401;S1168;S2360;S3956;S4004;S4023 diff --git a/shared-test.props b/shared-test.props index 1fdc957ac6..312c432d80 100644 --- a/shared-test.props +++ b/shared-test.props @@ -1,6 +1,6 @@ - $(NoWarn);S2094;NU5104 + $(NoWarn);S2094;S3900;NU5104 diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index e0777b95d1..1b7a7e21bc 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Steeltoe.Common; -using Steeltoe.Common.Certificate; +using Steeltoe.Common.Certificates; using Steeltoe.Common.DynamicTypeAccess; using Steeltoe.Common.Hosting; using Steeltoe.Common.Logging; @@ -81,7 +81,7 @@ public void ConfigureSteeltoe() WireIfLoaded(WirePrometheus, SteeltoeAssemblyNames.ManagementPrometheus); WireIfLoaded(WireWavefrontMetrics, SteeltoeAssemblyNames.ManagementWavefront); WireIfLoaded(WireDistributedTracing, SteeltoeAssemblyNames.ManagementTracing); - WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonCertificate); + WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonCertificates); } private void WireConfigServer() diff --git a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj index 70b34102c0..61b4597851 100644 --- a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj +++ b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs index b40c07fbaf..294a8b4f5d 100644 --- a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs +++ b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Bootstrap.AutoConfiguration; /// public static class SteeltoeAssemblyNames { - public const string CommonCertificate = "Steeltoe.Common.Certificate"; + public const string CommonCertificates = "Steeltoe.Common.Certificates"; public const string ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry"; public const string ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer"; public const string ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue"; diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 6a2882e9eb..7c0d93a6bb 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -228,7 +228,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonCertificate); + using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonCertificates); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); 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 822975fe75..7adb438ddf 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index 930132134a..653f76a8d3 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -229,7 +229,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonCertificate); + using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonCertificates); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index bc9540da92..b45af7f9ef 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -229,7 +229,7 @@ public void Tracing_IsAutowired() [Fact] public void AppInstanceIdentityCertificate_IsAutowired() { - using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonCertificate); + using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonCertificates); var configuration = host.Services.GetRequiredService(); configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); diff --git a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs index 9e09e116c3..076375e207 100644 --- a/src/Common/src/Abstractions/Properties/AssemblyInfo.cs +++ b/src/Common/src/Abstractions/Properties/AssemblyInfo.cs @@ -6,8 +6,8 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificates")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificates.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Connectors")] [assembly: InternalsVisibleTo("Steeltoe.Connectors.EntityFrameworkCore")] diff --git a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs b/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs index 0c2c2e983a..94cf10bd3b 100644 --- a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs +++ b/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -20,6 +20,8 @@ public sealed class SnakeCaseAllCapsEnumMemberJsonConverter : JsonConverterFacto /// public override bool CanConvert(Type typeToConvert) { + ArgumentGuard.NotNull(typeToConvert); + return typeToConvert.IsEnum; } diff --git a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberTypeConverter.cs b/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberTypeConverter.cs deleted file mode 100644 index b45632ccd5..0000000000 --- a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberTypeConverter.cs +++ /dev/null @@ -1,62 +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.ComponentModel; -using System.Globalization; - -namespace Steeltoe.Common.Util; - -/// -/// Converts between pascal-cased enum members and their snake-case-all-caps string representation. -/// -/// NON_PERSISTENT -/// ]]> -/// -/// -/// -/// The enumeration type. -/// -public sealed class SnakeCaseAllCapsEnumMemberTypeConverter : TypeConverter - where TEnum : struct, Enum -{ - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string); - } - - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return destinationType.IsEnum; - } - - /// - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - string snakeCaseText = (string)value; - - var converter = new SnakeCaseEnumConverter(SnakeCaseStyle.AllCaps); - string pascalCaseText = converter.ToPascalCase(snakeCaseText); - - return Enum.Parse(pascalCaseText); - } - - /// - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (value == null) - { - return null; - } - - string pascalCaseText = value.ToString(); - - var converter = new SnakeCaseEnumConverter(SnakeCaseStyle.AllCaps); - string snakeCaseText = converter.ToSnakeCase(pascalCaseText); - - return snakeCaseText; - } -} diff --git a/src/Common/src/Abstractions/Util/SnakeCaseNoCapsEnumMemberJsonConverter.cs b/src/Common/src/Abstractions/Util/SnakeCaseNoCapsEnumMemberJsonConverter.cs deleted file mode 100644 index 45a8c075de..0000000000 --- a/src/Common/src/Abstractions/Util/SnakeCaseNoCapsEnumMemberJsonConverter.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 System.Text.Json; -using System.Text.Json.Serialization; - -namespace Steeltoe.Common.Util; - -/// -/// Converts between pascal-cased enum members and their snake-case-no-caps string representation. -/// -/// write_acl -/// ]]> -/// -/// -public sealed class SnakeCaseNoCapsEnumMemberJsonConverter : JsonConverterFactory -{ - /// - public override bool CanConvert(Type typeToConvert) - { - return typeToConvert.IsEnum; - } - - /// - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - Type converterType = typeof(SnakeCaseEnumConverter<>).MakeGenericType(typeToConvert); - return (JsonConverter)Activator.CreateInstance(converterType, SnakeCaseStyle.NoCaps); - } -} diff --git a/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt b/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt deleted file mode 100644 index b58c792977..0000000000 --- a/src/Common/src/Common.Certificate/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,7 +0,0 @@ -#nullable enable -static Steeltoe.Common.Certificate.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! -static Steeltoe.Common.Certificate.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! -static Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -Steeltoe.Common.Certificate.CertificateConfigurationExtensions -Steeltoe.Common.Certificate.CertificateServiceCollectionExtensions diff --git a/src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs similarity index 93% rename from src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs rename to src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs index 56bc185887..4993c1c087 100644 --- a/src/Common/src/Common.Certificate/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; public static class CertificateConfigurationExtensions { @@ -92,13 +92,15 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf writer.Write(organizationId.Value, spaceId.Value); Environment.SetEnvironmentVariable("CF_SYSTEM_CERT_PATH", - Path.Combine(Directory.GetParent(LocalCertificateWriter.AppBasePath)!.ToString(), "GeneratedCertificates")); + Path.Combine(Directory.GetParent(LocalCertificateWriter.AppBasePath)!.ToString(), LocalCertificateWriter.CertificateDirectoryName)); Environment.SetEnvironmentVariable("CF_INSTANCE_CERT", - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem")); + Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, + $"{writer.CertificateFilenamePrefix}Cert.pem")); Environment.SetEnvironmentVariable("CF_INSTANCE_KEY", - Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem")); + Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, + $"{writer.CertificateFilenamePrefix}Key.pem")); } string? certificateFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); diff --git a/src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs similarity index 98% rename from src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs rename to src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs index af0bccf83a..87ad96f991 100644 --- a/src/Common/src/Common.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; public static class CertificateServiceCollectionExtensions { diff --git a/src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs b/src/Common/src/Common.Certificates/ConfigureCertificateOptions.cs similarity index 98% rename from src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs rename to src/Common/src/Common.Certificates/ConfigureCertificateOptions.cs index 849724c24f..26383a5956 100644 --- a/src/Common/src/Common.Certificate/ConfigureCertificateOptions.cs +++ b/src/Common/src/Common.Certificates/ConfigureCertificateOptions.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; using Steeltoe.Common.Configuration; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; internal sealed class ConfigureCertificateOptions : IConfigureNamedOptions { diff --git a/src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs b/src/Common/src/Common.Certificates/FilePathInOptionsChangeTokenSource.cs similarity index 97% rename from src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs rename to src/Common/src/Common.Certificates/FilePathInOptionsChangeTokenSource.cs index eb92c5b685..e24209eaf1 100644 --- a/src/Common/src/Common.Certificate/FilePathInOptionsChangeTokenSource.cs +++ b/src/Common/src/Common.Certificates/FilePathInOptionsChangeTokenSource.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; internal sealed class FilePathInOptionsChangeTokenSource : IOptionsChangeTokenSource { diff --git a/src/Common/src/Common.Certificate/LocalCertificateWriter.cs b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs similarity index 77% rename from src/Common/src/Common.Certificate/LocalCertificateWriter.cs rename to src/Common/src/Common.Certificates/LocalCertificateWriter.cs index a7162a2ad5..cd8b3d6027 100644 --- a/src/Common/src/Common.Certificate/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; internal sealed class LocalCertificateWriter { @@ -14,11 +14,13 @@ internal sealed class LocalCertificateWriter private static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); - internal string CertificateFilenamePrefix { get; set; } = "SteeltoeInstance"; + internal static string CertificateDirectoryName => "GeneratedCertificates"; - internal string RootCaPfxPath { get; set; } = Path.Combine(ParentPath, "GeneratedCertificates", "SteeltoeCA.pfx"); + internal string CertificateFilenamePrefix { get; set; } = "SteeltoeAppInstance"; - internal string IntermediatePfxPath { get; set; } = Path.Combine(ParentPath, "GeneratedCertificates", "SteeltoeIntermediate.pfx"); + internal string RootCaPfxPath { get; } = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeCA.pfx"); + + internal string IntermediatePfxPath { get; } = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeIntermediate.pfx"); public void Write(Guid orgId, Guid spaceId) { @@ -33,9 +35,9 @@ public void Write(Guid orgId, Guid spaceId) X509Certificate2 caCertificate; // Create a directory a level above the running project to contain the root and intermediate certificates - if (!Directory.Exists(Path.Combine(ParentPath, "GeneratedCertificates"))) + if (!Directory.Exists(Path.Combine(ParentPath, CertificateDirectoryName))) { - Directory.CreateDirectory(Path.Combine(ParentPath, "GeneratedCertificates")); + Directory.CreateDirectory(Path.Combine(ParentPath, CertificateDirectoryName)); } // Create the root certificate if it doesn't already exist (can be shared by multiple applications) @@ -67,22 +69,33 @@ public void Write(Guid orgId, Guid spaceId) 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(AppBasePath, CertificateDirectoryName))) { - Directory.CreateDirectory(Path.Combine(AppBasePath, "GeneratedCertificates")); + Directory.CreateDirectory(Path.Combine(AppBasePath, CertificateDirectoryName)); } #if NET6_0 - string chainedCertificateContents = "-----BEGIN CERTIFICATE-----" + Environment.NewLine + Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END CERTIFICATE-----" + Environment.NewLine + "-----BEGIN CERTIFICATE-----" + Environment.NewLine + Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END CERTIFICATE-----" + Environment.NewLine; - - string keyContents = "-----BEGIN RSA PRIVATE KEY-----" + Environment.NewLine + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + Environment.NewLine + "-----END RSA PRIVATE KEY-----"; + string chainedCertificateContents = $""" + -----BEGIN CERTIFICATE----- + {Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)} + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + {Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)} + -----END CERTIFICATE----- + """; + + string keyContents = $""" + -----BEGIN RSA PRIVATE KEY----- + {Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks)} + -----END RSA PRIVATE KEY----- + """; #else string chainedCertificateContents = clientCertificate.ExportCertificatePem() + Environment.NewLine + intermediateCertificate.ExportCertificatePem(); string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); #endif - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Cert.pem"), chainedCertificateContents); - File.WriteAllText(Path.Combine(AppBasePath, "GeneratedCertificates", CertificateFilenamePrefix + "Key.pem"), keyContents); + File.WriteAllText(Path.Combine(AppBasePath, CertificateDirectoryName, $"{CertificateFilenamePrefix}Cert.pem"), chainedCertificateContents); + File.WriteAllText(Path.Combine(AppBasePath, CertificateDirectoryName, $"{CertificateFilenamePrefix}Key.pem"), keyContents); } private static X509Certificate2 CreateRootCertificate(string distinguishedName) diff --git a/src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs similarity index 89% rename from src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs rename to src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs index fc025a7a32..f7b633fc26 100644 --- a/src/Common/src/Common.Certificate/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs @@ -6,6 +6,6 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificates.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate.Test")] diff --git a/src/Common/src/Common.Certificate/PublicAPI.Shipped.txt b/src/Common/src/Common.Certificates/PublicAPI.Shipped.txt similarity index 100% rename from src/Common/src/Common.Certificate/PublicAPI.Shipped.txt rename to src/Common/src/Common.Certificates/PublicAPI.Shipped.txt diff --git a/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt b/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000..08ce8e7dfe --- /dev/null +++ b/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt @@ -0,0 +1,7 @@ +#nullable enable +static Steeltoe.Common.Certificates.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Common.Certificates.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! +static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +Steeltoe.Common.Certificates.CertificateConfigurationExtensions +Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions diff --git a/src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs similarity index 98% rename from src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs rename to src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs index 44ced30011..83c7292a75 100644 --- a/src/Common/src/Common.Certificate/ServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; -namespace Steeltoe.Common.Certificate; +namespace Steeltoe.Common.Certificates; internal static class ServiceCollectionExtensions { diff --git a/src/Common/src/Common.Certificate/Steeltoe.Common.Certificate.csproj b/src/Common/src/Common.Certificates/Steeltoe.Common.Certificates.csproj similarity index 87% rename from src/Common/src/Common.Certificate/Steeltoe.Common.Certificate.csproj rename to src/Common/src/Common.Certificates/Steeltoe.Common.Certificates.csproj index 57d9faa24a..d981050562 100644 --- a/src/Common/src/Common.Certificate/Steeltoe.Common.Certificate.csproj +++ b/src/Common/src/Common.Certificates/Steeltoe.Common.Certificates.csproj @@ -1,7 +1,7 @@ net8.0;net6.0 - Steeltoe common library for security + Steeltoe common library for using certificates security;pem;certificate true enable diff --git a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs index 58c7d092d2..bd3bff3b2a 100644 --- a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs @@ -17,6 +17,8 @@ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { + ArgumentGuard.NotNull(writer); + #pragma warning disable S4040 // Strings should be normalized to uppercase writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); #pragma warning restore S4040 // Strings should be normalized to uppercase diff --git a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs index b8d6e2062c..fdb6cce85d 100644 --- a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs @@ -17,6 +17,8 @@ public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) { + ArgumentGuard.NotNull(writer); + writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); } } diff --git a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs index edd2528e4a..16e8c098e1 100644 --- a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs +++ b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs @@ -85,6 +85,8 @@ public class WindowsNetworkFileShare : IDisposable /// public WindowsNetworkFileShare(string networkName, NetworkCredential credentials, IMultipleProviderRouter multipleProviderRouter = null) { + ArgumentGuard.NotNull(credentials); + _multipleProviderRouter = multipleProviderRouter ?? new MultipleProviderRouter(); _networkName = networkName; @@ -135,6 +137,8 @@ public WindowsNetworkFileShare(string networkName, NetworkCredential credentials /// public int GetLastError(out int error, out StringBuilder errorBuf, int errorBufSize, out StringBuilder nameBuf, int nameBufSize) { + ArgumentGuard.NotNull(_multipleProviderRouter); + return _multipleProviderRouter.GetLastError(out error, out errorBuf, errorBufSize, out nameBuf, nameBufSize); } @@ -172,6 +176,8 @@ internal static string GetErrorForNumber(int errNum) /// protected virtual void Dispose(bool disposing) { + ArgumentGuard.NotNull(_multipleProviderRouter); + // With the current design, it's not possible to disconnect the network share from the finalizer, // because the _mpr instance may have already been garbage-collected. if (disposing) diff --git a/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs b/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs index faf98b0e75..5a9b8d69b8 100644 --- a/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs +++ b/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs @@ -50,6 +50,10 @@ public static string GetSetting(string key, IConfiguration primary, IConfigurati /// public static string GetSetting(string key, IConfiguration configuration, string defaultValue, params string[] sectionPrefixes) { + ArgumentGuard.NotNull(key); + ArgumentGuard.NotNull(configuration); + ArgumentGuard.NotNull(sectionPrefixes); + foreach (string prefix in sectionPrefixes) { IConfigurationSection section = configuration.GetSection(prefix); diff --git a/src/Common/src/Common/Properties/AssemblyInfo.cs b/src/Common/src/Common/Properties/AssemblyInfo.cs index b0ac7661b6..26a0880814 100644 --- a/src/Common/src/Common/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common/Properties/AssemblyInfo.cs @@ -4,8 +4,8 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate")] -[assembly: InternalsVisibleTo("Steeltoe.Common.Certificate.Test")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificates")] +[assembly: InternalsVisibleTo("Steeltoe.Common.Certificates.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting")] [assembly: InternalsVisibleTo("Steeltoe.Common.Hosting.Test")] diff --git a/src/Common/src/Common/Reflection/ReflectionHelpers.cs b/src/Common/src/Common/Reflection/ReflectionHelpers.cs index e68528e800..bddb98c250 100644 --- a/src/Common/src/Common/Reflection/ReflectionHelpers.cs +++ b/src/Common/src/Common/Reflection/ReflectionHelpers.cs @@ -136,6 +136,8 @@ public static IEnumerable FindAssembliesWithAttribute() public static IEnumerable FindTypesWithAttribute(Assembly assembly) where T : Attribute { + ArgumentGuard.NotNull(assembly); + return assembly.GetTypes().Where(type => type.IsDefined(typeof(T))); } @@ -209,6 +211,9 @@ public static IEnumerable FindInterfacedTypesFromAssemblyAttribute public static Type FindType(string[] assemblyNames, string[] typeNames) { + ArgumentGuard.NotNull(assemblyNames); + ArgumentGuard.NotNull(typeNames); + foreach (string assemblyName in assemblyNames) { Assembly assembly = FindAssembly(assemblyName); @@ -244,6 +249,8 @@ public static Type FindType(string[] assemblyNames, string[] typeNames) /// public static Type FindType(Assembly assembly, string typeName) { + ArgumentGuard.NotNull(assembly); + try { return assembly.GetType(typeName); @@ -306,6 +313,8 @@ public static Type FindTypeOrThrow(string[] assemblyNames, string[] typeNames, s /// public static PropertyInfo FindProperty(Type type, string propertyName) { + ArgumentGuard.NotNull(type); + try { return type.GetProperty(propertyName); @@ -335,6 +344,8 @@ public static PropertyInfo FindProperty(Type type, string propertyName) /// public static MethodInfo FindMethod(Type type, string methodName, Type[] parameters = null) { + ArgumentGuard.NotNull(type); + try { if (parameters != null) @@ -369,6 +380,8 @@ public static MethodInfo FindMethod(Type type, string methodName, Type[] paramet /// public static object Invoke(MethodBase member, object instance, object[] args) { + ArgumentGuard.NotNull(member); + try { return member.Invoke(instance, args); @@ -426,6 +439,8 @@ public static object CreateInstance(Type t, object[] args = null) /// public static void TrySetProperty(object obj, string property, object value) { + ArgumentGuard.NotNull(obj); + PropertyInfo prop = obj.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance); if (prop != null && prop.CanWrite) diff --git a/src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs b/src/Common/test/Common.Certificates.Test/CertificateConfigurationExtensionsTest.cs similarity index 94% rename from src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs rename to src/Common/test/Common.Certificates.Test/CertificateConfigurationExtensionsTest.cs index eebe9d3585..13443f3a73 100644 --- a/src/Common/test/Common.Certificate.Test/CertificateConfigurationExtensionsTest.cs +++ b/src/Common/test/Common.Certificates.Test/CertificateConfigurationExtensionsTest.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Configuration; using Xunit; -namespace Steeltoe.Common.Certificate.Test; +namespace Steeltoe.Common.Certificates.Test; public sealed class CertificateConfigurationExtensionsTest { diff --git a/src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Common.Certificates.Test/ConfigureCertificateOptionsTest.cs similarity index 99% rename from src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs rename to src/Common/test/Common.Certificates.Test/ConfigureCertificateOptionsTest.cs index 99e06f93f7..6448294e68 100644 --- a/src/Common/test/Common.Certificate.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Common.Certificates.Test/ConfigureCertificateOptionsTest.cs @@ -13,7 +13,7 @@ using Steeltoe.Common.Utils.IO; using Xunit; -namespace Steeltoe.Common.Certificate.Test; +namespace Steeltoe.Common.Certificates.Test; public sealed class ConfigureCertificateOptionsTest { diff --git a/src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs similarity index 89% rename from src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs rename to src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs index fb7da8c1c6..a10334b698 100644 --- a/src/Common/test/Common.Certificate.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs @@ -7,7 +7,7 @@ using FluentAssertions; using Xunit; -namespace Steeltoe.Common.Certificate.Test; +namespace Steeltoe.Common.Certificates.Test; public sealed class LocalCertificateWriterTest { @@ -23,10 +23,10 @@ public void CertificatesIncludeParams() var rootCertificate = new X509Certificate2(certificateWriter.RootCaPfxPath); var intermediateCertificate = new X509Certificate2(certificateWriter.IntermediatePfxPath); - rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem"))); + rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeAppInstanceKey.pem"))); X509Certificate2 certificate = - new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) + new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeAppInstanceCert.pem"))) .CopyWithPrivateKey(rsa); rootCertificate.Should().NotBeNull(); diff --git a/src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj b/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj similarity index 87% rename from src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj rename to src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj index fe427b9630..01d2bda861 100644 --- a/src/Common/test/Common.Certificate.Test/Steeltoe.Common.Certificate.Test.csproj +++ b/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj @@ -2,7 +2,6 @@ net8.0;net6.0 enable - Steeltoe.Common.Certificate.Test @@ -14,7 +13,7 @@ - + diff --git a/src/Common/test/Common.Certificate.Test/empty.crt b/src/Common/test/Common.Certificates.Test/empty.crt similarity index 100% rename from src/Common/test/Common.Certificate.Test/empty.crt rename to src/Common/test/Common.Certificates.Test/empty.crt diff --git a/src/Common/test/Common.Certificate.Test/instance.crt b/src/Common/test/Common.Certificates.Test/instance.crt similarity index 100% rename from src/Common/test/Common.Certificate.Test/instance.crt rename to src/Common/test/Common.Certificates.Test/instance.crt diff --git a/src/Common/test/Common.Certificate.Test/instance.key b/src/Common/test/Common.Certificates.Test/instance.key similarity index 100% rename from src/Common/test/Common.Certificate.Test/instance.key rename to src/Common/test/Common.Certificates.Test/instance.key diff --git a/src/Common/test/Common.Certificate.Test/instance.p12 b/src/Common/test/Common.Certificates.Test/instance.p12 similarity index 100% rename from src/Common/test/Common.Certificate.Test/instance.p12 rename to src/Common/test/Common.Certificates.Test/instance.p12 diff --git a/src/Common/test/Common.Certificate.Test/instance2.crt b/src/Common/test/Common.Certificates.Test/instance2.crt similarity index 100% rename from src/Common/test/Common.Certificate.Test/instance2.crt rename to src/Common/test/Common.Certificates.Test/instance2.crt diff --git a/src/Common/test/Common.Certificate.Test/instance2.key b/src/Common/test/Common.Certificates.Test/instance2.key similarity index 100% rename from src/Common/test/Common.Certificate.Test/instance2.key rename to src/Common/test/Common.Certificates.Test/instance2.key diff --git a/src/Common/test/Common.Certificate.Test/invalid.key b/src/Common/test/Common.Certificates.Test/invalid.key similarity index 100% rename from src/Common/test/Common.Certificate.Test/invalid.key rename to src/Common/test/Common.Certificates.Test/invalid.key diff --git a/src/Common/test/Common.Certificate.Test/xunit.runner.json b/src/Common/test/Common.Certificates.Test/xunit.runner.json similarity index 100% rename from src/Common/test/Common.Certificate.Test/xunit.runner.json rename to src/Common/test/Common.Certificates.Test/xunit.runner.json diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs index f32727cbcd..ad0542aa87 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationSource.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Steeltoe.Common; -using Steeltoe.Common.Certificate; +using Steeltoe.Common.Certificates; using Steeltoe.Common.Configuration; namespace Steeltoe.Configuration.ConfigServer; diff --git a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj index c23edef10a..fe85cb4c0a 100644 --- a/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj +++ b/src/Configuration/src/ConfigServer/Steeltoe.Configuration.ConfigServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs index 28b939c9bc..2c227d0eec 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs @@ -269,11 +269,7 @@ public void Processes_Identity_configuration() { 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") + Tuple.Create("credentials:client_secret", "test-secret") }; Dictionary configurationData = @@ -285,9 +281,10 @@ public void Processes_Identity_configuration() 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"); + string keyPrefix = $"{IdentityCloudFoundryPostProcessor.AuthenticationConfigurationKeyPrefix}:{scheme}"; + configurationData[$"{keyPrefix}:Authority"].Should().Be("test-domain"); + configurationData[$"{keyPrefix}:ClientId"].Should().Be("test-id"); + configurationData[$"{keyPrefix}:ClientSecret"].Should().Be("test-secret"); } } } diff --git a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj index e7fa74e56e..90086d9280 100644 --- a/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj +++ b/src/Configuration/test/ConfigServer.Test/Steeltoe.Configuration.ConfigServer.Test.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs index b4dbec43e3..30d9700ccc 100644 --- a/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs +++ b/src/Discovery/src/Eureka/EurekaServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Steeltoe.Common; -using Steeltoe.Common.Certificate; +using Steeltoe.Common.Certificates; using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.HealthChecks; diff --git a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj index 1fa9ef2caf..cf7d252f1e 100644 --- a/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj +++ b/src/Discovery/src/Eureka/Steeltoe.Discovery.Eureka.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj index 38bccb3dbe..3acf2635bb 100644 --- a/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj +++ b/src/Discovery/test/HttpClients.Test/Steeltoe.Discovery.HttpClients.Test.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs index b88b8a210f..7b2bf978ad 100644 --- a/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs @@ -11,9 +11,9 @@ namespace Steeltoe.Security.Authentication.JwtBearer; internal sealed class TokenKeyResolver { + private static readonly MediaTypeWithQualityHeaderValue AcceptHeader = new("application/json"); private readonly HttpClient _httpClient; - private readonly Uri _authority; - private readonly MediaTypeWithQualityHeaderValue _acceptHeader = new("application/json"); + private readonly Uri _authorityUri; internal static ConcurrentDictionary ResolvedSecurityKeysById { get; } = new(); @@ -27,7 +27,7 @@ public TokenKeyResolver(string authority, HttpClient httpClient) authority += '/'; } - _authority = new Uri($"{authority}token_keys"); + _authorityUri = new Uri($"{authority}token_keys"); _httpClient = httpClient; } @@ -38,6 +38,8 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken return [resolved]; } + // can't be async all the way until updates are complete in Microsoft libraries + // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468 JsonWebKeySet? keySet = FetchKeySetAsync(default).GetAwaiter().GetResult(); if (keySet != null) @@ -58,8 +60,8 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken internal async Task FetchKeySetAsync(CancellationToken cancellationToken) { - using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authority); - requestMessage.Headers.Accept.Add(_acceptHeader); + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authorityUri); + requestMessage.Headers.Accept.Add(AcceptHeader); HttpResponseMessage response = await _httpClient.SendAsync(requestMessage, cancellationToken); diff --git a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs index 9f0c5981c8..47525a7425 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Steeltoe.Common; -using Steeltoe.Security.Authentication.Shared; namespace Steeltoe.Security.Authentication.OpenIdConnect; @@ -16,14 +15,19 @@ internal sealed class PostConfigureOpenIdConnectOptions : IPostConfigureOptions< { // 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 => + private static Task MapScopesToClaimsAsync(TokenValidatedContext context) { - if (tokenValidatedContext.Principal?.Identity is not ClaimsIdentity claimsIdentity) + if (context.Principal?.Identity is not ClaimsIdentity claimsIdentity) { - return Task.FromResult(1); + return Task.CompletedTask; } - string scopes = tokenValidatedContext.TokenEndpointResponse?.Scope ?? string.Empty; + string? scopes = context.TokenEndpointResponse?.Scope; + + if (scopes == null) + { + return Task.CompletedTask; + } IEnumerable claimsFromScopes = scopes.Split(' ') .Where(scope => !claimsIdentity.Claims.Any(claim => claim.Type == "scope" && claim.Value == scope)) @@ -31,15 +35,14 @@ internal sealed class PostConfigureOpenIdConnectOptions : IPostConfigureOptions< claimsIdentity.AddClaims(claimsFromScopes); - return Task.FromResult(0); - }; + return Task.CompletedTask; + } public void PostConfigure(string? name, OpenIdConnectOptions options) { - ArgumentGuard.NotNull(name); ArgumentGuard.NotNull(options); - options.Events.OnTokenValidated = MapScopesToClaims; + options.Events.OnTokenValidated = MapScopesToClaimsAsync; options.ResponseType = OpenIdConnectResponseType.Code; options.SignInScheme ??= CookieAuthenticationDefaults.AuthenticationScheme; diff --git a/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs index ca492c286a..bd7ccc6d32 100644 --- a/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs @@ -7,13 +7,13 @@ using Microsoft.IdentityModel.Tokens; using Steeltoe.Common; -namespace Steeltoe.Security.Authentication.Shared; +namespace Steeltoe.Security.Authentication.OpenIdConnect; internal sealed class TokenKeyResolver { + private static readonly MediaTypeWithQualityHeaderValue AcceptHeader = new("application/json"); private readonly HttpClient _httpClient; - private readonly Uri _authority; - private readonly MediaTypeWithQualityHeaderValue _acceptHeader = new("application/json"); + private readonly Uri _authorityUri; internal static ConcurrentDictionary ResolvedSecurityKeysById { get; } = new(); @@ -27,7 +27,7 @@ public TokenKeyResolver(string authority, HttpClient httpClient) authority += '/'; } - _authority = new Uri($"{authority}token_keys"); + _authorityUri = new Uri($"{authority}token_keys"); _httpClient = httpClient; } @@ -38,6 +38,8 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken return [resolved]; } + // can't be async all the way until updates are complete in Microsoft libraries + // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468 JsonWebKeySet? keySet = FetchKeySetAsync(default).GetAwaiter().GetResult(); if (keySet != null) @@ -58,8 +60,8 @@ internal IEnumerable ResolveSigningKey(string token, SecurityToken internal async Task FetchKeySetAsync(CancellationToken cancellationToken) { - using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authority); - requestMessage.Headers.Accept.Add(_acceptHeader); + using var requestMessage = new HttpRequestMessage(HttpMethod.Get, _authorityUri); + requestMessage.Headers.Accept.Add(AcceptHeader); HttpResponseMessage response = await _httpClient.SendAsync(requestMessage, cancellationToken); diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs index f7fb3e9dba..6b6327875f 100644 --- a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -36,9 +36,9 @@ private ApplicationInstanceCertificate(string organizationId, string spaceId, st InstanceId = instanceId; } - public static bool TryParse(X509Certificate2 certificate, [NotNullWhen(true)] out ApplicationInstanceCertificate? outInstanceCertificate) + public static bool TryParse(X509Certificate2 certificate, [NotNullWhen(true)] out ApplicationInstanceCertificate? instanceCertificate) { - outInstanceCertificate = null; + instanceCertificate = null; Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex.Match(certificate.Subject); @@ -49,7 +49,7 @@ public static bool TryParse(X509Certificate2 certificate, [NotNullWhen(true)] ou if (instanceMatch.Success) { - outInstanceCertificate = new ApplicationInstanceCertificate(instanceMatch.Groups["org"].Value, instanceMatch.Groups["space"].Value, + instanceCertificate = new ApplicationInstanceCertificate(instanceMatch.Groups["org"].Value, instanceMatch.Groups["space"].Value, instanceMatch.Groups["app"].Value, instanceMatch.Groups["instance"].Value); } diff --git a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs index 065a287a1e..175b5822db 100644 --- a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -19,7 +19,7 @@ public static class CertificateApplicationBuilderExtensions /// public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder applicationBuilder) { - return applicationBuilder.UseCertificateAuthorization(new ForwardedHeadersOptions()); + return UseCertificateAuthorization(applicationBuilder, new ForwardedHeadersOptions()); } /// @@ -28,17 +28,17 @@ public static IApplicationBuilder UseCertificateAuthorization(this IApplicationB /// /// The . /// - /// + /// /// Custom header forwarding policy. is added to your . /// - public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder applicationBuilder, ForwardedHeadersOptions forwardedHeaders) + public static IApplicationBuilder UseCertificateAuthorization(this IApplicationBuilder applicationBuilder, ForwardedHeadersOptions options) { ArgumentGuard.NotNull(applicationBuilder); - ArgumentGuard.NotNull(forwardedHeaders); + ArgumentGuard.NotNull(options); - forwardedHeaders.ForwardedHeaders |= ForwardedHeaders.XForwardedProto; + options.ForwardedHeaders |= ForwardedHeaders.XForwardedProto; - applicationBuilder.UseForwardedHeaders(forwardedHeaders); + applicationBuilder.UseForwardedHeaders(options); applicationBuilder.UseCertificateForwarding(); applicationBuilder.UseAuthentication(); applicationBuilder.UseAuthorization(); diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs new file mode 100644 index 0000000000..5b9943cee6 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.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.AspNetCore.Authentication.Certificate; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Certificates; +using Steeltoe.Common.Configuration; + +namespace Steeltoe.Security.Authorization.Certificate; + +public static class CertificateAuthorizationBuilderExtensions +{ + /// + /// Adds the necessary components and policies for server-side authorization of application instance identity certificates.

Components + /// include named "AppInstanceIdentity" and certificate forwarding.
Secure your endpoints with the included + /// authorization polices by referencing . + ///
+ /// + /// The to add services to. + /// + public static AuthorizationBuilder AddAppInstanceIdentityCertificate(this AuthorizationBuilder authorizationBuilder) + { + ArgumentGuard.NotNull(authorizationBuilder); + + authorizationBuilder.Services.ConfigureCertificateOptions("AppInstanceIdentity"); + + authorizationBuilder.Services.AddCertificateForwarding(_ => + { + }); + + authorizationBuilder.Services.AddSingleton, PostConfigureCertificateAuthenticationOptions>(); + authorizationBuilder.Services.AddSingleton(); + + authorizationBuilder.AddPolicy(CertificateAuthorizationPolicies.SameOrganization, authorizationPolicyBuilder => + { + authorizationPolicyBuilder.RequireSameOrg(); + }).AddPolicy(CertificateAuthorizationPolicies.SameSpace, authorizationPolicyBuilder => + { + authorizationPolicyBuilder.RequireSameSpace(); + }); + + return authorizationBuilder; + } +} diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs deleted file mode 100644 index f0572026b9..0000000000 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs +++ /dev/null @@ -1,12 +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.Authorization.Certificate; - -public static class CertificateAuthorizationDefaults -{ - public const string SameOrganizationAuthorizationPolicy = "sameorg"; - public const string SameSpaceAuthorizationPolicy = "samespace"; - public const string HttpClientName = "IncludeClientCertificate"; -} diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index 355d6e8f80..8db8b8ee09 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -11,7 +11,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -public sealed class CertificateAuthorizationHandler : IAuthorizationHandler +internal sealed class CertificateAuthorizationHandler : IAuthorizationHandler { private readonly ILogger _logger; private ApplicationInstanceCertificate? _applicationInstanceCertificate; @@ -19,6 +19,7 @@ public sealed class CertificateAuthorizationHandler : IAuthorizationHandler public CertificateAuthorizationHandler(IOptionsMonitor certificateOptionsMonitor, ILogger logger) { ArgumentGuard.NotNull(certificateOptionsMonitor); + ArgumentGuard.NotNull(logger); _logger = logger; certificateOptionsMonitor.OnChange(OnCertificateRefresh); @@ -74,7 +75,7 @@ private void HandleCertificateAuthorizationRequirement(AuthorizationHandlerCo } else { - _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ClaimValue} but got {ClaimType}", claimValue, + _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ExpectedClaimValue} but got {ActualClaimValue}", claimValue, context.User.FindFirstValue(claimType)); } } diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs new file mode 100644 index 0000000000..786d21d8a6 --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Security.Authorization.Certificate; + +/// +/// Use this class to refer to authorization policies for use with Cloud Foundry style certificates. +/// +/// [Authorize(Policy = CertificateAuthorizationPolicies.SameOrganization)] +/// [HttpGet] +/// public string AnyAppInTheOrgCanAccess() +/// { +/// return "Certificate is valid, client and server are in the same org."; +/// } +/// +/// +public static class CertificateAuthorizationPolicies +{ + /// + /// Restrict access to application instances operating within the same organization. + /// + public const string SameOrganization = "sameorg"; + + /// + /// Restrict access to application instances operating within the same space. + /// + public const string SameSpace = "samespace"; +} diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs index 351bb99d0d..e7308305bf 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs @@ -7,7 +7,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -public static class CertificateAuthorizationPolicyBuilderExtensions +internal static class CertificateAuthorizationPolicyBuilderExtensions { public static AuthorizationPolicyBuilder RequireSameOrg(this AuthorizationPolicyBuilder builder) { diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs new file mode 100644 index 0000000000..bfcd05d37d --- /dev/null +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -0,0 +1,67 @@ +// 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.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Steeltoe.Common; +using Steeltoe.Common.Certificates; +using Steeltoe.Common.Configuration; + +namespace Steeltoe.Security.Authorization.Certificate; + +public static class CertificateHttpClientBuilderExtensions +{ + /// + /// Configures representing the application instance and attaches the certificate to outbound requests. + /// + /// + /// The to add a client certificate to. + /// + public static IHttpClientBuilder AddClientCertificateForAppInstance(this IHttpClientBuilder httpClientBuilder) + { + return AddClientCertificate(httpClientBuilder, "AppInstanceIdentity"); + } + + /// + /// Configures named and attaches the certificate to outbound requests. + /// + /// + /// The to add a client certificate to. + /// + /// + /// The name of the from which to add the client certificate. + /// + public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder httpClientBuilder, string certificateOptionsName) + { + ArgumentGuard.NotNull(httpClientBuilder); + ArgumentGuard.NotNull(certificateOptionsName); + + httpClientBuilder.Services.ConfigureCertificateOptions(certificateOptionsName); + + httpClientBuilder.ConfigureHttpClient((serviceProvider, client) => + { + var loggerFactory = serviceProvider.GetRequiredService(); + ILogger logger = loggerFactory.CreateLogger(nameof(CertificateAuthorizationBuilderExtensions)); + var optionsMonitor = serviceProvider.GetRequiredService>(); + CertificateOptions certificateOptions = optionsMonitor.Get(certificateOptionsName); + X509Certificate2? certificate = certificateOptions.Certificate; + + if (certificate != null) + { + logger.LogTrace("Adding certificate with subject {CertificateSubject} to outbound requests in header X-Client-Cert", certificate.Subject); + + string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); + client.DefaultRequestHeaders.Add("X-Client-Cert", b64); + } + else + { + logger.LogError("Failed to find a certificate under the name {CertificateOptionsName}", certificateOptionsName); + } + }); + + return httpClientBuilder; + } +} diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs deleted file mode 100644 index 8a19ed75c2..0000000000 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ /dev/null @@ -1,73 +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; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Steeltoe.Common.Certificate; -using Steeltoe.Common.Configuration; - -namespace Steeltoe.Security.Authorization.Certificate; - -public static class CertificateServiceCollectionExtensions -{ - /// - /// Adds the necessary components for server-side authorization of client certificates. - /// - /// - /// The to add services to. - /// - public static IServiceCollection AddCertificateAuthorizationServer(this IServiceCollection services) - { - services.ConfigureCertificateOptions("AppInstanceIdentity"); - - services.AddCertificateForwarding(_ => - { - }); - - services.TryAddEnumerable(ServiceDescriptor - .Singleton, PostConfigureCertificateAuthenticationOptions>()); - - services.AddSingleton(); - return services; - } - - /// - /// Adds a named and the necessary components for finding client certificates and attaching them to outbound requests. - /// - /// - /// The to add services to. - /// - public static IServiceCollection AddCertificateAuthorizationClient(this IServiceCollection services) - { - services.ConfigureCertificateOptions("AppInstanceIdentity"); - - services.AddHttpClient(CertificateAuthorizationDefaults.HttpClientName, (serviceProvider, client) => - { - var loggerFactory = serviceProvider.GetRequiredService(); - ILogger logger = loggerFactory.CreateLogger("Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions"); - var optionsMonitor = serviceProvider.GetService>(); - CertificateOptions? certificateOptions = optionsMonitor?.Get("AppInstanceIdentity"); - X509Certificate2? certificate = certificateOptions?.Certificate; - - if (certificate != null) - { - logger.LogDebug("Adding certificate with subject {CertificateSubject} to outbound requests in header X-Client-Cert", certificate.Subject); - - string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); - client.DefaultRequestHeaders.Add("X-Client-Cert", b64); - } - else - { - logger.LogError("Failed to find certificates"); - } - }); - - return services; - } -} diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index fef9bac392..98d5e51dbb 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Security.Authorization.Certificate; -public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions +internal sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions { private readonly IOptionsMonitor _certificateOptionsMonitor; private readonly ILogger _logger; @@ -21,6 +21,7 @@ public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor logger) { ArgumentGuard.NotNull(certificateOptionsMonitor); + ArgumentGuard.NotNull(logger); _certificateOptionsMonitor = certificateOptionsMonitor; _logger = logger; @@ -28,7 +29,6 @@ public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor string! -const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy = "sameorg" -> string! -const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy = "samespace" -> string! +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies.SameOrganization = "sameorg" -> string! +const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies.SameSpace = "samespace" -> string! static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! forwardedHeaders) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameSpace(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.DependencyInjection.IServiceCollection! -static Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions.AddCertificateAuthorizationServer(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddAppInstanceIdentityCertificate(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! authorizationBuilder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder, string! certificateOptionsName) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificateForAppInstance(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions 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! certificateOptionsMonitor, 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 +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies +Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions diff --git a/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs index 2d874b5557..1359e86dbf 100644 --- a/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs @@ -6,6 +6,6 @@ namespace Steeltoe.Security.Authorization.Certificate; -public sealed class SameOrgRequirement : IAuthorizationRequirement +internal sealed class SameOrgRequirement : IAuthorizationRequirement { } diff --git a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs index 1568f1026f..9eca535e57 100644 --- a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs @@ -6,6 +6,6 @@ namespace Steeltoe.Security.Authorization.Certificate; -public sealed class SameSpaceRequirement : IAuthorizationRequirement +internal sealed class SameSpaceRequirement : IAuthorizationRequirement { } diff --git a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj index c5a7a24c97..2e003ca8df 100644 --- a/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj +++ b/src/Security/src/Authorization.Certificate/Steeltoe.Security.Authorization.Certificate.csproj @@ -1,7 +1,7 @@ net8.0 - ASP.NET Core middleware that enables an application to support authorization via client certificates. + This package provides support for authorization with client certificates. aspnetcore;authorization;security;x509;certificate;mutualtls true enable @@ -14,6 +14,6 @@ - + diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs index 1d47c6e1c3..5cab8350f1 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -72,14 +72,15 @@ public void PostConfigure_ConfiguresForCloudFoundry() serviceCollection.ConfigureJwtBearerForCloudFoundry(); serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); - JwtBearerOptions jwtBearerOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() - .Get(JwtBearerDefaults.AuthenticationScheme); + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + var optionsMonitor = serviceProvider.GetRequiredService>(); + JwtBearerOptions options = optionsMonitor.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"); + options.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); + options.MetadataAddress.Should().Be("https://steeltoe.login.sys.cf-app.com/.well-known/openid-configuration"); + options.RequireHttpsMetadata.Should().BeTrue(); + options.TokenValidationParameters.ValidIssuer.Should().Be("https://steeltoe.login.sys.cf-app.com/oauth/token"); + options.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + options.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 index cf2e7fc9ad..70352f988f 100644 --- 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 @@ -10,5 +10,4 @@ - diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs index ed773ea151..db23f2d584 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -10,13 +10,6 @@ namespace Steeltoe.Security.Authentication.JwtBearer.Test; public sealed class TokenKeyResolverTest { - [Fact] - public void Constructor_ThrowsIfOptionsNull() - { - Assert.Throws(() => new TokenKeyResolver(null!, new HttpClient())); - Assert.Throws(() => new TokenKeyResolver("someAuthority", null!)); - } - [Fact] public void ResolveSigningKey_FindsExistingKey() { diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs index ac2ba766df..80a75a874d 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -29,14 +29,15 @@ public void PostConfigure_AddsClientIdToValidAudiences() serviceCollection.AddSingleton(configuration); serviceCollection.AddAuthentication().AddOpenIdConnect(); - OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() - .Get(OpenIdConnectDefaults.AuthenticationScheme); + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + var optionsMonitor = serviceProvider.GetRequiredService>(); + OpenIdConnectOptions options = optionsMonitor.Get(OpenIdConnectDefaults.AuthenticationScheme); var postConfigurer = new PostConfigureOpenIdConnectOptions(); - postConfigurer.PostConfigure(OpenIdConnectDefaults.AuthenticationScheme, openIdConnectOptions); + postConfigurer.PostConfigure(OpenIdConnectDefaults.AuthenticationScheme, options); - openIdConnectOptions.TokenValidationParameters.ValidAudience.Should().Be("testClient"); + options.TokenValidationParameters.ValidAudience.Should().Be("testClient"); } [Fact] @@ -74,14 +75,15 @@ public void PostConfigure_ConfiguresForCloudFoundry() serviceCollection.AddAuthentication().AddOpenIdConnect(); serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); - OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() - .Get(OpenIdConnectDefaults.AuthenticationScheme); + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + var optionsMonitor = serviceProvider.GetRequiredService>(); + OpenIdConnectOptions options = optionsMonitor.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"); + options.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); + options.MetadataAddress.Should().Be("https://steeltoe.login.sys.cf-app.com/.well-known/openid-configuration"); + options.RequireHttpsMetadata.Should().BeTrue(); + options.TokenValidationParameters.ValidIssuer.Should().Be("https://steeltoe.login.sys.cf-app.com/oauth/token"); + options.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); + options.TokenValidationParameters.ValidAudience.Should().Be("4e6f8e34-f42b-440e-a042-f2b13c1d5bed"); } } 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 index e571c4b17f..7fb9379020 100644 --- 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 @@ -1,12 +1,7 @@ - net8.0 - enable enable - - false - true @@ -15,5 +10,4 @@ - diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index 879fc22f76..11dab9d91a 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -6,17 +6,10 @@ using Microsoft.IdentityModel.Tokens; using Xunit; -namespace Steeltoe.Security.Authentication.Shared.Test; +namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; public sealed class TokenKeyResolverTest { - [Fact] - public void Constructor_ThrowsIfOptionsNull() - { - Assert.Throws(() => new TokenKeyResolver(null!, new HttpClient())); - Assert.Throws(() => new TokenKeyResolver("someAuthority", null!)); - } - [Fact] public void ResolveSigningKey_FindsExistingKey() { diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index d778d2b8ca..62dc802e6a 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; -using Steeltoe.Common.Certificate; +using Steeltoe.Common.Certificates; using Steeltoe.Common.TestResources; using Xunit; @@ -20,7 +20,7 @@ public async Task CertificateAuth_AcceptsSameSpace() { using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch).GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -31,7 +31,7 @@ public async Task CertificateAuth_AcceptsSameOrg() { using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch); HttpResponseMessage response = await httpClient.GetAsync(requestUri); @@ -43,7 +43,7 @@ public async Task CertificateAuth_RejectsOrgMismatch() { using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.SpaceMatch).GetAsync(requestUri); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); @@ -54,7 +54,7 @@ public async Task CertificateAuth_RejectsSpaceMismatch() { using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgMatch).GetAsync(requestUri); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); @@ -65,7 +65,7 @@ public async Task CertificateAuth_ForbiddenWithoutCert() { using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"http://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + var requestUri = new Uri($"http://localhost/{CertificateAuthorizationPolicies.SameSpace}"); HttpResponseMessage response = await host.GetTestClient().GetAsync(requestUri); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); @@ -80,7 +80,7 @@ public async Task CertificateAuth_AcceptsSameSpace_DiegoCert() using var caScope = new EnvironmentVariableScope("CF_SYSTEM_CERT_PATH", Path.Join(LocalCertificateWriter.AppBasePath, "root_certificates")); using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego).GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -96,15 +96,13 @@ public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() using IHost host = await GetHostBuilder().StartAsync(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - private IHostBuilder GetHostBuilder() - { private IHostBuilder GetHostBuilder() { var hostBuilder = new HostBuilder(); @@ -113,7 +111,6 @@ private IHostBuilder GetHostBuilder() hostBuilder.ConfigureWebHost(builder => builder.UseTestServer()); return hostBuilder; } - } private static HttpClient ClientWithCertificate(HttpClient httpClient, X509Certificate certificate) { diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs similarity index 86% rename from src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs rename to src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs index 58f4c24be0..8c6b73a9b4 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs @@ -8,13 +8,13 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Steeltoe.Common.Certificate; +using Steeltoe.Common.Certificates; using Steeltoe.Common.TestResources; using Xunit; namespace Steeltoe.Security.Authorization.Certificate.Test; -public sealed class CertificateServiceCollectionExtensionsTest +public sealed class CertificateHttpBuilderExtensionsTest { [Fact] public async Task AddCertificateAuthorizationClient_AddsNamedHttpClientWithCertificate() @@ -25,7 +25,7 @@ public async Task AddCertificateAuthorizationClient_AddsNamedHttpClientWithCerti using var keyScope = new EnvironmentVariableScope("CF_INSTANCE_KEY", "instance.key"); using IHost host = await GetHostBuilder().StartAsync(); var factory = host.Services.GetRequiredService(); - HttpClient client = factory.CreateClient(CertificateAuthorizationDefaults.HttpClientName); + HttpClient client = factory.CreateClient("test"); client.Should().NotBeNull(); client.DefaultRequestHeaders.Contains("X-Client-Cert").Should().BeTrue(); @@ -37,7 +37,7 @@ private static IHostBuilder GetHostBuilder() { var hostBuilder = new HostBuilder(); hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate()); - hostBuilder.ConfigureServices(services => services.AddCertificateAuthorizationClient()); + hostBuilder.ConfigureServices(services => services.AddHttpClient("test").AddClientCertificateForAppInstance()); hostBuilder.ConfigureWebHost(webBuilder => { diff --git a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs index c1a566293b..0fe23c88f4 100644 --- a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs +++ b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs @@ -2,11 +2,11 @@ // 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.Certificate; +using Steeltoe.Common.Certificates; namespace Steeltoe.Security.Authorization.Certificate.Test; -public sealed class ClientCertificatesFixture : IDisposable +public sealed class ClientCertificatesFixture { private LocalCertificateWriter CertificateWriter { get; } = new(); @@ -24,8 +24,4 @@ public ClientCertificatesFixture() CertificateWriter.CertificateFilenamePrefix = "OrgMatch"; CertificateWriter.Write(ServerOrgId, Guid.NewGuid()); } - - public void Dispose() - { - } } 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 index d76ca79564..32ef3b3ae8 100644 --- 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 @@ -21,5 +21,4 @@ PreserveNewest - diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs index f3556a570b..9db9bb1663 100644 --- a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -13,25 +13,12 @@ public sealed class TestServerCertificateStartup { public void ConfigureServices(IServiceCollection services) { - services.AddCertificateAuthorizationServer(); - services.AddAuthentication().AddCertificate(options => { options.ValidateValidityPeriod = false; }); - services.AddAuthorization(options => - { - options.AddPolicy(CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy, authorizationPolicyBuilder => - { - authorizationPolicyBuilder.RequireSameOrg(); - }); - - options.AddPolicy(CertificateAuthorizationDefaults.SameSpaceAuthorizationPolicy, authorizationPolicyBuilder => - { - authorizationPolicyBuilder.RequireSameSpace(); - }); - }); + services.AddAuthorizationBuilder().AddAppInstanceIdentityCertificate(); } public void Configure(IApplicationBuilder app, IAuthorizationService authorizationService) diff --git a/src/Steeltoe.All.sln b/src/Steeltoe.All.sln index 823b1df1d3..a81fd5c4e9 100644 --- a/src/Steeltoe.All.sln +++ b/src/Steeltoe.All.sln @@ -14,7 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http", "Com EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net", "Common\src\Common.Net\Steeltoe.Common.Net.csproj", "{8F27A2D4-FEF2-4783-99C4-6B2ABA3D9431}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificate", "Common\src\Common.Certificate\Steeltoe.Common.Certificate.csproj", "{DFE642E6-1CD0-4485-AC86-43CEBC451484}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificates", "Common\src\Common.Certificates\Steeltoe.Common.Certificates.csproj", "{DFE642E6-1CD0-4485-AC86-43CEBC451484}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{59874241-E276-4035-B31D-14924889A1C9}" ProjectSection(SolutionItems) = preProject @@ -25,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Http.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Net.Test", "Common\test\Common.Net.Test\Steeltoe.Common.Net.Test.csproj", "{1ACC6991-7D4C-48B2-A41C-4B179B19A85C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificate.Test", "Common\test\Common.Certificate.Test\Steeltoe.Common.Certificate.Test.csproj", "{880208DF-05C6-4763-A447-744D6C8DBDEA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Common.Certificates.Test", "Common\test\Common.Certificates.Test\Steeltoe.Common.Certificates.Test.csproj", "{880208DF-05C6-4763-A447-744D6C8DBDEA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_shared", "_shared", "{DC1BC61A-E0FA-4CF9-9F24-D4C564A07836}" ProjectSection(SolutionItems) = preProject diff --git a/src/Steeltoe.Common.slnf b/src/Steeltoe.Common.slnf index 38221f71de..26d9e1da88 100644 --- a/src/Steeltoe.Common.slnf +++ b/src/Steeltoe.Common.slnf @@ -6,7 +6,7 @@ "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", "Common\\src\\Common.Net\\Steeltoe.Common.Net.csproj", - "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", + "Common\\src\\Common.Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.Hosting.Test\\Steeltoe.Common.Hosting.Test.csproj", diff --git a/src/Steeltoe.Configuration.slnf b/src/Steeltoe.Configuration.slnf index 55119d692a..2ed15e743b 100644 --- a/src/Steeltoe.Configuration.slnf +++ b/src/Steeltoe.Configuration.slnf @@ -5,7 +5,7 @@ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", - "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", + "Common\\src\\Common.Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", diff --git a/src/Steeltoe.Discovery.slnf b/src/Steeltoe.Discovery.slnf index b56102e638..39e1b185d3 100644 --- a/src/Steeltoe.Discovery.slnf +++ b/src/Steeltoe.Discovery.slnf @@ -5,7 +5,7 @@ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", "Common\\src\\Common.Hosting\\Steeltoe.Common.Hosting.csproj", "Common\\src\\Common.Http\\Steeltoe.Common.Http.csproj", - "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", + "Common\\src\\Common.Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common.Utils\\Steeltoe.Common.Utils.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index 750f55f910..e8854b1018 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -3,7 +3,7 @@ "path": "Steeltoe.All.sln", "projects": [ "Common\\src\\Abstractions\\Steeltoe.Common.Abstractions.csproj", - "Common\\src\\Common.Certificate\\Steeltoe.Common.Certificate.csproj", + "Common\\src\\Common.Certificates\\Steeltoe.Common.Certificates.csproj", "Common\\src\\Common\\Steeltoe.Common.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", "Configuration\\src\\Abstractions\\Steeltoe.Configuration.Abstractions.csproj", diff --git a/versions.props b/versions.props index 072dbe47f1..c42d76f27e 100644 --- a/versions.props +++ b/versions.props @@ -68,7 +68,6 @@ --> 8.0.* - 8.0.* 7.5.* 1.6.*-* 1.8.*-* From cad92ae0226531b61c9a1fa7fd32a4eee0ad7d04 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 14 Jun 2024 06:41:22 -0500 Subject: [PATCH 12/28] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- .../Common.Certificates/LocalCertificateWriter.cs | 2 +- ...teeltoe.Security.Authentication.JwtBearer.csproj | 2 +- ...toe.Security.Authentication.OpenIdConnect.csproj | 2 +- .../CertificateAuthorizationBuilderExtensions.cs | 13 +++++++------ .../CertificateAuthorizationHandler.cs | 2 ++ .../CertificateAuthorizationPolicies.cs | 5 ++++- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs index cd8b3d6027..50a473a4b3 100644 --- a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs @@ -14,7 +14,7 @@ internal sealed class LocalCertificateWriter private static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); - internal static string CertificateDirectoryName => "GeneratedCertificates"; + internal const string CertificateDirectoryName = "GeneratedCertificates"; internal string CertificateFilenamePrefix { get; set; } = "SteeltoeAppInstance"; diff --git a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj index 070d115306..69ff3b878d 100644 --- a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj +++ b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -16,6 +16,6 @@ - + diff --git a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj index 3633be8a88..17527f347a 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -16,6 +16,6 @@ - + diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 5b9943cee6..31774499ab 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -15,13 +15,14 @@ namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateAuthorizationBuilderExtensions { /// - /// Adds the necessary components and policies for server-side authorization of application instance identity certificates.

Components - /// include named "AppInstanceIdentity" and certificate forwarding.
Secure your endpoints with the included - /// authorization polices by referencing . + /// Adds the necessary components and policies for server-side authorization of application instance identity certificates. + /// + /// Components include named "AppInstanceIdentity" and certificate forwarding. + /// + /// + /// Secure your endpoints with the included authorization policies by referencing . + /// ///
- /// - /// The to add services to. - /// public static AuthorizationBuilder AddAppInstanceIdentityCertificate(this AuthorizationBuilder authorizationBuilder) { ArgumentGuard.NotNull(authorizationBuilder); diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index 8db8b8ee09..e8be844b97 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -28,6 +28,8 @@ public CertificateAuthorizationHandler(IOptionsMonitor certi public Task HandleAsync(AuthorizationHandlerContext context) { + ArgumentGuard.NotNull(context); + HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.OrganizationId, _applicationInstanceCertificate?.OrganizationId); diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs index 786d21d8a6..34dad36454 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicies.cs @@ -6,14 +6,17 @@ namespace Steeltoe.Security.Authorization.Certificate; /// /// Use this class to refer to authorization policies for use with Cloud Foundry style certificates. -/// +/// +/// /// +/// /// public static class CertificateAuthorizationPolicies { From 89b918455058c178b1f4fc16e148e7ce5b016514 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 14 Jun 2024 06:41:54 -0500 Subject: [PATCH 13/28] small fixes --- .../src/AutoConfiguration/PublicAPI.Unshipped.txt | 2 +- .../Common.Certificates.Test/LocalCertificateWriterTest.cs | 7 ++++--- .../Steeltoe.Common.Certificates.Test.csproj | 6 ------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt index 799e1e5900..40e7851e14 100644 --- a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt +++ b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt @@ -1,5 +1,5 @@ #nullable enable -const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonCertificate = "Steeltoe.Common.Certificate" -> string! +const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonCertificates = "Steeltoe.Common.Certificates" -> 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! diff --git a/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs index a10334b698..b7312acb71 100644 --- a/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs @@ -23,11 +23,12 @@ public void CertificatesIncludeParams() var rootCertificate = new X509Certificate2(certificateWriter.RootCaPfxPath); var intermediateCertificate = new X509Certificate2(certificateWriter.IntermediatePfxPath); - rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeAppInstanceKey.pem"))); + rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, + "SteeltoeAppInstanceKey.pem"))); X509Certificate2 certificate = - new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.AppBasePath, "GeneratedCertificates", "SteeltoeAppInstanceCert.pem"))) - .CopyWithPrivateKey(rsa); + new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, + "SteeltoeAppInstanceCert.pem"))).CopyWithPrivateKey(rsa); rootCertificate.Should().NotBeNull(); intermediateCertificate.Should().NotBeNull(); diff --git a/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj b/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj index 01d2bda861..be3eb0ba94 100644 --- a/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj +++ b/src/Common/test/Common.Certificates.Test/Steeltoe.Common.Certificates.Test.csproj @@ -17,12 +17,6 @@ - - - - - - PreserveNewest From efd3d3b93fdae184a69a5ad1589073b295ff9eb1 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 14 Jun 2024 10:34:41 -0500 Subject: [PATCH 14/28] reduce sso product name specificity --- src/Security/README.md | 2 +- .../JwtBearerServiceCollectionExtensions.cs | 2 +- .../Steeltoe.Security.Authentication.JwtBearer.csproj | 2 +- .../OpenIdConnectServiceCollectionExtensions.cs | 2 +- .../Steeltoe.Security.Authentication.OpenIdConnect.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Security/README.md b/src/Security/README.md index e3d7180149..fc28f3771a 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -1,6 +1,6 @@ # Steeltoe Security -Authentication and DataProtection libraries which simplify interacting with security services on Tanzu Platforms. +Authentication and DataProtection libraries which simplify interacting with security services on Tanzu platforms. For more information on how to use these components see the [Steeltoe documentation](https://steeltoe.io/). diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs index 7584c03186..3711273f85 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Security.Authentication.JwtBearer; public static class JwtBearerServiceCollectionExtensions { /// - /// Configures for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// Configures for compatibility with UAA-based systems, including those found in Cloud Foundry. /// /// /// The to add services to. diff --git a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj index 69ff3b878d..70cb8edc17 100644 --- a/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj +++ b/src/Security/src/Authentication.JwtBearer/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -1,7 +1,7 @@ net8.0 - Library for using JWT Bearer tokens with Tanzu + Library for using JWT Bearer tokens with UAA-based systems, including Cloud Foundry CloudFoundry;uaa;security;jwt;bearer true enable diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs index d5b0a198d4..13ba183705 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -12,7 +12,7 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect; public static class OpenIdConnectServiceCollectionExtensions { /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application + /// Configures for compatibility with UAA-based systems, including those found in Cloud Foundry /// Service. /// /// diff --git a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj index 17527f347a..9135f3ca36 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -1,7 +1,7 @@ net8.0 - Library for using OpenID Connect with Tanzu + Library for using OpenID Connect with UAA-based systems, including Cloud Foundry CloudFoundry;uaa;security;sso;openid;oidc true enable From 60e4ae51ed809f07629aa82a720cee3bcaa30a03 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 14 Jun 2024 11:58:43 -0500 Subject: [PATCH 15/28] certificate subject parsing method signature change --- .../ApplicationInstanceCertificate.cs | 8 ++--- .../CertificateAuthorizationHandler.cs | 2 +- ...nfigureCertificateAuthenticationOptions.cs | 2 +- .../Properties/AssemblyInfo.cs | 7 ++++ .../ApplicationInstanceCertificateTest.cs | 32 +++++++++++++++++++ 5 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/Security/src/Authorization.Certificate/Properties/AssemblyInfo.cs create mode 100644 src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs index 6b6327875f..6f82397614 100644 --- a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; namespace Steeltoe.Security.Authorization.Certificate; @@ -36,15 +35,16 @@ private ApplicationInstanceCertificate(string organizationId, string spaceId, st InstanceId = instanceId; } - public static bool TryParse(X509Certificate2 certificate, [NotNullWhen(true)] out ApplicationInstanceCertificate? instanceCertificate) + public static bool TryParse(string certificateSubject, [NotNullWhen(true)] out ApplicationInstanceCertificate? instanceCertificate) { instanceCertificate = null; + certificateSubject = certificateSubject.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase); - Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex.Match(certificate.Subject); + Match instanceMatch = CloudFoundryInstanceCertificateSubjectRegex.Match(certificateSubject); if (!instanceMatch.Success) { - instanceMatch = SteeltoeInstanceCertificateSubjectRegex.Match(certificate.Subject); + instanceMatch = SteeltoeInstanceCertificateSubjectRegex.Match(certificateSubject); } if (instanceMatch.Success) diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index e8be844b97..3806862644 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -44,7 +44,7 @@ private void OnCertificateRefresh(CertificateOptions certificateOptions) return; } - if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate)) + if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate.Subject, out ApplicationInstanceCertificate? applicationInstanceCertificate)) { _applicationInstanceCertificate = applicationInstanceCertificate; } diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 98d5e51dbb..5dd8a1ba1c 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -62,7 +62,7 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options var claims = new List(context.Principal.Claims); - if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate, out ApplicationInstanceCertificate? clientCertificate)) + if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate.Subject, out ApplicationInstanceCertificate? clientCertificate)) { claims.Add(new Claim(ApplicationClaimTypes.OrganizationId, clientCertificate.OrganizationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); diff --git a/src/Security/src/Authorization.Certificate/Properties/AssemblyInfo.cs b/src/Security/src/Authorization.Certificate/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..21d41207fa --- /dev/null +++ b/src/Security/src/Authorization.Certificate/Properties/AssemblyInfo.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. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate.Test")] diff --git a/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs b/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs new file mode 100644 index 0000000000..01129d5c3f --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs @@ -0,0 +1,32 @@ +// 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 Xunit; + +namespace Steeltoe.Security.Authorization.Certificate.Test; + +public sealed class ApplicationInstanceCertificateTest +{ + [InlineData("This subject does not match a supported pattern", false)] + [InlineData("CN=34c9765d-b5da-49a7-4a53-1c58, OU=app:e5950275-afce-480a-a017-babafd3d5798 + OU=space:ab60aac2-fb64-43ab-ba24-c57a15a7e114 + OU=organization:7fe4d027-2058-4539-a40c-702ac1373905", true, "7fe4d027-2058-4539-a40c-702ac1373905", "ab60aac2-fb64-43ab-ba24-c57a15a7e114")] + [InlineData("CN=69ed1418-8434-4b14-9ad9-636d71ba782a, OU=app:e268b8bf-cdad-4014-a18f-1131c73d9450 + OU=space:122b942a-d7b9-4839-b26e-836654b9785f + OU=organization:a8fef16f-94c0-49e3-aa0b-ced7c3da6229", true, "a8fef16f-94c0-49e3-aa0b-ced7c3da6229", "122b942a-d7b9-4839-b26e-836654b9785f")] + [InlineData("CN=\"69ed1418\", OU=app:e268b8bf + OU=space:\"122b942a\" + OU=organization:a8fef16f", true, "a8fef16f", "122b942a")] + [Theory] + public void SubjectParsingProducesExpectedResults(string subject, bool shouldParse, string? expectedOrgId = null, string? expectedSpaceId = null) + { + ApplicationInstanceCertificate.TryParse(subject, out ApplicationInstanceCertificate? instanceCertificate).Should().Be(shouldParse); + + if (shouldParse) + { + instanceCertificate.Should().NotBeNull(); + instanceCertificate?.OrganizationId.Should().Be(expectedOrgId); + instanceCertificate?.SpaceId.Should().Be(expectedSpaceId); + } + else + { + instanceCertificate.Should().BeNull(); + } + } +} From dc679464acbbcd59ed1e2e5175eec03d357301e9 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Fri, 14 Jun 2024 16:33:17 -0500 Subject: [PATCH 16/28] PR suggestions, move OIDC and JWT extensions to AuthenticationBuilder flags for tag/label processing, null checks, wording changes, test improvements --- .../CertificateConfigurationExtensions.cs | 4 +- .../CertificateServiceCollectionExtensions.cs | 2 + .../LocalCertificateWriter.cs | 10 +- .../ServiceCollectionExtensions.cs | 4 + .../Serialization/BoolStringJsonConverter.cs | 4 +- .../Serialization/LongStringJsonConverter.cs | 4 +- .../src/Common.Net/WindowsNetworkFileShare.cs | 4 - .../Common/Reflection/ReflectionHelpers.cs | 5 + .../LocalCertificateWriterTest.cs | 4 +- .../CloudFoundryPostProcessor.cs | 16 +- .../CosmosDbCloudFoundryPostProcessor.cs | 2 +- .../EurekaCloudFoundryPostProcessor.cs | 2 +- .../IdentityCloudFoundryPostProcessor.cs | 2 +- .../MongoDbCloudFoundryPostProcessor.cs | 2 +- .../MySqlCloudFoundryPostProcessor.cs | 2 +- .../PostgreSqlCloudFoundryPostProcessor.cs | 2 +- .../RabbitMQCloudFoundryPostProcessor.cs | 2 +- .../RedisCloudFoundryPostProcessor.cs | 2 +- .../SqlServerCloudFoundryPostProcessor.cs | 2 +- .../BasePostProcessorsTest.cs | 13 +- .../PostProcessorsTest.cs | 20 +-- ...tBearerAuthenticationBuilderExtensions.cs} | 15 +- .../PublicAPI.Unshipped.txt | 4 +- .../TokenKeyResolver.cs | 2 +- ...ConnectAuthenticationBuilderExtensions.cs} | 18 +-- .../PublicAPI.Unshipped.txt | 4 +- .../TokenKeyResolver.cs | 2 +- ...rtificateAuthorizationBuilderExtensions.cs | 3 + .../CertificateHttpClientBuilderExtensions.cs | 14 +- .../PublicAPI.Unshipped.txt | 2 +- .../TokenKeyResolverTest.cs | 143 +++++++----------- .../TokenKeyResolverTest.cs | 143 +++++++----------- .../ApplicationInstanceCertificateTest.cs | 12 +- .../CertificateAuthorizationTest.cs | 79 +++++----- .../Certificates.cs | 58 +++++++ .../ClientCertificatesFixture.cs | 27 ---- src/Steeltoe.Common.slnf | 2 +- 37 files changed, 316 insertions(+), 320 deletions(-) rename src/Security/src/Authentication.JwtBearer/{JwtBearerServiceCollectionExtensions.cs => JwtBearerAuthenticationBuilderExtensions.cs} (53%) rename src/Security/src/Authentication.OpenIdConnect/{OpenIdConnectServiceCollectionExtensions.cs => OpenIdConnectAuthenticationBuilderExtensions.cs} (52%) create mode 100644 src/Security/test/Authorization.Certificate.Test/Certificates.cs delete mode 100644 src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs diff --git a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs index 4993c1c087..97e2f3b360 100644 --- a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs @@ -61,7 +61,7 @@ internal static IConfigurationBuilder AddCertificate(this IConfigurationBuilder /// public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConfigurationBuilder builder) { - return builder.AddAppInstanceIdentityCertificate(null, null); + return AddAppInstanceIdentityCertificate(builder, null, null); } /// @@ -83,6 +83,8 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf /// public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConfigurationBuilder builder, Guid? organizationId, Guid? spaceId) { + ArgumentGuard.NotNull(builder); + if (!Platform.IsCloudFoundry) { organizationId ??= Guid.NewGuid(); diff --git a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs index 87ad96f991..c979a0becb 100644 --- a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs @@ -41,6 +41,8 @@ public static IServiceCollection ConfigureCertificateOptions(this IServiceCollec /// public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, string certificateName, IFileProvider? fileProvider) { + ArgumentGuard.NotNull(services); + fileProvider ??= new PhysicalFileProvider(Environment.CurrentDirectory); string configurationKey = string.IsNullOrEmpty(certificateName) diff --git a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs index 50a473a4b3..ef834972a2 100644 --- a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs @@ -9,18 +9,18 @@ namespace Steeltoe.Common.Certificates; internal sealed class LocalCertificateWriter { + internal const string CertificateDirectoryName = "GeneratedCertificates"; + internal static readonly string AppBasePath = AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]; private static readonly string ParentPath = Directory.GetParent(AppBasePath)!.ToString(); - internal const string CertificateDirectoryName = "GeneratedCertificates"; - - internal string CertificateFilenamePrefix { get; set; } = "SteeltoeAppInstance"; + internal static readonly string RootCaPfxPath = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeCA.pfx"); - internal string RootCaPfxPath { get; } = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeCA.pfx"); + internal static readonly string IntermediatePfxPath = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeIntermediate.pfx"); - internal string IntermediatePfxPath { get; } = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeIntermediate.pfx"); + internal string CertificateFilenamePrefix { get; set; } = "SteeltoeAppInstance"; public void Write(Guid orgId, Guid spaceId) { diff --git a/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs index 83c7292a75..88e5eb15f1 100644 --- a/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/ServiceCollectionExtensions.cs @@ -39,6 +39,10 @@ internal static class ServiceCollectionExtensions public static IServiceCollection WatchFilePathInOptions(this IServiceCollection services, string key, string? optionName, string pathPropertyName, IFileProvider fileProvider) { + ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(key); + ArgumentGuard.NotNull(pathPropertyName); + services.AddSingleton>(serviceProvider => { var configuration = serviceProvider.GetRequiredService(); diff --git a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs index bd3bff3b2a..243a8836ae 100644 --- a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs @@ -12,7 +12,9 @@ public sealed class BoolStringJsonConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True ? reader.GetBoolean() : bool.Parse(reader.GetString()); + return reader.TokenType is JsonTokenType.False or JsonTokenType.True + ? reader.GetBoolean() + : bool.Parse(reader.GetString() ?? throw new ArgumentNullException(nameof(reader))); } public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) diff --git a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs index fdb6cce85d..52242187f4 100644 --- a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs @@ -12,7 +12,9 @@ public class LongStringJsonConverter : JsonConverter { public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.TokenType == JsonTokenType.Number ? reader.GetInt64() : long.Parse(reader.GetString(), CultureInfo.InvariantCulture); + return reader.TokenType == JsonTokenType.Number + ? reader.GetInt64() + : long.Parse(reader.GetString() ?? throw new ArgumentNullException(nameof(reader)), CultureInfo.InvariantCulture); } public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) diff --git a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs index 16e8c098e1..0703976a9b 100644 --- a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs +++ b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs @@ -137,8 +137,6 @@ public WindowsNetworkFileShare(string networkName, NetworkCredential credentials /// public int GetLastError(out int error, out StringBuilder errorBuf, int errorBufSize, out StringBuilder nameBuf, int nameBufSize) { - ArgumentGuard.NotNull(_multipleProviderRouter); - return _multipleProviderRouter.GetLastError(out error, out errorBuf, errorBufSize, out nameBuf, nameBufSize); } @@ -176,8 +174,6 @@ internal static string GetErrorForNumber(int errNum) /// protected virtual void Dispose(bool disposing) { - ArgumentGuard.NotNull(_multipleProviderRouter); - // With the current design, it's not possible to disconnect the network share from the finalizer, // because the _mpr instance may have already been garbage-collected. if (disposing) diff --git a/src/Common/src/Common/Reflection/ReflectionHelpers.cs b/src/Common/src/Common/Reflection/ReflectionHelpers.cs index bddb98c250..f99c29ed5f 100644 --- a/src/Common/src/Common/Reflection/ReflectionHelpers.cs +++ b/src/Common/src/Common/Reflection/ReflectionHelpers.cs @@ -250,6 +250,7 @@ public static Type FindType(string[] assemblyNames, string[] typeNames) public static Type FindType(Assembly assembly, string typeName) { ArgumentGuard.NotNull(assembly); + ArgumentGuard.NotNull(typeName); try { @@ -314,6 +315,7 @@ public static Type FindTypeOrThrow(string[] assemblyNames, string[] typeNames, s public static PropertyInfo FindProperty(Type type, string propertyName) { ArgumentGuard.NotNull(type); + ArgumentGuard.NotNull(propertyName); try { @@ -345,6 +347,7 @@ public static PropertyInfo FindProperty(Type type, string propertyName) public static MethodInfo FindMethod(Type type, string methodName, Type[] parameters = null) { ArgumentGuard.NotNull(type); + ArgumentGuard.NotNull(methodName); try { @@ -381,6 +384,7 @@ public static MethodInfo FindMethod(Type type, string methodName, Type[] paramet public static object Invoke(MethodBase member, object instance, object[] args) { ArgumentGuard.NotNull(member); + ArgumentGuard.NotNull(instance); try { @@ -440,6 +444,7 @@ public static object CreateInstance(Type t, object[] args = null) public static void TrySetProperty(object obj, string property, object value) { ArgumentGuard.NotNull(obj); + ArgumentGuard.NotNull(property); PropertyInfo prop = obj.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance); diff --git a/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs index b7312acb71..85758d6d73 100644 --- a/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Certificates.Test/LocalCertificateWriterTest.cs @@ -20,8 +20,8 @@ public void CertificatesIncludeParams() using var rsa = RSA.Create(); certificateWriter.Write(orgId, spaceId); - var rootCertificate = new X509Certificate2(certificateWriter.RootCaPfxPath); - var intermediateCertificate = new X509Certificate2(certificateWriter.IntermediatePfxPath); + var rootCertificate = new X509Certificate2(LocalCertificateWriter.RootCaPfxPath); + var intermediateCertificate = new X509Certificate2(LocalCertificateWriter.IntermediatePfxPath); rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, "SteeltoeAppInstanceKey.pem"))); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs index dbffa88db9..adab47a2b5 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs @@ -17,13 +17,14 @@ internal abstract class CloudFoundryPostProcessor : IConfigurationPostProcessor public abstract void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData); - protected IEnumerable FilterKeys(IDictionary configurationData, string tagOrLabelValueToFind) + protected IEnumerable FilterKeys(IDictionary configurationData, string valueToFind, KeyFilterSources sources) { List keys = []; foreach ((string key, string? value) in configurationData) { - if (TagsConfigurationKeyRegex.IsMatch(key) && string.Equals(value, tagOrLabelValueToFind, StringComparison.OrdinalIgnoreCase)) + if ((sources & KeyFilterSources.Tag) != 0 && TagsConfigurationKeyRegex.IsMatch(key) && + string.Equals(value, valueToFind, StringComparison.OrdinalIgnoreCase)) { string? parentKey = ConfigurationPath.GetParentPath(key); @@ -37,7 +38,9 @@ protected IEnumerable FilterKeys(IDictionary configurat } } } - else if (LabelConfigurationKeyRegex.IsMatch(key) && string.Equals(value, tagOrLabelValueToFind, StringComparison.OrdinalIgnoreCase)) + + if ((sources & KeyFilterSources.Label) != 0 && LabelConfigurationKeyRegex.IsMatch(key) && + string.Equals(value, valueToFind, StringComparison.OrdinalIgnoreCase)) { string? serviceBindingKey = ConfigurationPath.GetParentPath(key); @@ -50,4 +53,11 @@ protected IEnumerable FilterKeys(IDictionary configurat return keys; } + + [Flags] + internal enum KeyFilterSources + { + Tag = 1, + Label = 2 + } } diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CosmosDbCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CosmosDbCloudFoundryPostProcessor.cs index d687c7190d..f39e38751c 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CosmosDbCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CosmosDbCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class CosmosDbCloudFoundryPostProcessor : CloudFoundryPostProces public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/EurekaCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/EurekaCloudFoundryPostProcessor.cs index 2f379ce03d..2987eae1d2 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/EurekaCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/EurekaCloudFoundryPostProcessor.cs @@ -24,7 +24,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider { bool hasMapped = false; - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { if (hasMapped) { diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs index 815437fc46..10c130e36a 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs @@ -31,7 +31,7 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider { bool hasMapped = false; - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Label)) { if (hasMapped) { diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MongoDbCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MongoDbCloudFoundryPostProcessor.cs index 675bfa7693..3a9961d8e3 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MongoDbCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MongoDbCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class MongoDbCloudFoundryPostProcessor : CloudFoundryPostProcess public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MySqlCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MySqlCloudFoundryPostProcessor.cs index 7bc15dd270..065c45a614 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MySqlCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/MySqlCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class MySqlCloudFoundryPostProcessor : CloudFoundryPostProcessor public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs index e79160e980..c469b37120 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/PostgreSqlCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class PostgreSqlCloudFoundryPostProcessor : CloudFoundryPostProc public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs index 9335ea27b9..ff72b3c50d 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RabbitMQCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class RabbitMQCloudFoundryPostProcessor : CloudFoundryPostProces public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RedisCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RedisCloudFoundryPostProcessor.cs index 1e1bc9f815..f13b8fb4ea 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RedisCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/RedisCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class RedisCloudFoundryPostProcessor : CloudFoundryPostProcessor public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/SqlServerCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/SqlServerCloudFoundryPostProcessor.cs index 0ed28cf224..882ba6c4e9 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/SqlServerCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/SqlServerCloudFoundryPostProcessor.cs @@ -10,7 +10,7 @@ internal sealed class SqlServerCloudFoundryPostProcessor : CloudFoundryPostProce public override void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData) { - foreach (string key in FilterKeys(configurationData, BindingType)) + foreach (string key in FilterKeys(configurationData, BindingType, KeyFilterSources.Tag)) { var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType); diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/BasePostProcessorsTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/BasePostProcessorsTest.cs index e3aad0a506..0ab7ff9dd9 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/BasePostProcessorsTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/BasePostProcessorsTest.cs @@ -11,15 +11,24 @@ public abstract class BasePostProcessorsTest protected const string TestBindingName = "test-name"; protected const string TestProviderName = "test-provider"; - protected Dictionary GetConfigurationData(string bindingType, string bindingProvider, string bindingName, + protected Dictionary GetConfigurationData(string bindingProvider, string bindingName, string[] tags, string? label, params Tuple[] secrets) { var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) { - [$"vcap:services:{bindingProvider}:0:tags:0"] = bindingType, [$"vcap:services:{bindingProvider}:0:name"] = bindingName }; + for (int index = 0; index < tags.Length; index++) + { + dictionary[$"vcap:services:{bindingProvider}:0:tags:{index}"] = tags[index]; + } + + if (!string.IsNullOrEmpty(label)) + { + dictionary[$"vcap:services:{bindingProvider}:0:label"] = label; + } + foreach (Tuple tuple in secrets) { string secretKey = MakeSecretKey(bindingProvider, tuple.Item1); diff --git a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs index 2c227d0eec..d4d3abfcd0 100644 --- a/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs +++ b/src/Configuration/test/CloudFoundry.ServiceBinding.Test/PostProcessorsTest.cs @@ -26,7 +26,7 @@ public void Processes_MySql_configuration() }; Dictionary configurationData = - GetConfigurationData(MySqlCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [MySqlCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -58,7 +58,7 @@ public void Processes_PostgreSql_configuration() }; Dictionary configurationData = - GetConfigurationData(PostgreSqlCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [PostgreSqlCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -91,7 +91,7 @@ public void Processes_RabbitMQ_configuration() }; Dictionary configurationData = - GetConfigurationData(RabbitMQCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [RabbitMQCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -119,7 +119,7 @@ public void Processes_Redis_configuration() }; Dictionary configurationData = - GetConfigurationData(RedisCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [RedisCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -144,7 +144,7 @@ public void Processes_Redis_configuration_AzureBroker() }; Dictionary configurationData = - GetConfigurationData(RedisCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [RedisCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -172,7 +172,7 @@ public void Processes_SqlServer_configuration() }; Dictionary configurationData = - GetConfigurationData(SqlServerCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [SqlServerCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -196,7 +196,7 @@ public void Processes_MongoDb_configuration() }; Dictionary configurationData = - GetConfigurationData(MongoDbCloudFoundryPostProcessor.BindingType, "csb-azure-mongodb", TestBindingName, secrets); + GetConfigurationData("csb-azure-mongodb", TestBindingName, [MongoDbCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -220,7 +220,7 @@ public void Processes_CosmosDb_configuration() }; Dictionary configurationData = - GetConfigurationData(CosmosDbCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [CosmosDbCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -246,7 +246,7 @@ public void Processes_Eureka_configuration() }; Dictionary configurationData = - GetConfigurationData(EurekaCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [EurekaCloudFoundryPostProcessor.BindingType], null, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); @@ -273,7 +273,7 @@ public void Processes_Identity_configuration() }; Dictionary configurationData = - GetConfigurationData(IdentityCloudFoundryPostProcessor.BindingType, TestProviderName, TestBindingName, secrets); + GetConfigurationData(TestProviderName, TestBindingName, [], IdentityCloudFoundryPostProcessor.BindingType, secrets); PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor); diff --git a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.JwtBearer/JwtBearerAuthenticationBuilderExtensions.cs similarity index 53% rename from src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs rename to src/Security/src/Authentication.JwtBearer/JwtBearerAuthenticationBuilderExtensions.cs index 3711273f85..88beeccf4b 100644 --- a/src/Security/src/Authentication.JwtBearer/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.JwtBearer/JwtBearerAuthenticationBuilderExtensions.cs @@ -2,6 +2,7 @@ // 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.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -9,19 +10,19 @@ namespace Steeltoe.Security.Authentication.JwtBearer; -public static class JwtBearerServiceCollectionExtensions +public static class JwtBearerAuthenticationBuilderExtensions { /// /// Configures for compatibility with UAA-based systems, including those found in Cloud Foundry. /// - /// - /// The to add services to. + /// + /// The to configure. /// - public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services) + public static AuthenticationBuilder ConfigureJwtBearerForCloudFoundry(this AuthenticationBuilder authenticationBuilder) { - ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(authenticationBuilder); - services.AddSingleton, PostConfigureJwtBearerOptions>(); - return services; + authenticationBuilder.Services.AddSingleton, PostConfigureJwtBearerOptions>(); + return authenticationBuilder; } } diff --git a/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt index 6f9cffa424..473b0a71c1 100644 --- a/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authentication.JwtBearer/PublicAPI.Unshipped.txt @@ -1,3 +1,3 @@ #nullable enable -static Steeltoe.Security.Authentication.JwtBearer.JwtBearerServiceCollectionExtensions.ConfigureJwtBearerForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -Steeltoe.Security.Authentication.JwtBearer.JwtBearerServiceCollectionExtensions +static Steeltoe.Security.Authentication.JwtBearer.JwtBearerAuthenticationBuilderExtensions.ConfigureJwtBearerForCloudFoundry(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! authenticationBuilder) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder! +Steeltoe.Security.Authentication.JwtBearer.JwtBearerAuthenticationBuilderExtensions diff --git a/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs index 7b2bf978ad..2207992308 100644 --- a/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.JwtBearer/TokenKeyResolver.cs @@ -31,7 +31,7 @@ public TokenKeyResolver(string authority, HttpClient httpClient) _httpClient = httpClient; } - internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) + internal SecurityKey[] ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) { if (ResolvedSecurityKeysById.TryGetValue(keyId, out SecurityKey? resolved)) { diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectAuthenticationBuilderExtensions.cs similarity index 52% rename from src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs rename to src/Security/src/Authentication.OpenIdConnect/OpenIdConnectAuthenticationBuilderExtensions.cs index 13ba183705..9772d7159f 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectAuthenticationBuilderExtensions.cs @@ -2,6 +2,7 @@ // 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.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -9,20 +10,19 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect; -public static class OpenIdConnectServiceCollectionExtensions +public static class OpenIdConnectAuthenticationBuilderExtensions { /// - /// Configures for compatibility with UAA-based systems, including those found in Cloud Foundry - /// Service. + /// Configures for compatibility with UAA-based systems, including those found in Cloud Foundry. /// - /// - /// The to add services to. + /// + /// The to configure. /// - public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services) + public static AuthenticationBuilder ConfigureOpenIdConnectForCloudFoundry(this AuthenticationBuilder authenticationBuilder) { - ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(authenticationBuilder); - services.AddSingleton, PostConfigureOpenIdConnectOptions>(); - return services; + authenticationBuilder.Services.AddSingleton, PostConfigureOpenIdConnectOptions>(); + return authenticationBuilder; } } diff --git a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt index bad187262b..340f736293 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authentication.OpenIdConnect/PublicAPI.Unshipped.txt @@ -1,3 +1,3 @@ #nullable enable -static Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectServiceCollectionExtensions.ConfigureOpenIdConnectForCloudFoundry(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectServiceCollectionExtensions +static Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectAuthenticationBuilderExtensions.ConfigureOpenIdConnectForCloudFoundry(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder! authenticationBuilder) -> Microsoft.AspNetCore.Authentication.AuthenticationBuilder! +Steeltoe.Security.Authentication.OpenIdConnect.OpenIdConnectAuthenticationBuilderExtensions diff --git a/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs index bd7ccc6d32..18c24373b9 100644 --- a/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.OpenIdConnect/TokenKeyResolver.cs @@ -31,7 +31,7 @@ public TokenKeyResolver(string authority, HttpClient httpClient) _httpClient = httpClient; } - internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) + internal SecurityKey[] ResolveSigningKey(string token, SecurityToken securityToken, string keyId, TokenValidationParameters validationParameters) { if (ResolvedSecurityKeysById.TryGetValue(keyId, out SecurityKey? resolved)) { diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 31774499ab..5a144bdd59 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -23,6 +23,9 @@ public static class CertificateAuthorizationBuilderExtensions /// Secure your endpoints with the included authorization policies by referencing . /// /// + /// + /// The . + /// public static AuthorizationBuilder AddAppInstanceIdentityCertificate(this AuthorizationBuilder authorizationBuilder) { ArgumentGuard.NotNull(authorizationBuilder); diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index bfcd05d37d..4893bf1d84 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -31,22 +31,22 @@ public static IHttpClientBuilder AddClientCertificateForAppInstance(this IHttpCl /// /// The to add a client certificate to. /// - /// - /// The name of the from which to add the client certificate. + /// + /// The name of the certificate used in configuration. /// - public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder httpClientBuilder, string certificateOptionsName) + public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder httpClientBuilder, string certificateName) { ArgumentGuard.NotNull(httpClientBuilder); - ArgumentGuard.NotNull(certificateOptionsName); + ArgumentGuard.NotNull(certificateName); - httpClientBuilder.Services.ConfigureCertificateOptions(certificateOptionsName); + httpClientBuilder.Services.ConfigureCertificateOptions(certificateName); httpClientBuilder.ConfigureHttpClient((serviceProvider, client) => { var loggerFactory = serviceProvider.GetRequiredService(); ILogger logger = loggerFactory.CreateLogger(nameof(CertificateAuthorizationBuilderExtensions)); var optionsMonitor = serviceProvider.GetRequiredService>(); - CertificateOptions certificateOptions = optionsMonitor.Get(certificateOptionsName); + CertificateOptions certificateOptions = optionsMonitor.Get(certificateName); X509Certificate2? certificate = certificateOptions.Certificate; if (certificate != null) @@ -58,7 +58,7 @@ public static IHttpClientBuilder AddClientCertificate(this IHttpClientBuilder ht } else { - logger.LogError("Failed to find a certificate under the name {CertificateOptionsName}", certificateOptionsName); + logger.LogError("Failed to find a certificate under the name {CertificateOptionsName}", certificateName); } }); diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index ca326f0edf..7d9b391a20 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -4,7 +4,7 @@ const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolici static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddAppInstanceIdentityCertificate(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! authorizationBuilder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder, string! certificateOptionsName) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificateForAppInstance(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs index db23f2d584..6882861fd1 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -3,95 +3,76 @@ // See the LICENSE file in the project root for more information. using System.Net; +using FluentAssertions; using Microsoft.IdentityModel.Tokens; +using Moq; using Xunit; namespace Steeltoe.Security.Authentication.JwtBearer.Test; public sealed class TokenKeyResolverTest { - [Fact] - public void ResolveSigningKey_FindsExistingKey() - { - // ReSharper disable StringLiteralTypo - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + private static readonly SecurityToken MockToken = new Mock().Object; + private static readonly TokenValidationParameters MockParameters = new Mock().Object; // ReSharper disable StringLiteralTypo - const string keySet = """ + private const string Token = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + + private const string KeySet = """ + { + "keys": [ { - "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" - } - ] + "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 keys = JsonWebKeySet.Create(keySet); + [Fact] + public void ResolveSigningKey_FindsExistingKey() + { + var keys = JsonWebKeySet.Create(KeySet); JsonWebKey webKey = keys.Keys[0]; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; - IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); - Assert.True(result.First() == webKey); + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + result.Should().NotBeEmpty(); + result[0].Should().BeEquivalentTo(webKey); } [Fact] public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() { - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + using var handler = new TestMessageHandler(); - const string keySet = """ - { - "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 handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Content = new StringContent(keySet) + Content = new StringContent(KeySet) }; - handler.Response = response; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - 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(TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"]); - Assert.NotNull(result); + + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + handler.LastRequest.Should().NotBeNull(); + TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().Contain("legacy-token-key"); + result.Should().NotBeEmpty(); } [Fact] public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() { - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; - - const string keySet = """ + const string alternateKeySet = """ { "keys": [ { @@ -107,56 +88,40 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() } """; - var handler = new TestMessageHandler(); + using var handler = new TestMessageHandler(); - var response = new HttpResponseMessage(HttpStatusCode.OK) + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Content = new StringContent(keySet) + Content = new StringContent(alternateKeySet) }; - handler.Response = response; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - 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(TokenKeyResolver.ResolvedSecurityKeysById.ContainsKey("legacy-token-key")); - Assert.Empty(result); + + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + handler.LastRequest.Should().NotBeNull(); + TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().NotContain("legacy-token-key"); + result.Should().BeEmpty(); } [Fact] public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() { - const string keySet = """ - { - "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" - } - ] - } - """; + using var handler = new TestMessageHandler(); - var handler = new TestMessageHandler + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(keySet) - } + Content = new StringContent(KeySet) }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); - Assert.NotNull(result); + + result.Should().NotBeNull(); + result!.Keys.Should().NotBeEmpty(); } private sealed class TestMessageHandler : HttpMessageHandler diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index 11dab9d91a..4dd8069e95 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -3,95 +3,76 @@ // See the LICENSE file in the project root for more information. using System.Net; +using FluentAssertions; using Microsoft.IdentityModel.Tokens; +using Moq; using Xunit; namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; public sealed class TokenKeyResolverTest { - [Fact] - public void ResolveSigningKey_FindsExistingKey() - { - // ReSharper disable StringLiteralTypo - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + private static readonly SecurityToken MockToken = new Mock().Object; + private static readonly TokenValidationParameters MockParameters = new Mock().Object; // ReSharper disable StringLiteralTypo - const string keySet = """ + private const string Token = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + + private const string KeySet = """ + { + "keys": [ { - "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" - } - ] + "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 keys = JsonWebKeySet.Create(keySet); + [Fact] + public void ResolveSigningKey_FindsExistingKey() + { + var keys = JsonWebKeySet.Create(KeySet); JsonWebKey webKey = keys.Keys[0]; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; - IEnumerable result = resolver.ResolveSigningKey(token, null!, "legacy-token-key", null!); - Assert.True(result.First() == webKey); + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + result.Should().NotBeEmpty(); + result[0].Should().BeEquivalentTo(webKey); } [Fact] public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() { - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + using var handler = new TestMessageHandler(); - const string keySet = """ - { - "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 handler = new TestMessageHandler(); - - var response = new HttpResponseMessage(HttpStatusCode.OK) + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Content = new StringContent(keySet) + Content = new StringContent(KeySet) }; - handler.Response = response; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - 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(TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"]); - Assert.NotNull(result); + + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + handler.LastRequest.Should().NotBeNull(); + TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().Contain("legacy-token-key"); + result.Should().NotBeEmpty(); } [Fact] public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() { - const string token = - "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; - - const string keySet = """ + const string alternateKeySet = """ { "keys": [ { @@ -107,56 +88,40 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() } """; - var handler = new TestMessageHandler(); + using var handler = new TestMessageHandler(); - var response = new HttpResponseMessage(HttpStatusCode.OK) + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Content = new StringContent(keySet) + Content = new StringContent(alternateKeySet) }; - handler.Response = response; - TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - 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(TokenKeyResolver.ResolvedSecurityKeysById.ContainsKey("legacy-token-key")); - Assert.Empty(result); + + SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); + + handler.LastRequest.Should().NotBeNull(); + TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().NotContain("legacy-token-key"); + result.Should().BeEmpty(); } [Fact] public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() { - const string keySet = """ - { - "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" - } - ] - } - """; + using var handler = new TestMessageHandler(); - var handler = new TestMessageHandler + handler.Response = new HttpResponseMessage(HttpStatusCode.OK) { - Response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(keySet) - } + Content = new StringContent(KeySet) }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); - Assert.NotNull(result); + + result.Should().NotBeNull(); + result!.Keys.Should().NotBeEmpty(); } private sealed class TestMessageHandler : HttpMessageHandler diff --git a/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs b/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs index 01129d5c3f..c33a66c394 100644 --- a/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/ApplicationInstanceCertificateTest.cs @@ -9,12 +9,16 @@ namespace Steeltoe.Security.Authorization.Certificate.Test; public sealed class ApplicationInstanceCertificateTest { - [InlineData("This subject does not match a supported pattern", false)] - [InlineData("CN=34c9765d-b5da-49a7-4a53-1c58, OU=app:e5950275-afce-480a-a017-babafd3d5798 + OU=space:ab60aac2-fb64-43ab-ba24-c57a15a7e114 + OU=organization:7fe4d027-2058-4539-a40c-702ac1373905", true, "7fe4d027-2058-4539-a40c-702ac1373905", "ab60aac2-fb64-43ab-ba24-c57a15a7e114")] - [InlineData("CN=69ed1418-8434-4b14-9ad9-636d71ba782a, OU=app:e268b8bf-cdad-4014-a18f-1131c73d9450 + OU=space:122b942a-d7b9-4839-b26e-836654b9785f + OU=organization:a8fef16f-94c0-49e3-aa0b-ced7c3da6229", true, "a8fef16f-94c0-49e3-aa0b-ced7c3da6229", "122b942a-d7b9-4839-b26e-836654b9785f")] + [InlineData("This subject does not match a supported pattern", false, null, null)] + [InlineData( + "CN=34c9765d-b5da-49a7-4a53-1c58, OU=app:e5950275-afce-480a-a017-babafd3d5798 + OU=space:ab60aac2-fb64-43ab-ba24-c57a15a7e114 + OU=organization:7fe4d027-2058-4539-a40c-702ac1373905", + true, "7fe4d027-2058-4539-a40c-702ac1373905", "ab60aac2-fb64-43ab-ba24-c57a15a7e114")] + [InlineData( + "CN=69ed1418-8434-4b14-9ad9-636d71ba782a, OU=app:e268b8bf-cdad-4014-a18f-1131c73d9450 + OU=space:122b942a-d7b9-4839-b26e-836654b9785f + OU=organization:a8fef16f-94c0-49e3-aa0b-ced7c3da6229", + true, "a8fef16f-94c0-49e3-aa0b-ced7c3da6229", "122b942a-d7b9-4839-b26e-836654b9785f")] [InlineData("CN=\"69ed1418\", OU=app:e268b8bf + OU=space:\"122b942a\" + OU=organization:a8fef16f", true, "a8fef16f", "122b942a")] [Theory] - public void SubjectParsingProducesExpectedResults(string subject, bool shouldParse, string? expectedOrgId = null, string? expectedSpaceId = null) + public void SubjectParsingProducesExpectedResults(string subject, bool shouldParse, string? expectedOrgId, string? expectedSpaceId) { ApplicationInstanceCertificate.TryParse(subject, out ApplicationInstanceCertificate? instanceCertificate).Should().Be(shouldParse); diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 62dc802e6a..a2274a9448 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -6,67 +6,78 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Steeltoe.Common.Certificates; +using Steeltoe.Common.Configuration; using Steeltoe.Common.TestResources; using Xunit; namespace Steeltoe.Security.Authorization.Certificate.Test; -public sealed class CertificateAuthorizationTest(ClientCertificatesFixture fixture) : IClassFixture +public sealed class CertificateAuthorizationTest { [Fact] - public async Task CertificateAuth_AcceptsSameSpace() + public async Task CertificateAuth_ForbiddenWithoutCert() { + var requestUri = new Uri($"http://localhost/{CertificateAuthorizationPolicies.SameSpace}"); using IHost host = await GetHostBuilder().StartAsync(); + using HttpClient httpClient = host.GetTestClient(); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch).GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } [Fact] public async Task CertificateAuth_AcceptsSameOrg() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); using IHost host = await GetHostBuilder().StartAsync(); + var optionsMonitor = host.Services.GetRequiredService>(); + X509Certificate2 certificate = optionsMonitor.Get("AppInstanceIdentity").Certificate!; + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), certificate); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); - HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch); - HttpResponseMessage response = await httpClient.GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] - public async Task CertificateAuth_RejectsOrgMismatch() + public async Task CertificateAuth_AcceptsSameSpace() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); using IHost host = await GetHostBuilder().StartAsync(); + var optionsMonitor = host.Services.GetRequiredService>(); + X509Certificate2 certificate = optionsMonitor.Get("AppInstanceIdentity").Certificate!; + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), certificate); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.SpaceMatch).GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); } [Fact] - public async Task CertificateAuth_RejectsSpaceMismatch() + public async Task CertificateAuth_RejectsOrgMismatch() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); using IHost host = await GetHostBuilder().StartAsync(); + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.SpaceMatch); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.OrgMatch).GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } [Fact] - public async Task CertificateAuth_ForbiddenWithoutCert() + public async Task CertificateAuth_RejectsSpaceMismatch() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); using IHost host = await GetHostBuilder().StartAsync(); + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgMatch); - var requestUri = new Uri($"http://localhost/{CertificateAuthorizationPolicies.SameSpace}"); - HttpResponseMessage response = await host.GetTestClient().GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } @@ -74,14 +85,15 @@ public async Task CertificateAuth_ForbiddenWithoutCert() [Fact] public async Task CertificateAuth_AcceptsSameSpace_DiegoCert() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); 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.AppBasePath, "root_certificates")); using IHost host = await GetHostBuilder().StartAsync(); + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); - HttpResponseMessage response = await ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego).GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } @@ -89,24 +101,23 @@ public async Task CertificateAuth_AcceptsSameSpace_DiegoCert() [Fact] public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() { + var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); 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.AppBasePath, "root_certificates")); - using IHost host = await GetHostBuilder().StartAsync(); + using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); - var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); - HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); - HttpResponseMessage response = await httpClient.GetAsync(requestUri); + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - private IHostBuilder GetHostBuilder() + private HostBuilder GetHostBuilder() { var hostBuilder = new HostBuilder(); - hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)); + hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate(Certificates.ServerOrgId, Certificates.ServerSpaceId)); hostBuilder.ConfigureWebHostDefaults(builder => builder.UseStartup()); hostBuilder.ConfigureWebHost(builder => builder.UseTestServer()); return hostBuilder; @@ -119,20 +130,4 @@ private static HttpClient ClientWithCertificate(HttpClient httpClient, X509Certi httpClient.DefaultRequestHeaders.Add("X-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")); - - public static X509Certificate2 FromDiego { get; } = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); - } } diff --git a/src/Security/test/Authorization.Certificate.Test/Certificates.cs b/src/Security/test/Authorization.Certificate.Test/Certificates.cs new file mode 100644 index 0000000000..b7350421e7 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/Certificates.cs @@ -0,0 +1,58 @@ +// 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; +using System.Security.Cryptography.X509Certificates; + +namespace Steeltoe.Security.Authorization.Certificate.Test; + +internal 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 SDigitalSignatureOnlyUsage = new(X509KeyUsageFlags.DigitalSignature, true); + internal static Guid ServerOrgId { get; } = new("a8fef16f-94c0-49e3-aa0b-ced7c3da6229"); + internal static Guid ServerSpaceId { get; } = new("122b942a-d7b9-4839-b26e-836654b9785f"); + + public static X509Certificate2 OrgMatch { get; private set; } + + public static X509Certificate2 SpaceMatch { get; private set; } + + public static X509Certificate2 FromDiego { get; } = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); + + static Certificates() + { + DateTimeOffset now = TimeProvider.System.GetUtcNow(); + + OrgMatch = MakeCert(SubjectName(ServerOrgId.ToString(), Guid.NewGuid().ToString()), now); + SpaceMatch = MakeCert(SubjectName(Guid.NewGuid().ToString(), ServerSpaceId.ToString()), now); + } + + private static string SubjectName(string orgId, string spaceId) + { + return $"CN={Guid.NewGuid()}, OU=app:{Guid.NewGuid()} + OU=space:{spaceId} + OU=organization:{orgId}"; + } + + private static X509Certificate2 MakeCert(string subjectName, DateTimeOffset now) + { + return MakeCert(subjectName, now, now.AddYears(5)); + } + + private static X509Certificate2 MakeCert(string subjectName, DateTimeOffset notBefore, DateTimeOffset notAfter) + { + using var key = RSA.Create(2048); + + var request = new CertificateRequest(subjectName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + request.CertificateExtensions.Add(SDigitalSignatureOnlyUsage); + + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([ + new Oid(ClientEku), + new Oid(ServerEku) + ], false)); + + return request.CreateSelfSigned(notBefore, notAfter); + } +} diff --git a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs b/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.cs deleted file mode 100644 index 0fe23c88f4..0000000000 --- a/src/Security/test/Authorization.Certificate.Test/ClientCertificatesFixture.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.Common.Certificates; - -namespace Steeltoe.Security.Authorization.Certificate.Test; - -public sealed class ClientCertificatesFixture -{ - private LocalCertificateWriter CertificateWriter { get; } = new(); - - internal Guid ServerOrgId { get; } = new("a8fef16f-94c0-49e3-aa0b-ced7c3da6229"); - internal Guid ServerSpaceId { get; } = new("122b942a-d7b9-4839-b26e-836654b9785f"); - - public ClientCertificatesFixture() - { - CertificateWriter.CertificateFilenamePrefix = "OrgAndSpaceMatch"; - CertificateWriter.Write(ServerOrgId, ServerSpaceId); - - CertificateWriter.CertificateFilenamePrefix = "SpaceMatch"; - CertificateWriter.Write(Guid.NewGuid(), ServerSpaceId); - - CertificateWriter.CertificateFilenamePrefix = "OrgMatch"; - CertificateWriter.Write(ServerOrgId, Guid.NewGuid()); - } -} diff --git a/src/Steeltoe.Common.slnf b/src/Steeltoe.Common.slnf index 26d9e1da88..6f8dab8545 100644 --- a/src/Steeltoe.Common.slnf +++ b/src/Steeltoe.Common.slnf @@ -12,7 +12,7 @@ "Common\\test\\Common.Hosting.Test\\Steeltoe.Common.Hosting.Test.csproj", "Common\\test\\Common.Http.Test\\Steeltoe.Common.Http.Test.csproj", "Common\\test\\Common.Net.Test\\Steeltoe.Common.Net.Test.csproj", - "Common\\test\\Common.Certificate.Test\\Steeltoe.Common.Certificate.Test.csproj", + "Common\\test\\Common.Certificates.Test\\Steeltoe.Common.Certificates.Test.csproj", "Common\\test\\Common.TestResources\\Steeltoe.Common.TestResources.csproj", "Common\\test\\Common.Test\\Steeltoe.Common.Test.csproj", "Common\\test\\Common.Utils.Test\\Steeltoe.Common.Utils.Test.csproj" From 23f72b77f7e55b94ca2c864f1b2abcc2f873e46b Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 08:40:36 -0500 Subject: [PATCH 17/28] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- .../ApplicationInstanceCertificate.cs | 11 ++++------- .../TokenKeyResolverTest.cs | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs index 6f82397614..56b4d819ea 100644 --- a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -19,13 +19,10 @@ internal sealed class ApplicationInstanceCertificate new(@"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$", RegexOptions.Compiled | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); - 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 string OrganizationId { get; } + public string SpaceId { get; } + public string ApplicationId { get; } + public string InstanceId { get; } private ApplicationInstanceCertificate(string organizationId, string spaceId, string applicationId, string instanceId) { diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs index 6882861fd1..4f8343fc7e 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -46,7 +46,7 @@ public void ResolveSigningKey_FindsExistingKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); result.Should().NotBeEmpty(); - result[0].Should().BeEquivalentTo(webKey); + result[0].Should().Be(webKey); } [Fact] @@ -65,7 +65,7 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); handler.LastRequest.Should().NotBeNull(); - TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().Contain("legacy-token-key"); + TokenKeyResolver.ResolvedSecurityKeysById.Should().ContainKey("legacy-token-key"); result.Should().NotBeEmpty(); } @@ -101,7 +101,7 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); handler.LastRequest.Should().NotBeNull(); - TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().NotContain("legacy-token-key"); + TokenKeyResolver.ResolvedSecurityKeysById.Should().NotContainKey("legacy-token-key"); result.Should().BeEmpty(); } From 862510f6008de116bb99a0478672639700685401 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 08:57:55 -0500 Subject: [PATCH 18/28] build fixes, revise s3900 again, disposable httpclient --- shared-package.props | 2 +- shared-project.props | 2 +- .../CertificateConfigurationExtensions.cs | 4 ++-- .../Common.Certificates/LocalCertificateWriter.cs | 4 ++-- .../src/Common.Net/WindowsNetworkFileShare.cs | 2 -- .../CancellationTokenSourceExtensions.cs | 2 ++ .../FluentAssertionsExtensions.cs | 2 ++ .../IgnoreLineEndingsComparer.cs | 2 ++ ...tBearerAuthenticationBuilderExtensionsTest.cs} | 7 ++++--- .../PostConfigureJwtBearerOptionsTest.cs | 3 +-- .../TokenKeyResolverTest.cs | 15 ++++++++++----- ...ConnectAuthenticationBuilderExtensionsTest.cs} | 7 ++++--- .../PostConfigureOpenIdConnectOptionsTest.cs | 4 ++-- .../TokenKeyResolverTest.cs | 15 ++++++++++----- 14 files changed, 43 insertions(+), 28 deletions(-) rename src/Security/test/Authentication.JwtBearer.Test/{JwtBearerServiceCollectionExtensionsTest.cs => JwtBearerAuthenticationBuilderExtensionsTest.cs} (60%) rename src/Security/test/Authentication.OpenIdConnect.Test/{OpenIdConnectServiceCollectionExtensionsTest.cs => OpenIdConnectAuthenticationBuilderExtensionsTest.cs} (59%) diff --git a/shared-package.props b/shared-package.props index cbed6bde28..4f258e19c6 100644 --- a/shared-package.props +++ b/shared-package.props @@ -68,6 +68,6 @@ !$(MSBuildProjectName.StartsWith('Steeltoe.Management')) And !$(MSBuildProjectName.StartsWith('Steeltoe.Security'))"> - $(NoWarn);SA1401;S1168;S2360;S3956;S4004;S4023 + $(NoWarn);SA1401;S1168;S2360;S3900;S3956;S4004;S4023 diff --git a/shared-project.props b/shared-project.props index d4ac1d41c0..2dfa2fa8de 100644 --- a/shared-project.props +++ b/shared-project.props @@ -1,6 +1,6 @@ - $(NoWarn);SA0001;S3900 + $(NoWarn);SA0001 false diff --git a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs index 97e2f3b360..2ddd4a8a91 100644 --- a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs @@ -98,11 +98,11 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf Environment.SetEnvironmentVariable("CF_INSTANCE_CERT", Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, - $"{writer.CertificateFilenamePrefix}Cert.pem")); + $"{LocalCertificateWriter.CertificateFilenamePrefix}Cert.pem")); Environment.SetEnvironmentVariable("CF_INSTANCE_KEY", Path.Combine(LocalCertificateWriter.AppBasePath, LocalCertificateWriter.CertificateDirectoryName, - $"{writer.CertificateFilenamePrefix}Key.pem")); + $"{LocalCertificateWriter.CertificateFilenamePrefix}Key.pem")); } string? certificateFile = Environment.GetEnvironmentVariable("CF_INSTANCE_CERT"); diff --git a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs index ef834972a2..5e0a55f565 100644 --- a/src/Common/src/Common.Certificates/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Certificates/LocalCertificateWriter.cs @@ -11,6 +11,8 @@ internal sealed class LocalCertificateWriter { internal const string CertificateDirectoryName = "GeneratedCertificates"; + internal const string CertificateFilenamePrefix = "SteeltoeAppInstance"; + internal static readonly string AppBasePath = AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]; @@ -20,8 +22,6 @@ internal sealed class LocalCertificateWriter internal static readonly string IntermediatePfxPath = Path.Combine(ParentPath, CertificateDirectoryName, "SteeltoeIntermediate.pfx"); - internal string CertificateFilenamePrefix { get; set; } = "SteeltoeAppInstance"; - public void Write(Guid orgId, Guid spaceId) { var appId = Guid.NewGuid(); diff --git a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs index 0703976a9b..edd2528e4a 100644 --- a/src/Common/src/Common.Net/WindowsNetworkFileShare.cs +++ b/src/Common/src/Common.Net/WindowsNetworkFileShare.cs @@ -85,8 +85,6 @@ public class WindowsNetworkFileShare : IDisposable /// public WindowsNetworkFileShare(string networkName, NetworkCredential credentials, IMultipleProviderRouter multipleProviderRouter = null) { - ArgumentGuard.NotNull(credentials); - _multipleProviderRouter = multipleProviderRouter ?? new MultipleProviderRouter(); _networkName = networkName; diff --git a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs index 5508c83817..c2ab50a4a6 100644 --- a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs +++ b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs @@ -11,7 +11,9 @@ public static class CancellationTokenSourceExtensions // Workaround for Sonar S6966 bug: CancelAsync is suggested, which isn't available in .NET 6. public static Task CancelAsync(this CancellationTokenSource source) { +#pragma warning disable S3900 // Arguments of public methods should be validated against null source.Cancel(); +#pragma warning restore S3900 // Arguments of public methods should be validated against null return Task.CompletedTask; } } diff --git a/src/Common/test/Common.TestResources/FluentAssertionsExtensions.cs b/src/Common/test/Common.TestResources/FluentAssertionsExtensions.cs index c2d2f67738..7658132b83 100644 --- a/src/Common/test/Common.TestResources/FluentAssertionsExtensions.cs +++ b/src/Common/test/Common.TestResources/FluentAssertionsExtensions.cs @@ -31,7 +31,9 @@ public static class FluentAssertionsExtensions [CustomAssertion] public static void BeJson(this StringAssertions source, string expected) { +#pragma warning disable S3900 // Arguments of public methods should be validated against null using JsonDocument sourceJson = JsonDocument.Parse(source.Subject); +#pragma warning restore S3900 // Arguments of public methods should be validated against null using JsonDocument expectedJson = JsonDocument.Parse(expected); string sourceText = ToJsonString(sourceJson); diff --git a/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs b/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs index af8df332ad..a4213de86b 100644 --- a/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs +++ b/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs @@ -35,6 +35,8 @@ public bool Equals(string x, string y) public int GetHashCode(string obj) { +#pragma warning disable S3900 // Arguments of public methods should be validated against null return obj.GetHashCode(); +#pragma warning restore S3900 // Arguments of public methods should be validated against null } } diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs similarity index 60% rename from src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs rename to src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs index 27a3d4e6b7..51ff92da96 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs @@ -3,20 +3,21 @@ // See the LICENSE file in the project root for more information. using FluentAssertions; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Steeltoe.Security.Authentication.JwtBearer.Test; -public sealed class JwtBearerServiceCollectionExtensionsTest +public sealed class JwtBearerAuthenticationBuilderExtensionsTest { [Fact] public void ConfigureJwtBearerForCloudFoundry_AddsExpectedRegistrations() { - var serviceCollection = new ServiceCollection(); + AuthenticationBuilder serviceCollection = new ServiceCollection().AddAuthentication().AddJwtBearer(); serviceCollection.ConfigureJwtBearerForCloudFoundry(); - serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); + serviceCollection.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); } } diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs index 5cab8350f1..df876a19a4 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -68,8 +68,7 @@ public void PostConfigure_ConfiguresForCloudFoundry() IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); - serviceCollection.AddAuthentication().AddJwtBearer(); - serviceCollection.ConfigureJwtBearerForCloudFoundry(); + serviceCollection.AddAuthentication().AddJwtBearer().ConfigureJwtBearerForCloudFoundry(); serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs index 4f8343fc7e..db6663ccb1 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -13,8 +13,9 @@ namespace Steeltoe.Security.Authentication.JwtBearer.Test; public sealed class TokenKeyResolverTest { private static readonly SecurityToken MockToken = new Mock().Object; - private static readonly TokenValidationParameters MockParameters = new Mock().Object; // ReSharper disable StringLiteralTypo + private static readonly TokenValidationParameters MockParameters = new Mock().Object; + // ReSharper disable StringLiteralTypo private const string Token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; @@ -40,7 +41,8 @@ public void ResolveSigningKey_FindsExistingKey() var keys = JsonWebKeySet.Create(KeySet); JsonWebKey webKey = keys.Keys[0]; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); + using var httpClient = new HttpClient(); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -60,7 +62,8 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -96,7 +99,8 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -116,7 +120,8 @@ public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs similarity index 59% rename from src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs rename to src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs index 26a874f0e3..8b15de6c4d 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs @@ -3,20 +3,21 @@ // See the LICENSE file in the project root for more information. using FluentAssertions; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; -public sealed class OpenIdConnectServiceCollectionExtensionsTest +public sealed class OpenIdConnectAuthenticationBuilderExtensionsTest { [Fact] public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() { - var serviceCollection = new ServiceCollection(); + AuthenticationBuilder serviceCollection = new ServiceCollection().AddAuthentication().AddOpenIdConnect(); serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); - serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); + serviceCollection.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); } } diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs index 80a75a874d..2a5bf2ca66 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -72,8 +72,8 @@ public void PostConfigure_ConfiguresForCloudFoundry() IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); - serviceCollection.AddAuthentication().AddOpenIdConnect(); - serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + + serviceCollection.AddAuthentication().AddOpenIdConnect().ConfigureOpenIdConnectForCloudFoundry(); using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); var optionsMonitor = serviceProvider.GetRequiredService>(); diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index 4dd8069e95..c9df56dcfb 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -13,8 +13,9 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; public sealed class TokenKeyResolverTest { private static readonly SecurityToken MockToken = new Mock().Object; - private static readonly TokenValidationParameters MockParameters = new Mock().Object; // ReSharper disable StringLiteralTypo + private static readonly TokenValidationParameters MockParameters = new Mock().Object; + // ReSharper disable StringLiteralTypo private const string Token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; @@ -40,7 +41,8 @@ public void ResolveSigningKey_FindsExistingKey() var keys = JsonWebKeySet.Create(KeySet); JsonWebKey webKey = keys.Keys[0]; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient()); + using var httpClient = new HttpClient(); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); TokenKeyResolver.ResolvedSecurityKeysById["legacy-token-key"] = webKey; SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -60,7 +62,8 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -96,7 +99,8 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); @@ -116,7 +120,8 @@ public async Task FetchKeySet_IssuesHttpRequest_ReturnsKeySet() }; TokenKeyResolver.ResolvedSecurityKeysById.Clear(); - var resolver = new TokenKeyResolver("https://foo.bar", new HttpClient(handler)); + using var httpClient = new HttpClient(handler); + var resolver = new TokenKeyResolver("https://foo.bar", httpClient); JsonWebKeySet? result = await resolver.FetchKeySetAsync(default); From 0231cdc5df8b3dcfbc91fe9c2946901587e89f09 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 14:53:40 -0500 Subject: [PATCH 19/28] drop certificates from autoconfiguration, test for non-null feature but null session with trace observer, add a constance for AppInstanceIdentity --- .../src/AutoConfiguration/BootstrapScanner.cs | 10 ---- .../AutoConfiguration/PublicAPI.Unshipped.txt | 1 - ...teeltoe.Bootstrap.AutoConfiguration.csproj | 1 - .../SteeltoeAssemblyNames.cs | 1 - .../HostBuilderExtensionsTest.cs | 15 ------ .../WebApplicationBuilderExtensionsTest.cs | 15 ------ .../WebHostBuilderExtensionsTest.cs | 15 ------ .../CertificateConfigurationExtensions.cs | 4 +- .../Properties/AssemblyInfo.cs | 1 + .../Trace/EndpointServiceCollectionTest.cs | 49 ++++++++++++++----- .../Trace/TraceDiagnosticObserverTest.cs | 1 + ...rtificateAuthorizationBuilderExtensions.cs | 2 +- .../CertificateAuthorizationHandler.cs | 3 +- ...ateAuthorizationPolicyBuilderExtensions.cs | 14 +++++- .../CertificateHttpClientBuilderExtensions.cs | 2 +- ...nfigureCertificateAuthenticationOptions.cs | 3 +- .../PublicAPI.Unshipped.txt | 7 +++ .../SameOrgRequirement.cs | 5 +- .../SameSpaceRequirement.cs | 5 +- .../TokenKeyResolverTest.cs | 6 +-- .../TokenKeyResolverTest.cs | 6 +-- .../CertificateAuthorizationTest.cs | 4 +- 22 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs index 1b7a7e21bc..d3d6192416 100644 --- a/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs +++ b/src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Steeltoe.Common; -using Steeltoe.Common.Certificates; using Steeltoe.Common.DynamicTypeAccess; using Steeltoe.Common.Hosting; using Steeltoe.Common.Logging; @@ -81,7 +80,6 @@ public void ConfigureSteeltoe() WireIfLoaded(WirePrometheus, SteeltoeAssemblyNames.ManagementPrometheus); WireIfLoaded(WireWavefrontMetrics, SteeltoeAssemblyNames.ManagementWavefront); WireIfLoaded(WireDistributedTracing, SteeltoeAssemblyNames.ManagementTracing); - WireIfLoaded(WireAppInstanceIdentity, SteeltoeAssemblyNames.CommonCertificates); } private void WireConfigServer() @@ -256,14 +254,6 @@ private void WireDistributedTracing() _logger.LogInformation("Configured distributed tracing"); } - private void WireAppInstanceIdentity() - { - _wrapper.ConfigureAppConfiguration(configurationBuilder => configurationBuilder.AddAppInstanceIdentityCertificate()); - _wrapper.ConfigureServices(services => services.ConfigureCertificateOptions("AppInstanceIdentity", null)); - - _logger.LogInformation("Configured application instance identity certificate"); - } - private bool WireIfLoaded(Action wireAction, string assemblyName) { if (!_loader.IsAssemblyLoaded(assemblyName)) diff --git a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt index 40e7851e14..6e7332ac62 100644 --- a/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt +++ b/src/Bootstrap/src/AutoConfiguration/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -const Steeltoe.Bootstrap.AutoConfiguration.SteeltoeAssemblyNames.CommonCertificates = "Steeltoe.Common.Certificates" -> 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! diff --git a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj index 61b4597851..6c81bed4cd 100644 --- a/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj +++ b/src/Bootstrap/src/AutoConfiguration/Steeltoe.Bootstrap.AutoConfiguration.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs index 294a8b4f5d..16c585d198 100644 --- a/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs +++ b/src/Bootstrap/src/AutoConfiguration/SteeltoeAssemblyNames.cs @@ -11,7 +11,6 @@ namespace Steeltoe.Bootstrap.AutoConfiguration; ///
public static class SteeltoeAssemblyNames { - public const string CommonCertificates = "Steeltoe.Common.Certificates"; public const string ConfigurationCloudFoundry = "Steeltoe.Configuration.CloudFoundry"; public const string ConfigurationConfigServer = "Steeltoe.Configuration.ConfigServer"; public const string ConfigurationRandomValue = "Steeltoe.Configuration.RandomValue"; diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 7c0d93a6bb..bbb4bb9bbb 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using MongoDB.Driver; using MySqlConnector; using Npgsql; @@ -22,7 +21,6 @@ using RabbitMQ.Client; using StackExchange.Redis; using Steeltoe.Common; -using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.TestResources; using Steeltoe.Configuration; @@ -225,19 +223,6 @@ public void Tracing_IsAutowired() instrumentations.Should().ContainSingle(instance => instance.GetType().Name == "AspNetCoreInstrumentation"); } - [Fact] - public void AppInstanceIdentityCertificate_IsAutowired() - { - using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonCertificates); - var configuration = host.Services.GetRequiredService(); - - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:PrivateKeyFilePath"].Should().NotBeNull(); - - host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.Should().NotBeNull(); - host.Services.GetService>().Should().NotBeNull(); - } - private static IHost GetHostForOnly(string assemblyNameToInclude) { IReadOnlySet exclusions = SteeltoeAssemblyNames.Only(assemblyNameToInclude); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index 653f76a8d3..913a3b2c2c 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using MongoDB.Driver; using MySqlConnector; using Npgsql; @@ -23,7 +22,6 @@ using RabbitMQ.Client; using StackExchange.Redis; using Steeltoe.Common; -using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.TestResources; using Steeltoe.Configuration; @@ -226,19 +224,6 @@ public void Tracing_IsAutowired() instrumentations.Should().ContainSingle(instance => instance.GetType().Name == "AspNetCoreInstrumentation"); } - [Fact] - public void AppInstanceIdentityCertificate_IsAutowired() - { - using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonCertificates); - var configuration = host.Services.GetRequiredService(); - - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:PrivateKeyFilePath"].Should().NotBeNull(); - - host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.Should().NotBeNull(); - host.Services.GetService>().Should().NotBeNull(); - } - private static WebApplication GetWebApplicationForOnly(string assemblyNameToInclude) { IReadOnlySet exclusions = SteeltoeAssemblyNames.Only(assemblyNameToInclude); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index b45af7f9ef..d9d7a85f63 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using MongoDB.Driver; using MySqlConnector; using Npgsql; @@ -23,7 +22,6 @@ using RabbitMQ.Client; using StackExchange.Redis; using Steeltoe.Common; -using Steeltoe.Common.Configuration; using Steeltoe.Common.Discovery; using Steeltoe.Common.TestResources; using Steeltoe.Configuration; @@ -226,19 +224,6 @@ public void Tracing_IsAutowired() instrumentations.Should().ContainSingle(instance => instance.GetType().Name == "AspNetCoreInstrumentation"); } - [Fact] - public void AppInstanceIdentityCertificate_IsAutowired() - { - using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonCertificates); - var configuration = host.Services.GetRequiredService(); - - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:CertificateFilePath"].Should().NotBeNull(); - configuration[$"{CertificateOptions.ConfigurationKeyPrefix}:AppInstanceIdentity:PrivateKeyFilePath"].Should().NotBeNull(); - - host.Services.GetService>()?.Get("AppInstanceIdentity").Certificate.Should().NotBeNull(); - host.Services.GetService>().Should().NotBeNull(); - } - private static IWebHost GetWebHostForOnly(string assemblyNameToInclude) { IReadOnlySet exclusions = SteeltoeAssemblyNames.Only(assemblyNameToInclude); diff --git a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs index 2ddd4a8a91..4d26b0c504 100644 --- a/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateConfigurationExtensions.cs @@ -9,6 +9,8 @@ namespace Steeltoe.Common.Certificates; public static class CertificateConfigurationExtensions { + internal const string AppInstanceIdentityCertificateName = "AppInstanceIdentity"; + /// /// Adds file path information for a certificate and (optional) private key to configuration, for use with . /// @@ -110,7 +112,7 @@ public static IConfigurationBuilder AddAppInstanceIdentityCertificate(this IConf if (certificateFile != null && keyFile != null) { - builder.AddCertificate("AppInstanceIdentity", certificateFile, keyFile); + builder.AddCertificate(AppInstanceIdentityCertificateName, certificateFile, keyFile); } return builder; diff --git a/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs index f7b633fc26..e35a5856fb 100644 --- a/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs @@ -8,4 +8,5 @@ [assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Certificates.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] +[assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate.Test")] diff --git a/src/Management/test/Endpoint.Test/Trace/EndpointServiceCollectionTest.cs b/src/Management/test/Endpoint.Test/Trace/EndpointServiceCollectionTest.cs index 86d27acd45..db222b9620 100644 --- a/src/Management/test/Endpoint.Test/Trace/EndpointServiceCollectionTest.cs +++ b/src/Management/test/Endpoint.Test/Trace/EndpointServiceCollectionTest.cs @@ -2,10 +2,10 @@ // 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; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Steeltoe.Management.Diagnostics; using Steeltoe.Management.Endpoint.Trace; using Xunit; @@ -13,33 +13,60 @@ namespace Steeltoe.Management.Endpoint.Test.Trace; public sealed class EndpointServiceCollectionTest : BaseTest { + private readonly Dictionary _appSettings = new() + { + ["management:endpoints:enabled"] = "false", + ["management:endpoints:path"] = "/cloudfoundryapplication", + ["management:endpoints:trace:enabled"] = "false" + }; + [Fact] public void AddTraceActuator_AddsCorrectServices() { var services = new ServiceCollection(); + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(_appSettings); + IConfigurationRoot configurationRoot = configurationBuilder.Build(); - var appSettings = new Dictionary - { - ["management:endpoints:enabled"] = "false", - ["management:endpoints:path"] = "/cloudfoundryapplication", - ["management:endpoints:trace:enabled"] = "false" - }; + services.AddLogging(); + services.AddSingleton(configurationRoot); + services.AddTraceActuator(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(true); + var options = serviceProvider.GetService>(); + Assert.NotNull(options); + var handler = serviceProvider.GetService(); + Assert.NotNull(handler); + + IEnumerable observers = serviceProvider.GetServices(); + List list = observers.ToList(); + Assert.Single(list); + Assert.IsType(list[0]); + } + + [Fact] + public void AddTraceActuatorV1_AddsCorrectServices() + { + var services = new ServiceCollection(); var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddInMemoryCollection(appSettings); + configurationBuilder.AddInMemoryCollection(_appSettings); IConfigurationRoot configurationRoot = configurationBuilder.Build(); - using var listener = new DiagnosticListener("Test"); services.AddLogging(); services.AddSingleton(configurationRoot); - services.AddSingleton(listener); - services.AddTraceActuator(); + services.AddTraceActuator(MediaTypeVersion.V1); ServiceProvider serviceProvider = services.BuildServiceProvider(true); var options = serviceProvider.GetService>(); Assert.NotNull(options); var handler = serviceProvider.GetService(); Assert.NotNull(handler); + + IEnumerable observers = serviceProvider.GetServices(); + List list = observers.ToList(); + Assert.Single(list); + Assert.IsType(list[0]); } } diff --git a/src/Management/test/Endpoint.Test/Trace/TraceDiagnosticObserverTest.cs b/src/Management/test/Endpoint.Test/Trace/TraceDiagnosticObserverTest.cs index 717e671665..fa0043d845 100644 --- a/src/Management/test/Endpoint.Test/Trace/TraceDiagnosticObserverTest.cs +++ b/src/Management/test/Endpoint.Test/Trace/TraceDiagnosticObserverTest.cs @@ -376,6 +376,7 @@ private HttpContext CreateRequest() TraceIdentifier = Guid.NewGuid().ToString() }; + context.Features.Set(new DefaultSessionFeature()); context.Response.Body = new MemoryStream(); context.Request.Method = "GET"; context.Request.Path = "/myPath"; diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 5a144bdd59..540744e9ac 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -30,7 +30,7 @@ public static AuthorizationBuilder AddAppInstanceIdentityCertificate(this Author { ArgumentGuard.NotNull(authorizationBuilder); - authorizationBuilder.Services.ConfigureCertificateOptions("AppInstanceIdentity"); + authorizationBuilder.Services.ConfigureCertificateOptions(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName); authorizationBuilder.Services.AddCertificateForwarding(_ => { diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index 3806862644..ac24843160 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Steeltoe.Common; +using Steeltoe.Common.Certificates; using Steeltoe.Common.Configuration; namespace Steeltoe.Security.Authorization.Certificate; @@ -23,7 +24,7 @@ public CertificateAuthorizationHandler(IOptionsMonitor certi _logger = logger; certificateOptionsMonitor.OnChange(OnCertificateRefresh); - OnCertificateRefresh(certificateOptionsMonitor.Get("AppInstanceIdentity")); + OnCertificateRefresh(certificateOptionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName)); } public Task HandleAsync(AuthorizationHandlerContext context) diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs index e7308305bf..0014fd88a3 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationPolicyBuilderExtensions.cs @@ -7,8 +7,14 @@ namespace Steeltoe.Security.Authorization.Certificate; -internal static class CertificateAuthorizationPolicyBuilderExtensions +public static class CertificateAuthorizationPolicyBuilderExtensions { + /// + /// Require a client certificate to originate from within the same organization. + /// + /// + /// The . + /// public static AuthorizationPolicyBuilder RequireSameOrg(this AuthorizationPolicyBuilder builder) { ArgumentGuard.NotNull(builder); @@ -17,6 +23,12 @@ public static AuthorizationPolicyBuilder RequireSameOrg(this AuthorizationPolicy return builder; } + /// + /// Require a client certificate to originate from within the same space. + /// + /// + /// The . + /// public static AuthorizationPolicyBuilder RequireSameSpace(this AuthorizationPolicyBuilder builder) { ArgumentGuard.NotNull(builder); diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index 4893bf1d84..79937bef9e 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -22,7 +22,7 @@ public static class CertificateHttpClientBuilderExtensions /// public static IHttpClientBuilder AddClientCertificateForAppInstance(this IHttpClientBuilder httpClientBuilder) { - return AddClientCertificate(httpClientBuilder, "AppInstanceIdentity"); + return AddClientCertificate(httpClientBuilder, CertificateConfigurationExtensions.AppInstanceIdentityCertificateName); } /// diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 5dd8a1ba1c..837fbd8f90 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Steeltoe.Common; +using Steeltoe.Common.Certificates; using Steeltoe.Common.Configuration; namespace Steeltoe.Security.Authorization.Certificate; @@ -31,7 +32,7 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options { ArgumentGuard.NotNull(options); - CertificateOptions appInstanceIdentityOptions = _certificateOptionsMonitor.Get("AppInstanceIdentity"); + CertificateOptions appInstanceIdentityOptions = _certificateOptionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName); options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; options.ClaimsIssuer = appInstanceIdentityOptions.Certificate?.Issuer; options.RevocationMode = X509RevocationMode.NoCheck; diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index 7d9b391a20..ff78ae2a78 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -4,9 +4,16 @@ const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolici static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddAppInstanceIdentityCertificate(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! authorizationBuilder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameSpace(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificateForAppInstance(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies +Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions +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/Security/src/Authorization.Certificate/SameOrgRequirement.cs b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs index 1359e86dbf..c68649fe9f 100644 --- a/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameOrgRequirement.cs @@ -6,6 +6,9 @@ namespace Steeltoe.Security.Authorization.Certificate; -internal sealed class SameOrgRequirement : IAuthorizationRequirement +/// +/// Client certificates must originate in the same organization as the authorizing application. +/// +public sealed class SameOrgRequirement : IAuthorizationRequirement { } diff --git a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs index 9eca535e57..7863a37814 100644 --- a/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs +++ b/src/Security/src/Authorization.Certificate/SameSpaceRequirement.cs @@ -6,6 +6,9 @@ namespace Steeltoe.Security.Authorization.Certificate; -internal sealed class SameSpaceRequirement : IAuthorizationRequirement +/// +/// Client certificates must originate in the same space as the authorizing application. +/// +public sealed class SameSpaceRequirement : IAuthorizationRequirement { } diff --git a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs index db6663ccb1..e9d5466e76 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/TokenKeyResolverTest.cs @@ -12,13 +12,13 @@ namespace Steeltoe.Security.Authentication.JwtBearer.Test; public sealed class TokenKeyResolverTest { - private static readonly SecurityToken MockToken = new Mock().Object; - private static readonly TokenValidationParameters MockParameters = new Mock().Object; - // ReSharper disable StringLiteralTypo private const string Token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + private static readonly SecurityToken MockToken = new Mock().Object; + private static readonly TokenValidationParameters MockParameters = new Mock().Object; + private const string KeySet = """ { "keys": [ diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index c9df56dcfb..3dc7b16838 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -12,13 +12,13 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect.Test; public sealed class TokenKeyResolverTest { - private static readonly SecurityToken MockToken = new Mock().Object; - private static readonly TokenValidationParameters MockParameters = new Mock().Object; - // ReSharper disable StringLiteralTypo private const string Token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0YjM2NmY4MDdlMjU0MzlmYmRkOTEwZDc4ZjcwYzlhMSIsInN1YiI6ImZlNmExYmUyLWM5MTEtNDM3OC05Y2MxLTVhY2Y1NjA1Y2ZjMiIsInNjb3BlIjpbImNsb3VkX2NvbnRyb2xsZXIucmVhZCIsImNsb3VkX2NvbnRyb2xsZXJfc2VydmljZV9wZXJtaXNzaW9ucy5yZWFkIiwidGVzdGdyb3VwIiwib3BlbmlkIl0sImNsaWVudF9pZCI6Im15VGVzdEFwcCIsImNpZCI6Im15VGVzdEFwcCIsImF6cCI6Im15VGVzdEFwcCIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX2lkIjoiZmU2YTFiZTItYzkxMS00Mzc4LTljYzEtNWFjZjU2MDVjZmMyIiwib3JpZ2luIjoidWFhIiwidXNlcl9uYW1lIjoiZGF2ZSIsImVtYWlsIjoiZGF2ZSIsImF1dGhfdGltZSI6MTQ3MzYxNTU0MSwicmV2X3NpZyI6IjEwZDM1NzEyIiwiaWF0IjoxNDczNjI0MjU1LCJleHAiOjE0NzM2Njc0NTUsImlzcyI6Imh0dHBzOi8vdWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbImNsb3VkX2NvbnRyb2xsZXIiLCJteVRlc3RBcHAiLCJvcGVuaWQiLCJjbG91ZF9jb250cm9sbGVyX3NlcnZpY2VfcGVybWlzc2lvbnMiXX0.Hth_SXpMAyiTf--U75r40qODlSUr60U730IW28K2VidEltW3lN3_CE7HkSjolRGr-DYuWHRvy3i_EwBfj1WTkBaXL373UzPVvNBnat9Gi-vjz07LwmBohk3baG1mmlL8IoGbQwtsmfUPhmO5C6_M4s9wKmTf9XIZPVo_w7zPJadrXfHLfx6iQob7CYpTTix2VBWya29iL7kmD1J1UDT5YRg2J9XT30iFuL6BvPQTkuGnX3ivDuUOSdxM8Z451i0VJmc0LYFBCLJ-Tz6bJ2d0wrtfsbCfuNtxjmGJevcL2jKQbEoiliYj60qNtZdT-ijGUdZjE9caxQ2nOkDkowacpw"; + private static readonly SecurityToken MockToken = new Mock().Object; + private static readonly TokenValidationParameters MockParameters = new Mock().Object; + private const string KeySet = """ { "keys": [ diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index a2274a9448..02e3a29114 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -36,7 +36,7 @@ public async Task CertificateAuth_AcceptsSameOrg() var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameOrganization}"); using IHost host = await GetHostBuilder().StartAsync(); var optionsMonitor = host.Services.GetRequiredService>(); - X509Certificate2 certificate = optionsMonitor.Get("AppInstanceIdentity").Certificate!; + X509Certificate2 certificate = optionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName).Certificate!; using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), certificate); using HttpResponseMessage response = await httpClient.GetAsync(requestUri); @@ -50,7 +50,7 @@ public async Task CertificateAuth_AcceptsSameSpace() var requestUri = new Uri($"https://localhost/{CertificateAuthorizationPolicies.SameSpace}"); using IHost host = await GetHostBuilder().StartAsync(); var optionsMonitor = host.Services.GetRequiredService>(); - X509Certificate2 certificate = optionsMonitor.Get("AppInstanceIdentity").Certificate!; + X509Certificate2 certificate = optionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName).Certificate!; using HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), certificate); using HttpResponseMessage response = await httpClient.GetAsync(requestUri); From 5b0db2c57557dc70a61262ede7edc0996386f478 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 15:10:47 -0500 Subject: [PATCH 20/28] certificateName explicitly nullable, fix Security.slnf --- .../CertificateServiceCollectionExtensions.cs | 4 ++-- src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt | 4 ++-- src/Steeltoe.Security.slnf | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs index c979a0becb..17dc2739a3 100644 --- a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs @@ -22,7 +22,7 @@ public static class CertificateServiceCollectionExtensions /// /// Name of the certificate used in configuration and IOptions, or for an unnamed certificate. /// - public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, string certificateName) + public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, string? certificateName) { return ConfigureCertificateOptions(services, certificateName, null); } @@ -39,7 +39,7 @@ public static IServiceCollection ConfigureCertificateOptions(this IServiceCollec /// /// Provides access to the file system. /// - public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, string certificateName, IFileProvider? fileProvider) + public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, string? certificateName, IFileProvider? fileProvider) { ArgumentGuard.NotNull(services); diff --git a/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt b/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt index 08ce8e7dfe..2c95369d08 100644 --- a/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt +++ b/src/Common/src/Common.Certificates/PublicAPI.Unshipped.txt @@ -1,7 +1,7 @@ #nullable enable static Steeltoe.Common.Certificates.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! static Steeltoe.Common.Certificates.CertificateConfigurationExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.Configuration.IConfigurationBuilder! builder, System.Guid? organizationId, System.Guid? spaceId) -> Microsoft.Extensions.Configuration.IConfigurationBuilder! -static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string? certificateName) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! +static Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions.ConfigureCertificateOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string? certificateName, Microsoft.Extensions.FileProviders.IFileProvider? fileProvider) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! Steeltoe.Common.Certificates.CertificateConfigurationExtensions Steeltoe.Common.Certificates.CertificateServiceCollectionExtensions diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index e8854b1018..59e6a8d6c0 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -12,14 +12,12 @@ "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", "Security\\src\\Authentication.JwtBearer\\Steeltoe.Security.Authentication.JwtBearer.csproj", "Security\\src\\Authentication.OpenIdConnect\\Steeltoe.Security.Authentication.OpenIdConnect.csproj", - "Security\\src\\Authentication.Shared\\Steeltoe.Security.Authentication.Shared.csproj", "Security\\src\\Authorization.Certificate\\Steeltoe.Security.Authorization.Certificate.csproj", "Security\\src\\DataProtection.Redis\\Steeltoe.Security.DataProtection.Redis.csproj", "Security\\test\\Authentication.JwtBearer.Test\\Steeltoe.Security.Authentication.JwtBearer.Test.csproj", "Security\\test\\Authentication.OpenIdConnect.Test\\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", - "Security\\test\\Authentication.Shared.Test\\Steeltoe.Security.Authentication.Shared.Test.csproj", "Security\\test\\Authorization.Certificate.Test\\Steeltoe.Security.Authorization.Certificate.Test.csproj", "Security\\test\\DataProtection.Redis.Test\\Steeltoe.Security.DataProtection.Redis.Test.csproj" ] } -} +} \ No newline at end of file From 50454ee48f86d7b6a50bda93c6e4f43df566264e Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 15:35:37 -0500 Subject: [PATCH 21/28] rename test class --- ...onsTest.cs => CertificateHttpClientBuilderExtensionsTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Security/test/Authorization.Certificate.Test/{CertificateHttpBuilderExtensionsTest.cs => CertificateHttpClientBuilderExtensionsTest.cs} (97%) diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs similarity index 97% rename from src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs rename to src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs index 8c6b73a9b4..24999ca013 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateHttpBuilderExtensionsTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Security.Authorization.Certificate.Test; -public sealed class CertificateHttpBuilderExtensionsTest +public sealed class CertificateHttpClientBuilderExtensionsTest { [Fact] public async Task AddCertificateAuthorizationClient_AddsNamedHttpClientWithCertificate() From 94ab5bff0adaa6226ab1da566a41a6fc1ca978a0 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 16:15:45 -0500 Subject: [PATCH 22/28] revert more s3900 --- ...SnakeCaseAllCapsEnumMemberJsonConverter.cs | 2 -- .../Serialization/BoolStringJsonConverter.cs | 6 +----- .../ConfigurationValuesHelper.cs | 4 ---- .../Common/Reflection/ReflectionHelpers.cs | 20 ------------------- 4 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs b/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs index 94cf10bd3b..1ad162dc5e 100644 --- a/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs +++ b/src/Common/src/Abstractions/Util/SnakeCaseAllCapsEnumMemberJsonConverter.cs @@ -20,8 +20,6 @@ public sealed class SnakeCaseAllCapsEnumMemberJsonConverter : JsonConverterFacto /// public override bool CanConvert(Type typeToConvert) { - ArgumentGuard.NotNull(typeToConvert); - return typeToConvert.IsEnum; } diff --git a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs index 243a8836ae..58c7d092d2 100644 --- a/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/BoolStringJsonConverter.cs @@ -12,15 +12,11 @@ public sealed class BoolStringJsonConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.TokenType is JsonTokenType.False or JsonTokenType.True - ? reader.GetBoolean() - : bool.Parse(reader.GetString() ?? throw new ArgumentNullException(nameof(reader))); + return reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True ? reader.GetBoolean() : bool.Parse(reader.GetString()); } public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { - ArgumentGuard.NotNull(writer); - #pragma warning disable S4040 // Strings should be normalized to uppercase writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); #pragma warning restore S4040 // Strings should be normalized to uppercase diff --git a/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs b/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs index 5a9b8d69b8..faf98b0e75 100644 --- a/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs +++ b/src/Common/src/Common/Configuration/ConfigurationValuesHelper.cs @@ -50,10 +50,6 @@ public static string GetSetting(string key, IConfiguration primary, IConfigurati /// public static string GetSetting(string key, IConfiguration configuration, string defaultValue, params string[] sectionPrefixes) { - ArgumentGuard.NotNull(key); - ArgumentGuard.NotNull(configuration); - ArgumentGuard.NotNull(sectionPrefixes); - foreach (string prefix in sectionPrefixes) { IConfigurationSection section = configuration.GetSection(prefix); diff --git a/src/Common/src/Common/Reflection/ReflectionHelpers.cs b/src/Common/src/Common/Reflection/ReflectionHelpers.cs index f99c29ed5f..e68528e800 100644 --- a/src/Common/src/Common/Reflection/ReflectionHelpers.cs +++ b/src/Common/src/Common/Reflection/ReflectionHelpers.cs @@ -136,8 +136,6 @@ public static IEnumerable FindAssembliesWithAttribute() public static IEnumerable FindTypesWithAttribute(Assembly assembly) where T : Attribute { - ArgumentGuard.NotNull(assembly); - return assembly.GetTypes().Where(type => type.IsDefined(typeof(T))); } @@ -211,9 +209,6 @@ public static IEnumerable FindInterfacedTypesFromAssemblyAttribute public static Type FindType(string[] assemblyNames, string[] typeNames) { - ArgumentGuard.NotNull(assemblyNames); - ArgumentGuard.NotNull(typeNames); - foreach (string assemblyName in assemblyNames) { Assembly assembly = FindAssembly(assemblyName); @@ -249,9 +244,6 @@ public static Type FindType(string[] assemblyNames, string[] typeNames) /// public static Type FindType(Assembly assembly, string typeName) { - ArgumentGuard.NotNull(assembly); - ArgumentGuard.NotNull(typeName); - try { return assembly.GetType(typeName); @@ -314,9 +306,6 @@ public static Type FindTypeOrThrow(string[] assemblyNames, string[] typeNames, s /// public static PropertyInfo FindProperty(Type type, string propertyName) { - ArgumentGuard.NotNull(type); - ArgumentGuard.NotNull(propertyName); - try { return type.GetProperty(propertyName); @@ -346,9 +335,6 @@ public static PropertyInfo FindProperty(Type type, string propertyName) /// public static MethodInfo FindMethod(Type type, string methodName, Type[] parameters = null) { - ArgumentGuard.NotNull(type); - ArgumentGuard.NotNull(methodName); - try { if (parameters != null) @@ -383,9 +369,6 @@ public static MethodInfo FindMethod(Type type, string methodName, Type[] paramet /// public static object Invoke(MethodBase member, object instance, object[] args) { - ArgumentGuard.NotNull(member); - ArgumentGuard.NotNull(instance); - try { return member.Invoke(instance, args); @@ -443,9 +426,6 @@ public static object CreateInstance(Type t, object[] args = null) /// public static void TrySetProperty(object obj, string property, object value) { - ArgumentGuard.NotNull(obj); - ArgumentGuard.NotNull(property); - PropertyInfo prop = obj.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance); if (prop != null && prop.CanWrite) From 5cae4a50b3c8214ff84a52e884a83d6b69808210 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 17 Jun 2024 16:20:43 -0500 Subject: [PATCH 23/28] cleanup --- .../src/Common.Certificates/Properties/AssemblyInfo.cs | 2 -- .../Common.Http/Serialization/LongStringJsonConverter.cs | 6 +----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs index e35a5856fb..0d781ab0ee 100644 --- a/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs +++ b/src/Common/src/Common.Certificates/Properties/AssemblyInfo.cs @@ -4,8 +4,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration")] -[assembly: InternalsVisibleTo("Steeltoe.Bootstrap.AutoConfiguration.Test")] [assembly: InternalsVisibleTo("Steeltoe.Common.Certificates.Test")] [assembly: InternalsVisibleTo("Steeltoe.Configuration.ConfigServer")] [assembly: InternalsVisibleTo("Steeltoe.Security.Authorization.Certificate")] diff --git a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs index 52242187f4..b8d6e2062c 100644 --- a/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs +++ b/src/Common/src/Common.Http/Serialization/LongStringJsonConverter.cs @@ -12,15 +12,11 @@ public class LongStringJsonConverter : JsonConverter { public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.TokenType == JsonTokenType.Number - ? reader.GetInt64() - : long.Parse(reader.GetString() ?? throw new ArgumentNullException(nameof(reader)), CultureInfo.InvariantCulture); + return reader.TokenType == JsonTokenType.Number ? reader.GetInt64() : long.Parse(reader.GetString(), CultureInfo.InvariantCulture); } public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) { - ArgumentGuard.NotNull(writer); - writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); } } From 97e57f0a216dac17ad1018ae5dddce87131d9cc5 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 18 Jun 2024 08:26:19 -0500 Subject: [PATCH 24/28] Apply suggestions from code review Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com> --- .../Common.TestResources/CancellationTokenSourceExtensions.cs | 4 ++-- .../test/Common.TestResources/IgnoreLineEndingsComparer.cs | 4 +--- .../CertificateHttpClientBuilderExtensions.cs | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs index c2ab50a4a6..aff7c8d0b3 100644 --- a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs +++ b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs @@ -11,9 +11,9 @@ public static class CancellationTokenSourceExtensions // Workaround for Sonar S6966 bug: CancelAsync is suggested, which isn't available in .NET 6. public static Task CancelAsync(this CancellationTokenSource source) { -#pragma warning disable S3900 // Arguments of public methods should be validated against null + ArgumentNullException.ThrowIfNull(source); + source.Cancel(); -#pragma warning restore S3900 // Arguments of public methods should be validated against null return Task.CompletedTask; } } diff --git a/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs b/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs index a4213de86b..485e88f3de 100644 --- a/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs +++ b/src/Common/test/Common.TestResources/IgnoreLineEndingsComparer.cs @@ -35,8 +35,6 @@ public bool Equals(string x, string y) public int GetHashCode(string obj) { -#pragma warning disable S3900 // Arguments of public methods should be validated against null - return obj.GetHashCode(); -#pragma warning restore S3900 // Arguments of public methods should be validated against null + return HashCode.Combine(obj); } } diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index 79937bef9e..505377a283 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -29,7 +29,7 @@ public static IHttpClientBuilder AddClientCertificateForAppInstance(this IHttpCl /// Configures named and attaches the certificate to outbound requests. /// /// - /// The to add a client certificate to. + /// The to configure. /// /// /// The name of the certificate used in configuration. From 34f04c6ff874e193d9201daa3e05eab6630ffb53 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 18 Jun 2024 09:22:02 -0500 Subject: [PATCH 25/28] pr feedback --- ...oe.Bootstrap.AutoConfiguration.Test.csproj | 1 - .../CertificateServiceCollectionExtensions.cs | 4 +- .../CancellationTokenSourceExtensions.cs | 2 +- ...rtificateAuthorizationBuilderExtensions.cs | 2 +- .../CertificateHttpClientBuilderExtensions.cs | 6 +-- .../PublicAPI.Unshipped.txt | 4 +- ...arerAuthenticationBuilderExtensionsTest.cs | 6 +-- ...nectAuthenticationBuilderExtensionsTest.cs | 6 +-- .../TokenKeyResolverTest.cs | 6 +-- .../CertificateAuthorizationTest.cs | 52 +++++++++++++++++++ ...tificateHttpClientBuilderExtensionsTest.cs | 2 +- .../TestServerCertificateStartup.cs | 2 +- 12 files changed, 72 insertions(+), 21 deletions(-) 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 7adb438ddf..f7e02269a7 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj +++ b/src/Bootstrap/test/AutoConfiguration.Test/Steeltoe.Bootstrap.AutoConfiguration.Test.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs index 17dc2739a3..af928ae272 100644 --- a/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Certificates/CertificateServiceCollectionExtensions.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Common.Certificates; public static class CertificateServiceCollectionExtensions { /// - /// Configure for use with client certificates. + /// Binds certificate paths in configuration to for use with client certificates. /// /// /// The to add services to. @@ -28,7 +28,7 @@ public static IServiceCollection ConfigureCertificateOptions(this IServiceCollec } /// - /// Configure for use with client certificates. + /// Binds certificate paths in configuration to for use with client certificates. /// /// /// The to add services to. diff --git a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs index aff7c8d0b3..719099e910 100644 --- a/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs +++ b/src/Common/test/Common.TestResources/CancellationTokenSourceExtensions.cs @@ -12,7 +12,7 @@ public static class CancellationTokenSourceExtensions public static Task CancelAsync(this CancellationTokenSource source) { ArgumentNullException.ThrowIfNull(source); - + source.Cancel(); return Task.CompletedTask; } diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 540744e9ac..8c9e94039e 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -26,7 +26,7 @@ public static class CertificateAuthorizationBuilderExtensions /// /// The . /// - public static AuthorizationBuilder AddAppInstanceIdentityCertificate(this AuthorizationBuilder authorizationBuilder) + public static AuthorizationBuilder AddOrgAndSpacePolicies(this AuthorizationBuilder authorizationBuilder) { ArgumentGuard.NotNull(authorizationBuilder); diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index 505377a283..2406aac4c6 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -15,18 +15,18 @@ namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateHttpClientBuilderExtensions { /// - /// Configures representing the application instance and attaches the certificate to outbound requests. + /// Binds certificate paths in configuration to representing the application instance and attaches the certificate to outbound requests. /// /// /// The to add a client certificate to. /// - public static IHttpClientBuilder AddClientCertificateForAppInstance(this IHttpClientBuilder httpClientBuilder) + public static IHttpClientBuilder AddAppInstanceIdentityCertificate(this IHttpClientBuilder httpClientBuilder) { return AddClientCertificate(httpClientBuilder, CertificateConfigurationExtensions.AppInstanceIdentityCertificateName); } /// - /// Configures named and attaches the certificate to outbound requests. + /// Binds certificate paths in configuration to and attaches the certificate to outbound requests. /// /// /// The to configure. diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index ff78ae2a78..3920d78ba1 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -3,11 +3,11 @@ const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolici const Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies.SameSpace = "samespace" -> string! static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions.UseCertificateAuthorization(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder, Microsoft.AspNetCore.Builder.ForwardedHeadersOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddAppInstanceIdentityCertificate(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! authorizationBuilder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions.AddOrgAndSpacePolicies(this Microsoft.AspNetCore.Authorization.AuthorizationBuilder! authorizationBuilder) -> Microsoft.AspNetCore.Authorization.AuthorizationBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameOrg(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilderExtensions.RequireSameSpace(this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! builder) -> Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder! +static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddAppInstanceIdentityCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificate(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder, string! certificateName) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! -static Steeltoe.Security.Authorization.Certificate.CertificateHttpClientBuilderExtensions.AddClientCertificateForAppInstance(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! httpClientBuilder) -> Microsoft.Extensions.DependencyInjection.IHttpClientBuilder! Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateApplicationBuilderExtensions Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicies diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs index 51ff92da96..93ca9b1f2d 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerAuthenticationBuilderExtensionsTest.cs @@ -14,10 +14,10 @@ public sealed class JwtBearerAuthenticationBuilderExtensionsTest [Fact] public void ConfigureJwtBearerForCloudFoundry_AddsExpectedRegistrations() { - AuthenticationBuilder serviceCollection = new ServiceCollection().AddAuthentication().AddJwtBearer(); + AuthenticationBuilder authenticationBuilder = new ServiceCollection().AddAuthentication().AddJwtBearer(); - serviceCollection.ConfigureJwtBearerForCloudFoundry(); + authenticationBuilder.ConfigureJwtBearerForCloudFoundry(); - serviceCollection.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); + authenticationBuilder.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureJwtBearerOptions)); } } diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs index 8b15de6c4d..96a412af8a 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectAuthenticationBuilderExtensionsTest.cs @@ -14,10 +14,10 @@ public sealed class OpenIdConnectAuthenticationBuilderExtensionsTest [Fact] public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() { - AuthenticationBuilder serviceCollection = new ServiceCollection().AddAuthentication().AddOpenIdConnect(); + AuthenticationBuilder authenticationBuilder = new ServiceCollection().AddAuthentication().AddOpenIdConnect(); - serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + authenticationBuilder.ConfigureOpenIdConnectForCloudFoundry(); - serviceCollection.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); + authenticationBuilder.Services.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); } } diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs index 3dc7b16838..13bbae1af3 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/TokenKeyResolverTest.cs @@ -48,7 +48,7 @@ public void ResolveSigningKey_FindsExistingKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); result.Should().NotBeEmpty(); - result[0].Should().BeEquivalentTo(webKey); + result[0].Should().Be(webKey); } [Fact] @@ -68,7 +68,7 @@ public void ResolveSigningKey_IssuesHttpRequest_ResolvesKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); handler.LastRequest.Should().NotBeNull(); - TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().Contain("legacy-token-key"); + TokenKeyResolver.ResolvedSecurityKeysById.Should().ContainKey("legacy-token-key"); result.Should().NotBeEmpty(); } @@ -105,7 +105,7 @@ public void ResolveSigningKey_IssuesHttpRequest_DoesNotResolveKey() SecurityKey[] result = resolver.ResolveSigningKey(Token, MockToken, "legacy-token-key", MockParameters); handler.LastRequest.Should().NotBeNull(); - TokenKeyResolver.ResolvedSecurityKeysById.Keys.Should().NotContain("legacy-token-key"); + TokenKeyResolver.ResolvedSecurityKeysById.Should().NotContainKey("legacy-token-key"); result.Should().BeEmpty(); } diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 02e3a29114..d04e9cd717 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -4,6 +4,7 @@ using System.Net; using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -114,6 +115,57 @@ public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + [Fact] + public async Task CertificateAuth_SetDefaultPolicyWithRequirements() + { + var requestUri = new Uri("https://localhost/request"); + WebApplicationBuilder builder = TestHelpers.GetTestWebApplicationBuilder(); + builder.Configuration.AddAppInstanceIdentityCertificate(Certificates.ServerOrgId, Certificates.ServerSpaceId); + builder.Services.AddAuthentication().AddCertificate(); + + builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies().AddDefaultPolicy("sameOrgAndSpace", + policyBuilder => policyBuilder.AddRequirements([ + new SameOrgRequirement(), + new SameSpaceRequirement() + ])); + + await using WebApplication application = builder.Build(); + application.UseCertificateAuthorization(); + application.MapGet("/request", () => "response").RequireAuthorization(); + await application.StartAsync(); + var optionsMonitor = application.Services.GetRequiredService>(); + X509Certificate2 certificate = optionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName).Certificate!; + using HttpClient httpClient = ClientWithCertificate(application.GetTestClient(), certificate); + + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task CertificateAuth_SetDefaultPolicyWithPolicyBuilder() + { + var requestUri = new Uri("https://localhost/request"); + WebApplicationBuilder builder = TestHelpers.GetTestWebApplicationBuilder(); + builder.Configuration.AddAppInstanceIdentityCertificate(Certificates.ServerOrgId, Certificates.ServerSpaceId); + builder.Services.AddAuthentication().AddCertificate(); + + builder.Services.AddAuthorizationBuilder().AddOrgAndSpacePolicies() + .AddDefaultPolicy("sameOrgAndSpace", policyBuilder => policyBuilder.RequireSameOrg().RequireSameSpace()); + + await using WebApplication application = builder.Build(); + application.UseCertificateAuthorization(); + application.MapGet("/request", () => "response").RequireAuthorization(); + await application.StartAsync(); + var optionsMonitor = application.Services.GetRequiredService>(); + X509Certificate2 certificate = optionsMonitor.Get(CertificateConfigurationExtensions.AppInstanceIdentityCertificateName).Certificate!; + using HttpClient httpClient = ClientWithCertificate(application.GetTestClient(), certificate); + + using HttpResponseMessage response = await httpClient.GetAsync(requestUri); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + private HostBuilder GetHostBuilder() { var hostBuilder = new HostBuilder(); diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs index 24999ca013..9614390d5e 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateHttpClientBuilderExtensionsTest.cs @@ -37,7 +37,7 @@ private static IHostBuilder GetHostBuilder() { var hostBuilder = new HostBuilder(); hostBuilder.ConfigureAppConfiguration(builder => builder.AddAppInstanceIdentityCertificate()); - hostBuilder.ConfigureServices(services => services.AddHttpClient("test").AddClientCertificateForAppInstance()); + hostBuilder.ConfigureServices(services => services.AddHttpClient("test").AddAppInstanceIdentityCertificate()); hostBuilder.ConfigureWebHost(webBuilder => { diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs index 9db9bb1663..ccb827e0a7 100644 --- a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -18,7 +18,7 @@ public void ConfigureServices(IServiceCollection services) options.ValidateValidityPeriod = false; }); - services.AddAuthorizationBuilder().AddAppInstanceIdentityCertificate(); + services.AddAuthorizationBuilder().AddOrgAndSpacePolicies(); } public void Configure(IApplicationBuilder app, IAuthorizationService authorizationService) From 208b1e3499b49a6d7ca2639ad308cc11a2fe503a Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 18 Jun 2024 10:13:59 -0500 Subject: [PATCH 26/28] style --- .../CertificateHttpClientBuilderExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs index 2406aac4c6..6a6bdb54aa 100644 --- a/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateHttpClientBuilderExtensions.cs @@ -15,7 +15,8 @@ namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateHttpClientBuilderExtensions { /// - /// Binds certificate paths in configuration to representing the application instance and attaches the certificate to outbound requests. + /// Binds certificate paths in configuration to representing the application instance and attaches the certificate to + /// outbound requests. /// /// /// The to add a client certificate to. From 5c8605efaa352a0396158c75f24a2467c4135d16 Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 18 Jun 2024 10:39:30 -0500 Subject: [PATCH 27/28] update method description, don't run cert auth tests in parallel --- .../CertificateAuthorizationBuilderExtensions.cs | 8 ++++---- .../test/Authorization.Certificate.Test/xunit.runner.json | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 src/Security/test/Authorization.Certificate.Test/xunit.runner.json diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 8c9e94039e..6f6254c376 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -8,19 +8,19 @@ using Microsoft.Extensions.Options; using Steeltoe.Common; using Steeltoe.Common.Certificates; -using Steeltoe.Common.Configuration; namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateAuthorizationBuilderExtensions { /// - /// Adds the necessary components and policies for server-side authorization of application instance identity certificates. + /// Defines policies that verify the space/org in the incoming client certificate matches the space/org of the local application instance identity + /// certificate in configuration. /// - /// Components include named "AppInstanceIdentity" and certificate forwarding. + /// Secure your endpoints with the included authorization policies by referencing . /// /// - /// Secure your endpoints with the included authorization policies by referencing . + /// This method also certificate forwarding. /// /// /// diff --git a/src/Security/test/Authorization.Certificate.Test/xunit.runner.json b/src/Security/test/Authorization.Certificate.Test/xunit.runner.json new file mode 100644 index 0000000000..ba955e0f60 --- /dev/null +++ b/src/Security/test/Authorization.Certificate.Test/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "maxParallelThreads": 1, + "parallelizeTestCollections": false +} From cd017115fb80bad248db9ed61640f27a184f9dfe Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Tue, 18 Jun 2024 10:49:22 -0500 Subject: [PATCH 28/28] add missing word --- .../CertificateAuthorizationBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs index 6f6254c376..7a675a0fa2 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationBuilderExtensions.cs @@ -20,7 +20,7 @@ public static class CertificateAuthorizationBuilderExtensions /// Secure your endpoints with the included authorization policies by referencing . /// /// - /// This method also certificate forwarding. + /// This method also configures certificate forwarding. /// /// ///