diff --git a/ActiveLogin.Authentication.sln b/ActiveLogin.Authentication.sln index 86a33376..b9fc1ef3 100644 --- a/ActiveLogin.Authentication.sln +++ b/ActiveLogin.Authentication.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11415.281 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9A0CF459-87CC-448D-B49D-EFC3D6482AA6}" ProjectSection(SolutionItems) = preProject @@ -36,8 +36,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution files", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8E4C5EA1-1B2E-4B78-9F44-6BC85E42A752}" EndProject -Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "AzureProvisioningSample", "samples\AzureProvisioningSample\AzureProvisioningSample.deployproj", "{ABC52082-8A5D-426E-B25A-D92934A51A4D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActiveLogin.Authentication.BankId.AzureKeyVault", "src\ActiveLogin.Authentication.BankId.AzureKeyVault\ActiveLogin.Authentication.BankId.AzureKeyVault.csproj", "{D113E0AC-BDF2-4EAB-A2A3-F4B31E7871C6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{4764448D-A014-403F-A956-3F4CFFA00AF2}" @@ -113,6 +111,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".devcontainer", ".devcontai EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Phone.ConsoleSample", "samples\Phone.ConsoleSample\Phone.ConsoleSample.csproj", "{A77ECBA8-5F9F-4C60-A4D0-08CAC023E4BE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureProvisioningSample", "AzureProvisioningSample", "{D365882F-BCB2-43BF-96B3-D5AC2B4D1810}" + ProjectSection(SolutionItems) = preProject + samples\AzureProvisioningSample\ActiveLogin-Monitor.json = samples\AzureProvisioningSample\ActiveLogin-Monitor.json + samples\AzureProvisioningSample\ActiveLogin.json = samples\AzureProvisioningSample\ActiveLogin.json + samples\AzureProvisioningSample\ActiveLogin.parameters.json = samples\AzureProvisioningSample\ActiveLogin.parameters.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,10 +136,6 @@ Global {951B23A8-E123-4223-BF52-D28BD2665748}.Debug|Any CPU.Build.0 = Debug|Any CPU {951B23A8-E123-4223-BF52-D28BD2665748}.Release|Any CPU.ActiveCfg = Release|Any CPU {951B23A8-E123-4223-BF52-D28BD2665748}.Release|Any CPU.Build.0 = Release|Any CPU - {ABC52082-8A5D-426E-B25A-D92934A51A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABC52082-8A5D-426E-B25A-D92934A51A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABC52082-8A5D-426E-B25A-D92934A51A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABC52082-8A5D-426E-B25A-D92934A51A4D}.Release|Any CPU.Build.0 = Release|Any CPU {D113E0AC-BDF2-4EAB-A2A3-F4B31E7871C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D113E0AC-BDF2-4EAB-A2A3-F4B31E7871C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {D113E0AC-BDF2-4EAB-A2A3-F4B31E7871C6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -195,7 +196,6 @@ Global {A15234E7-CBF9-49CD-8D13-4CB02B045881} = {9A0CF459-87CC-448D-B49D-EFC3D6482AA6} {478B6428-4076-4446-8358-3B0C6FCC29D5} = {12A0FDF2-523E-42C7-81FE-4FDCA5E66A69} {951B23A8-E123-4223-BF52-D28BD2665748} = {9A0CF459-87CC-448D-B49D-EFC3D6482AA6} - {ABC52082-8A5D-426E-B25A-D92934A51A4D} = {8E4C5EA1-1B2E-4B78-9F44-6BC85E42A752} {D113E0AC-BDF2-4EAB-A2A3-F4B31E7871C6} = {9A0CF459-87CC-448D-B49D-EFC3D6482AA6} {7D32EBEE-65D6-46D3-BC7E-65951C143969} = {4764448D-A014-403F-A956-3F4CFFA00AF2} {2FD5CA1D-04BB-4704-B8D8-B0A566E247A9} = {12A0FDF2-523E-42C7-81FE-4FDCA5E66A69} @@ -214,6 +214,7 @@ Global {61360399-F211-4B18-88EE-2A6F4D2C1FE6} = {9A0CF459-87CC-448D-B49D-EFC3D6482AA6} {D3673E53-A774-46F0-9E44-97DF8F52D6D8} = {12A0FDF2-523E-42C7-81FE-4FDCA5E66A69} {A77ECBA8-5F9F-4C60-A4D0-08CAC023E4BE} = {8E4C5EA1-1B2E-4B78-9F44-6BC85E42A752} + {D365882F-BCB2-43BF-96B3-D5AC2B4D1810} = {8E4C5EA1-1B2E-4B78-9F44-6BC85E42A752} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2076F58C-968B-489D-94CA-B3729F5DE10D} diff --git a/global.json b/global.json index 889cc182..37bf7f81 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.414", + "version": "10.0.101", "rollForward": "latestFeature" } } diff --git a/samples/AzureProvisioningSample/AzureProvisioningSample.deployproj b/samples/AzureProvisioningSample/AzureProvisioningSample.deployproj deleted file mode 100644 index cbfacb2e..00000000 --- a/samples/AzureProvisioningSample/AzureProvisioningSample.deployproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - - Debug - AnyCPU - - - Release - AnyCPU - - - - abc52082-8a5d-426e-b25a-d92934a51a4d - - - Deployment - 1.0 - - - - - - - - - - - False - - - - - - - - \ No newline at end of file diff --git a/samples/AzureProvisioningSample/Deploy-AzureResourceGroup.ps1 b/samples/AzureProvisioningSample/Deploy-AzureResourceGroup.ps1 deleted file mode 100644 index 21925f96..00000000 --- a/samples/AzureProvisioningSample/Deploy-AzureResourceGroup.ps1 +++ /dev/null @@ -1,118 +0,0 @@ -#Requires -Version 3.0 - -Param( - [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, - [string] $ResourceGroupName = 'activelogin-demo', - [switch] $UploadArtifacts, - [string] $StorageAccountName, - [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts', - [string] $TemplateFile = 'ActiveLogin.json', - [string] $TemplateParametersFile = 'ActiveLogin.parameters.json', - [string] $ArtifactStagingDirectory = '.', - [string] $DSCSourceFolder = 'DSC', - [switch] $ValidateOnly -) - -try { - [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0') -} catch { } - -$ErrorActionPreference = 'Stop' -Set-StrictMode -Version 3 - -function Format-ValidationOutput { - param ($ValidationOutput, [int] $Depth = 0) - Set-StrictMode -Off - return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) -} - -$OptionalParameters = New-Object -TypeName Hashtable -$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile)) -$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile)) - -if ($UploadArtifacts) { - # Convert relative paths to absolute paths if needed - $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory)) - $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder)) - - # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present - $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json - if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) { - $JsonParameters = $JsonParameters.parameters - } - $ArtifactsLocationName = '_artifactsLocation' - $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken' - $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore - $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore - - # Create DSC configuration archive - if (Test-Path $DSCSourceFolder) { - $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName}) - foreach ($DSCSourceFilePath in $DSCSourceFilePaths) { - $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip' - Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose - } - } - - # Create a storage account name if none was provided - if ($StorageAccountName -eq '') { - $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19) - } - - $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName}) - - # Create the storage account if it doesn't already exist - if ($StorageAccount -eq $null) { - $StorageResourceGroupName = 'ARM_Deploy_Staging' - New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force - $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation" - } - - # Generate the value for artifacts location if it is not provided in the parameter file - if ($OptionalParameters[$ArtifactsLocationName] -eq $null) { - $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName - } - - # Copy files from the local storage staging location to the storage account container - New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1 - - $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName} - foreach ($SourcePath in $ArtifactFilePaths) { - Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) ` - -Container $StorageContainerName -Context $StorageAccount.Context -Force - } - - # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file - if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) { - $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force ` - (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4)) - } -} - -# Create or update the resource group using the specified template file and template parameters file -New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force - -if ($ValidateOnly) { - $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` - -TemplateFile $TemplateFile ` - -TemplateParameterFile $TemplateParametersFile ` - @OptionalParameters) - if ($ErrorMessages) { - Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' - } - else { - Write-Output '', 'Template is valid.' - } -} -else { - New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` - -ResourceGroupName $ResourceGroupName ` - -TemplateFile $TemplateFile ` - -TemplateParameterFile $TemplateParametersFile ` - @OptionalParameters ` - -Force -Verbose ` - -ErrorVariable ErrorMessages - if ($ErrorMessages) { - Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) - } -} \ No newline at end of file diff --git a/samples/AzureProvisioningSample/Deployment.targets b/samples/AzureProvisioningSample/Deployment.targets deleted file mode 100644 index 0d792ec6..00000000 --- a/samples/AzureProvisioningSample/Deployment.targets +++ /dev/null @@ -1,123 +0,0 @@ - - - - Debug - AnyCPU - bin\$(Configuration)\ - false - true - false - None - obj\ - $(BaseIntermediateOutputPath)\ - $(BaseIntermediateOutputPath)$(Configuration)\ - $(IntermediateOutputPath)ProjectReferences - $(ProjectReferencesOutputPath)\ - true - - - - false - false - - - - - - - - - - - Always - - - Never - - - false - Build - - - - - - - - _GetDeploymentProjectContent; - _CalculateContentOutputRelativePaths; - _GetReferencedProjectsOutput; - _CalculateArtifactStagingDirectory; - _CopyOutputToArtifactStagingDirectory; - - - - - - - - - - - - - - - - - Configuration=$(Configuration);Platform=$(Platform) - - - - - - - $([System.IO.Path]::GetFileNameWithoutExtension('%(ProjectReference.Identity)')) - - - - - - - $(OutDir) - $(OutputPath) - $(ArtifactStagingDirectory)\ - $(ArtifactStagingDirectory)staging\ - $(Build_StagingDirectory) - - - - - - - <_OriginalIdentity>%(DeploymentProjectContentOutput.Identity) - <_RelativePath>$(_OriginalIdentity.Replace('$(MSBuildProjectDirectory)', '')) - - - - - $(_RelativePath) - - - - - - - - - PrepareForRun - - - - - - - - - - - diff --git a/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj b/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj index d9f1966c..3d47c300 100644 --- a/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj +++ b/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 latest enable enable @@ -18,8 +18,8 @@ - - + + diff --git a/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj b/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj index 95e6d728..39da9a72 100644 --- a/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj +++ b/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 latest enable enable @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/samples/Phone.ConsoleSample/Phone.ConsoleSample.csproj b/samples/Phone.ConsoleSample/Phone.ConsoleSample.csproj index fbd913c4..18168188 100644 --- a/samples/Phone.ConsoleSample/Phone.ConsoleSample.csproj +++ b/samples/Phone.ConsoleSample/Phone.ConsoleSample.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 latest enable enable @@ -20,9 +20,9 @@ - - - + + + diff --git a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj index 82a91222..f7bd8945 100644 --- a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj +++ b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 latest enable enable @@ -31,6 +31,6 @@ - + diff --git a/src/ActiveLogin.Authentication.BankId.Api/ActiveLogin.Authentication.BankId.Api.csproj b/src/ActiveLogin.Authentication.BankId.Api/ActiveLogin.Authentication.BankId.Api.csproj index 89c5c51f..1bd4c982 100644 --- a/src/ActiveLogin.Authentication.BankId.Api/ActiveLogin.Authentication.BankId.Api.csproj +++ b/src/ActiveLogin.Authentication.BankId.Api/ActiveLogin.Authentication.BankId.Api.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj b/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj index c1faedbc..06b1b0a4 100644 --- a/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj +++ b/src/ActiveLogin.Authentication.BankId.AspNetCore/ActiveLogin.Authentication.BankId.AspNetCore.csproj @@ -9,12 +9,11 @@ - + - @@ -54,7 +53,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/ActiveLogin.Authentication.BankId.AzureKeyVault.csproj b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/ActiveLogin.Authentication.BankId.AzureKeyVault.csproj index 1613f7e5..c9ecb215 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/ActiveLogin.Authentication.BankId.AzureKeyVault.csproj +++ b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/ActiveLogin.Authentication.BankId.AzureKeyVault.csproj @@ -7,13 +7,13 @@ - - + + - + diff --git a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/AzureKeyVaultCertificateClient.cs b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/AzureKeyVaultCertificateClient.cs index 3dc94fc4..1aba1e18 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/AzureKeyVaultCertificateClient.cs +++ b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/AzureKeyVaultCertificateClient.cs @@ -4,6 +4,8 @@ using Azure.Identity; using Azure.Security.KeyVault.Secrets; +using ActiveLogin.Authentication.BankId.Core.Certificate; + namespace ActiveLogin.Authentication.BankId.AzureKeyVault; internal class AzureKeyVaultCertificateClient(SecretClient secretClient) @@ -47,9 +49,9 @@ private static TokenCredential GetTokenCredential(ClientCertificateFromAzureKeyV private const string CertificateContentType = "application/x-pkcs12"; - public X509Certificate2 GetX509Certificate2(string keyVaultSecretKey) + public X509Certificate2 GetX509Certificate2(string keyVaultSecretName, X509KeyStorageFlags keyStorageFlags) { - var secret = secretClient.GetSecret(keyVaultSecretKey).Value; + var secret = secretClient.GetSecret(keyVaultSecretName).Value; if (secret.Properties.ContentType != CertificateContentType) { throw new ArgumentException($"This certificate must be of type {CertificateContentType}"); @@ -57,14 +59,18 @@ public X509Certificate2 GetX509Certificate2(string keyVaultSecretKey) var certificateBytes = Convert.FromBase64String(secret.Value); - return GetX509Certificate2(certificateBytes); + return GetX509Certificate2(certificateBytes, keyStorageFlags); } - private static X509Certificate2 GetX509Certificate2(byte[] certificate) + private static X509Certificate2 GetX509Certificate2(byte[] certificate, X509KeyStorageFlags keyStorageFlags) { - var exportedCertCollection = new X509Certificate2Collection(); - exportedCertCollection.Import(certificate, null, X509KeyStorageFlags.MachineKeySet); + var certs = X509CertificateLoader.LoadPkcs12Collection(certificate, password: null, keyStorageFlags); + var cert = certs.FirstOrDefault(c => c.HasPrivateKey); + if (cert is null) + { + throw new InvalidOperationException("The Azure Key Vault secret does not contain a certificate with a private key."); + } - return exportedCertCollection.Cast().First(x => x.HasPrivateKey); + return cert; } } diff --git a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/BankIdBuilderAzureKeyVaultExtensions.cs b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/BankIdBuilderAzureKeyVaultExtensions.cs index 52f53202..801dc2e1 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureKeyVault/BankIdBuilderAzureKeyVaultExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AzureKeyVault/BankIdBuilderAzureKeyVaultExtensions.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography.X509Certificates; + using ActiveLogin.Authentication.BankId.Core; using Microsoft.Extensions.Configuration; @@ -8,36 +10,48 @@ public static class BankIdBuilderAzureKeyVaultExtensions /// /// Use client certificate for authenticating against the BankID API from Azure Key Vault. /// - /// + /// The BankID builder. /// Configuration section to bind the Key Vault options from. + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// /// - public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, IConfigurationSection configurationSection) + public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, IConfigurationSection configurationSection, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { var options = new ClientCertificateFromAzureKeyVaultOptions(); configurationSection.Bind(options); - return UseClientCertificateFromAzureKeyVault(builder, options); + return UseClientCertificateFromAzureKeyVault(builder, options, keyStorageFlags); } /// /// Use client certificate for authenticating against the BankID API from Azure KeyVault. /// - /// + /// The BankID builder. /// Callback to configure the Key Vault options. - /// - public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, Action configureOptions) + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// + /// The updated . + public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, Action configureOptions, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { var options = new ClientCertificateFromAzureKeyVaultOptions(); configureOptions(options); - return UseClientCertificateFromAzureKeyVault(builder, options); + return UseClientCertificateFromAzureKeyVault(builder, options, keyStorageFlags); } /// /// Use client certificate for authenticating against the BankID API from Azure Key Vault. /// - /// + /// The BankID builder. /// The Key Vault options. - /// - public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, ClientCertificateFromAzureKeyVaultOptions options) + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// + /// The updated . + public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, ClientCertificateFromAzureKeyVaultOptions options, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { if (string.IsNullOrWhiteSpace(options.AzureKeyVaultSecretName)) { @@ -48,7 +62,7 @@ public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdB { var keyVaultCertificateClient = AzureKeyVaultCertificateClient.Create(options); - return keyVaultCertificateClient.GetX509Certificate2(options.AzureKeyVaultSecretName); + return keyVaultCertificateClient.GetX509Certificate2(options.AzureKeyVaultSecretName, keyStorageFlags); }); return builder; @@ -58,36 +72,48 @@ public static IBankIdBuilder UseClientCertificateFromAzureKeyVault(this IBankIdB /// /// Add client certificate for authenticating against the BankID API from Azure Key Vault. /// - /// + /// The BankID builder. /// Configuration section to bind the Key Vault options from. - /// - public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, IConfigurationSection configurationSection) + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// + /// The updated . + public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, IConfigurationSection configurationSection, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { var options = new ClientCertificateFromAzureKeyVaultOptions(); configurationSection.Bind(options); - return AddClientCertificateFromAzureKeyVault(builder, options); + return AddClientCertificateFromAzureKeyVault(builder, options, keyStorageFlags); } /// /// Add client certificate for authenticating against the BankID API from Azure KeyVault. /// - /// + /// The BankID builder. /// Callback to configure the Key Vault options. - /// - public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, Action configureOptions) + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// + /// The updated . + public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, Action configureOptions, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { var options = new ClientCertificateFromAzureKeyVaultOptions(); configureOptions(options); - return AddClientCertificateFromAzureKeyVault(builder, options); + return AddClientCertificateFromAzureKeyVault(builder, options, keyStorageFlags); } /// /// Add client certificate for authenticating against the BankID API from Azure Key Vault. /// - /// - /// The Key Vault options. - /// - public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, ClientCertificateFromAzureKeyVaultOptions options) + /// The BankID builder. + /// The Azure Key Vault configuration options. + /// + /// Specifies how the private key of the certificate is stored and managed when loaded. + /// Defaults to . + /// + /// The updated . + public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdBuilder builder, ClientCertificateFromAzureKeyVaultOptions options, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { if (string.IsNullOrWhiteSpace(options.AzureKeyVaultSecretName)) { @@ -98,7 +124,7 @@ public static IBankIdBuilder AddClientCertificateFromAzureKeyVault(this IBankIdB { var keyVaultCertificateClient = AzureKeyVaultCertificateClient.Create(options); - return keyVaultCertificateClient.GetX509Certificate2(options.AzureKeyVaultSecretName); + return keyVaultCertificateClient.GetX509Certificate2(options.AzureKeyVaultSecretName, keyStorageFlags); }); return builder; diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj index 61576c5a..99e81bb0 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj @@ -7,12 +7,12 @@ - + - + diff --git a/src/ActiveLogin.Authentication.BankId.Core/ActiveLogin.Authentication.BankId.Core.csproj b/src/ActiveLogin.Authentication.BankId.Core/ActiveLogin.Authentication.BankId.Core.csproj index d5a77bef..9b240b0c 100644 --- a/src/ActiveLogin.Authentication.BankId.Core/ActiveLogin.Authentication.BankId.Core.csproj +++ b/src/ActiveLogin.Authentication.BankId.Core/ActiveLogin.Authentication.BankId.Core.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/ActiveLogin.Authentication.BankId.Core/BankIdCertificates.cs b/src/ActiveLogin.Authentication.BankId.Core/BankIdCertificates.cs index b6385d5c..21a99a92 100644 --- a/src/ActiveLogin.Authentication.BankId.Core/BankIdCertificates.cs +++ b/src/ActiveLogin.Authentication.BankId.Core/BankIdCertificates.cs @@ -17,35 +17,42 @@ internal static class BankIdCertificates public static X509Certificate2 GetBankIdApiRootCertificateProd() => GetCertFromResourceStream(BankIdApiRootCertificateProd); public static X509Certificate2 GetBankIdApiRootCertificateTest() => GetCertFromResourceStream(BankIdApiRootCertificateTest); - public static X509Certificate2 GetBankIdApiClientCertificateTest(TestCertificateFormat certificateFormat) => certificateFormat switch + public static X509Certificate2 GetBankIdApiClientCertificateTest(TestCertificateFormat certificateFormat, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) => certificateFormat switch { - TestCertificateFormat.P12 => GetCertFromResourceStream(BankIdApiClientCertificateTestP12), + TestCertificateFormat.P12 => GetCertFromResourceStream(BankIdApiClientCertificateTestP12, keyStorageFlags), TestCertificateFormat.PEM => GetPemCertFromResourceStream(BankIdApiClientCertificateTestPem), - TestCertificateFormat.PFX => GetCertFromResourceStream(BankIdApiClientCertificateTestPfx), - _ => GetCertFromResourceStream(BankIdApiClientCertificateTestPfx) + TestCertificateFormat.PFX => GetCertFromResourceStream(BankIdApiClientCertificateTestPfx, keyStorageFlags), + _ => GetCertFromResourceStream(BankIdApiClientCertificateTestPfx, keyStorageFlags) }; - private static X509Certificate2 GetCertFromResourceStream(CertificateResource resource) + private static X509Certificate2 GetCertFromResourceStream(CertificateResource resource, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { - return GetCertFromResourceStream(resource.Filename, resource.Password); + return GetCertFromResourceStream(resource.Filename, resource.Password, keyStorageFlags); } - private static X509Certificate2 GetCertFromResourceStream(string filename, string? password = null) + private static X509Certificate2 GetCertFromResourceStream(string filename, string? password = null, X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet) { - var certStream = GetBankIdResourceStream(filename); + using var certStream = GetBankIdResourceStream(filename); using var memory = new MemoryStream((int)certStream.Length); certStream.CopyTo(memory); - if (password == null) + + var certBytes = memory.ToArray(); + + if (password is null) return X509CertificateLoader.LoadCertificate(certBytes); + + var cert = X509CertificateLoader.LoadPkcs12(certBytes, password, keyStorageFlags); + + if (!cert.HasPrivateKey) { - return new X509Certificate2(memory.ToArray()); + throw new InvalidOperationException($"Certificate {filename} does not contain a private key."); } - return new X509Certificate2(memory.ToArray(), password); + return cert; } private static X509Certificate2 GetPemCertFromResourceStream(CertificateResource resource) { - var certStream = GetBankIdResourceStream(resource.Filename); + using var certStream = GetBankIdResourceStream(resource.Filename); using var streamReader = new StreamReader(certStream); var certAndKeyString = streamReader.ReadToEnd(); diff --git a/src/ActiveLogin.Authentication.BankId.Core/IBankIdBuilderExtensions.cs b/src/ActiveLogin.Authentication.BankId.Core/IBankIdBuilderExtensions.cs index c909888f..e5373e09 100644 --- a/src/ActiveLogin.Authentication.BankId.Core/IBankIdBuilderExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.Core/IBankIdBuilderExtensions.cs @@ -106,8 +106,7 @@ void ConfigureHttpClientHandler(IServiceProvider sp, SocketsHttpHandler httpClie /// public static IBankIdBuilder UseRootCaCertificate(this IBankIdBuilder builder, string certificateFilePath) { - builder.UseRootCaCertificate(() => new X509Certificate2(certificateFilePath)); - + builder.UseRootCaCertificate(() => X509CertificateLoader.LoadCertificateFromFile(certificateFilePath)); return builder; } @@ -207,12 +206,19 @@ internal static IBankIdBuilder UseEnvironment(this IBankIdBuilder builder, Uri a /// Use the BankID root certificate (for test) from the BankID documentation. /// Use the BankID client certificate (for test) from the BankID documentation. /// If using the BankID client certificate (for test). Select the preferred format p12, pem or pfx. + /// + /// Specifies how the private key for the BankID client certificate is loaded and stored. + /// Defaults to . + /// This parameter is only applicable to client certificates that contain a private key + /// (P12/PFX). It is ignored for root certificates and PEM-based certificates. + /// /// public static IBankIdBuilder UseTestEnvironment( this IBankIdBuilder builder, bool useBankIdRootCertificate = true, bool useBankIdClientCertificate = true, - TestCertificateFormat clientCertificateFormat = TestCertificateFormat.PFX + TestCertificateFormat clientCertificateFormat = TestCertificateFormat.PFX, + X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet ) { builder.UseEnvironment(BankIdUrls.AppApiTestBaseUrl, BankIdUrls.VerifyApiTestBaseUrl, BankIdEnvironments.Test); @@ -226,7 +232,7 @@ public static IBankIdBuilder UseTestEnvironment( if (useBankIdClientCertificate) { - var cert = BankIdCertificates.GetBankIdApiClientCertificateTest(clientCertificateFormat); + var cert = BankIdCertificates.GetBankIdApiClientCertificateTest(clientCertificateFormat, keyStorageFlags); builder.UseClientCertificate(() => cert); } diff --git a/src/ActiveLogin.Authentication.BankId.QRCoder/ActiveLogin.Authentication.BankId.QRCoder.csproj b/src/ActiveLogin.Authentication.BankId.QRCoder/ActiveLogin.Authentication.BankId.QRCoder.csproj index 1b791949..db00bcac 100644 --- a/src/ActiveLogin.Authentication.BankId.QRCoder/ActiveLogin.Authentication.BankId.QRCoder.csproj +++ b/src/ActiveLogin.Authentication.BankId.QRCoder/ActiveLogin.Authentication.BankId.QRCoder.csproj @@ -8,11 +8,11 @@ - + - + diff --git a/src/ActiveLogin.Authentication.BankId.UAParser/ActiveLogin.Authentication.BankId.UAParser.csproj b/src/ActiveLogin.Authentication.BankId.UAParser/ActiveLogin.Authentication.BankId.UAParser.csproj index 28af9d57..c2876a1b 100644 --- a/src/ActiveLogin.Authentication.BankId.UAParser/ActiveLogin.Authentication.BankId.UAParser.csproj +++ b/src/ActiveLogin.Authentication.BankId.UAParser/ActiveLogin.Authentication.BankId.UAParser.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c7d02904..406cc41d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - net8.0 + net10.0 latest enable enable diff --git a/test/ActiveLogin.Authentication.BankId.Api.Test/ActiveLogin.Authentication.BankId.Api.Test.csproj b/test/ActiveLogin.Authentication.BankId.Api.Test/ActiveLogin.Authentication.BankId.Api.Test.csproj index fa8a8d3e..d6874493 100644 --- a/test/ActiveLogin.Authentication.BankId.Api.Test/ActiveLogin.Authentication.BankId.Api.Test.csproj +++ b/test/ActiveLogin.Authentication.BankId.Api.Test/ActiveLogin.Authentication.BankId.Api.Test.csproj @@ -5,10 +5,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/ActiveLogin.Authentication.BankId.AspNetCore.Test.csproj b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/ActiveLogin.Authentication.BankId.AspNetCore.Test.csproj index 3d60ecd3..9cec2827 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/ActiveLogin.Authentication.BankId.AspNetCore.Test.csproj +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/ActiveLogin.Authentication.BankId.AspNetCore.Test.csproj @@ -5,14 +5,15 @@ - + - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs index bf3f19e6..ee4ca53d 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiAuth_Tests.cs @@ -26,6 +26,7 @@ using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Moq; @@ -70,19 +71,12 @@ public BankId_UiAuth_Tests() .Returns("Ignored"); } + [Fact] public async Task BankIdUiAuthController_Returns_404_If_BankId_Is_Not_Registered() { - // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddAuthentication(); - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.GetAsync("/ActiveLogin/BankId/Auth"); @@ -95,15 +89,8 @@ public async Task BankIdUiAuthController_Returns_404_If_BankId_Is_Not_Registered public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registered() { // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddAuthentication(); - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.PostAsync("/ActiveLogin/BankId/Auth/Api/Initialize", null); @@ -116,7 +103,7 @@ public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registe public async Task Challenge_Redirects_To_Login() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -141,7 +128,7 @@ public async Task Challenge_Redirects_To_Login() public async Task Challenge_Redirects_To_Login_With_Path_Base() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -192,7 +179,7 @@ await context.ChallengeAsync( public async Task Authentication_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -233,7 +220,7 @@ public async Task Init_Returns_Ui_With_Resolved_Cancel_Url() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(options); - using var server = CreateServer(o => + using var server = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -267,7 +254,7 @@ public async Task Init_Returns_Ui_With_Resolved_Cancel_Url() public async Task Init_Returns_Ui_With_Script() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -307,7 +294,7 @@ public async Task Init_Returns_Ui_With_Script() public async Task Init_Requires_State_And_UiOptions_Cookie_To_Be_Present() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateAuthTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -348,7 +335,7 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); - using var server = CreateServer( + using var server = TestHostFactory.CreateAuthTestServer( o => { o.UseSimulatedEnvironment(); @@ -370,7 +357,7 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() // Arrange acting request var testReturnUrl = "/TestReturnUrl"; - var initializeRequestBody = new {returnUrl = testReturnUrl}; + var initializeRequestBody = new { returnUrl = testReturnUrl }; // Act var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); @@ -380,7 +367,7 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() var responseContent = await initializeTransaction.Content.ReadAsStringAsync(); var responseObject = JsonConvert.DeserializeAnonymousType(responseContent, - new {RedirectUri = "", OrderRef = "", IsAutoLaunch = false}); + new { RedirectUri = "", OrderRef = "", IsAutoLaunch = false }); Assert.True(responseObject.IsAutoLaunch); var encodedReturnParam = UrlEncoder.Default.Encode(testReturnUrl); @@ -402,7 +389,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); - using var server = CreateServer( + using var server = TestHostFactory.CreateAuthTestServer( o => { o.UseSimulatedEnvironment(); @@ -420,17 +407,13 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() { services.AddTransient(s => mockProtector.Object); services.AddTransient(s => _bankIdUiStateProtector.Object); - services.AddMvc().AddJsonOptions(configure => - { - configure.JsonSerializerOptions.PropertyNamingPolicy = null; - }); }); // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; - var initializeRequestBody = new {returnUrl = testReturnUrl, uiOptions = testOptions}; + var initializeRequestBody = new { returnUrl = testReturnUrl, uiOptions = testOptions }; //Act var initializeTransaction = await GetInitializeResponse(server, initializeRequestBody); @@ -454,7 +437,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); - using var server = CreateServer( + using var server = TestHostFactory.CreateAuthTestServer( o => { o.UseSimulatedEnvironment(); @@ -502,7 +485,7 @@ public async Task Cancel_Calls_CancelApi() var testBankIdApi = new TestBankIdAppApi(new BankIdSimulatedAppApiClient()); - using var server = CreateServer( + using var server = TestHostFactory.CreateAuthTestServer( o => { o.UseSimulatedEnvironment(); @@ -533,7 +516,7 @@ public async Task Cancel_Calls_CancelApi() // Arrange acting request var testReturnUrl = "/TestReturnUrl"; var testOptions = "TestOptions"; - var initializeRequest = new JsonContent(new {returnUrl = testReturnUrl, uiOptions = testOptions}); + var initializeRequest = new JsonContent(new { returnUrl = testReturnUrl, uiOptions = testOptions }); initializeRequest.Headers.Add("Cookie", loginCookies); initializeRequest.Headers.Add("RequestVerificationToken", csrfToken); @@ -543,11 +526,13 @@ public async Task Cancel_Calls_CancelApi() await client.PostAsync("/ActiveLogin/BankId/Auth/Api/Initialize", initializeRequest); var initializeResponseContent = await initializeTransaction.Content.ReadAsStringAsync(); var initializeObject = JsonConvert.DeserializeAnonymousType(initializeResponseContent, - new {RedirectUri = "", OrderRef = "", IsAutoLaunch = false}); + new { RedirectUri = "", OrderRef = "", IsAutoLaunch = false }); var cancelRequest = new JsonContent(new { - orderRef = initializeObject.OrderRef, uiOptions = "TestOptions", cancelReturnUrl = "/" + orderRef = initializeObject.OrderRef, + uiOptions = "TestOptions", + cancelReturnUrl = "/" }); cancelRequest.Headers.Add("Cookie", loginCookies); cancelRequest.Headers.Add("RequestVerificationToken", csrfToken); @@ -560,31 +545,6 @@ public async Task Cancel_Calls_CancelApi() Assert.True(testBankIdApi.CancelAsyncIsCalled); } - private TestServer CreateServer( - Action configureBankId, - Action configureBankIdAuth, - Action configureApplication, - Action configureServices = null) - { - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => - { - configureApplication.Invoke(app); - }) - .ConfigureServices(services => - { - services.AddBankId(configureBankId); - services.AddAuthentication() - .AddCookie() - .AddBankIdAuth(configureBankIdAuth); - services.AddMvc(); - configureServices?.Invoke(services); - }); - - return new TestServer(webHostBuilder); - } - private static Action DefaultAppConfiguration(Func testpath) { return app => diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs index bea272ec..fc74f5e8 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiPayment_Tests.cs @@ -87,14 +87,8 @@ public BankId_UiPayment_Tests() public async Task BankIdUiPaymentController_Returns_404_If_BankId_Is_Not_Registered() { // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.GetAsync("/ActiveLogin/BankId/Payment"); @@ -107,14 +101,8 @@ public async Task BankIdUiPaymentController_Returns_404_If_BankId_Is_Not_Registe public async Task BankIdUiPaymentApiController_Returns_404_If_BankId_Is_Not_Registered() { // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.PostAsync("/ActiveLogin/BankId/Payment/Api/Initialize", null); @@ -127,7 +115,7 @@ public async Task BankIdUiPaymentApiController_Returns_404_If_BankId_Is_Not_Regi public async Task InitiatePayment_Redirects_To_Payment() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -153,7 +141,7 @@ public async Task InitiatePayment_Redirects_To_Payment() public async Task InitiatePaymentAsync_Redirects_To_Payment_Without_Path_Base() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -197,7 +185,7 @@ public async Task InitiatePaymentAsync_Redirects_To_Payment_Without_Path_Base() public async Task Payment_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -238,7 +226,7 @@ public async Task PaymentInit_Returns_Ui_With_Resolved_Cancel_Url() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(options); - using var server = CreateServer(o => + using var server = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -271,7 +259,7 @@ public async Task PaymentInit_Returns_Ui_With_Resolved_Cancel_Url() public async Task PaymentInit_Returns_Ui_With_Script() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -311,7 +299,7 @@ public async Task PaymentInit_Returns_Ui_With_Script() public async Task PaymentInit_Requires_State_Cookie_To_Be_Present() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreatePaymentTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -348,7 +336,7 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() .Setup(protector => protector.Retrieve()) .Returns(autoLaunchOptions); - using var server = CreateServer( + using var server = TestHostFactory.CreatePaymentTestServer( o => { o.UseSimulatedEnvironment(); @@ -401,7 +389,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); - using var server = CreateServer( + using var server = TestHostFactory.CreatePaymentTestServer( o => { o.UseSimulatedEnvironment(); @@ -459,7 +447,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); - using var server = CreateServer( + using var server = TestHostFactory.CreatePaymentTestServer( o => { o.UseSimulatedEnvironment(); @@ -505,7 +493,7 @@ public async Task Cancel_Calls_CancelApi() .Returns(autoLaunchOptions); var testBankIdApi = new TestBankIdAppApi(new BankIdSimulatedAppApiClient()); - using var server = CreateServer( + using var server = TestHostFactory.CreatePaymentTestServer( o => { o.UseSimulatedEnvironment(); @@ -542,29 +530,6 @@ public async Task Cancel_Calls_CancelApi() Assert.Equal(HttpStatusCode.OK, cancelTransaction.StatusCode); Assert.True(testBankIdApi.CancelAsyncIsCalled); } - - private TestServer CreateServer( - Action configureBankId, - Action configureBankIdPayment, - Action configureApplication, - Action configureServices = null) - { - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => - { - configureApplication.Invoke(app); - }) - .ConfigureServices(services => - { - services.AddBankId(configureBankId); - services.AddBankIdPayment(configureBankIdPayment); - services.AddMvc(); - configureServices?.Invoke(services); - }); - - return new TestServer(webHostBuilder); - } private static Action DefaultAppConfiguration(Func testpath) { diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs index 7dc3ed61..f45481a9 100644 --- a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/BankId_UiSign_Tests.cs @@ -84,14 +84,8 @@ public BankId_UiSign_Tests() public async Task BankIdUiSignController_Returns_404_If_BankId_Is_Not_Registered() { // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.GetAsync("/ActiveLogin/BankId/Sign"); @@ -104,14 +98,8 @@ public async Task BankIdUiSignController_Returns_404_If_BankId_Is_Not_Registered public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registered() { // Arrange - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => DefaultAppConfiguration(x => Task.CompletedTask)) - .ConfigureServices(services => - { - services.AddMvc(); - }); - using var client = new TestServer(webHostBuilder).CreateClient(); + var server = TestHostFactory.CreateTestServer(); + using var client = server.CreateClient(); // Act var transaction = await client.PostAsync("/ActiveLogin/BankId/Sign/Api/Initialize", null); @@ -124,7 +112,7 @@ public async Task BankIdUiAuthApiController_Returns_404_If_BankId_Is_Not_Registe public async Task InitiateSign_Redirects_To_Sign() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -150,7 +138,7 @@ public async Task InitiateSign_Redirects_To_Sign() public async Task InitiateSignAsync_Redirects_To_Sign_Without_Path_Base() { // Arrange - using var client = CreateServer(o => + using var client = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -194,7 +182,7 @@ public async Task InitiateSignAsync_Redirects_To_Sign_Without_Path_Base() public async Task Sign_UI_Should_Be_Accessible_Even_When_Site_Requires_Auth() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -235,7 +223,7 @@ public async Task SignInit_Returns_Ui_With_Resolved_Cancel_Url() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(options); - using var server = CreateServer(o => + using var server = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -268,7 +256,7 @@ public async Task SignInit_Returns_Ui_With_Resolved_Cancel_Url() public async Task SignInit_Returns_Ui_With_Script() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -308,7 +296,7 @@ public async Task SignInit_Returns_Ui_With_Script() public async Task SignInit_Requires_State_Cookie_To_Be_Present() { // Arrange - using var server = CreateServer(o => + using var server = TestHostFactory.CreateSignTestServer(o => { o.UseSimulatedEnvironment(); }, @@ -345,7 +333,7 @@ public async Task AutoLaunch_Sets_Correct_RedirectUri() .Setup(protector => protector.Retrieve()) .Returns(autoLaunchOptions); - using var server = CreateServer( + using var server = TestHostFactory.CreateSignTestServer( o => { o.UseSimulatedEnvironment(); @@ -398,7 +386,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http200Ok() .Setup(protector => protector.Protect(It.IsAny())) .Returns("Ignored"); - using var server = CreateServer( + using var server = TestHostFactory.CreateSignTestServer( o => { o.UseSimulatedEnvironment(); @@ -456,7 +444,7 @@ public async Task Api_Always_Returns_CamelCase_Json_For_Http400BadRequest() .Setup(protector => protector.Unprotect(It.IsAny())) .Returns(autoLaunchOptions); - using var server = CreateServer( + using var server = TestHostFactory.CreateSignTestServer( o => { o.UseSimulatedEnvironment(); @@ -502,7 +490,7 @@ public async Task Cancel_Calls_CancelApi() .Returns(autoLaunchOptions); var testBankIdApi = new TestBankIdAppApi(new BankIdSimulatedAppApiClient()); - using var server = CreateServer( + using var server = TestHostFactory.CreateSignTestServer( o => { o.UseSimulatedEnvironment(); @@ -540,29 +528,6 @@ public async Task Cancel_Calls_CancelApi() Assert.True(testBankIdApi.CancelAsyncIsCalled); } - private TestServer CreateServer( - Action configureBankId, - Action configureBankIdSign, - Action configureApplication, - Action configureServices = null) - { - var webHostBuilder = new WebHostBuilder() - .UseSolutionRelativeContentRoot(Path.Combine("test", "ActiveLogin.Authentication.BankId.AspNetCore.Test")) - .Configure(app => - { - configureApplication.Invoke(app); - }) - .ConfigureServices(services => - { - services.AddBankId(configureBankId); - services.AddBankIdSign(configureBankIdSign); - services.AddMvc(); - configureServices?.Invoke(services); - }); - - return new TestServer(webHostBuilder); - } - private static Action DefaultAppConfiguration(Func testpath) { return app => diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/Helpers/TestHostFactory.cs b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/Helpers/TestHostFactory.cs new file mode 100644 index 00000000..70c3a46e --- /dev/null +++ b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/Helpers/TestHostFactory.cs @@ -0,0 +1,198 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; + +using ActiveLogin.Authentication.BankId.AspNetCore.Auth; +using ActiveLogin.Authentication.BankId.AspNetCore.Payment; +using ActiveLogin.Authentication.BankId.AspNetCore.Sign; +using ActiveLogin.Authentication.BankId.Core; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace ActiveLogin.Authentication.BankId.AspNetCore.Test.Helpers; + +internal static class TestHostFactory +{ + + public static TestServer CreateAuthTestServer( + Action configureBankId, + Action configureBankIdAuth, + Action configureApplication, + Action configureServices = null) + { + var solutionRoot = Path.GetFullPath( + Path.Combine(AppContext.BaseDirectory, "../../../../..")); + + var contentRoot = Path.Combine( + solutionRoot, + "test", + "ActiveLogin.Authentication.BankId.AspNetCore.Test"); + + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + ContentRootPath = contentRoot + }); + + builder.WebHost.UseTestServer(); + + builder.Services.AddBankId(configureBankId); + + builder.Services + .AddAuthentication() + .AddCookie() + .AddBankIdAuth(configureBankIdAuth); + + builder.Services + .AddControllersWithViews() + .AddApplicationPart( + typeof(ActiveLogin.Authentication.BankId.AspNetCore.BankIdConstants).Assembly) + .AddRazorRuntimeCompilation(); + + configureServices?.Invoke(builder.Services); + + var app = builder.Build(); + + configureApplication(app); + + app.Start(); + + return app.GetTestServer(); + } + + public static TestServer CreateSignTestServer( + Action configureBankId, + Action configureBankIdSign, + Action configureApplication, + Action configureServices = null) + { + var solutionRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../..")); + + var contentRoot = Path.Combine( + solutionRoot, + "test", + "ActiveLogin.Authentication.BankId.AspNetCore.Test"); + + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + ContentRootPath = contentRoot + }); + + builder.WebHost.UseTestServer(); + + builder.Services.AddBankId(configureBankId); + builder.Services.AddBankIdSign(configureBankIdSign); + + builder.Services + .AddControllersWithViews() + .AddApplicationPart( + typeof(ActiveLogin.Authentication.BankId.AspNetCore.BankIdConstants).Assembly) + .AddRazorRuntimeCompilation(); + + configureServices?.Invoke(builder.Services); + + var app = builder.Build(); + + configureApplication(app); + + app.Start(); + + return app.GetTestServer(); + } + + public static TestServer CreatePaymentTestServer( + Action configureBankId, + Action configureBankIdPayment, + Action configureApplication, + Action configureServices = null) + { + + var solutionRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../..")); + + var contentRoot = Path.Combine( + solutionRoot, + "test", + "ActiveLogin.Authentication.BankId.AspNetCore.Test"); + + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + ContentRootPath = contentRoot + }); + + builder.WebHost.UseTestServer(); + + builder.Services.AddBankId(configureBankId); + builder.Services.AddBankIdPayment(configureBankIdPayment); + + builder.Services + .AddControllersWithViews() + .AddApplicationPart( + typeof(ActiveLogin.Authentication.BankId.AspNetCore.BankIdConstants).Assembly) + .AddRazorRuntimeCompilation(); + + configureServices?.Invoke(builder.Services); + + var app = builder.Build(); + + configureApplication(app); + + app.Start(); + + return app.GetTestServer(); + } + + + public static TestServer CreateTestServer() + { + var solutionRoot = Path.GetFullPath( + Path.Combine(AppContext.BaseDirectory, "../../../../..")); + + var contentRoot = Path.Combine( + solutionRoot, + "test", + "ActiveLogin.Authentication.BankId.AspNetCore.Test"); + + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + ContentRootPath = contentRoot + }); + + builder.WebHost.UseTestServer(); + + builder.Services.AddAuthentication(); + builder.Services.AddAuthorization(); + + var app = builder.Build(); + + DefaultAppConfiguration(_ => Task.CompletedTask)(app); + + app.Start(); + + return app.GetTestServer(); + } + + private static Action DefaultAppConfiguration(Func testpath) + { + return app => + { + app.UseMiddleware(IPAddress.Parse("192.0.2.1")); + app.UseMiddleware(FakeUserAgentMiddleware.DefaultUserAgent); + app.UseMiddleware("https://localhost:3000"); + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.Use(async (context, next) => + { + await testpath(context); + await next(); + }); + }; + } +} diff --git a/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/VIews/Shared/_Layout.cshtml b/test/ActiveLogin.Authentication.BankId.AspNetCore.Test/Views/Shared/_Layout.cshtml similarity index 100% rename from test/ActiveLogin.Authentication.BankId.AspNetCore.Test/VIews/Shared/_Layout.cshtml rename to test/ActiveLogin.Authentication.BankId.AspNetCore.Test/Views/Shared/_Layout.cshtml diff --git a/test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test.csproj b/test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test.csproj index a0d4a98e..e811754e 100644 --- a/test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test.csproj +++ b/test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test/ActiveLogin.Authentication.BankId.AzureKeyVault.Test.csproj @@ -4,9 +4,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/ActiveLogin.Authentication.BankId.Core.Test/ActiveLogin.Authentication.BankId.Core.Test.csproj b/test/ActiveLogin.Authentication.BankId.Core.Test/ActiveLogin.Authentication.BankId.Core.Test.csproj index cc0b8a8c..4cc061cd 100644 --- a/test/ActiveLogin.Authentication.BankId.Core.Test/ActiveLogin.Authentication.BankId.Core.Test.csproj +++ b/test/ActiveLogin.Authentication.BankId.Core.Test/ActiveLogin.Authentication.BankId.Core.Test.csproj @@ -5,10 +5,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/test/ActiveLogin.Authentication.BankId.UAParser.Test/ActiveLogin.Authentication.BankId.UAParser.Test.csproj b/test/ActiveLogin.Authentication.BankId.UAParser.Test/ActiveLogin.Authentication.BankId.UAParser.Test.csproj index 4e0b5595..1ea4e012 100644 --- a/test/ActiveLogin.Authentication.BankId.UAParser.Test/ActiveLogin.Authentication.BankId.UAParser.Test.csproj +++ b/test/ActiveLogin.Authentication.BankId.UAParser.Test/ActiveLogin.Authentication.BankId.UAParser.Test.csproj @@ -5,14 +5,14 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 63619147..ab307410 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,6 +1,6 @@ - net8.0 + net10.0 latest true