From db83439942d8beff90f77d5a93f0548dcfd694c8 Mon Sep 17 00:00:00 2001 From: CDR-AidenJ Date: Wed, 25 Jun 2025 16:11:05 +1000 Subject: [PATCH] v3.0.1 release (#86) Co-authored-by: CDR Open Source --- .azuredevops/pipelines/build-v2.yml | 113 +++-- .azuredevops/pipelines/set-tag-name.yml | 33 ++ .github/workflows/codeql-analysis.yml | 8 +- .github/workflows/dotnet.yml | 2 +- .github/workflows/test-report.yml | 6 +- CHANGELOG.md | 4 + Help/container/HELP.md | 2 +- README.md | 4 +- Source/.editorconfig | 99 ---- Source/CDR.DCR/CDR.DCR.csproj | 2 +- .../CDR.DataRecipient.API.Logger.csproj | 2 +- .../IRequestResponseLogger.cs | 12 +- .../LoggerExtensions.cs | 14 +- .../RequestResponseLogger.cs | 27 +- .../RequestResponseLoggingMiddleware.cs | 162 +++--- .../CDR.DataRecipient.E2ETests.csproj | 2 +- .../CDR.DataRecipient.IntegrationTests.csproj | 2 +- .../CDR.DataRecipient.Repository.SQL.csproj | 10 +- .../Extensions/SqlExtensions.cs | 8 +- .../ISqlDataAccess.cs | 4 +- ...cipientDatabaseContextDesignTimeFactory.cs | 1 - .../SqlConsentsRepository.cs | 42 +- .../SqlDataAccess.cs | 208 ++++---- .../SqlDataHoldersRepository.cs | 24 +- .../SqlRegistrationsRepository.cs | 33 +- .../Extensions/HttpClientHandlerExtensions.cs | 29 ++ .../Extensions/HttpExtensions.cs | 8 +- .../Extensions/TokenExtensions.cs | 14 +- .../Extensions/UrlExtensions.cs | 8 +- .../{Enumerations.cs => Industry.cs} | 0 .../{MessageEnum.cs => Message.cs} | 0 Source/CDR.DataRecipient.SDK/Models/Acr.cs | 13 + .../Models/{AuthDetails.cs => AuthDetail.cs} | 0 .../Models/AuthorisationRequestClaims.cs | 49 +- .../AuthorisationRequestClaimsUserInfo.cs | 13 + .../Models/Certificate.cs | 14 +- .../CDR.DataRecipient.SDK/Models/DRBrand.cs | 18 + .../CDR.DataRecipient.SDK/Models/DRProduct.cs | 26 + .../Models/DataRecipientModel.cs | 34 -- .../Models/DataRecipientViewModel.cs | 13 - .../Models/DcrResponse.cs | 6 +- Source/CDR.DataRecipient.SDK/Models/Error.cs | 2 +- .../CDR.DataRecipient.SDK/Models/ErrorList.cs | 16 +- .../CDR.DataRecipient.SDK/Models/IdToken.cs | 15 + .../Models/JsonWebKey.cs | 25 +- .../Models/JsonWebKeySet.cs | 7 +- .../Models/MtlsAliases.cs | 25 + .../Models/OidcDiscovery.cs | 88 ++-- Source/CDR.DataRecipient.SDK/Models/Pkce.cs | 18 +- .../Models/Registration.cs | 14 +- .../CDR.DataRecipient.SDK/Models/Response.cs | 5 - .../Models/Response{T}.cs | 9 + .../Models/ServiceConfiguration.cs | 22 +- .../Models/SoftwareProduct.cs | 2 +- .../Models/SoftwareProductViewModel.cs | 15 + .../Models/{TokenResponse.cs => Token.cs} | 0 .../Services/BaseService.cs | 29 +- .../DynamicClientRegistrationService.cs | 45 +- .../Services/DataHolder/InfosecService.cs | 62 +-- .../Services/PrivateKeyJwt.cs | 16 +- .../Services/Register/IMetadataService.cs | 4 +- .../Services/Register/InfosecService.cs | 14 +- .../Services/Register/MetadataService.cs | 20 +- .../Services/Register/SsaService.cs | 10 +- .../Services/Tokens/AccessTokenService.cs | 42 +- .../CDR.DataRecipient.Web.csproj | 4 +- .../Caching/CacheManager.cs | 21 +- .../Caching/ICacheManager.cs | 3 +- .../CDR.DataRecipient.Web/Common/Constants.cs | 78 +-- .../Common/DataHolderDiscoveryCache.cs | 42 +- .../Common/Extensions.cs | 7 +- .../Common/IDataHolderDiscoveryCache.cs | 4 +- .../Common/OidcSettingsProvider.cs | 14 +- .../Controllers/ApiController.cs | 14 +- .../Controllers/ConsentController.cs | 467 +++++++++--------- .../Controllers/DataHoldersController.cs | 73 ++- .../DataSharingBankingController.cs | 26 +- .../DataSharingCommonController.cs | 26 +- .../Controllers/DataSharingControllerBase.cs | 137 +++-- .../DataSharingEnergyController.cs | 28 +- .../DynamicClientRegistrationController.cs | 413 ++++++++-------- .../Controllers/HealthController.cs | 2 +- .../Controllers/HomeController.cs | 34 +- .../Controllers/JwksController.cs | 68 +-- .../Controllers/OidcController.cs | 30 +- .../Controllers/ParController.cs | 115 +++-- .../Controllers/RevocationController.cs | 97 ++-- .../Controllers/SettingsController.cs | 12 +- .../Controllers/SsaController.cs | 83 ++-- .../Controllers/UtilitiesController.cs | 16 +- Source/CDR.DataRecipient.Web/Extensions.cs | 7 +- .../Extensions/CacheExtensions.cs | 4 +- .../Extensions/ClaimsPrincipalExtensions.cs | 6 +- .../Extensions/ConfigExtensions.cs | 4 +- .../Extensions/KeyExtensions.cs | 4 +- .../Extensions/LinqExtensions.cs | 4 +- .../Features/{Enums.cs => Feature.cs} | 0 .../Filters/LogActionEntryAttribute.cs | 8 +- .../Filters/MustConsumeAttribute.cs | 8 +- .../ClientAuthorizationMiddleware.cs | 44 +- .../CDR.DataRecipient.Web/Models/BaseModel.cs | 12 +- .../Models/ConsentCallbackGetModel.cs | 11 + .../Models/ConsentCallbackPostModel.cs | 25 + .../Models/DataHoldersModel.cs | 18 +- .../Models/DynamicClientRegistrationModel.cs | 14 +- .../Models/ErrorListModel.cs | 2 +- .../Models/ErrorModel.cs | 10 +- .../Models/ErrorViewModel.cs | 6 +- .../Models/HttpRequestModel.cs | 4 + .../CDR.DataRecipient.Web/Models/ParModel.cs | 6 +- .../Models/PrivateKeyJwtModel.cs | 2 + .../Models/RegistrationsModel.cs | 8 +- .../Models/SettingsModel.cs | 9 +- .../CDR.DataRecipient.Web/Models/SsaModel.cs | 6 +- Source/CDR.DataRecipient.Web/Program.cs | 63 ++- Source/CDR.DataRecipient.Web/Startup.cs | 123 +++-- .../CDR.DiscoverDataHolders.csproj | 2 +- Source/Directory.Build.props | 5 +- .../docker-compose.TestsBase.yml | 2 +- .../docker-compose.UnitTests.yml | 2 +- Source/DockerCompose/docker-compose.yml | 2 +- Source/docker-compose.yml | 2 +- 122 files changed, 1961 insertions(+), 1850 deletions(-) create mode 100644 .azuredevops/pipelines/set-tag-name.yml create mode 100644 Source/CDR.DataRecipient.SDK/Extensions/HttpClientHandlerExtensions.cs rename Source/CDR.DataRecipient.SDK/{Enumerations.cs => Industry.cs} (100%) rename Source/CDR.DataRecipient.SDK/{MessageEnum.cs => Message.cs} (100%) create mode 100644 Source/CDR.DataRecipient.SDK/Models/Acr.cs rename Source/CDR.DataRecipient.SDK/Models/{AuthDetails.cs => AuthDetail.cs} (100%) create mode 100644 Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaimsUserInfo.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/DRBrand.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/DRProduct.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/IdToken.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/MtlsAliases.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/Response{T}.cs create mode 100644 Source/CDR.DataRecipient.SDK/Models/SoftwareProductViewModel.cs rename Source/CDR.DataRecipient.SDK/Models/{TokenResponse.cs => Token.cs} (100%) rename Source/CDR.DataRecipient.Web/Features/{Enums.cs => Feature.cs} (100%) create mode 100644 Source/CDR.DataRecipient.Web/Models/ConsentCallbackGetModel.cs create mode 100644 Source/CDR.DataRecipient.Web/Models/ConsentCallbackPostModel.cs diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml index fca897e..64b3860 100644 --- a/.azuredevops/pipelines/build-v2.yml +++ b/.azuredevops/pipelines/build-v2.yml @@ -6,14 +6,19 @@ schedules: include: - develop +parameters: + # https://github.com/microsoft/azure-pipelines-yaml/blob/master/design/runtime-parameters.md#syntax + - name: MockRegisterOverrideDependentImageTag + type: string + default: ' ' # default value; if no default, then the parameter MUST be given by the user at runtime + - name: MdhBankingOverrideDependentImageTag + type: string + default: ' ' # default value; if no default, then the parameter MUST be given by the user at runtime + - name: MdhEnergyOverrideDependentImageTag + type: string + default: ' ' # default value; if no default, then the parameter MUST be given by the user at runtime variables: - - ${{ if eq(variables['Build.SourceBranchName'], 'main') }}: - containerTag: main - ${{ else }}: - containerTag: develop - baseSourceDirectory: $(Build.SourcesDirectory)/Source dockerComposeDirectory: $(baseSourceDirectory)/DockerCompose @@ -30,6 +35,27 @@ jobs: timeoutInMinutes: 120 steps: + # set the register tag to use based on the logic in the template file + - template: set-tag-name.yml + parameters: + name: RegisterTag + input: ${{ parameters.MockRegisterOverrideDependentImageTag }} + context: $[replace(variables['Build.SourceBranch'], 'refs/heads/', '')] + + # set the DataHolderBankingTag to use + - template: set-tag-name.yml + parameters: + name: DataHolderBankingTag + input: ${{ parameters.MdhBankingOverrideDependentImageTag }} + context: $[replace(variables['Build.SourceBranch'], 'refs/heads/', '')] + + # set the DataHolderEnergyTag to use + - template: set-tag-name.yml + parameters: + name: DataHolderEnergyTag + input: ${{ parameters.MdhEnergyOverrideDependentImageTag }} + context: $[replace(variables['Build.SourceBranch'], 'refs/heads/', '')] + # Build mock-data-recipient - task: Docker@2 displayName: Build mock-data-recipient image @@ -40,13 +66,12 @@ jobs: repository: mock-data-recipient tags: latest - # Login to ACR + # Login to Shared ACR - task: Docker@2 - displayName: Login to ACR - condition: always() + displayName: Login to Shared ACR inputs: - command: login - containerRegistry: $(AcrBaseUrl) + command: login + containerRegistry: $(SpSharedAcr) # Pull and re-tag images from Azure ACR - task: Bash@3 @@ -54,12 +79,12 @@ jobs: inputs: targetType: inline script: | - docker pull $(AcrBaseUrl).azurecr.io/mock-register:$(containerTag) - docker pull $(AcrBaseUrl).azurecr.io/mock-data-holder:$(containerTag) - docker pull $(AcrBaseUrl).azurecr.io/mock-data-holder-energy:$(containerTag) - docker tag $(AcrBaseUrl).azurecr.io/mock-register:$(containerTag) mock-register:latest - docker tag $(AcrBaseUrl).azurecr.io/mock-data-holder:$(containerTag) mock-data-holder:latest - docker tag $(AcrBaseUrl).azurecr.io/mock-data-holder-energy:$(containerTag) mock-data-holder-energy:latest + docker pull $(SharedAcrBaseUrl).azurecr.io/mock-register:$(RegisterTag) + docker pull $(SharedAcrBaseUrl).azurecr.io/mock-data-holder:$(DataHolderBankingTag) + docker pull $(SharedAcrBaseUrl).azurecr.io/mock-data-holder-energy:$(DataHolderEnergyTag) + docker tag $(SharedAcrBaseUrl).azurecr.io/mock-register:$(RegisterTag) mock-register:latest + docker tag $(SharedAcrBaseUrl).azurecr.io/mock-data-holder:$(DataHolderBankingTag) mock-data-holder:latest + docker tag $(SharedAcrBaseUrl).azurecr.io/mock-data-holder-energy:$(DataHolderEnergyTag) mock-data-holder-energy:latest # List docker images - task: Docker@2 @@ -175,7 +200,7 @@ jobs: docker run \ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults/results.trx:/app/results.trx:ro \ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults/formatted/:/app/out/:rw \ - $(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR" --outputprefix "MDR" -o out/ + $(SharedAcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR" --outputprefix "MDR" -o out/ displayName: 'Run trx-formatter for integration tests' condition: always() @@ -190,7 +215,7 @@ jobs: docker run \ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/results.trx:/app/results.trx:ro \ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/formatted/:/app/out/:rw \ - $(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR-E2E" --outputprefix "MDR-E2E" -o out/ + $(SharedAcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR-E2E" --outputprefix "MDR-E2E" -o out/ displayName: 'Run trx-formatter for E2E tests' condition: always() @@ -230,32 +255,34 @@ jobs: - publish: $(baseSourceDirectory)/CDR.DataRecipient.Repository.SQL/efbundle displayName: Publish EF Migration bundle condition: always() - artifact: Database Migration Scripts - - - task: Docker@2 - displayName: 'Re-Tag Mock Data Recipient image with :branch-name' - inputs: - containerRegistry: $(AcrBaseUrl) - repository: 'mock-data-recipient' - command: tag - arguments: 'mock-data-recipient $(AcrBaseUrl).azurecr.io/mock-data-recipient:$(Build.SourceBranchName)' + artifact: Database Migration Scripts - - task: Docker@2 - displayName: 'Re-Tag Mock Data Recipient image with :latest (for develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) + - task: Bash@3 + displayName: 'Tag and push Mock Data Recipient image with Source Branch Name for any successful builds.' + condition: succeeded() inputs: - containerRegistry: $(AcrBaseUrl) - repository: 'mock-data-recipient' - command: tag - arguments: 'mock-data-recipient $(AcrBaseUrl).azurecr.io/mock-data-recipient:latest' + targetType: inline + script: | + echo Tagging mock-data-recipient with Source Branch Name: $(Build.SourceBranchName) + docker tag mock-data-recipient $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient:$(Build.SourceBranchName) - - task: CmdLine@2 - displayName: 'Push Mock Data Recipient image with :branch-name tag to ACR' - inputs: - script: 'docker push $(AcrBaseUrl).azurecr.io/mock-data-recipient:$(Build.SourceBranchName)' + echo Pushing all tags to $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient + docker image push --all-tags $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient - - task: CmdLine@2 - displayName: 'Push Mock Data Recipient image with :latest tag to ACR (develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) + - task: Bash@3 + displayName: 'Tag and Push Mock Data Recipient images for develop, main and release branches.' + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'))) inputs: - script: 'docker push $(AcrBaseUrl).azurecr.io/mock-data-recipient:latest' + targetType: inline + script: | + echo Tagging mock-data-recipient with latest tag + docker tag mock-data-recipient $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient:latest + + echo Tagging mock-data-recipient with Source Branch Name-latest: $(Build.SourceBranchName)-latest + docker tag mock-data-recipient $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient:$(Build.SourceBranchName)-latest + + echo Tagging mock-data-recipient with build id: $(Build.BuildId) + docker tag mock-data-recipient $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient:$(Build.BuildId) + + echo Pushing all tags to $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient + docker image push --all-tags $(SharedAcrBaseUrl).azurecr.io/mock-data-recipient \ No newline at end of file diff --git a/.azuredevops/pipelines/set-tag-name.yml b/.azuredevops/pipelines/set-tag-name.yml new file mode 100644 index 0000000..2d7fcab --- /dev/null +++ b/.azuredevops/pipelines/set-tag-name.yml @@ -0,0 +1,33 @@ +parameters: + - name: name # Name of the variable to set + type: string + - name: input + type: string + default: '' + - name: context + type: string # e.g., $(Build.SourceBranchName) + +steps: +- powershell: | + $input = "${{ parameters.input }}" + $context = "${{ parameters.context }}" + $varName = "${{ parameters.name }}" + + Write-Host "Setting variable: $varName" + Write-Host "Input provided: $input" + Write-Host "Context: $context" + + if (-not [string]::IsNullOrWhiteSpace($input)) { + Write-Host "##vso[task.setvariable variable=$varName]$input" + Write-Host "Used provided value for ${varName}: ${input}" + } else { + switch -Wildcard ($context) { + "main" { $value = "main-latest" } + "develop" { $value = "develop-latest" } + "*release*" { $value = "main-latest" } + default { $value = "develop-latest" } + } + Write-Host "Resolved ${varName} to: ${value}" + Write-Host "##vso[task.setvariable variable=$varName]$value" + } + displayName: 'Set ${{ parameters.name }} variable' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ae2d76e..eb3bd9e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -55,11 +55,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell # 📚 https://git.io/JvXDl @@ -84,4 +84,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1976cd5..4578a43 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout Data Recipient - uses: actions/checkout@v4 + uses: actions/checkout@v2 with: path: ./mock-data-recipient diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml index ba43d14..3414539 100644 --- a/.github/workflows/test-report.yml +++ b/.github/workflows/test-report.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 - name: Publish Unit Test Report uses: dorny/test-reporter@v1 @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 - name: Publish Integration Test Report uses: dorny/test-reporter@v1 @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 - name: Publish e2e Test Report uses: dorny/test-reporter@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index f90af45..e3ba295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [3.0.1] - 2025-06-19 +### Changed +- Fixed multiple build warnings to improve code quality and maintainability + ## [3.0.0] - 2025-03-19 ### Changed - Updated NuGet packages diff --git a/Help/container/HELP.md b/Help/container/HELP.md index bdfa1aa..997ced5 100644 --- a/Help/container/HELP.md +++ b/Help/container/HELP.md @@ -109,7 +109,7 @@ docker build -f Dockerfile -t mock-data-recipient . ``` Run the SQL Server image. ``` -docker run -d -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Pa{}w0rd2019" -p 1433:1433 --name sql1 -h sql1 -d mcr.microsoft.com/mssql/server:2022-latest +docker run -d -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=Pa{}w0rd2019" -p 1433:1433 --name sql1 -h sql1 -d mcr.microsoft.com/mssql/server:2022-latest ``` Run the new docker image. ``` diff --git a/README.md b/README.md index de73ab4..a18bd39 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Consumer Data Right Logo](./cdr-logo.png?raw=true) -[![Consumer Data Standards v1.33.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.33.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.33.0/#introduction) +[![Consumer Data Standards v1.34.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.34.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction) [![made-with-dotnet](https://img.shields.io/badge/Made%20with-.NET-1f425Ff.svg)](https://dotnet.microsoft.com/) [![made-with-csharp](https://img.shields.io/badge/Made%20with-C%23-1f425Ff.svg)](https://docs.microsoft.com/en-us/dotnet/csharp/) [![MIT License](https://img.shields.io/github/license/ConsumerDataRight/mock-data-recipient)](./LICENSE) @@ -13,7 +13,7 @@ This repository contains a mock implementation of a Data Recipient and is offere ## Mock Data Recipient - Alignment The Mock Data Recipient in this release: -* aligns to [v1.33.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.33.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.33.0/#introduction); +* aligns to [v1.34.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction); * can connect to and complete authentication against both [FAPI 1.0 Migration Phase 2 and Phase 3](https://consumerdatastandardsaustralia.github.io/standards/#authentication-flows) compliant data holders. ## Getting Started diff --git a/Source/.editorconfig b/Source/.editorconfig index fac3dc8..29e5bbe 100644 --- a/Source/.editorconfig +++ b/Source/.editorconfig @@ -145,122 +145,23 @@ csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent -#Readability Rules - -#A call to an instance member of the local class or a base class is not prefixed with 'this.' -dotnet_diagnostic.SA1101.severity = none - -#The parameter spans multiple lines -dotnet_diagnostic.SA1118.severity = none - -#Spacing Rules -dotnet_diagnostic.SA1010.severity = none - #Documentation Rules # SA1600: Elements should be documented dotnet_diagnostic.SA1600.severity = none - # partial elements should be documented dotnet_diagnostic.SA1601.severity = none - #file header is missing dotnet_diagnostic.SA1633.severity = none - #Enumeration items should be documented dotnet_diagnostic.SA1602.severity = none - # SA1623: summary text should begin with: 'Gets or sets' dotnet_diagnostic.SA1623.severity = none - -# documentation for parameter 'cdrArrangementId' is missing -dotnet_diagnostic.SA1611.severity = none - # Parameter '' has no matching param tag in the XML comment dotnet_diagnostic.CS1591.severity = none - -# Element return value should be documented -dotnet_diagnostic.SA1615.severity = none - #parameter has no matching tag in xml dotnet_diagnostic.CS1573.severity = none - -#Ordering Rules - -#static members should appear before non-static members -dotnet_diagnostic.SA1204.severity = none - -# public members should come before private -dotnet_diagnostic.SA1202.severity = none - -# 'public' members should come before 'private' members -dotnet_diagnostic.SA1200.severity = none - -# using directive ordering -dotnet_diagnostic.SA1208.severity = none - -# field should not follow class -dotnet_diagnostic.SA1201.severity = none - #Maintainability - -# S2325: Methods and properties that don't access instance data should be static -dotnet_diagnostic.S2325.severity = none - -# USE model binding intead of accessing the raw request data. -dotnet_diagnostic.S6932.severity = none - -# calling BuildServiceProvider from application code results in additional copy -dotnet_diagnostic.ASP0000.severity = none - -# File name should match first type -dotnet_diagnostic.SA1649.severity = none - -# Constructor summary documentation should being with standard text -dotnet_diagnostic.SA1642.severity = none - -# documentation text should end with a period -dotnet_diagnostic.SA1629.severity = none - -# File may only contain a single type -dotnet_diagnostic.SA1402.severity = none - -# Field should be private -dotnet_diagnostic.SA1401.severity = none - -#Element should being with an uppercase letter -dotnet_diagnostic.SA1300.severity = none - -# Do not use regions -dotnet_diagnostic.SA1124.severity = none - # ModelState.IsValid should be checked in controller actions dotnet_diagnostic.S6967.severity = none - -# Value type property used as input in a controller action should be nullable -dotnet_diagnostic.S6964.severity = none - -# Await IsDBNullAsync instead -dotnet_diagnostic.S6966.severity = none - -# Enable server certificate validation on this SSL/TLS connection -dotnet_diagnostic.S4830.severity = none - -# Reuse the HttpClient instances rather than create new ones -dotnet_diagnostic.S6962.severity = none - -# is null on at leat one execution path -dotnet_diagnostic.S2259.severity = none - -[RevocationController.cs] -# Specify the RouteAttribute when an HttpMethod or RouteAttribute is specified at an action level -dotnet_diagnostic.S6934.severity = none - -[JwksController.cs] -# Specify the RouteAttribute when an HttpMethod or RouteAttribute is specified at an action level -dotnet_diagnostic.S6934.severity = none - -[Extensions.cs] -# Use a format provider when parsing date and time -dotnet_diagnostic.S6580.severity = none \ No newline at end of file diff --git a/Source/CDR.DCR/CDR.DCR.csproj b/Source/CDR.DCR/CDR.DCR.csproj index 89b420d..2e637ab 100644 --- a/Source/CDR.DCR/CDR.DCR.csproj +++ b/Source/CDR.DCR/CDR.DCR.csproj @@ -15,7 +15,7 @@ - + diff --git a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj index cc291dd..012850c 100644 --- a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj +++ b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj @@ -9,7 +9,7 @@ True - + diff --git a/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs b/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs index a22c9cb..dbea63f 100644 --- a/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs +++ b/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs @@ -1,9 +1,15 @@ -namespace CDR.DataRecipient.API.Logger -{ - using Serilog; +using Serilog; +namespace CDR.DataRecipient.API.Logger +{ + /// + /// Interface for logging request and response information. + /// public interface IRequestResponseLogger { + /// + /// Gets the logger instance used for logging. + /// ILogger Log { get; } } } diff --git a/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs b/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs index 0128592..dabc129 100644 --- a/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs +++ b/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs @@ -1,9 +1,17 @@ -namespace CDR.DataRecipient.API.Logger -{ - using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +namespace CDR.DataRecipient.API.Logger +{ + /// + /// Provides extension methods for registering request and response logging services. + /// public static class LoggerExtensions { + /// + /// Adds the service to the specified as a singleton. + /// + /// The service collection to add the logger to. + /// The updated instance. public static IServiceCollection AddRequestResponseLogging(this IServiceCollection services) { services.AddSingleton(); diff --git a/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs b/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs index d6af11e..b8a00f3 100644 --- a/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs +++ b/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs @@ -1,22 +1,16 @@ -namespace CDR.DataRecipient.API.Logger -{ - using Microsoft.Extensions.Configuration; - using Serilog; - using Serilog.Core; - using Serilog.Settings.Configuration; +using Microsoft.Extensions.Configuration; +using Serilog; +using Serilog.Settings.Configuration; +namespace CDR.DataRecipient.API.Logger +{ public class RequestResponseLogger : IRequestResponseLogger, IDisposable { - private readonly Logger _logger; - - public ILogger Log - { - get { return _logger; } - } + private readonly Serilog.Core.Logger _logger; public RequestResponseLogger(IConfiguration configuration) { - _logger = new LoggerConfiguration() + this._logger = new LoggerConfiguration() .ReadFrom.Configuration( configuration, new ConfigurationReaderOptions() { SectionName = "SerilogRequestResponseLogger" }) @@ -38,9 +32,14 @@ public RequestResponseLogger(IConfiguration configuration) .CreateLogger(); } + public ILogger Log + { + get { return this._logger; } + } + public void Dispose() { - Dispose(true); + this.Dispose(true); GC.SuppressFinalize(this); } diff --git a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs index 295d2ea..2677991 100644 --- a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs +++ b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs @@ -11,6 +11,9 @@ namespace CDR.DataRecipient.API.Logger { + /// + /// Middleware that logs HTTP requests and responses. + /// public class RequestResponseLoggingMiddleware { private const string HttpSummaryMessageTemplate = @@ -43,54 +46,81 @@ public class RequestResponseLoggingMiddleware private string? _fapiInteractionId; private string? _dataHolderBrandId; + /// + /// Initializes a new instance of the class. + /// + /// The next middleware delegate in the HTTP request pipeline. + /// The logger used to log request and response information. + /// The application configuration settings. + /// Thrown when is null. public RequestResponseLoggingMiddleware(RequestDelegate next, IRequestResponseLogger requestResponseLogger, IConfiguration configuration) { - _requestResponseLogger = requestResponseLogger.Log.ForContext(); - _next = next ?? throw new ArgumentNullException(nameof(next)); - _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); - _configuration = configuration; + this._requestResponseLogger = requestResponseLogger.Log.ForContext(); + this._next = next ?? throw new ArgumentNullException(nameof(next)); + this._recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); + this._configuration = configuration; } + /// + /// Processes an HTTP request by initializing members and extracting request and response properties asynchronously. + /// + /// The for the current HTTP request. + /// A that represents the asynchronous operation. public async Task InvokeAsync(HttpContext context) { - InitMembers(); - await ExtractRequestProperties(context); - await ExtractResponseProperties(context); + this.InitMembers(); + await this.ExtractRequestProperties(context); + await this.ExtractResponseProperties(context); } + private static void SetIdFromJwt(string jwt, string identifierType, ref string idToSet) + { + var handler = new JwtSecurityTokenHandler(); + if (handler.CanReadToken(jwt)) + { + var decodedJwt = handler.ReadJwtToken(jwt); + var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; + + idToSet = id; + } + } + + /// + /// Initializes or resets all member variables related to the HTTP request and response data to empty strings. + /// private void InitMembers() { - _requestMethod = _requestBody = _requestHeaders = _requestPath = _requestQueryString = - _statusCode = _elapsedTime = _responseHeaders = _responseBody = _requestHost = _requestScheme = - _exceptionMessage = _requestPathBase = _clientId = _softwareId = _fapiInteractionId = _dataHolderBrandId = _requestIpAddress = string.Empty; + this._requestMethod = this._requestBody = this._requestHeaders = this._requestPath = this._requestQueryString = + this._statusCode = this._elapsedTime = this._responseHeaders = this._responseBody = this._requestHost = this._requestScheme = + this._exceptionMessage = this._requestPathBase = this._clientId = this._softwareId = this._fapiInteractionId = this._dataHolderBrandId = this._requestIpAddress = string.Empty; } private void LogWithContext() { - var logger = _requestResponseLogger + var logger = this._requestResponseLogger .ForContext("SourceContext", "SB-DR-RES") - .ForContext("RequestMethod", _requestMethod) - .ForContext("RequestHost", _requestHost) - .ForContext("RequestIpAddress", _requestIpAddress) - .ForContext("RequestBody", _requestBody) - .ForContext("RequestHeaders", _requestHeaders) - .ForContext("RequestPath", _requestPath) - .ForContext("RequestQueryString", _requestQueryString) - .ForContext("StatusCode", _statusCode) - .ForContext("ResponseHeaders", _responseHeaders) - .ForContext("ResponseBody", _responseBody) - .ForContext("ClientId", _clientId) - .ForContext("SoftwareId", _softwareId) - .ForContext("FapiInteractionId", _fapiInteractionId) - .ForContext("DataHolderBrandId", _dataHolderBrandId); - - if (!string.IsNullOrEmpty(_exceptionMessage)) + .ForContext("RequestMethod", this._requestMethod) + .ForContext("RequestHost", this._requestHost) + .ForContext("RequestIpAddress", this._requestIpAddress) + .ForContext("RequestBody", this._requestBody) + .ForContext("RequestHeaders", this._requestHeaders) + .ForContext("RequestPath", this._requestPath) + .ForContext("RequestQueryString", this._requestQueryString) + .ForContext("StatusCode", this._statusCode) + .ForContext("ResponseHeaders", this._responseHeaders) + .ForContext("ResponseBody", this._responseBody) + .ForContext("ClientId", this._clientId) + .ForContext("SoftwareId", this._softwareId) + .ForContext("FapiInteractionId", this._fapiInteractionId) + .ForContext("DataHolderBrandId", this._dataHolderBrandId); + + if (!string.IsNullOrEmpty(this._exceptionMessage)) { - logger.Error(HttpSummaryExceptionMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _exceptionMessage); + logger.Error(HttpSummaryExceptionMessageTemplate, this._requestMethod, this._requestScheme, this._requestHost, this._requestPathBase, this._requestPath, this._exceptionMessage); } else { - logger.Write(LogEventLevel.Information, HttpSummaryMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _statusCode, _elapsedTime); + logger.Write(LogEventLevel.Information, HttpSummaryMessageTemplate, this._requestMethod, this._requestScheme, this._requestHost, this._requestPathBase, this._requestPath, this._statusCode, this._elapsedTime); } } @@ -99,45 +129,28 @@ private async Task ExtractRequestProperties(HttpContext context) try { context.Request.EnableBuffering(); - await using var requestStream = _recyclableMemoryStreamManager.GetStream(); + await using var requestStream = this._recyclableMemoryStreamManager.GetStream(); await context.Request.Body.CopyToAsync(requestStream); - _requestBody = ReadStreamInChunks(requestStream); + this._requestBody = this.ReadStreamInChunks(requestStream); context.Request.Body.Position = 0; - _requestHost = GetHost(context.Request); - _requestIpAddress = GetIpAddress(context); - _requestMethod = context.Request.Method; - _requestScheme = context.Request.Scheme; - _requestPath = context.Request.Path; - _requestQueryString = context.Request.QueryString.ToString(); - _requestPathBase = context.Request.PathBase.ToString(); + this._requestHost = this.GetHost(context.Request); + this._requestIpAddress = this.GetIpAddress(context); + this._requestMethod = context.Request.Method; + this._requestScheme = context.Request.Scheme; + this._requestPath = context.Request.Path; + this._requestQueryString = context.Request.QueryString.ToString(); + this._requestPathBase = context.Request.PathBase.ToString(); IEnumerable keyValues = context.Request.Headers.Keys.Select(key => key + ": " + string.Join(",", context.Request.Headers[key].ToArray())); - _requestHeaders = string.Join(Environment.NewLine, keyValues); + this._requestHeaders = string.Join(Environment.NewLine, keyValues); - ExtractIdFromRequest(context.Request); + this.ExtractIdFromRequest(context.Request); } catch (Exception ex) { - _exceptionMessage = ex.Message; - } - } - - public static class ClaimIdentifiers - { - public const string Iss = "iss"; - } - - private static void SetIdFromJwt(string jwt, string identifierType, ref string idToSet) - { - var handler = new JwtSecurityTokenHandler(); - if (handler.CanReadToken(jwt)) - { - var decodedJwt = handler.ReadJwtToken(jwt); - var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; - - idToSet = id; + this._exceptionMessage = ex.Message; } } @@ -154,14 +167,14 @@ private void ExtractIdFromRequest(HttpRequest request) if (scheme == JwtBearerDefaults.AuthenticationScheme && parameter != null) { - _dataHolderBrandId = string.Empty; - SetIdFromJwt(parameter, ClaimIdentifiers.Iss, ref _dataHolderBrandId); + this._dataHolderBrandId = string.Empty; + SetIdFromJwt(parameter, ClaimIdentifiers.Iss, ref this._dataHolderBrandId); } } } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; } } @@ -185,7 +198,7 @@ private string ReadStreamInChunks(Stream stream) } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; } return string.Empty; @@ -194,35 +207,35 @@ private string ReadStreamInChunks(Stream stream) private async Task ExtractResponseProperties(HttpContext httpContext) { var originalBodyStream = httpContext.Response.Body; - await using var responseBody = _recyclableMemoryStreamManager.GetStream(); + await using var responseBody = this._recyclableMemoryStreamManager.GetStream(); httpContext.Response.Body = responseBody; var sw = Stopwatch.StartNew(); try { - await _next(httpContext); + await this._next(httpContext); } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; throw; } finally { sw.Stop(); - _elapsedTime = sw.ElapsedMilliseconds.ToString(); + this._elapsedTime = sw.ElapsedMilliseconds.ToString(); responseBody.Seek(0, SeekOrigin.Begin); - _responseBody = await new StreamReader(responseBody).ReadToEndAsync(); + this._responseBody = await new StreamReader(responseBody).ReadToEndAsync(); responseBody.Seek(0, SeekOrigin.Begin); IEnumerable keyValues = httpContext.Response.Headers.Keys.Select(key => key + ": " + string.Join(",", httpContext.Response.Headers[key].ToArray())); - _responseHeaders = string.Join(System.Environment.NewLine, keyValues); + this._responseHeaders = string.Join(System.Environment.NewLine, keyValues); - _statusCode = httpContext.Response.StatusCode.ToString(); + this._statusCode = httpContext.Response.StatusCode.ToString(); - LogWithContext(); + this.LogWithContext(); // This is for middleware hooked before us to see our changes. // Otherwise the original stream would be seen which cannot be read again. @@ -235,7 +248,7 @@ private async Task ExtractResponseProperties(HttpContext httpContext) { // 1. check if the X-Forwarded-Host header has been provided -> use that // 2. If not, use the request.Host - string hostHeaderKey = _configuration.GetValue("SerilogRequestResponseLogger:HostNameHeaderKey") ?? "X-Forwarded-Host"; + string hostHeaderKey = this._configuration.GetValue("SerilogRequestResponseLogger:HostNameHeaderKey") ?? "X-Forwarded-Host"; if (!request.Headers.TryGetValue(hostHeaderKey, out var keys)) { @@ -247,7 +260,7 @@ private async Task ExtractResponseProperties(HttpContext httpContext) private string? GetIpAddress(HttpContext context) { - string ipHeaderKey = _configuration.GetValue("SerilogRequestResponseLogger:IPAddressHeaderKey") ?? "X-Forwarded-For"; + string ipHeaderKey = this._configuration.GetValue("SerilogRequestResponseLogger:IPAddressHeaderKey") ?? "X-Forwarded-For"; if (!context.Request.Headers.TryGetValue(ipHeaderKey, out var keys)) { @@ -261,5 +274,10 @@ private async Task ExtractResponseProperties(HttpContext httpContext) .Split(',')[0] // Get the first IP address in the list, in case there are multiple. .Split(':')[0]; // Strip off the port number, in case it is attached to the IP address. } + + public static class ClaimIdentifiers + { + public const string Iss = "iss"; + } } } diff --git a/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj b/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj index 1327b1b..b61d404 100644 --- a/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj +++ b/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj @@ -23,7 +23,7 @@ - + diff --git a/Source/CDR.DataRecipient.IntegrationTests/CDR.DataRecipient.IntegrationTests.csproj b/Source/CDR.DataRecipient.IntegrationTests/CDR.DataRecipient.IntegrationTests.csproj index 21b737f..d2108e7 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/CDR.DataRecipient.IntegrationTests.csproj +++ b/Source/CDR.DataRecipient.IntegrationTests/CDR.DataRecipient.IntegrationTests.csproj @@ -14,7 +14,7 @@ - + diff --git a/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj b/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj index 933f5c6..de85dbf 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj +++ b/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj @@ -7,14 +7,14 @@ True - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs b/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs index eebed1b..ad8c992 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs @@ -1,5 +1,5 @@ -using Microsoft.Data.SqlClient; -using System; +using System; +using Microsoft.Data.SqlClient; namespace CDR.DataRecipient.Repository.SQL.Extensions { @@ -8,6 +8,8 @@ public static class SqLExtensions /// /// Execute scalar command and return result as Int32. Throw error if no results or conversion error. /// + /// sql command. + /// integer result. public static int ExecuteScalarInt32(this SqlCommand command) { var res = command.ExecuteScalar(); @@ -23,6 +25,8 @@ public static int ExecuteScalarInt32(this SqlCommand command) /// /// Execute scalar command and return result as string. Throw error if no results or conversion error. /// + /// sql command. + /// string result. public static string ExecuteScalarString(this SqlCommand command) { var res = command.ExecuteScalar(); diff --git a/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs b/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs index 86cbcbf..b1ccb4f 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs @@ -1,6 +1,6 @@ -using CDR.DataRecipient.SDK.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Models; namespace CDR.DataRecipient.Repository.SQL { diff --git a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs index 12f0db3..e661db7 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs @@ -1,7 +1,6 @@ using CDR.DataRecipient.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -using System; namespace CDR.DataRecipient.Repository.SQL.Infrastructure { diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs index 74424a0..227b1a0 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs @@ -1,77 +1,79 @@ -using CDR.DataRecipient.Infrastructure; -using CDR.DataRecipient.Models; -using Microsoft.Extensions.Configuration; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using CDR.DataRecipient.Infrastructure; +using CDR.DataRecipient.Models; +using Microsoft.Extensions.Configuration; namespace CDR.DataRecipient.Repository.SQL { public class SqlConsentsRepository : IConsentsRepository { - protected readonly IConfiguration _config; - - public SqlDataAccess SqlDataAccess { get; } + private readonly IConfiguration _config; public SqlConsentsRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { - _config = config; - SqlDataAccess = new SqlDataAccess(_config, recipientDatabaseContext); + this._config = config; + this.SqlDataAccess = new SqlDataAccess(this._config, recipientDatabaseContext); } + public SqlDataAccess SqlDataAccess { get; } + + protected IConfiguration Config => this._config; + public async Task GetConsentByArrangement(string cdrArrangementId) { - return await SqlDataAccess.GetConsentByArrangement(cdrArrangementId); + return await this.SqlDataAccess.GetConsentByArrangement(cdrArrangementId); } public async Task> GetConsents(string clientId, string dataHolderBrandId, string userId, string industry = null) { // filter consents by industry. - var cdrArrangements = await SqlDataAccess.GetConsents(clientId, dataHolderBrandId, userId); + var cdrArrangements = await this.SqlDataAccess.GetConsents(clientId, dataHolderBrandId, userId); return cdrArrangements.OrderByDescending(x => x.CreatedOn); } public async Task PersistConsent(ConsentArrangement consentArrangement) { - var existingArrangement = await GetConsentByArrangement(consentArrangement.CdrArrangementId); + var existingArrangement = await this.GetConsentByArrangement(consentArrangement.CdrArrangementId); if (existingArrangement == null) { - await SqlDataAccess.InsertCdrArrangement(consentArrangement); + await this.SqlDataAccess.InsertCdrArrangement(consentArrangement); return; } - await SqlDataAccess.UpdateCdrArrangement(consentArrangement); + await this.SqlDataAccess.UpdateCdrArrangement(consentArrangement); } public async Task UpdateTokens(string cdrArrangementId, string idToken, string accessToken, string refreshToken) { - var consent = await GetConsentByArrangement(cdrArrangementId); + var consent = await this.GetConsentByArrangement(cdrArrangementId); consent.IdToken = idToken; consent.AccessToken = accessToken; consent.RefreshToken = refreshToken; - await SqlDataAccess.UpdateCdrArrangement(consent); + await this.SqlDataAccess.UpdateCdrArrangement(consent); } public async Task DeleteConsent(string cdrArrangementId) { - var consent = await GetConsentByArrangement(cdrArrangementId); + var consent = await this.GetConsentByArrangement(cdrArrangementId); if (!string.IsNullOrEmpty(consent?.CdrArrangementId)) { - await SqlDataAccess.DeleteConsent(cdrArrangementId); + await this.SqlDataAccess.DeleteConsent(cdrArrangementId); } } public async Task RevokeConsent(string cdrArrangementId, string dataHolderBrandId) { - var consent = await GetConsentByArrangement(cdrArrangementId); + var consent = await this.GetConsentByArrangement(cdrArrangementId); if (!string.IsNullOrEmpty(consent?.CdrArrangementId) && string.Equals(consent.DataHolderBrandId, dataHolderBrandId, StringComparison.OrdinalIgnoreCase)) { - await SqlDataAccess.DeleteConsent(cdrArrangementId); + await this.SqlDataAccess.DeleteConsent(cdrArrangementId); return true; } diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs index d104b9d..aaef09d 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs @@ -1,4 +1,9 @@ -using CDR.DataRecipient.Infrastructure; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CDR.DataRecipient.Infrastructure; using CDR.DataRecipient.Models; using CDR.DataRecipient.SDK.Enum; using CDR.DataRecipient.SDK.Models; @@ -6,37 +11,31 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CDR.DataRecipient.Repository.SQL { public class SqlDataAccess : ISqlDataAccess { - public IConfiguration Config { get; } - - public string DbConn { get; set; } - public SqlDataAccess(IConfiguration configuration, RecipientDatabaseContext recipientDatabaseContext) { - Config = configuration; - DbConn = Config.GetConnectionString(DbConstants.ConnectionStringNames.Default); + this.Config = configuration; + this.DbConn = this.Config.GetConnectionString(DbConstants.ConnectionStringNames.Default); } public SqlDataAccess(string connString) { - DbConn = connString; + this.DbConn = connString; } - #region CdrArragements + public IConfiguration Config { get; } + + public string DbConn { get; set; } + public async Task GetConsentByArrangement(string cdrArrangementId) { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -66,7 +65,7 @@ public async Task GetConsentByArrangement(string cdrArrangem public async Task> GetConsents(string clientId, string dataHolderBrandId, string userId) { List cdrArrangements = new(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -107,7 +106,7 @@ public async Task> GetConsents(string clientId, { foreach (var arr in cdrArrangements) { - arr.BrandName = await GetDataHolderBrandName(arr.DataHolderBrandId); + arr.BrandName = await this.GetDataHolderBrandName(arr.DataHolderBrandId); } } @@ -117,7 +116,7 @@ public async Task> GetConsents(string clientId, public async Task InsertCdrArrangement(ConsentArrangement consentArrangement) { - using SqlConnection db = new(DbConn); + using SqlConnection db = new(this.DbConn); await db.OpenAsync(); var jsonDocument = JsonConvert.SerializeObject(consentArrangement); @@ -152,7 +151,7 @@ public async Task InsertCdrArrangement(ConsentArrangement consentArrangement) public async Task UpdateCdrArrangement(ConsentArrangement consentArrangement) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -174,7 +173,7 @@ public async Task UpdateCdrArrangement(ConsentArrangement consentArrangement) public async Task DeleteCdrArrangementData() { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement"; @@ -187,7 +186,7 @@ public async Task DeleteCdrArrangementData() public async Task DeleteCdrArrangementData(string clientId) { - using (SqlConnection db = new SqlConnection(DbConn)) + using (SqlConnection db = new SqlConnection(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement WHERE ClientId = @clientId"; @@ -201,7 +200,7 @@ public async Task DeleteCdrArrangementData(string clientId) public async Task DeleteRegistrationData() { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.Registration"; @@ -214,7 +213,7 @@ public async Task DeleteRegistrationData() public async Task DeleteConsent(string cdrArrangementId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement WHERE CdrArrangementId=@id"; @@ -227,10 +226,6 @@ public async Task DeleteConsent(string cdrArrangementId) } } - #endregion - - #region CrdRegistrations - /// /// Get the Registrations for this Data Holders. /// @@ -241,7 +236,7 @@ public async Task DeleteConsent(string cdrArrangementId) /// [true|false]. public async Task CheckRegistrationExist(string dhBrandId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT [DataHolderBrandId] FROM [Registration] WHERE [DataHolderBrandId] = @id", db); @@ -273,7 +268,7 @@ public async Task> CheckRegistrationsExist(IList dhBrandsIns = new List(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { foreach (var dh in newDhBrands) { @@ -304,7 +299,7 @@ public async Task> CheckRegistrationsExist(IList GetRegistration(string clientId, string dataHolderBrandId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -340,7 +335,7 @@ public async Task GetRegByDHBrandId(string dhBrandId) { var clientId = string.Empty; - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -364,7 +359,7 @@ public async Task GetRegByDHBrandId(string dhBrandId) public async Task> GetRegistrations() { List registrations = new(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT ClientId, JsonDocument FROM dbo.Registration", db); @@ -382,7 +377,7 @@ public async Task> GetRegistrations() { foreach (var reg in registrations) { - reg.BrandName = await GetDataHolderBrandName(reg.DataHolderBrandId); + reg.BrandName = await this.GetDataHolderBrandName(reg.DataHolderBrandId); } } @@ -392,7 +387,7 @@ public async Task> GetRegistrations() public async Task DeleteRegistration(string clientId, string dataHolderBrandId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlCommand = "DELETE FROM dbo.Registration WHERE [ClientId] = @clientId AND [DataHolderBrandId] = @dataHolderBrandId"; @@ -410,7 +405,7 @@ public async Task InsertRegistration(Registration registration) { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var dhBrandId = new Guid(registration.DataHolderBrandId); @@ -442,7 +437,7 @@ public async Task InsertRegistration(Registration registration) public async Task> GetDcrMessageRegistrations() { List registrations = new(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "SELECT [ClientId],[DataHolderBrandId],[BrandName],[MessageState],[LastUpdated] FROM [dbo].[DcrMessage] WHERE [MessageState] != @msgState"; @@ -455,9 +450,9 @@ public async Task> GetDcrMessageRegistrations() { var registration = new Registration { - ClientId = reader.IsDBNull(0) ? string.Empty : reader.GetString(0), + ClientId = await reader.IsDBNullAsync(0) ? string.Empty : await reader.GetFieldValueAsync(0), DataHolderBrandId = Convert.ToString(reader.GetGuid(1)), - BrandName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2), + BrandName = await reader.IsDBNullAsync(2) ? string.Empty : await reader.GetFieldValueAsync(2), MessageState = reader.GetString(3), LastUpdated = reader.GetDateTime(4), }; @@ -484,7 +479,7 @@ public async Task InsertDcrRegistration(string regClientId, string dcrDHBr { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var dhBrandId = new Guid(dcrDHBrandId); @@ -507,7 +502,7 @@ public async Task InsertDcrRegistration(string regClientId, string dcrDHBr public async Task UpdateRegistration(Registration registration) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -524,13 +519,9 @@ public async Task UpdateRegistration(Registration registration) } } - #endregion - - #region DataHolderBrand - public async Task GetDataHolderBrand(string brandId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -554,7 +545,7 @@ public async Task GetDataHolderBrand(string brandId) public async Task GetDataHolderBrandName(string brandId) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT JsonDocument FROM dbo.DataHolderBrand WHERE DataHolderBrandId = @id", db); @@ -578,7 +569,7 @@ public async Task GetDataHolderBrandName(string brandId) public async Task> GetDataHolderBrands() { List dataHolderBrands = new(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -605,7 +596,7 @@ public async Task> GetDataHolderBrands() public async Task DataHolderBrandsDelete() { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -627,7 +618,7 @@ public async Task DataHolderBrandsDelete() List dhBrandsIns = []; List dhBrandsUpd = []; - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -662,12 +653,12 @@ public async Task DataHolderBrandsDelete() if (dhBrandsIns.Count > 0) { - dhBrandsIns.ToList().ForEach(async dataholder => await InsertDataHolder(dataholder)); + dhBrandsIns.ToList().ForEach(async dataholder => await this.InsertDataHolder(dataholder)); } if (dhBrandsUpd.Count > 0) { - dhBrandsUpd.ToList().ForEach(async dataholder => await UpdateDataHolder(dataholder)); + dhBrandsUpd.ToList().ForEach(async dataholder => await this.UpdateDataHolder(dataholder)); } } else @@ -676,7 +667,7 @@ public async Task DataHolderBrandsDelete() if (dhBrandsNew.Count > 0) { dhBrandsIns = dhBrandsNew.ToList(); - dhBrandsNew.ToList().ForEach(async dataholder => await InsertDataHolder(dataholder)); + dhBrandsNew.ToList().ForEach(async dataholder => await this.InsertDataHolder(dataholder)); } } } @@ -697,7 +688,7 @@ public async Task InsertDataHolder(DataHolderBrand dataholder) { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var jsonDocument = System.Text.Json.JsonSerializer.Serialize(dataholder); @@ -730,7 +721,7 @@ public async Task UpdateDataHolder(DataHolderBrand dataholder) { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -765,7 +756,7 @@ public async Task DeleteDataHolder(string dataholderBrndId) { try { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -815,7 +806,7 @@ public async Task DeleteDataHolder(string dataholderBrndId) public async Task PersistDataHolderBrands(IEnumerable dataHolderBrands) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -826,7 +817,7 @@ public async Task PersistDataHolderBrands(IEnumerable dataHolde if (dataHolderBrands.Any()) { - dataHolderBrands.ToList().ForEach(async dataholder => await InsertDataHolder(dataholder)); + dataHolderBrands.ToList().ForEach(async dataholder => await this.InsertDataHolder(dataholder)); } } } @@ -835,7 +826,7 @@ public async Task> GetSoftwareProducts() { List rtnList = new(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -865,7 +856,7 @@ public async Task PersistSoftwareProducts(IEnumerable dataRe { if (dataRecipients.Any()) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "DELETE FROM SoftwareProduct"; @@ -873,46 +864,8 @@ public async Task PersistSoftwareProducts(IEnumerable dataRe await sqlCommand.ExecuteNonQueryAsync(); await db.CloseAsync(); - dataRecipients.ToList().ForEach(async dataRecipient => await InsertSoftwareProduct(dataRecipient.DataRecipientBrands)); - } - } - } - - private async Task InsertSoftwareProduct(IEnumerable drBrands) - { - if (drBrands == null || !drBrands.Any()) - { - // Nothing to insert. - return; - } - - using (SqlConnection db = new(DbConn)) - { - await db.OpenAsync(); - foreach (var brand in drBrands) - { - if (brand.SoftwareProducts != null && brand.SoftwareProducts.Count > 0) - { - foreach (var swProduct in brand.SoftwareProducts) - { - var sqlQuery = "INSERT INTO SoftwareProduct (SoftwareProductId, BrandId, SoftwareProductName, SoftwareProductDescription, LogoUri, RecipientBaseUri, RedirectUri, Scope, Status) " + - "VALUES (@softwareProductId, @dataRecipientBrandId, @softwareProductName, @softwareProductDescription, @logoUri, @recipientBaseUri, @redirectUri, @scope, @status)"; - using var sqlCommand = new SqlCommand(sqlQuery, db); - sqlCommand.Parameters.AddWithValue("@softwareProductId", swProduct.SoftwareProductId); - sqlCommand.Parameters.AddWithValue("@dataRecipientBrandId", brand.DataRecipientBrandId); - sqlCommand.Parameters.AddWithValue("@softwareProductName", swProduct.SoftwareProductName); - sqlCommand.Parameters.AddWithValue("@softwareProductDescription", swProduct.SoftwareProductDescription); - sqlCommand.Parameters.AddWithValue("@logoUri", swProduct.LogoUri); - sqlCommand.Parameters.AddWithValue("@recipientBaseUri", swProduct.RecipientBaseUri); - sqlCommand.Parameters.AddWithValue("@redirectUri", swProduct.RedirectUri); - sqlCommand.Parameters.AddWithValue("@scope", swProduct.Scope); - sqlCommand.Parameters.AddWithValue("@status", swProduct.Status); - await sqlCommand.ExecuteNonQueryAsync(); - } - } + dataRecipients.ToList().ForEach(async dataRecipient => await this.InsertSoftwareProduct(dataRecipient.DataRecipientBrands)); } - - await db.CloseAsync(); } } @@ -927,7 +880,7 @@ private async Task InsertSoftwareProduct(IEnumerable drBrands) public async Task GetDHBrandById(string dhBrandId) { DataHolderBrand dh = new DataHolderBrand(); - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -966,7 +919,7 @@ public async Task GetDHBrandById(string dhBrandId) { string msgId = string.Empty; string msgState = string.Empty; - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -1001,7 +954,7 @@ public async Task GetDHBrandById(string dhBrandId) { string msgId = string.Empty; string msgState = string.Empty; - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -1036,7 +989,7 @@ public async Task InsertDcrMessage(DcrMessage dcrMessage) { try { - using SqlConnection db = new(DbConn); + using SqlConnection db = new(this.DbConn); await db.OpenAsync(); var sqlQuery = "INSERT INTO [DcrMessage] ([DataHolderBrandId], [MessageId], [MessageState], [MessageError], [LastUpdated], [ClientId], [BrandName], [Created], [InfosecBaseUri]) VALUES (@dhBrandId, @msgId, @msgState, @msgErr, GETUTCDATE(), @clientId, @brandName, GETUTCDATE(), @infosecBaseUri)"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -1065,9 +1018,10 @@ public async Task InsertDcrMessage(DcrMessage dcrMessage) /// /// This is called from Azure Functions DCR. /// + /// Task result representing the asynchronous operation. public async Task UpdateDcrMsgByDHBrandId(DcrMessage dcrMessage) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); @@ -1093,9 +1047,10 @@ public async Task UpdateDcrMsgByDHBrandId(DcrMessage dcrMessage) /// /// This is called from Azure Functions DCR. /// + /// Task representing the asynchronous operation. public async Task UpdateDcrMsgByMessageId(DcrMessage dcrMessage) { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; @@ -1112,13 +1067,15 @@ public async Task UpdateDcrMsgByMessageId(DcrMessage dcrMessage) /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId. /// /// The message object to be updated. + /// The replacementMsgId. /// This is called from Azure DCR Functions /// /// BrandName not available when DCR. /// + /// Task representing the asynchronous operation. public async Task UpdateDcrMsgReplaceMessageIdWithoutBrand(DcrMessage dcrMessage, string replacementMsgId = "") { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageId] = @replaceMsgId, [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; @@ -1136,13 +1093,15 @@ public async Task UpdateDcrMsgReplaceMessageIdWithoutBrand(DcrMessage dcrMessage /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId. /// /// The message object to be updated. + /// The replacementMsgId. /// Update BrandName for Discovery Data Holder /// /// This is called from Azure DiscoverDataHolders. /// + /// Task representing the asynchronous operation. public async Task UpdateDcrMsgReplaceMessageId(DcrMessage dcrMessage, string replacementMsgId = "") { - using (SqlConnection db = new(DbConn)) + using (SqlConnection db = new(this.DbConn)) { await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageId] = @replaceMsgId, [BrandName] = @brandName, [InfosecBaseUri] = @infosecBaseUri, [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; @@ -1158,7 +1117,42 @@ public async Task UpdateDcrMsgReplaceMessageId(DcrMessage dcrMessage, string rep } } - #endregion + private async Task InsertSoftwareProduct(IEnumerable drBrands) + { + if (drBrands == null || !drBrands.Any()) + { + // Nothing to insert. + return; + } + + using (SqlConnection db = new(this.DbConn)) + { + await db.OpenAsync(); + foreach (var brand in drBrands) + { + if (brand.SoftwareProducts != null && brand.SoftwareProducts.Count > 0) + { + foreach (var swProduct in brand.SoftwareProducts) + { + var sqlQuery = "INSERT INTO SoftwareProduct (SoftwareProductId, BrandId, SoftwareProductName, SoftwareProductDescription, LogoUri, RecipientBaseUri, RedirectUri, Scope, Status) " + + "VALUES (@softwareProductId, @dataRecipientBrandId, @softwareProductName, @softwareProductDescription, @logoUri, @recipientBaseUri, @redirectUri, @scope, @status)"; + using var sqlCommand = new SqlCommand(sqlQuery, db); + sqlCommand.Parameters.AddWithValue("@softwareProductId", swProduct.SoftwareProductId); + sqlCommand.Parameters.AddWithValue("@dataRecipientBrandId", brand.DataRecipientBrandId); + sqlCommand.Parameters.AddWithValue("@softwareProductName", swProduct.SoftwareProductName); + sqlCommand.Parameters.AddWithValue("@softwareProductDescription", swProduct.SoftwareProductDescription); + sqlCommand.Parameters.AddWithValue("@logoUri", swProduct.LogoUri); + sqlCommand.Parameters.AddWithValue("@recipientBaseUri", swProduct.RecipientBaseUri); + sqlCommand.Parameters.AddWithValue("@redirectUri", swProduct.RedirectUri); + sqlCommand.Parameters.AddWithValue("@scope", swProduct.Scope); + sqlCommand.Parameters.AddWithValue("@status", swProduct.Status); + await sqlCommand.ExecuteNonQueryAsync(); + } + } + } + await db.CloseAsync(); + } + } } } diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs index 5817944..98314f8 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs @@ -1,53 +1,53 @@ -using CDR.DataRecipient.Infrastructure; +using System.Collections.Generic; +using System.Threading.Tasks; +using CDR.DataRecipient.Infrastructure; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; -using System.Collections.Generic; -using System.Threading.Tasks; namespace CDR.DataRecipient.Repository.SQL { public class SqlDataHoldersRepository : IDataHoldersRepository { - public SqlDataAccess SqlDataAccess { get; } - public SqlDataHoldersRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { - SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); + this.SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); } + public SqlDataAccess SqlDataAccess { get; } + public async Task GetDataHolderBrand(string brandId) { - var dataHolderBrand = await SqlDataAccess.GetDataHolderBrand(brandId); + var dataHolderBrand = await this.SqlDataAccess.GetDataHolderBrand(brandId); return dataHolderBrand; } public async Task GetDHBrandById(string brandId) { - var dataHolderBrand = await SqlDataAccess.GetDHBrandById(brandId); + var dataHolderBrand = await this.SqlDataAccess.GetDHBrandById(brandId); return dataHolderBrand; } public async Task> GetDataHolderBrands() { - return await SqlDataAccess.GetDataHolderBrands(); + return await this.SqlDataAccess.GetDataHolderBrands(); } public async Task DataHolderBrandsDelete() { // Delete existing data first then add all data - await SqlDataAccess.DataHolderBrandsDelete(); + await this.SqlDataAccess.DataHolderBrandsDelete(); } public async Task<(int, int)> AggregateDataHolderBrands(IList dataHolderBrands) { // Aggregate Old with New data - return await SqlDataAccess.AggregateDataHolderBrands(dataHolderBrands); + return await this.SqlDataAccess.AggregateDataHolderBrands(dataHolderBrands); } public async Task PersistDataHolderBrands(IEnumerable dataHolderBrands) { // Delete existing data first then add all data - await SqlDataAccess.PersistDataHolderBrands(dataHolderBrands); + await this.SqlDataAccess.PersistDataHolderBrands(dataHolderBrands); } } } diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs index 5b6b504..1bbd635 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs @@ -1,67 +1,66 @@ -using CDR.DataRecipient.Infrastructure; +using System.Collections.Generic; +using System.Threading.Tasks; +using CDR.DataRecipient.Infrastructure; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; namespace CDR.DataRecipient.Repository.SQL { public class SqlRegistrationsRepository : IRegistrationsRepository { - public SqlDataAccess SqlDataAccess { get; } - public SqlRegistrationsRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { - SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); + this.SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); } + public SqlDataAccess SqlDataAccess { get; } + public async Task GetRegistration(string clientId, string dataHolderBrandId) { - return await SqlDataAccess.GetRegistration(clientId, dataHolderBrandId); + return await this.SqlDataAccess.GetRegistration(clientId, dataHolderBrandId); } public async Task> GetRegistrations() { - return await SqlDataAccess.GetRegistrations(); + return await this.SqlDataAccess.GetRegistrations(); } public async Task> GetDcrMessageRegistrations() { - return await SqlDataAccess.GetDcrMessageRegistrations(); + return await this.SqlDataAccess.GetDcrMessageRegistrations(); } public async Task DeleteRegistration(string clientId, string dataHolderBrandId) { - var registration = await GetRegistration(clientId, dataHolderBrandId); + var registration = await this.GetRegistration(clientId, dataHolderBrandId); // Delete existing data. if (!string.IsNullOrEmpty(registration?.ClientId)) { - await SqlDataAccess.DeleteRegistration(clientId, dataHolderBrandId); - await SqlDataAccess.DeleteCdrArrangementData(clientId); + await this.SqlDataAccess.DeleteRegistration(clientId, dataHolderBrandId); + await this.SqlDataAccess.DeleteCdrArrangementData(clientId); } } // Check is DH id is present public async Task PersistRegistration(Registration registration) { - var existingRegistration = await GetRegistration(registration.ClientId, registration.DataHolderBrandId); + var existingRegistration = await this.GetRegistration(registration.ClientId, registration.DataHolderBrandId); if (string.IsNullOrEmpty(existingRegistration?.ClientId)) { - await SqlDataAccess.InsertRegistration(registration); + await this.SqlDataAccess.InsertRegistration(registration); } } public async Task UpdateRegistration(Registration registration) { - var currentRegstration = await GetRegistration(registration.ClientId, registration.DataHolderBrandId); + var currentRegstration = await this.GetRegistration(registration.ClientId, registration.DataHolderBrandId); // Update existing data. if (!string.IsNullOrEmpty(currentRegstration?.ClientId)) { - await SqlDataAccess.UpdateRegistration(registration); + await this.SqlDataAccess.UpdateRegistration(registration); } } } diff --git a/Source/CDR.DataRecipient.SDK/Extensions/HttpClientHandlerExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/HttpClientHandlerExtensions.cs new file mode 100644 index 0000000..7b627dc --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Extensions/HttpClientHandlerExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace CDR.DataRecipient.SDK.Extensions +{ + public static class HttpClientHandlerExtensions + { + public static void SetServerCertificateValidation(this HttpClientHandler httpClientHandler, bool acceptAnyServerCertificate) + { + httpClientHandler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidationCallback(acceptAnyServerCertificate); + } + + public static Func ServerCertificateCustomValidationCallback( + bool acceptAnyServerCertificate) + { + return (message, serverCert, chain, errors) => + { + if (acceptAnyServerCertificate) + { + return true; + } + + return errors == SslPolicyErrors.None; + }; + } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs index ebe17d8..e716089 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs @@ -1,12 +1,10 @@ -using CDR.DataRecipient.SDK.Models; -using CDR.DataRecipient.SDK.Register; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Models; +using CDR.DataRecipient.SDK.Register; namespace CDR.DataRecipient.SDK.Extensions { diff --git a/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs index 41e25ef..2b6ae76 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs @@ -1,7 +1,4 @@ -using Jose; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; -using System; +using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; @@ -9,6 +6,10 @@ using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Jose; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; namespace CDR.DataRecipient.SDK.Extensions { @@ -84,10 +85,7 @@ public static async Task GetJwks( bool enforceHttpsEndpoint = false) { var clientHandler = new HttpClientHandler(); - if (acceptAnyServerCertificate) - { - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - } + clientHandler.SetServerCertificateValidation(acceptAnyServerCertificate); var jwksClient = new HttpClient(clientHandler); var jwksResponse = await jwksClient.GetAsync(jwksEndpoint.ValidateEndpoint(enforceHttpsEndpoint)); diff --git a/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs index a503f9c..dd2a13d 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs @@ -1,9 +1,5 @@ -using CDR.DataRecipient.SDK.Exceptions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; +using CDR.DataRecipient.SDK.Exceptions; namespace CDR.DataRecipient.SDK.Extensions { diff --git a/Source/CDR.DataRecipient.SDK/Enumerations.cs b/Source/CDR.DataRecipient.SDK/Industry.cs similarity index 100% rename from Source/CDR.DataRecipient.SDK/Enumerations.cs rename to Source/CDR.DataRecipient.SDK/Industry.cs diff --git a/Source/CDR.DataRecipient.SDK/MessageEnum.cs b/Source/CDR.DataRecipient.SDK/Message.cs similarity index 100% rename from Source/CDR.DataRecipient.SDK/MessageEnum.cs rename to Source/CDR.DataRecipient.SDK/Message.cs diff --git a/Source/CDR.DataRecipient.SDK/Models/Acr.cs b/Source/CDR.DataRecipient.SDK/Models/Acr.cs new file mode 100644 index 0000000..819e46a --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/Acr.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models.AuthorisationRequest +{ + public class Acr + { + [JsonProperty(PropertyName = "essential")] + public bool Essential { get; set; } + + [JsonProperty(PropertyName = "values")] + public string[] Values { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs b/Source/CDR.DataRecipient.SDK/Models/AuthDetail.cs similarity index 100% rename from Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs rename to Source/CDR.DataRecipient.SDK/Models/AuthDetail.cs diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs index c5e8620..5e7e5bf 100644 --- a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs +++ b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs @@ -6,49 +6,20 @@ public class AuthorisationRequestClaims { public AuthorisationRequestClaims(int supportedAcr) { - this.userinfo = new UserInfo(); - this.id_token = new IdToken(supportedAcr); + this.Userinfo = new AuthorisationRequestClaimsUserInfo(); + this.Id_token = new IdToken(supportedAcr); } - [JsonProperty(nameof(sharing_duration))] - public int? sharing_duration { get; set; } + [JsonProperty("sharing_duration")] + public int? Sharing_duration { get; set; } - [JsonProperty(nameof(cdr_arrangement_id))] - public string cdr_arrangement_id { get; set; } + [JsonProperty("cdr_arrangement_id")] + public string Cdr_arrangement_id { get; set; } - [JsonProperty(nameof(userinfo))] - public UserInfo userinfo { get; set; } + [JsonProperty("userinfo")] + public AuthorisationRequestClaimsUserInfo Userinfo { get; set; } - [JsonProperty(nameof(id_token))] - public IdToken id_token { get; set; } - } - - public class UserInfo - { - [JsonProperty(nameof(given_name))] - public string given_name { get; set; } - - [JsonProperty(nameof(family_name))] - public string family_name { get; set; } - } - - public class IdToken - { - public IdToken(int supportedAcr) - { - this.acr = new Acr() { essential = true, values = new string[] { $"urn:cds.au:cdr:{supportedAcr}" } }; - } - - [JsonProperty(nameof(acr))] - public Acr acr { get; set; } - } - - public class Acr - { - [JsonProperty(PropertyName = "essential")] - public bool essential { get; set; } - - [JsonProperty(PropertyName = "values")] - public string[] values { get; set; } + [JsonProperty("id_token")] + public IdToken Id_token { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaimsUserInfo.cs b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaimsUserInfo.cs new file mode 100644 index 0000000..8b9979d --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaimsUserInfo.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models.AuthorisationRequest +{ + public class AuthorisationRequestClaimsUserInfo + { + [JsonProperty("given_name")] + public string Given_name { get; set; } + + [JsonProperty("family_name")] + public string Family_name { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/Certificate.cs b/Source/CDR.DataRecipient.SDK/Models/Certificate.cs index 69e82e9..cbc3cfb 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Certificate.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Certificate.cs @@ -19,23 +19,23 @@ public X509Certificate2 X509Certificate { get { - if (_certificate != null) + if (this._certificate != null) { - return _certificate; + return this._certificate; } - if (!string.IsNullOrEmpty(Path) && !string.IsNullOrEmpty(Password)) + if (!string.IsNullOrEmpty(this.Path) && !string.IsNullOrEmpty(this.Password)) { - _certificate = new X509Certificate2(Path, Password, X509KeyStorageFlags.Exportable); + this._certificate = new X509Certificate2(this.Path, this.Password, X509KeyStorageFlags.Exportable); } - if (!string.IsNullOrEmpty(Url) && !string.IsNullOrEmpty(Password)) + if (!string.IsNullOrEmpty(this.Url) && !string.IsNullOrEmpty(this.Password)) { // Retrieve the raw bytes from the URL value. - _certificate = new X509Certificate2(DownloadData(Url), Password, X509KeyStorageFlags.Exportable); + this._certificate = new X509Certificate2(DownloadData(this.Url), this.Password, X509KeyStorageFlags.Exportable); } - return _certificate; + return this._certificate; } } diff --git a/Source/CDR.DataRecipient.SDK/Models/DRBrand.cs b/Source/CDR.DataRecipient.SDK/Models/DRBrand.cs new file mode 100644 index 0000000..cc78687 --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/DRBrand.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace CDR.DataRecipient.SDK.Models +{ + public class DRBrand + { + public string DataRecipientBrandId { get; set; } + + public string BrandName { get; set; } + + public string LogoUri { get; set; } + + public List SoftwareProducts { get; set; } + + public string Status { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DRProduct.cs b/Source/CDR.DataRecipient.SDK/Models/DRProduct.cs new file mode 100644 index 0000000..9caf7d7 --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/DRProduct.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace CDR.DataRecipient.SDK.Models +{ + public class DRProduct + { + public string SoftwareProductId { get; set; } + + public string SoftwareProductName { get; set; } + + public string SoftwareProductDescription { get; set; } + + public string LogoUri { get; set; } + + public string RecipientBaseUri { get; set; } + + public string RedirectUri { get; set; } + + public string JwksUri { get; set; } + + public string Scope { get; set; } + + public string Status { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs b/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs index ae87858..fd02e73 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs @@ -19,38 +19,4 @@ public class DataRecipientModel public DateTime LastUpdated { get; set; } } - - public class DRBrand - { - public string DataRecipientBrandId { get; set; } - - public string BrandName { get; set; } - - public string LogoUri { get; set; } - - public List SoftwareProducts { get; set; } - - public string Status { get; set; } - } - - public class DRProduct - { - public string SoftwareProductId { get; set; } - - public string SoftwareProductName { get; set; } - - public string SoftwareProductDescription { get; set; } - - public string LogoUri { get; set; } - - public string RecipientBaseUri { get; set; } - - public string RedirectUri { get; set; } - - public string JwksUri { get; set; } - - public string Scope { get; set; } - - public string Status { get; set; } - } } diff --git a/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs b/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs index df76629..0145840 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs @@ -6,17 +6,4 @@ public class DataRecipientViewModel public string SoftwareProductName { get; set; } } - - public class SoftwareProductViewModel - { - public string BrandId { get; set; } - - public string RecipientBaseUri { get; set; } - - public string RedirectUri { get; set; } - - public string JwksUri { get; set; } - - public string Scope { get; set; } - } } diff --git a/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs b/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs index 90757ff..70019e0 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs @@ -21,13 +21,13 @@ public string Payload { get { - return _payload; + return this._payload; } set { - _payload = value; - this.Data = Newtonsoft.Json.JsonConvert.DeserializeObject(_payload); + this._payload = value; + this.Data = Newtonsoft.Json.JsonConvert.DeserializeObject(this._payload); } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Error.cs b/Source/CDR.DataRecipient.SDK/Models/Error.cs index b94f098..90bea34 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Error.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Error.cs @@ -31,7 +31,7 @@ public Error(string code, string title, string detail) public string Detail { get; set; } /// - /// Optional additional data for specific error types + /// Optional additional data for specific error types. /// public object Meta { get; set; } } diff --git a/Source/CDR.DataRecipient.SDK/Models/ErrorList.cs b/Source/CDR.DataRecipient.SDK/Models/ErrorList.cs index 8ed4cb2..3ce0fae 100644 --- a/Source/CDR.DataRecipient.SDK/Models/ErrorList.cs +++ b/Source/CDR.DataRecipient.SDK/Models/ErrorList.cs @@ -4,16 +4,9 @@ namespace CDR.DataRecipient.SDK.Models { public class ErrorList { - public List Errors { get; set; } - - public bool HasErrors() - { - return Errors != null && Errors.Count > 0; - } - public ErrorList() { - this.Errors = new List(); + this.Errors = []; } public ErrorList(Error error) @@ -26,5 +19,12 @@ public ErrorList(string errorCode, string errorTitle, string errorDetail) var error = new Error(errorCode, errorTitle, errorDetail); this.Errors = new List() { error }; } + + public List Errors { get; set; } + + public bool HasErrors() + { + return this.Errors != null && this.Errors.Count > 0; + } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/IdToken.cs b/Source/CDR.DataRecipient.SDK/Models/IdToken.cs new file mode 100644 index 0000000..963b611 --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/IdToken.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models.AuthorisationRequest +{ + public class IdToken + { + public IdToken(int supportedAcr) + { + this.Acr = new Acr() { Essential = true, Values = new string[] { $"urn:cds.au:cdr:{supportedAcr}" } }; + } + + [JsonProperty("acr")] + public Acr Acr { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs b/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs index 0321d3b..391ed7d 100644 --- a/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs +++ b/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs @@ -1,19 +1,28 @@ -namespace CDR.DataRecipient.SDK.Models +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models { public class JsonWebKey { - public string alg { get; set; } + [JsonProperty("alg")] + public string Alg { get; set; } - public string e { get; set; } + [JsonProperty("e")] + public string E { get; set; } - public string use { get; set; } + [JsonProperty("use")] + public string Use { get; set; } - public string kid { get; set; } + [JsonProperty("kid")] + public string Kid { get; set; } - public string kty { get; set; } + [JsonProperty("kty")] + public string Kty { get; set; } - public string n { get; set; } + [JsonProperty("n")] + public string N { get; set; } - public string d { get; set; } + [JsonProperty("d")] + public string D { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/JsonWebKeySet.cs b/Source/CDR.DataRecipient.SDK/Models/JsonWebKeySet.cs index 156de5a..e4d06a1 100644 --- a/Source/CDR.DataRecipient.SDK/Models/JsonWebKeySet.cs +++ b/Source/CDR.DataRecipient.SDK/Models/JsonWebKeySet.cs @@ -1,7 +1,10 @@ -namespace CDR.DataRecipient.SDK.Models +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models { public class JsonWebKeySet { - public JsonWebKey[] keys { get; set; } + [JsonProperty("keys")] + public JsonWebKey[] Keys { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/MtlsAliases.cs b/Source/CDR.DataRecipient.SDK/Models/MtlsAliases.cs new file mode 100644 index 0000000..159f476 --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/MtlsAliases.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace CDR.DataRecipient.SDK.Models +{ + public class MtlsAliases + { + [JsonProperty("token_endpoint")] + public string TokenEndpoint { get; set; } + + [JsonProperty("revocation_endpoint")] + public string RevocationEndpoint { get; set; } + + [JsonProperty("introspection_endpoint")] + public string IntrospectionEndpoint { get; set; } + + [JsonProperty("userinfo_endpoint")] + public string UserInfoEndpoint { get; set; } + + [JsonProperty("registration_endpoint")] + public string RegistrationEndpoint { get; set; } + + [JsonProperty("pushed_authorization_request_endpoint")] + public string PushedAuthorizationRequestEndpoint { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs b/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs index 01e5ffe..5823b4f 100644 --- a/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs +++ b/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs @@ -4,132 +4,127 @@ namespace CDR.DataRecipient.SDK.Models { public class OidcDiscovery { + private string _tokenEndpoint; + private string _introspectionEndpoint; + private string _revocationEndpoint; + private string _userInfoEndpoint; + private string _registrationEndpoint; + private string _pushedAuthorizationRequestEndpoint; + [JsonProperty("issuer")] public string Issuer { get; set; } [JsonProperty("authorization_endpoint")] public string AuthorizationEndpoint { get; set; } - private string _tokenEndpoint; - [JsonProperty("token_endpoint")] public string TokenEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.TokenEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.TokenEndpoint)) { - return MtlsEndpointAliases.TokenEndpoint; + return this.MtlsEndpointAliases.TokenEndpoint; } - return _tokenEndpoint; + return this._tokenEndpoint; } set { - _tokenEndpoint = value; + this._tokenEndpoint = value; } } - private string _introspectionEndpoint; - [JsonProperty("introspection_endpoint")] public string IntrospectionEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.IntrospectionEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.IntrospectionEndpoint)) { - return MtlsEndpointAliases.IntrospectionEndpoint; + return this.MtlsEndpointAliases.IntrospectionEndpoint; } - return _introspectionEndpoint; + return this._introspectionEndpoint; } set { - _introspectionEndpoint = value; + this._introspectionEndpoint = value; } } - private string _revocationEndpoint; - [JsonProperty("revocation_endpoint")] public string RevocationEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RevocationEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.RevocationEndpoint)) { - return MtlsEndpointAliases.RevocationEndpoint; + return this.MtlsEndpointAliases.RevocationEndpoint; } - return _revocationEndpoint; + return this._revocationEndpoint; } - set => _revocationEndpoint = value; + set => this._revocationEndpoint = value; } - private string _userInfoEndpoint; - [JsonProperty("userinfo_endpoint")] public string UserInfoEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.UserInfoEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.UserInfoEndpoint)) { - return MtlsEndpointAliases.UserInfoEndpoint; + return this.MtlsEndpointAliases.UserInfoEndpoint; } - return _userInfoEndpoint; + return this._userInfoEndpoint; } set { - _userInfoEndpoint = value; + this._userInfoEndpoint = value; } } - private string _registrationEndpoint; - [JsonProperty("registration_endpoint")] public string RegistrationEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RegistrationEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.RegistrationEndpoint)) { - return MtlsEndpointAliases.RegistrationEndpoint; + return this.MtlsEndpointAliases.RegistrationEndpoint; } - return _registrationEndpoint; + return this._registrationEndpoint; } set { - _registrationEndpoint = value; + this._registrationEndpoint = value; } } - private string _pushedAuthorizationRequestEndpoint; - [JsonProperty("pushed_authorization_request_endpoint")] public string PushedAuthorizationRequestEndpoint { get { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.PushedAuthorizationRequestEndpoint)) + if (!string.IsNullOrEmpty(this.MtlsEndpointAliases?.PushedAuthorizationRequestEndpoint)) { - return MtlsEndpointAliases.PushedAuthorizationRequestEndpoint; + return this.MtlsEndpointAliases.PushedAuthorizationRequestEndpoint; } - return _pushedAuthorizationRequestEndpoint; + return this._pushedAuthorizationRequestEndpoint; } set { - _pushedAuthorizationRequestEndpoint = value; + this._pushedAuthorizationRequestEndpoint = value; } } @@ -184,25 +179,4 @@ public string PushedAuthorizationRequestEndpoint [JsonProperty("mtls_endpoint_aliases")] public MtlsAliases MtlsEndpointAliases { get; set; } } - - public class MtlsAliases - { - [JsonProperty("token_endpoint")] - public string TokenEndpoint { get; set; } - - [JsonProperty("revocation_endpoint")] - public string RevocationEndpoint { get; set; } - - [JsonProperty("introspection_endpoint")] - public string IntrospectionEndpoint { get; set; } - - [JsonProperty("userinfo_endpoint")] - public string UserInfoEndpoint { get; set; } - - [JsonProperty("registration_endpoint")] - public string RegistrationEndpoint { get; set; } - - [JsonProperty("pushed_authorization_request_endpoint")] - public string PushedAuthorizationRequestEndpoint { get; set; } - } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Pkce.cs b/Source/CDR.DataRecipient.SDK/Models/Pkce.cs index a57a8c2..5d771a6 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Pkce.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Pkce.cs @@ -1,22 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CDR.DataRecipient.SDK.Models +namespace CDR.DataRecipient.SDK.Models { public class Pkce { + public Pkce() + { + this.CodeChallengeMethod = Constants.Infosec.CODE_CHALLENGE_METHOD; + } + public string CodeVerifier { get; set; } public string CodeChallenge { get; set; } public string CodeChallengeMethod { get; private set; } - - public Pkce() - { - this.CodeChallengeMethod = Constants.Infosec.CODE_CHALLENGE_METHOD; - } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Registration.cs b/Source/CDR.DataRecipient.SDK/Models/Registration.cs index 70f6648..f89d385 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Registration.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Registration.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System; +using System; +using Newtonsoft.Json; namespace CDR.DataRecipient.SDK.Models { @@ -93,11 +93,6 @@ public class Registration [JsonProperty("scope")] public string Scope { get; set; } - public string GetRegistrationId() - { - return $"{ClientId}{IdDelimeter}{DataHolderBrandId}"; - } - public static (string ClientId, string DataHolderBrandId) SplitRegistrationId(string id) { var idParts = id?.Split(IdDelimeter); @@ -113,5 +108,10 @@ public static (string ClientId, string DataHolderBrandId) SplitRegistrationId(st return (idParts[0], idParts[1]); } + + public string GetRegistrationId() + { + return $"{this.ClientId}{IdDelimeter}{this.DataHolderBrandId}"; + } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Response.cs b/Source/CDR.DataRecipient.SDK/Models/Response.cs index 68d4aa9..1e450fa 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Response.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Response.cs @@ -23,9 +23,4 @@ public bool IsSuccessful public ErrorList Errors { get; set; } } - - public class Response : Response - { - public T Data { get; set; } - } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Response{T}.cs b/Source/CDR.DataRecipient.SDK/Models/Response{T}.cs new file mode 100644 index 0000000..2db3a0c --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/Response{T}.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace CDR.DataRecipient.SDK.Models +{ + public class Response : Response + { + public T Data { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs b/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs index f8be610..58d0008 100644 --- a/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs +++ b/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs @@ -1,23 +1,25 @@ namespace CDR.DataRecipient.SDK.Models { - public class ServiceConfiguration : IServiceConfiguration +#pragma warning disable SA1649 // File name should match first type name + public interface IServiceConfiguration +#pragma warning restore SA1649 // File name should match first type name { - public bool AcceptAnyServerCertificate { get; set; } + bool AcceptAnyServerCertificate { get; set; } - public bool EnforceHttpsEndpoints { get; set; } + bool EnforceHttpsEndpoints { get; set; } + } + public class ServiceConfiguration : IServiceConfiguration + { public ServiceConfiguration() { // Set defaults. - AcceptAnyServerCertificate = false; - EnforceHttpsEndpoints = true; + this.AcceptAnyServerCertificate = false; + this.EnforceHttpsEndpoints = true; } - } - public interface IServiceConfiguration - { - bool AcceptAnyServerCertificate { get; set; } + public bool AcceptAnyServerCertificate { get; set; } - bool EnforceHttpsEndpoints { get; set; } + public bool EnforceHttpsEndpoints { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs b/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs index 9362359..d666bb2 100644 --- a/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs +++ b/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs @@ -20,7 +20,7 @@ public string RedirectUri } } - public string RevocationUri => $"{RecipientBaseUri}/{Constants.Urls.ClientArrangementRevokeUrl}"; + public string RevocationUri => $"{this.RecipientBaseUri}/{Constants.Urls.ClientArrangementRevokeUrl}"; public string Scope { get; set; } diff --git a/Source/CDR.DataRecipient.SDK/Models/SoftwareProductViewModel.cs b/Source/CDR.DataRecipient.SDK/Models/SoftwareProductViewModel.cs new file mode 100644 index 0000000..47b8e01 --- /dev/null +++ b/Source/CDR.DataRecipient.SDK/Models/SoftwareProductViewModel.cs @@ -0,0 +1,15 @@ +namespace CDR.DataRecipient.SDK.Models +{ + public class SoftwareProductViewModel + { + public string BrandId { get; set; } + + public string RecipientBaseUri { get; set; } + + public string RedirectUri { get; set; } + + public string JwksUri { get; set; } + + public string Scope { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/TokenResponse.cs b/Source/CDR.DataRecipient.SDK/Models/Token.cs similarity index 100% rename from Source/CDR.DataRecipient.SDK/Models/TokenResponse.cs rename to Source/CDR.DataRecipient.SDK/Models/Token.cs diff --git a/Source/CDR.DataRecipient.SDK/Services/BaseService.cs b/Source/CDR.DataRecipient.SDK/Services/BaseService.cs index 205d58f..37ef708 100644 --- a/Source/CDR.DataRecipient.SDK/Services/BaseService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/BaseService.cs @@ -11,20 +11,27 @@ namespace CDR.DataRecipient.SDK.Services { public abstract class BaseService { - protected readonly IConfiguration _config; - protected readonly ILogger _logger; - protected readonly IServiceConfiguration _serviceConfiguration; + private readonly IConfiguration _config; + private readonly ILogger _logger; + private readonly IServiceConfiguration _serviceConfiguration; protected BaseService( IConfiguration config, ILogger logger, IServiceConfiguration serviceConfiguration) { - _config = config; - _logger = logger; - _serviceConfiguration = serviceConfiguration; + this._config = config; + this._logger = logger; + this._serviceConfiguration = serviceConfiguration; } + // Expose protected read-only properties for derived classes + protected IConfiguration Config => this._config; + + protected ILogger Logger => this._logger; + + protected IServiceConfiguration ServiceConfiguration => this._serviceConfiguration; + protected virtual HttpClient GetHttpClient( X509Certificate2 clientCertificate = null, string accessToken = null, @@ -33,9 +40,9 @@ protected virtual HttpClient GetHttpClient( var clientHandler = new HttpClientHandler(); // If accepting any TLS server certificate, then ignore certificate validation. - if (_serviceConfiguration.AcceptAnyServerCertificate) + if (this._serviceConfiguration.AcceptAnyServerCertificate) { - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + clientHandler.SetServerCertificateValidation(this._serviceConfiguration.AcceptAnyServerCertificate); } // Set the client certificate for the connection if supplied. @@ -63,17 +70,17 @@ protected virtual HttpClient GetHttpClient( protected virtual string EnsureValidEndpoint(string uri) { - return uri.ValidateEndpoint(_serviceConfiguration.EnforceHttpsEndpoints); + return uri.ValidateEndpoint(this._serviceConfiguration.EnforceHttpsEndpoints); } protected virtual Uri EnsureValidEndpoint(Uri uri) { - return uri.ValidateEndpoint(_serviceConfiguration.EnforceHttpsEndpoints); + return uri.ValidateEndpoint(this._serviceConfiguration.EnforceHttpsEndpoints); } protected virtual HttpRequestMessage EnsureValidEndpoint(HttpRequestMessage request) { - request.RequestUri.ValidateEndpoint(_serviceConfiguration.EnforceHttpsEndpoints); + request.RequestUri.ValidateEndpoint(this._serviceConfiguration.EnforceHttpsEndpoints); return request; } } diff --git a/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs b/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs index ac77021..586c895 100644 --- a/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs @@ -1,11 +1,10 @@ -using CDR.DataRecipient.SDK.Extensions; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace CDR.DataRecipient.SDK.Services.DataHolder { @@ -25,19 +24,19 @@ public async Task DeleteRegistration( string accessToken, string clientId) { - _logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(DeleteRegistration)}."); + this.Logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(this.DeleteRegistration)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken); + var client = this.GetHttpClient(clientCertificate, accessToken); - _logger.LogDebug("Deleting registration from Data Holder: {RegistrationEndpoint}. Client ID: {ClientId}. Client Certificate: {Thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); + this.Logger.LogDebug("Deleting registration from Data Holder: {RegistrationEndpoint}. Client ID: {ClientId}. Client Certificate: {Thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); // Make the request to the data holder's registration endpoint. var uri = string.Concat(registrationEndpoint.TrimEnd('/'), "/", clientId); - var response = await client.DeleteAsync(EnsureValidEndpoint(uri)); + var response = await client.DeleteAsync(this.EnsureValidEndpoint(uri)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse { @@ -53,17 +52,17 @@ public async Task GetRegistration( string accessToken, string clientId) { - _logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(GetRegistration)}."); + this.Logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(this.GetRegistration)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken); + var client = this.GetHttpClient(clientCertificate, accessToken); // Make the request to the data holder's registration endpoint. var uri = string.Concat(registrationEndpoint.TrimEnd('/'), "/", clientId); - var response = await client.GetAsync(EnsureValidEndpoint(uri)); + var response = await client.GetAsync(this.EnsureValidEndpoint(uri)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { @@ -78,22 +77,22 @@ public async Task Register( X509Certificate2 clientCertificate, string payload) { - _logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(Register)}."); + this.Logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(this.Register)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); - _logger.LogDebug("Registering with Data Holder: {RegistrationEndpoint}. Client Certificate: {Thumbprint}", registrationEndpoint, clientCertificate.Thumbprint); + this.Logger.LogDebug("Registering with Data Holder: {RegistrationEndpoint}. Client Certificate: {Thumbprint}", registrationEndpoint, clientCertificate.Thumbprint); // Create the post content. var content = new StringContent(payload); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/jwt"); // Make the request to the data holder's registration endpoint. - var response = await client.PostAsync(EnsureValidEndpoint(registrationEndpoint), content); + var response = await client.PostAsync(this.EnsureValidEndpoint(registrationEndpoint), content); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { @@ -111,12 +110,12 @@ public async Task UpdateRegistration( string clientId, string payload) { - _logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(UpdateRegistration)}."); + this.Logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(this.UpdateRegistration)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken); + var client = this.GetHttpClient(clientCertificate, accessToken); - _logger.LogDebug("Updating registration with Data Holder: {RegistrationEndpoint}. Client ID: {ClientId}. Client Certificate: {Thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); + this.Logger.LogDebug("Updating registration with Data Holder: {RegistrationEndpoint}. Client ID: {ClientId}. Client Certificate: {Thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); // Create the put content. var content = new StringContent(payload); @@ -124,10 +123,10 @@ public async Task UpdateRegistration( // Make the request to the data holder's registration endpoint. var uri = string.Concat(registrationEndpoint.TrimEnd('/'), "/", clientId); - var response = await client.PutAsync(EnsureValidEndpoint(uri), content); + var response = await client.PutAsync(this.EnsureValidEndpoint(uri), content); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { diff --git a/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs index 47ac255..bad9b46 100644 --- a/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs @@ -26,7 +26,7 @@ public InfosecService( IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) { - _accessTokenService = accessTokenService; + this._accessTokenService = accessTokenService; } public async Task> GetOidcDiscovery( @@ -34,11 +34,11 @@ public async Task> GetOidcDiscovery( { var oidcResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(GetOidcDiscovery)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.GetOidcDiscovery)}."); - var client = GetHttpClient(); + var client = this.GetHttpClient(); var configUrl = string.Concat(infosecBaseUri.TrimEnd('/'), "/.well-known/openid-configuration"); - var configResponse = await client.GetAsync(EnsureValidEndpoint(configUrl)); + var configResponse = await client.GetAsync(this.EnsureValidEndpoint(configUrl)); oidcResponse.StatusCode = configResponse.StatusCode; @@ -60,10 +60,10 @@ public async Task> PushedAuthorisationRequest( { var parResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(PushedAuthorisationRequest)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.PushedAuthorisationRequest)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); var formFields = new Dictionary(); formFields.Add("request", request); @@ -73,7 +73,7 @@ public async Task> PushedAuthorisationRequest( signingCertificate, issuer: clientId, additionalFormFields: formFields, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); @@ -92,7 +92,7 @@ public async Task> PushedAuthorisationRequest( public string BuildAuthorisationRequestJwt(AuthorisationRequestJwt authorisationRequestJwt) { - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(BuildAuthorisationRequestJwt)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.BuildAuthorisationRequestJwt)}."); // Build the list of claims to include in the authorisation request jwt. var authorisationRequestClaims = new Dictionary @@ -106,7 +106,7 @@ public string BuildAuthorisationRequestJwt(AuthorisationRequestJwt authorisation { "nonce", authorisationRequestJwt.Nonce }, { "claims", JsonSerializer.SerializeToElement(new AuthorisationRequestClaims(authorisationRequestJwt.AcrValueSupported) - { sharing_duration = authorisationRequestJwt.SharingDuration, cdr_arrangement_id = authorisationRequestJwt.CdrArrangementId }) + { Sharing_duration = authorisationRequestJwt.SharingDuration, Cdr_arrangement_id = authorisationRequestJwt.CdrArrangementId }) }, }; @@ -127,9 +127,9 @@ public async Task BuildAuthorisationRequestUri( string scope, string responseType = "code") { - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(BuildAuthorisationRequestUri)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.BuildAuthorisationRequestUri)}."); - var config = (await GetOidcDiscovery(infosecBaseUri)).Data; + var config = (await this.GetOidcDiscovery(infosecBaseUri)).Data; string authRequestUri = config.AuthorizationEndpoint .AppendQueryString("client_id", clientId) @@ -142,9 +142,9 @@ public async Task BuildAuthorisationRequestUri( public async Task> GetAccessToken(AccessToken accessToken) { - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(GetAccessToken)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.GetAccessToken)}."); - return await _accessTokenService.GetAccessToken(accessToken); + return await this._accessTokenService.GetAccessToken(accessToken); } public async Task> RefreshAccessToken( @@ -158,10 +158,10 @@ public async Task> RefreshAccessToken( { var tokenResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(RefreshAccessToken)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.RefreshAccessToken)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); var formFields = new Dictionary(); formFields.Add("refresh_token", refreshToken); @@ -175,7 +175,7 @@ public async Task> RefreshAccessToken( scope: scope, grantType: TokenTypes.REFRESH_TOKEN, additionalFormFields: formFields, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); @@ -202,10 +202,10 @@ public async Task RevokeToken( { var revocationResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(RevokeToken)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.RevokeToken)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); var formFields = new Dictionary(); formFields.Add("token", token); @@ -218,7 +218,7 @@ public async Task RevokeToken( clientId: clientId, scope: string.Empty, additionalFormFields: formFields, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); @@ -241,10 +241,10 @@ public async Task> Introspect( { var introspectionResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(Introspect)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.Introspect)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); var formFields = new Dictionary(); formFields.Add("token", refreshToken); @@ -257,7 +257,7 @@ public async Task> Introspect( clientId: clientId, scope: string.Empty, additionalFormFields: formFields, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); @@ -282,12 +282,12 @@ public async Task> Introspect( { var userInfoResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(UserInfo)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.UserInfo)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken); + var client = this.GetHttpClient(clientCertificate, accessToken); - var response = await client.GetAsync(EnsureValidEndpoint(userInfoEndpoint)); + var response = await client.GetAsync(this.EnsureValidEndpoint(userInfoEndpoint)); var body = await response.Content.ReadAsStringAsync(); userInfoResponse.StatusCode = response.StatusCode; @@ -313,10 +313,10 @@ public async Task RevokeCdrArrangement( { var revocationResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(RevokeCdrArrangement)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.RevokeCdrArrangement)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate); + var client = this.GetHttpClient(clientCertificate); var formFields = new Dictionary(); formFields.Add("cdr_arrangement_id", cdrArrangementId); @@ -328,7 +328,7 @@ public async Task RevokeCdrArrangement( clientId: clientId, scope: string.Empty, additionalFormFields: formFields, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); @@ -349,12 +349,12 @@ public async Task RevokeCdrArrangement( { var parResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(PushedAuthorizationRequest)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.PushedAuthorizationRequest)}."); // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken); + var client = this.GetHttpClient(clientCertificate, accessToken); - var response = await client.GetAsync(EnsureValidEndpoint(parEndpoint)); + var response = await client.GetAsync(this.EnsureValidEndpoint(parEndpoint)); var body = await response.Content.ReadAsStringAsync(); parResponse.StatusCode = response.StatusCode; diff --git a/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs b/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs index 36ed67f..5d708fe 100644 --- a/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs +++ b/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs @@ -14,9 +14,8 @@ namespace CDR.DataRecipient.SDK.Register /// public class PrivateKeyJwt { - public SigningCredentials SigningCredentials { get; set; } - /// + /// Initializes a new instance of the class. /// Provide the Pkcs8 private key from X509 certificate. /// /// The path to the certificate. @@ -27,6 +26,7 @@ public PrivateKeyJwt(string certFilePath, string pwd) } /// + /// Initializes a new instance of the class. /// Provide the Pkcs8 private key from X509 certificate. /// /// The certificate used to sign the private key jwt. @@ -36,6 +36,7 @@ public PrivateKeyJwt(X509Certificate2 signingCertificate) } /// + /// Initializes a new instance of the class. /// Provide the private key directly. /// /// The path to the certificate. @@ -49,12 +50,17 @@ public PrivateKeyJwt(string privateKey) this.SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSsaPssSha256); } + public SigningCredentials SigningCredentials { get; set; } + /// /// Generate the private_key_jwt using the provided private key. /// - /// The issuer of the JWT, usually set to the softwareProductId - /// The audience of the JWT, usually set to the target token endpoint - /// A base64 encoded JWT + /// The issuer of the JWT, usually set to the softwareProductId. + /// The audience of the JWT, usually set to the target token endpoint. + /// jti. + /// The expiry in Minutes. + /// Key Id. + /// A base64 encoded JWT. public string Generate( string issuer, string audience, diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs index 0cbd540..2a395b7 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs @@ -1,6 +1,6 @@ -using CDR.DataRecipient.SDK.Enumerations; -using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Enumerations; namespace CDR.DataRecipient.SDK.Services.Register { diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs index f108bc9..9833e5e 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs @@ -18,7 +18,7 @@ public InfosecService( IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) { - _accessTokenService = accessTokenService; + this._accessTokenService = accessTokenService; } public async Task> GetAccessToken( @@ -28,7 +28,7 @@ public async Task> GetAccessToken( X509Certificate2 signingCertificate, string scope = Constants.Scopes.CDR_REGISTER) { - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(GetAccessToken)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.GetAccessToken)}."); var accessToken = new AccessToken() { @@ -39,17 +39,17 @@ public async Task> GetAccessToken( Scope = scope, }; - return await _accessTokenService.GetAccessToken(accessToken); + return await this._accessTokenService.GetAccessToken(accessToken); } public async Task> GetOidcDiscovery(string registerOidcConfigEndpoint) { var oidcResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(GetOidcDiscovery)}."); + this.Logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(this.GetOidcDiscovery)}."); - var client = GetHttpClient(); - var configResponse = await client.GetAsync(EnsureValidEndpoint(registerOidcConfigEndpoint)); + var client = this.GetHttpClient(); + var configResponse = await client.GetAsync(this.EnsureValidEndpoint(registerOidcConfigEndpoint)); oidcResponse.StatusCode = configResponse.StatusCode; @@ -64,7 +64,7 @@ public async Task> GetOidcDiscovery(string registerOidcC public async Task GetTokenEndpoint(string registerOidcConfigEndpoint) { - var oidd = await GetOidcDiscovery(registerOidcConfigEndpoint); + var oidd = await this.GetOidcDiscovery(registerOidcConfigEndpoint); return oidd.Data.TokenEndpoint; } } diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs index a99397a..61f6999 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs @@ -28,15 +28,15 @@ public MetadataService( int? page = null, int? pageSize = null) { - _logger.LogDebug($"Request received to {nameof(MetadataService)}.{nameof(GetDataHolderBrands)}."); + this.Logger.LogDebug($"Request received to {nameof(MetadataService)}.{nameof(this.GetDataHolderBrands)}."); // Setup the request to the get data holder brands endpoint. var endpoint = $"{registerMtlsBaseUri.TrimEnd('/')}/cdr-register/v1/{industry.ToPath()}/data-holders/brands"; // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken, version); + var client = this.GetHttpClient(clientCertificate, accessToken, version); - _logger.LogDebug("Requesting data holder brands from Register: {Endpoint}. Client Certificate: {Thumbprint}", endpoint, clientCertificate.Thumbprint); + this.Logger.LogDebug("Requesting data holder brands from Register: {Endpoint}. Client Certificate: {Thumbprint}", endpoint, clientCertificate.Thumbprint); // Add the query parameters. if (page.HasValue) @@ -50,10 +50,10 @@ public MetadataService( } // Make the request to the get data holder brands endpoint. - var response = await client.GetAsync(EnsureValidEndpoint(endpoint)); + var response = await client.GetAsync(this.EnsureValidEndpoint(endpoint)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get Data Holder Brands Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Get Data Holder Brands Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return (body, response.StatusCode, response.ReasonPhrase.ToString()); } @@ -63,21 +63,21 @@ public MetadataService( string version, Industry industry) { - _logger.LogDebug($"Request received to {nameof(MetadataService)}.{nameof(GetDataRecipients)}."); + this.Logger.LogDebug($"Request received to {nameof(MetadataService)}.{nameof(this.GetDataRecipients)}."); // Setup the request to the get data recipients endpoint. var endpoint = $"{registerTlsBaseUri.TrimEnd('/')}/cdr-register/v1/{industry.ToPath()}/data-recipients"; // Setup the http client. - var client = GetHttpClient(version: version); + var client = this.GetHttpClient(version: version); - _logger.LogDebug("Requesting data recipients from Register: {Endpoint}.", endpoint); + this.Logger.LogDebug("Requesting data recipients from Register: {Endpoint}.", endpoint); // Make the request to the get data recipients endpoint. - var response = await client.GetAsync(EnsureValidEndpoint(endpoint)); + var response = await client.GetAsync(this.EnsureValidEndpoint(endpoint)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get Data Recipients Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Get Data Recipients Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return (body, response.StatusCode, response.ReasonPhrase.ToString()); } diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs index d749fd4..c5424c5 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs @@ -31,21 +31,21 @@ public async Task> GetSoftwareStatementAssertion( { var ssaResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(SsaService)}.{nameof(GetSoftwareStatementAssertion)}."); + this.Logger.LogDebug($"Request received to {nameof(SsaService)}.{nameof(this.GetSoftwareStatementAssertion)}."); // Setup the request to the get ssa endpoint. var ssaEndpoint = $"{mtlsBaseUri}/cdr-register/v1/{industry.ToPath()}/data-recipients/brands/{brandId}/software-products/{softwareProductId}/ssa"; // Setup the http client. - var client = GetHttpClient(clientCertificate, accessToken, version); + var client = this.GetHttpClient(clientCertificate, accessToken, version); - _logger.LogDebug("Requesting SSA from Register: {SsaEndpoint}. Client Certificate: {Thumbprint}", ssaEndpoint, clientCertificate.Thumbprint); + this.Logger.LogDebug("Requesting SSA from Register: {SsaEndpoint}. Client Certificate: {Thumbprint}", ssaEndpoint, clientCertificate.Thumbprint); // Make the request to the get data holder brands endpoint. - var response = await client.GetAsync(EnsureValidEndpoint(ssaEndpoint)); + var response = await client.GetAsync(this.EnsureValidEndpoint(ssaEndpoint)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get SSA Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Get SSA Response: {StatusCode}. Body: {Body}", response.StatusCode, body); ssaResponse.StatusCode = response.StatusCode; diff --git a/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs b/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs index 264292c..9cd5f98 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs @@ -8,8 +8,6 @@ namespace CDR.DataRecipient.SDK.Services.Tokens { public class AccessTokenService : BaseService, IAccessTokenService { - private AccessToken AccessToken { get; set; } - public AccessTokenService( IConfiguration config, ILogger logger, @@ -17,42 +15,44 @@ public AccessTokenService( : base(config, logger, serviceConfiguration) { // LEGACY CODE - AccessToken = new AccessToken() { Scope = string.Empty }; + this.AccessToken = new AccessToken() { Scope = string.Empty }; } + private AccessToken AccessToken { get; set; } + public async Task> GetAccessToken(AccessToken accessToken) { var tokenResponse = new Response(); - _logger.LogDebug($"Request received to {nameof(AccessTokenService)}.{nameof(GetAccessToken)}."); + this.Logger.LogDebug($"Request received to {nameof(AccessTokenService)}.{nameof(this.GetAccessToken)}."); - AccessToken = accessToken; + this.AccessToken = accessToken; // Setup the http client. - var client = GetHttpClient(AccessToken.ClientCertificate); + var client = this.GetHttpClient(this.AccessToken.ClientCertificate); - _logger.LogDebug( + this.Logger.LogDebug( "Requesting access token from: {TokenEndpoint}. Software Product ID: {ClientId}. Client Certificate: {Thumbprint}", - AccessToken.TokenEndpoint, - AccessToken.ClientId, - AccessToken.ClientCertificate.Thumbprint); + this.AccessToken.TokenEndpoint, + this.AccessToken.ClientId, + this.AccessToken.ClientCertificate.Thumbprint); // Make the request to the token endpoint. var response = await client.SendPrivateKeyJwtRequest( - AccessToken.TokenEndpoint, - AccessToken.SigningCertificate, - AccessToken.ClientId, - AccessToken.ClientId, - AccessToken.Scope, - AccessToken.RedirectUri, - AccessToken.Code, - AccessToken.GrantType, - pkce: AccessToken.Pkce, - enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); + this.AccessToken.TokenEndpoint, + this.AccessToken.SigningCertificate, + this.AccessToken.ClientId, + this.AccessToken.ClientId, + this.AccessToken.Scope, + this.AccessToken.RedirectUri, + this.AccessToken.Code, + this.AccessToken.GrantType, + pkce: this.AccessToken.Pkce, + enforceHttpsEndpoint: this.ServiceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Access Token Response: {StatusCode}. Body: {Body}", response.StatusCode, body); + this.Logger.LogDebug("Access Token Response: {StatusCode}. Body: {Body}", response.StatusCode, body); tokenResponse.StatusCode = response.StatusCode; diff --git a/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj b/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj index f521114..f6c911b 100644 --- a/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj +++ b/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj @@ -19,7 +19,7 @@ - + @@ -36,7 +36,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs b/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs index 5bb08f2..b5bab90 100644 --- a/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs +++ b/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs @@ -1,8 +1,7 @@ -using CDR.DataRecipient.SDK.Services.Register; -using Microsoft.Extensions.Caching.Distributed; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Services.Register; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Caching { @@ -17,25 +16,25 @@ public CacheManager( ILogger logger, IInfosecService infosecService) { - _memCache = memCache; - _logger = logger; - _infosecService = infosecService; + this._memCache = memCache; + this._logger = logger; + this._infosecService = infosecService; } public async Task GetRegisterTokenEndpoint(string oidcDiscoveryUri) { var key = $"RegisterTokenEndpoint:{oidcDiscoveryUri}"; - var item = _memCache.Get(key); + var item = this._memCache.Get(key); if (!string.IsNullOrEmpty(item)) { - _logger.LogInformation("Cache hit: {Key}", key); + this._logger.LogInformation("Cache hit: {Key}", key); return item; } - var tokenEndpoint = await _infosecService.GetTokenEndpoint(oidcDiscoveryUri); + var tokenEndpoint = await this._infosecService.GetTokenEndpoint(oidcDiscoveryUri); - _memCache.Set(key, tokenEndpoint); - _logger.LogInformation("Adding item to cache: {Key}", key); + this._memCache.Set(key, tokenEndpoint); + this._logger.LogInformation("Adding item to cache: {Key}", key); return tokenEndpoint; } diff --git a/Source/CDR.DataRecipient.Web/Caching/ICacheManager.cs b/Source/CDR.DataRecipient.Web/Caching/ICacheManager.cs index 8fd42ad..760526d 100644 --- a/Source/CDR.DataRecipient.Web/Caching/ICacheManager.cs +++ b/Source/CDR.DataRecipient.Web/Caching/ICacheManager.cs @@ -1,5 +1,4 @@ -using CDR.DataRecipient.SDK.Models; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Caching { diff --git a/Source/CDR.DataRecipient.Web/Common/Constants.cs b/Source/CDR.DataRecipient.Web/Common/Constants.cs index aff195d..d7d533d 100644 --- a/Source/CDR.DataRecipient.Web/Common/Constants.cs +++ b/Source/CDR.DataRecipient.Web/Common/Constants.cs @@ -2,8 +2,47 @@ { public static class Constants { + public const string DEFAULT_KEY_ID = "7EFA85C18FDE857949BC2EAA21C25E49627D4865"; + + public const string DEFAULT_PRIVATE_KEY = + @"-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCx9oZn9tVOAXy +2XyjLv2JWYtn+cJ0CLPlym3A/r2pFAH4rQnoM2IAHcM5n6uWA9ke1bBUZM2+9UTj +y5jrNa+CGPYzsWnGOOpUcKgv3yaHlksBOi5xBPcx9UnraVpLiLLqcucCH1F1Soit +1Pg+OLwFqMW9rVNZYFFikc/KQO6/ZkKVGCVJyua4iVU5fANY0Je8gYTiobARfabd +QZgU8S/d2QGKcnFyxuYR81xaeOd6jJZ6ZzD3Hnl37cPHCksp+NQS7S7EKFhMNWT0 +iLe7NmWz2odUHA62tPpz0Aoma0L+yWoy1qrG+AERMjy0fwmXf7mM2hCDT3jEt3Nd ++0dzTp2zAgMBAAECggEACx5XX9EVNxccl9E8YSBEjruSzpueMvtwMXTNsQ+ZifY/ +ao+OGjgcpv8L7tUjeUu88Bqolxit+fGMPiiYEQ0eeKGuJCNDc3I6RhmsMBdf3quA +mpBUqFTtO2fSEWMRKXCjLejjMObSwow/oxSeGwcoDHam2v3y3Q43dxX1s4jjV/+H +uwgwghOqd9gTWeu7tOQefYkJ4Tsj6UMrf42LbSazjQmz+4sABmYv1TiuGW9Uj+vB +iV7Jozc9rVx4ZOhKrGxnM8kMsG6RWCfP5Nm3PUM/9tCzqNcPJCN3FyEcj2tfU/0I +CBuUqXaX4V/usg+TOf2tO2n/5TIZQozUatOAhWeJcQKBgQD3UJ9F6kIpkcO9qZLp +sLN0eVpt3hk2A//XV+QrKg8sYFxlYuX93TnF3q+V56h+UqXgCLnj0Q7MeAWLap2e +ApbumYRz8/qpthUqi0wyR8e8JEMDi3L5cOXNRYcVY2F3XKmxFXjfnNQ6gkms0Gfe +3Pni+q/xceQ7nhc0h/HAdNm3SwKBgQDJnvF6sW8bwamFoi0CWPOBcmb52QJ6tZ7h +Duh6fNZVE+FoRFv2q6PUNsm9rnTZtR7uevYcfnt2NrXNpsMQmnpaEU73nYWhiPn4 +zxaavAEFQPzUFaocYEAj5uXSKv1KNK2lvT9bjQFDT1lU1eginXJAbWa63zDnkV89 +AxH9LjeqOQKBgDtg8QzBROdkJwIHj81p7nw9krekRptQdIHIiXDPpVr7O9Pf3eaI +0hEu+Stdtne18juK/M605/+xpWsmyvcgGgrpcwLABmPu4sAXN9EuqMcEUc6tEYrQ +T2xskBVTihg1eEybIi1WIyJ1G6lRVE8O8TRNCidHOAwUVe/339RcedVnAoGAGcr7 +mXaZgDOGPFJC78nxXN4FznC0oH4blS8TDphp0vh4HZ6hJS1QCBX6OQnYaQGCs3+H +fJ2xra3SFD0BN16LyHnuYD8GmWOslufnPGRQvRtTPM6ItJibm/wt6nUVcijLDijn +sg6X2sSL6Q50Y/lAZH2aZs2ms/kk9ekuo/UFqgECgYEAlGWBKSsYH4OijwOSA54W +tHeKOMkv5DySAPQUhMPSE13y56XaoHdtl+wGElYQ2/lt1h8dhj0L04QZjhqkAJn3 +79xiYVOkKJvDjHCnjLIHkrDOKWBVApX31sD2uKl4BXRzLO+9iV+W67T89z71Ftdx +dQMjPQnK/1R3jqD9OWNr818= +-----END PRIVATE KEY----- +"; + public static class ConfigurationKeys { + public const string AllowSpecificOrigins = "AllowSpecificOrigins"; + public const string AllowSpecificHeaders = "AllowSpecificHeaders"; + public const string AcceptAnyServerCertificate = "AcceptAnyServerCertificate"; + public const string EnforceHttpsEndpoints = "EnforceHttpsEndpoints"; + public const string ContentSecurityPolicy = "ContentSecurityPolicy"; + public static class MockDataRecipient { public const string Hostname = "MockDataRecipient:Hostname"; @@ -48,12 +87,6 @@ public static class Register public const string MtlsBareUri = "MockDataRecipient:Register:mtlsBaseUri"; public const string OidcDiscoveryUri = "MockDataRecipient:Register:oidcDiscoveryUri"; } - - public const string AllowSpecificOrigins = "AllowSpecificOrigins"; - public const string AllowSpecificHeaders = "AllowSpecificHeaders"; - public const string AcceptAnyServerCertificate = "AcceptAnyServerCertificate"; - public const string EnforceHttpsEndpoints = "EnforceHttpsEndpoints"; - public const string ContentSecurityPolicy = "ContentSecurityPolicy"; } public static class Urls @@ -121,38 +154,5 @@ public static class Defaults { public const string DefaultUserName = "unknown"; } - - public const string DEFAULT_KEY_ID = "7EFA85C18FDE857949BC2EAA21C25E49627D4865"; - - public const string DEFAULT_PRIVATE_KEY = - @"-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCx9oZn9tVOAXy -2XyjLv2JWYtn+cJ0CLPlym3A/r2pFAH4rQnoM2IAHcM5n6uWA9ke1bBUZM2+9UTj -y5jrNa+CGPYzsWnGOOpUcKgv3yaHlksBOi5xBPcx9UnraVpLiLLqcucCH1F1Soit -1Pg+OLwFqMW9rVNZYFFikc/KQO6/ZkKVGCVJyua4iVU5fANY0Je8gYTiobARfabd -QZgU8S/d2QGKcnFyxuYR81xaeOd6jJZ6ZzD3Hnl37cPHCksp+NQS7S7EKFhMNWT0 -iLe7NmWz2odUHA62tPpz0Aoma0L+yWoy1qrG+AERMjy0fwmXf7mM2hCDT3jEt3Nd -+0dzTp2zAgMBAAECggEACx5XX9EVNxccl9E8YSBEjruSzpueMvtwMXTNsQ+ZifY/ -ao+OGjgcpv8L7tUjeUu88Bqolxit+fGMPiiYEQ0eeKGuJCNDc3I6RhmsMBdf3quA -mpBUqFTtO2fSEWMRKXCjLejjMObSwow/oxSeGwcoDHam2v3y3Q43dxX1s4jjV/+H -uwgwghOqd9gTWeu7tOQefYkJ4Tsj6UMrf42LbSazjQmz+4sABmYv1TiuGW9Uj+vB -iV7Jozc9rVx4ZOhKrGxnM8kMsG6RWCfP5Nm3PUM/9tCzqNcPJCN3FyEcj2tfU/0I -CBuUqXaX4V/usg+TOf2tO2n/5TIZQozUatOAhWeJcQKBgQD3UJ9F6kIpkcO9qZLp -sLN0eVpt3hk2A//XV+QrKg8sYFxlYuX93TnF3q+V56h+UqXgCLnj0Q7MeAWLap2e -ApbumYRz8/qpthUqi0wyR8e8JEMDi3L5cOXNRYcVY2F3XKmxFXjfnNQ6gkms0Gfe -3Pni+q/xceQ7nhc0h/HAdNm3SwKBgQDJnvF6sW8bwamFoi0CWPOBcmb52QJ6tZ7h -Duh6fNZVE+FoRFv2q6PUNsm9rnTZtR7uevYcfnt2NrXNpsMQmnpaEU73nYWhiPn4 -zxaavAEFQPzUFaocYEAj5uXSKv1KNK2lvT9bjQFDT1lU1eginXJAbWa63zDnkV89 -AxH9LjeqOQKBgDtg8QzBROdkJwIHj81p7nw9krekRptQdIHIiXDPpVr7O9Pf3eaI -0hEu+Stdtne18juK/M605/+xpWsmyvcgGgrpcwLABmPu4sAXN9EuqMcEUc6tEYrQ -T2xskBVTihg1eEybIi1WIyJ1G6lRVE8O8TRNCidHOAwUVe/339RcedVnAoGAGcr7 -mXaZgDOGPFJC78nxXN4FznC0oH4blS8TDphp0vh4HZ6hJS1QCBX6OQnYaQGCs3+H -fJ2xra3SFD0BN16LyHnuYD8GmWOslufnPGRQvRtTPM6ItJibm/wt6nUVcijLDijn -sg6X2sSL6Q50Y/lAZH2aZs2ms/kk9ekuo/UFqgECgYEAlGWBKSsYH4OijwOSA54W -tHeKOMkv5DySAPQUhMPSE13y56XaoHdtl+wGElYQ2/lt1h8dhj0L04QZjhqkAJn3 -79xiYVOkKJvDjHCnjLIHkrDOKWBVApX31sD2uKl4BXRzLO+9iV+W67T89z71Ftdx -dQMjPQnK/1R3jqD9OWNr818= ------END PRIVATE KEY----- -"; } } diff --git a/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs b/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs index 7d51bd0..6484bc7 100644 --- a/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs +++ b/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs @@ -7,6 +7,7 @@ using CDR.DataRecipient.Web.Features; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.FeatureManagement; @@ -16,45 +17,48 @@ public class DataHolderDiscoveryCache : IDataHolderDiscoveryCache { private readonly IDistributedCache _cache; private readonly IInfosecService _dhInfosecService; - private readonly IDataHoldersRepository _dhRepository; private readonly ILogger _logger; + private readonly IServiceScopeFactory _scopeFactory; public DataHolderDiscoveryCache( IDistributedCache cache, IInfosecService dhInfosecService, - IDataHoldersRepository dhRepository, - ILogger logger) + ILogger logger, + IServiceScopeFactory scopeFactory) { - _cache = cache; - _dhInfosecService = dhInfosecService; - _dhRepository = dhRepository; - _logger = logger; + this._cache = cache; + this._dhInfosecService = dhInfosecService; + this._scopeFactory = scopeFactory; + this._logger = logger; } public async Task GetOidcDiscoveryByBrandId(string dataHolderBrandId) { var key = "dh:oidc:discovery:" + dataHolderBrandId; - var oidcDiscovery = await _cache.GetAsync(key); + var oidcDiscovery = await this._cache.GetAsync(key); if (oidcDiscovery == null) { - _logger.LogDebug("Discovery document for {DataHolderBrandId} not found in cache. Retrieving...", dataHolderBrandId); + this._logger.LogDebug("Discovery document for {DataHolderBrandId} not found in cache. Retrieving...", dataHolderBrandId); + + using var scope = this._scopeFactory.CreateScope(); + var dhRepository = scope.ServiceProvider.GetRequiredService(); DataHolderBrand dataHolder; - dataHolder = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); + dataHolder = await dhRepository.GetDataHolderBrand(dataHolderBrandId); if (dataHolder == null) { - _logger.LogError("Data Holder {DataHolderBrandId} not found.", dataHolderBrandId); + this._logger.LogError("Data Holder {DataHolderBrandId} not found.", dataHolderBrandId); return null; } string infosecBaseUri = dataHolder.EndpointDetail.InfoSecBaseUri; - oidcDiscovery = (await _dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; + oidcDiscovery = (await this._dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; if (oidcDiscovery != null) { - _logger.LogDebug("Data Holder {DataHolderBrandId} discovery document added to cache.", dataHolderBrandId); - await _cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); + this._logger.LogDebug("Data Holder {DataHolderBrandId} discovery document added to cache.", dataHolderBrandId); + await this._cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); } } @@ -64,16 +68,16 @@ public async Task GetOidcDiscoveryByBrandId(string dataHolderBran public async Task GetOidcDiscoveryByInfoSecBaseUri(string infosecBaseUri) { var key = "dh:oidc:discovery:" + infosecBaseUri; - var oidcDiscovery = await _cache.GetAsync(key); + var oidcDiscovery = await this._cache.GetAsync(key); if (oidcDiscovery == null) { - _logger.LogDebug("Discovery document for {InfosecBaseUri} not found in cache. Retrieving...", infosecBaseUri); + this._logger.LogDebug("Discovery document for {InfosecBaseUri} not found in cache. Retrieving...", infosecBaseUri); - oidcDiscovery = (await _dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; + oidcDiscovery = (await this._dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; if (oidcDiscovery != null) { - _logger.LogDebug("Discovery document for {InfosecBaseUri} added to cache.", infosecBaseUri); - await _cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); + this._logger.LogDebug("Discovery document for {InfosecBaseUri} added to cache.", infosecBaseUri); + await this._cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); } } diff --git a/Source/CDR.DataRecipient.Web/Common/Extensions.cs b/Source/CDR.DataRecipient.Web/Common/Extensions.cs index 21a6e8f..dc9e7d3 100644 --- a/Source/CDR.DataRecipient.Web/Common/Extensions.cs +++ b/Source/CDR.DataRecipient.Web/Common/Extensions.cs @@ -1,8 +1,7 @@ -using CDR.DataRecipient.SDK.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.IdentityModel.Tokens; -using System.Collections.Generic; +using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; +using CDR.DataRecipient.SDK.Extensions; +using Microsoft.IdentityModel.Tokens; namespace CDR.DataRecipient.Web.Common { diff --git a/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs b/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs index 427fced..4438f33 100644 --- a/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs +++ b/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs @@ -1,5 +1,5 @@ -using CDR.DataRecipient.SDK.Models; -using System.Threading.Tasks; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Models; namespace CDR.DataRecipient.Web.Common { diff --git a/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs b/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs index 2962d93..f169354 100644 --- a/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs +++ b/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs @@ -10,21 +10,21 @@ public class OidcSettingsProvider : IOidcSettingsProvider public OidcSettingsProvider(IConfiguration configuration, ILogger logger) { - _configuration = configuration; - _logger = logger; + this._configuration = configuration; + this._logger = logger; } public string GetSecret() { - var secretVolume = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.SecretVolumePath); - var secret = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientSecret); - var mountedSecretName = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.MountedSecretName); + var secretVolume = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.SecretVolumePath); + var secret = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientSecret); + var mountedSecretName = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.MountedSecretName); // if volume mount configured the mounted secret is used instead of one configured in the appsettings, enviroment variables etc. if (!string.IsNullOrEmpty(secretVolume)) { - _logger.LogInformation("Picking the secret from the volume - {SecretVolume},{MountedSecretName}", secretVolume, mountedSecretName); - secret = _configuration[mountedSecretName]; + this._logger.LogInformation("Picking the secret from the volume - {SecretVolume},{MountedSecretName}", secretVolume, mountedSecretName); + secret = this._configuration[mountedSecretName]; } return secret; diff --git a/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs b/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs index 2bec49b..a390292 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs @@ -1,10 +1,10 @@ -using CDR.DataRecipient.SDK.Register; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Register; using CDR.DataRecipient.Web.Caching; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -19,8 +19,8 @@ public ApiController( IConfiguration config, ICacheManager cacheManager) { - _config = config; - _cacheManager = cacheManager; + this._config = config; + this._cacheManager = cacheManager; } [Route("generate-client-assertion")] @@ -32,14 +32,14 @@ public async Task GenerateClientAssertion( [FromQuery] string kid = null, [FromQuery] int? exp = null) { - var sp = _config.GetSoftwareProductConfig(); - var reg = _config.GetRegisterConfig(); + var sp = this._config.GetSoftwareProductConfig(); + var reg = this._config.GetRegisterConfig(); var privateKeyFormatted = Constants.DEFAULT_PRIVATE_KEY.FormatPrivateKey(); var privateKeyJwt = new PrivateKeyJwt(privateKeyFormatted); return privateKeyJwt.Generate( iss ?? sp.SoftwareProductId, - aud ?? await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri), + aud ?? await this._cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri), jti ?? System.Guid.NewGuid().ToString(), exp ?? 10, kid ?? Constants.DEFAULT_KEY_ID); diff --git a/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs b/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs index a29781b..91fd5f3 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs @@ -1,9 +1,13 @@ -using Azure; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; using CDR.DataRecipient.Models; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; -using CDR.DataRecipient.SDK.Models.AuthorisationRequest; using CDR.DataRecipient.SDK.Services.DataHolder; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; @@ -12,22 +16,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.IO; -using System.Linq; -using System.Runtime.ConstrainedExecution; -using System.Text; -using System.Threading.Tasks; -using System.Web; using static CDR.DataRecipient.Web.Common.Constants; namespace CDR.DataRecipient.Web.Controllers @@ -42,7 +34,7 @@ public class ConsentController : Controller private readonly IRegistrationsRepository _registrationsRepository; private readonly IConsentsRepository _consentsRepository; private readonly IDataHolderDiscoveryCache _dataHolderDiscoveryCache; - protected readonly ILogger _logger; + private readonly ILogger _logger; public ConsentController( IConfiguration config, @@ -53,13 +45,13 @@ public ConsentController( IDataHolderDiscoveryCache dataHolderDiscoveryCache, ILogger logger) { - _config = config; - _cache = cache; - _dhInfosecService = dhInfosecService; - _registrationsRepository = registrationsRepository; - _consentsRepository = consentsRepository; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _logger = logger; + this._config = config; + this._cache = cache; + this._dhInfosecService = dhInfosecService; + this._registrationsRepository = registrationsRepository; + this._consentsRepository = consentsRepository; + this._dataHolderDiscoveryCache = dataHolderDiscoveryCache; + this._logger = logger; } [HttpPost] @@ -73,7 +65,7 @@ public async Task RegistrationDetail(string registrationId) string scope = string.Empty; var registrationInfo = Registration.SplitRegistrationId(registrationId); - Registration myResponse = await _registrationsRepository.GetRegistration(registrationInfo.ClientId, registrationInfo.DataHolderBrandId); + Registration myResponse = await this._registrationsRepository.GetRegistration(registrationInfo.ClientId, registrationInfo.DataHolderBrandId); if (myResponse == null) { message = "Registration not found"; @@ -99,12 +91,21 @@ public async Task RegistrationDetail(string registrationId) [Route("callback")] [ServiceFilter(typeof(LogActionEntryAttribute))] [AllowAnonymous] - public async Task Callback() + public async Task Callback([FromQuery] ConsentCallbackGetModel getModel, [FromForm] ConsentCallbackPostModel postModel) { + if (this.HttpContext.Request.Method.Equals("get", StringComparison.OrdinalIgnoreCase)) + { + postModel = null; + } + else + { + getModel = null; + } + var model = new TokenModel(); - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); - (bool isvalid, string authCode, AuthorisationState authState, ErrorList errorList) = await ValidateCallback(this.Request); + (bool isvalid, string authCode, AuthorisationState authState, ErrorList errorList) = await this.ValidateCallback(getModel, postModel); if (errorList != null && errorList.Errors.Count > 0) { @@ -115,7 +116,7 @@ public async Task Callback() if (isvalid) { // Request a token from the data holder. - var tokenEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri)).TokenEndpoint; + var tokenEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri)).TokenEndpoint; var accessToken = new AccessToken() { @@ -130,7 +131,7 @@ public async Task Callback() Pkce = authState.Pkce, }; - model.TokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + model.TokenResponse = await this._dhInfosecService.GetAccessToken(accessToken); if (model.TokenResponse.IsSuccessful) { @@ -151,7 +152,7 @@ public async Task Callback() CreatedOn = DateTime.UtcNow, }; - await _consentsRepository.PersistConsent(consentArrangement); + await this._consentsRepository.PersistConsent(consentArrangement); } } else @@ -166,104 +167,131 @@ public async Task Callback() } } - return View(model); + return this.View(model); } - private async Task<(bool Isvalid, string AuthCode, AuthorisationState AuthState, ErrorList ErrorList)> ValidateCallback(HttpRequest request) + [HttpGet] + [Route("consents")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Consents() { - bool isValid = false; - string authCode = string.Empty; - string state = string.Empty; - AuthorisationState authState = null; - ErrorList errorList = new ErrorList(); + var model = new ConsentsModel(); + model.ConsentArrangements = await this._consentsRepository.GetConsents(string.Empty, string.Empty, this.HttpContext.User.GetUserId(), string.Empty); + return this.View(model); + } - // GET Request - if (request.Method.Equals("get", StringComparison.OrdinalIgnoreCase)) + [HttpGet] + [Route("userinfo/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task UserInfo(string cdrArrangementId) + { + var reg = await this.GetUserInfo(cdrArrangementId); + var response = new { - // code, jwt - if (request.QueryString.HasValue && request.QueryString.Value.Contains("response")) - { - var responseToken = request.Query["response"].ToString(); - - if (string.IsNullOrEmpty(responseToken)) - { - isValid = false; - errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); - return (isValid, authCode, authState, errorList); - } + StatusCode = reg.StatusCode, + Messages = reg.Messages, + Payload = reg.Payload, + }; - (isValid, authCode, authState, errorList) = await ValidateJARMToken(responseToken); - } - } + return new JsonResult(response) + { + StatusCode = 200, + }; + } - // code id_token, form_post - // code, form_post.jwt - else if (request.Method.Equals("post", StringComparison.OrdinalIgnoreCase) && request.Form != null) + [HttpGet] + [Route("introspection/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Introspection(string cdrArrangementId) + { + var reg = await this.GetIntrospection(cdrArrangementId); + var response = new { - // form_post - if (request.Form.ContainsKey(SDK.Constants.TokenTypes.ID_TOKEN)) - { - authCode = request.Form["code"].ToString(); - state = request.Form["state"].ToString(); + StatusCode = reg.StatusCode, + Messages = reg.Messages, + Payload = reg.Payload, + }; - if (string.IsNullOrEmpty(state)) - { - isValid = false; - errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingState)); - return (isValid, authCode, authState, errorList); - } + return new JsonResult(response) + { + StatusCode = 200, + }; + } - authState = await _cache.GetAsync(state); + [HttpGet] + [Route("revoke/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Revoke(string cdrArrangementId) + { + var reg = await this.RevokeArrangement(cdrArrangementId); + var response = new + { + StatusCode = reg.StatusCode, + Messages = reg.Messages, + Payload = reg.Payload, + }; - if (authState == null) - { - isValid = false; - errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthState)); - return (isValid, authCode, authState, errorList); - } + return new JsonResult(response) + { + StatusCode = 200, + }; + } - if (string.IsNullOrEmpty(authCode)) - { - isValid = false; - errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthCode)); - return (isValid, authCode, authState, errorList); - } + [HttpGet] + [Route("revoke-token/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Revoke(string cdrArrangementId, [FromQuery] string tokenType) + { + var reg = await this.RevokeToken(cdrArrangementId, tokenType); + var response = new + { + StatusCode = reg.StatusCode, + Messages = reg.Messages, + Payload = reg.Payload, + }; - isValid = true; - } + return new JsonResult(response) + { + StatusCode = 200, + }; + } - // form_post.jwt is JARM callback - else if (request.Form.ContainsKey("response")) - { - var responseToken = request.Form["response"].ToString(); + [HttpGet] + [Route("refresh/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Refresh(string cdrArrangementId) + { + var reg = await this.RefreshAccessToken(cdrArrangementId); + var response = new + { + StatusCode = reg.StatusCode, + Messages = reg.Messages, + Payload = reg.Payload, + }; - if (string.IsNullOrEmpty(responseToken)) - { - isValid = false; - errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); - return (isValid, authCode, authState, errorList); - } + return new JsonResult(response) + { + StatusCode = 200, + }; + } - (isValid, authCode, authState, errorList) = await ValidateJARMToken(responseToken); - } - else if (request.Form.ContainsKey("error")) - { - // check for error response from form_post - var error = request.Form["error"].ToString(); - var errorDescription = request.Form["error_description"].ToString(); - var errorCode = request.Form["error_code"].ToString(); + [HttpDelete] + [Route("consents/{cdrArrangementId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task Delete(string cdrArrangementId) + { + await this._consentsRepository.DeleteConsent(cdrArrangementId); - // error and error_description - if (!string.IsNullOrEmpty(error)) - { - errorList.Errors.Add(new Error(code: errorCode, title: error, detail: errorDescription)); - isValid = false; - return (isValid, authCode, authState, errorList); - } - } - } + var response = new + { + StatusCode = 204, + Messages = "CDR Arrangement deleted", + }; - return (isValid, authCode, authState, errorList); + return new JsonResult(response) + { + StatusCode = 200, + }; } // Validating JARM token @@ -288,7 +316,7 @@ public async Task Callback() var failedDecryptionError = new Error(code: string.Empty, title: ErrorTitles.InvalidResponse, detail: ErrorDescription.FailedDecryption); // Load the signing certificate and make sure the keys match - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); var encryptionKeys = sp.SigningCertificate.X509Certificate.GetEncryptionCredentials(); if (!encryptionKeys.TryGetValue(token.Header.Kid, out var encryptionKey) || token.Header.Alg != encryptionKey.Enc) { @@ -318,7 +346,7 @@ public async Task Callback() return (isValid, authCode, authState, errorList); } - authState = await _cache.GetAsync(state); + authState = await this._cache.GetAsync(state); if (authState == null) { @@ -328,7 +356,7 @@ public async Task Callback() } // Validate token against JWKS of the Data holder - var dataholderDiscoveryDocument = await _dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri); + var dataholderDiscoveryDocument = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri); if (dataholderDiscoveryDocument == null) { isValid = false; @@ -336,18 +364,18 @@ public async Task Callback() return (isValid, authCode, authState, errorList); } - _logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); + this._logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); // Validate the token var validated = await responseToken.ValidateToken( dataholderDiscoveryDocument.JwksUri, - _logger, + this._logger, dataholderDiscoveryDocument.Issuer, new[] { authState.ClientId }, validateLifetime: true, - acceptAnyServerCertificate: _config.IsAcceptingAnyServerCertificate()); + acceptAnyServerCertificate: this._config.IsAcceptingAnyServerCertificate()); - _logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); + this._logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); isValid = validated.IsValid; var errorTitle = token.Claims.FirstOrDefault(x => x.Type == "error")?.Value ?? validated.ValidationError?.Title ?? string.Empty; @@ -373,7 +401,7 @@ public async Task Callback() } catch (Exception ex) { - _logger.LogError(ex, "An error occurred validating the JARM token"); + this._logger.LogError(ex, "An error occurred validating the JARM token"); var (errorCode, errorTitle, errorDescription) = ex.Message.ParseErrorString("Token Validation Error", "error", ex.Message); errorList.Errors.Add(new Error(code: errorCode, title: errorTitle, detail: errorDescription)); @@ -384,136 +412,81 @@ public async Task Callback() return (isValid, authCode, authState, errorList); } - [HttpGet] - [Route("consents")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Consents() - { - var model = new ConsentsModel(); - model.ConsentArrangements = await _consentsRepository.GetConsents(string.Empty, string.Empty, HttpContext.User.GetUserId(), string.Empty); - return View(model); - } - - [HttpGet] - [Route("userinfo/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task UserInfo(string cdrArrangementId) + private async Task<(bool Isvalid, string AuthCode, AuthorisationState AuthState, ErrorList ErrorList)> ValidateCallback(ConsentCallbackGetModel getModel, ConsentCallbackPostModel postModel) { - var reg = await GetUserInfo(cdrArrangementId); - var response = new - { - StatusCode = reg.StatusCode, - Messages = reg.Messages, - Payload = reg.Payload, - }; - - return new JsonResult(response) - { - StatusCode = 200, - }; - } + bool isValid = false; + string authCode = string.Empty; + AuthorisationState authState = null; + ErrorList errorList = new ErrorList(); - [HttpGet] - [Route("introspection/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Introspection(string cdrArrangementId) - { - var reg = await GetIntrospection(cdrArrangementId); - var response = new + // Handle GET request + if (getModel?.Response != null) { - StatusCode = reg.StatusCode, - Messages = reg.Messages, - Payload = reg.Payload, - }; + // code, jwt + if (string.IsNullOrEmpty(getModel.Response)) + { + errorList.Errors.Add(new Error(string.Empty, ErrorTitles.MissingField, ErrorDescription.MissingResponse)); + return (false, authCode, authState, errorList); + } - return new JsonResult(response) - { - StatusCode = 200, - }; - } + (isValid, authCode, authState, errorList) = await this.ValidateJARMToken(getModel.Response); + } - [HttpGet] - [Route("revoke/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Revoke(string cdrArrangementId) - { - var reg = await RevokeArrangement(cdrArrangementId); - var response = new + // Handle POST request + if (postModel != null) { - StatusCode = reg.StatusCode, - Messages = reg.Messages, - Payload = reg.Payload, - }; + // Handle form_post with code/state + if (!string.IsNullOrEmpty(postModel.Code)) + { + authCode = postModel.Code; - return new JsonResult(response) - { - StatusCode = 200, - }; - } + if (string.IsNullOrEmpty(postModel.State)) + { + errorList.Errors.Add(new Error(string.Empty, ErrorTitles.MissingField, ErrorDescription.MissingState)); + return (false, authCode, authState, errorList); + } - [HttpGet] - [Route("revoke-token/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Revoke(string cdrArrangementId, [FromQuery] string tokenType) - { - var reg = await RevokeToken(cdrArrangementId, tokenType); - var response = new - { - StatusCode = reg.StatusCode, - Messages = reg.Messages, - Payload = reg.Payload, - }; + authState = await this._cache.GetAsync(postModel.State); - return new JsonResult(response) - { - StatusCode = 200, - }; - } + if (authState == null) + { + errorList.Errors.Add(new Error(string.Empty, ErrorTitles.MissingField, ErrorDescription.MissingAuthState)); + return (false, authCode, authState, errorList); + } - [HttpGet] - [Route("refresh/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Refresh(string cdrArrangementId) - { - var reg = await RefreshAccessToken(cdrArrangementId); - var response = new - { - StatusCode = reg.StatusCode, - Messages = reg.Messages, - Payload = reg.Payload, - }; + return (true, authCode, authState, errorList); + } - return new JsonResult(response) - { - StatusCode = 200, - }; - } + // Handle JARM response + if (string.IsNullOrEmpty(postModel.Response)) + { + isValid = false; + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); + return (isValid, authCode, authState, errorList); + } - [HttpDelete] - [Route("consents/{cdrArrangementId}")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task Delete(string cdrArrangementId) - { - await _consentsRepository.DeleteConsent(cdrArrangementId); + if (!string.IsNullOrEmpty(postModel.Response)) + { + return await this.ValidateJARMToken(postModel.Response); + } - var response = new - { - StatusCode = 204, - Messages = "CDR Arrangement deleted", - }; + // Handle error response + if (!string.IsNullOrEmpty(postModel.Error)) + { + errorList.Errors.Add(new Error(postModel.ErrorCode, postModel.Error, postModel.ErrorDescription)); + return (false, authCode, authState, errorList); + } + } - return new JsonResult(response) - { - StatusCode = 200, - }; + return (isValid, authCode, authState, errorList); } private async Task RefreshAccessToken(string cdrArrangementId) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Retrieve the arrangement details from the local repository. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); if (arrangement == null) { return new ResponseModel() @@ -524,8 +497,8 @@ private async Task RefreshAccessToken(string cdrArrangementId) } // Call the DH to refresh the access token. - var tokenEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).TokenEndpoint; - var tokenResponse = await _dhInfosecService.RefreshAccessToken( + var tokenEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).TokenEndpoint; + var tokenResponse = await this._dhInfosecService.RefreshAccessToken( tokenEndpoint, sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate, @@ -536,7 +509,7 @@ private async Task RefreshAccessToken(string cdrArrangementId) if (tokenResponse.IsSuccessful) { - await _consentsRepository.UpdateTokens(arrangement.CdrArrangementId, tokenResponse.Data.IdToken, tokenResponse.Data.AccessToken, tokenResponse.Data.RefreshToken); + await this._consentsRepository.UpdateTokens(arrangement.CdrArrangementId, tokenResponse.Data.IdToken, tokenResponse.Data.AccessToken, tokenResponse.Data.RefreshToken); } return new ResponseModel() @@ -549,10 +522,10 @@ private async Task RefreshAccessToken(string cdrArrangementId) private async Task RevokeArrangement(string cdrArrangementId) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Retrieve the arrangement details from the local repository. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); if (arrangement == null) { return new ResponseModel() @@ -563,8 +536,8 @@ private async Task RevokeArrangement(string cdrArrangementId) } // Call the DH to revoke the arrangement. - var revocationEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).CdrArrangementRevocationEndpoint; - var revocation = await _dhInfosecService.RevokeCdrArrangement( + var revocationEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).CdrArrangementRevocationEndpoint; + var revocation = await this._dhInfosecService.RevokeCdrArrangement( revocationEndpoint, sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate, @@ -574,7 +547,7 @@ private async Task RevokeArrangement(string cdrArrangementId) // The consent has been revoked, so remove from the local repository. if (revocation.IsSuccessful) { - await _consentsRepository.DeleteConsent(arrangement.CdrArrangementId); + await this._consentsRepository.DeleteConsent(arrangement.CdrArrangementId); } return new ResponseModel() @@ -586,10 +559,10 @@ private async Task RevokeArrangement(string cdrArrangementId) private async Task GetIntrospection(string cdrArrangementId) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Retrieve the arrangement details from the local repository. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); if (arrangement == null) { return new ResponseModel() @@ -609,8 +582,8 @@ private async Task GetIntrospection(string cdrArrangementId) } // Call the DH to introspect the refresh token. - var introspectEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).IntrospectionEndpoint; - var introspection = await _dhInfosecService.Introspect( + var introspectEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).IntrospectionEndpoint; + var introspection = await this._dhInfosecService.Introspect( introspectEndpoint, sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate, @@ -627,10 +600,10 @@ private async Task GetIntrospection(string cdrArrangementId) private async Task GetUserInfo(string cdrArrangementId) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Retrieve the arrangement details from the local repository. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); if (arrangement == null) { return new ResponseModel() @@ -650,8 +623,8 @@ private async Task GetUserInfo(string cdrArrangementId) } // Call the DH to get the userinfo for the access token. - var userInfoEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).UserInfoEndpoint; - var userInfo = await _dhInfosecService.UserInfo( + var userInfoEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).UserInfoEndpoint; + var userInfo = await this._dhInfosecService.UserInfo( userInfoEndpoint, sp.ClientCertificate.X509Certificate, arrangement.AccessToken); @@ -666,10 +639,10 @@ private async Task GetUserInfo(string cdrArrangementId) private async Task RevokeToken(string cdrArrangementId, string tokenType) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Retrieve the arrangement details from the local repository. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); if (arrangement == null) { return new ResponseModel() @@ -680,8 +653,8 @@ private async Task RevokeToken(string cdrArrangementId, string to } // Call the DH to revoke the token. - var tokenRevocationEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).RevocationEndpoint; - var revocation = await _dhInfosecService.RevokeToken( + var tokenRevocationEndpoint = (await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(arrangement.DataHolderBrandId)).RevocationEndpoint; + var revocation = await this._dhInfosecService.RevokeToken( tokenRevocationEndpoint, sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate, diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs index 78370ca..e793bae 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs @@ -1,5 +1,6 @@ -using CDR.DataRecipient.Repository; -using CDR.DataRecipient.SDK; +using System.Collections.Generic; +using System.Threading.Tasks; +using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.SDK.Services.Register; using CDR.DataRecipient.Web.Caching; @@ -12,8 +13,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.FeatureManagement; using Microsoft.FeatureManagement.Mvc; -using System.Collections.Generic; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -36,12 +35,12 @@ public DataHoldersController( IDataHoldersRepository repository, IFeatureManager featureManager) { - _config = config; - _infosecService = infosecService; - _cacheManager = cacheManager; - _metadataService = metadataService; - _repository = repository; - _featureManager = featureManager; + this._config = config; + this._infosecService = infosecService; + this._cacheManager = cacheManager; + this._metadataService = metadataService; + this._repository = repository; + this._featureManager = featureManager; } [HttpGet] @@ -49,8 +48,8 @@ public DataHoldersController( public async Task Index() { var model = new DataHoldersModel(); - await PopulateModel(model); - return View(model); + await this.PopulateModel(model); + return this.View(model); } [FeatureGate(nameof(Feature.AllowDataHolderRefresh))] @@ -58,19 +57,29 @@ public async Task Index() [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Index(DataHoldersModel model) { - await GetDataHolderBrands(model); - await PopulateModel(model); - return View(model); + await this.GetDataHolderBrands(model); + await this.PopulateModel(model); + return this.View(model); + } + + [FeatureGate(nameof(Feature.AllowDataHolderRefresh))] + [HttpPost] + [Route("reset/dataholderbrands")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task ResetDataHolderBrands() + { + await this._repository.DataHolderBrandsDelete(); + return this.Json(this.Url.Action("Index")); } private async Task GetDataHolderBrands(DataHoldersModel model) { - var reg = _config.GetRegisterConfig(); - var sp = _config.GetSoftwareProductConfig(); - var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); + var reg = this._config.GetRegisterConfig(); + var sp = this._config.GetSoftwareProductConfig(); + var tokenEndpoint = await this._cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); // Get the access token from the Register. - var tokenResponse = await _infosecService.GetAccessToken( + var tokenResponse = await this._infosecService.GetAccessToken( tokenEndpoint, sp.SoftwareProductId, sp.ClientCertificate.X509Certificate, @@ -83,14 +92,14 @@ private async Task GetDataHolderBrands(DataHoldersModel model) } // Using the access token, make a request to Get Data Holder Brands. - (string respBody, System.Net.HttpStatusCode statusCode, string reason) = await _metadataService.GetDataHolderBrands( + (string respBody, System.Net.HttpStatusCode statusCode, string reason) = await this._metadataService.GetDataHolderBrands( reg.MtlsBaseUri, model.Version, tokenResponse.Data.AccessToken, sp.ClientCertificate.X509Certificate, sp.SoftwareProductId, model.Industry, - pageSize: _config.GetDefaultPageSize()); + pageSize: this._config.GetDefaultPageSize()); if (statusCode != System.Net.HttpStatusCode.OK) { @@ -108,27 +117,17 @@ private async Task GetDataHolderBrands(DataHoldersModel model) // Save the data holder brands Response> dhResponse = Newtonsoft.Json.JsonConvert.DeserializeObject>>(respBody); - (int inserted, int updated) = await _repository.AggregateDataHolderBrands(dhResponse.Data); + (int inserted, int updated) = await this._repository.AggregateDataHolderBrands(dhResponse.Data); model.Messages = $"{statusCode}: {inserted} data holder brands added. {updated} data holder brands updated."; } - [FeatureGate(nameof(Feature.AllowDataHolderRefresh))] - [HttpPost] - [Route("reset/dataholderbrands")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public async Task ResetDataHolderBrands() - { - await _repository.DataHolderBrandsDelete(); - return Json(Url.Action("Index")); - } - private async Task PopulateModel(DataHoldersModel model) { - var reg = _config.GetRegisterConfig(); - var allowDataHolderRefresh = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDataHolderRefresh)); + var reg = this._config.GetRegisterConfig(); + var allowDataHolderRefresh = await this._featureManager.IsEnabledAsync(nameof(Feature.AllowDataHolderRefresh)); - model.DataHolders = (await _repository.GetDataHolderBrands()).OrderByMockDataHolders(allowDataHolderRefresh); + model.DataHolders = (await this._repository.GetDataHolderBrands()).OrderByMockDataHolders(allowDataHolderRefresh); // Populate the view model.RefreshRequest = new HttpRequestModel() @@ -139,12 +138,12 @@ private async Task PopulateModel(DataHoldersModel model) RequiresAccessToken = true, SupportsVersion = true, }; - SetDefaults(model); + this.SetDefaults(model); } private void SetDefaults(DataHoldersModel model) { - var defaultPageSize = _config.GetDefaultPageSize(); + var defaultPageSize = this._config.GetDefaultPageSize(); if (defaultPageSize.HasValue) { model.RefreshRequest.QueryParameters.Add("page-size", defaultPageSize.ToString()); diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs index 92ed345..26309ee 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs @@ -15,6 +15,18 @@ public class DataSharingBankingController : DataSharingControllerBase { private const string PATH = "data-sharing-banking"; + public DataSharingBankingController( + IConfiguration config, + IDistributedCache cache, + IConsentsRepository consentsRepository, + IDataHoldersRepository dhRepository, + IInfosecService infosecService, + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) + { + } + protected override string BasePath { get @@ -37,22 +49,10 @@ protected override string CdsSwaggerLocation { get { - return _config["ConsumerDataStandardsSwaggerBanking"]; + return this.Config["ConsumerDataStandardsSwaggerBanking"]; } } - public DataSharingBankingController( - IConfiguration config, - IDistributedCache cache, - IConsentsRepository consentsRepository, - IDataHoldersRepository dhRepository, - IInfosecService infosecService, - ILogger logger, - IHttpClientFactory httpClientFactory) - : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) - { - } - protected override JObject PrepareSwaggerJson(JObject json, Uri uri) { json["servers"][0]["url"] = $"https://{uri.Host}:{uri.Port}/{PATH}/proxy/cds-au/v1"; diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs index c8987b7..8dfb41c 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs @@ -16,6 +16,18 @@ public class DataSharingCommonController : DataSharingControllerBase { private const string PATH = "data-sharing-common"; + public DataSharingCommonController( + IConfiguration config, + IDistributedCache cache, + IConsentsRepository consentsRepository, + IDataHoldersRepository dhRepository, + IInfosecService infosecService, + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) + { + } + protected override string BasePath { get @@ -38,22 +50,10 @@ protected override string CdsSwaggerLocation { get { - return _config["ConsumerDataStandardsSwaggerCommon"]; + return this.Config["ConsumerDataStandardsSwaggerCommon"]; } } - public DataSharingCommonController( - IConfiguration config, - IDistributedCache cache, - IConsentsRepository consentsRepository, - IDataHoldersRepository dhRepository, - IInfosecService infosecService, - ILogger logger, - IHttpClientFactory httpClientFactory) - : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) - { - } - protected override JObject PrepareSwaggerJson(JObject json, Uri uri) { json["servers"][0]["url"] = $"https://{uri.Host}:{uri.Port}/{PATH}/proxy/cds-au/v1"; diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs index 49fbad9..84a9d9e 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs @@ -28,15 +28,35 @@ namespace CDR.DataRecipient.Web.Controllers [Authorize] public abstract class DataSharingControllerBase : Controller { + protected const string HEADER_INJECT_CDR_ARRANGEMENT_ID = "x-inject-cdr-arrangement-id"; + private readonly IHttpClientFactory _clientFactory; + private readonly IConfiguration _config; + private readonly IDistributedCache _cache; + private readonly IConsentsRepository _consentsRepository; + private readonly IDataHoldersRepository _dhRepository; + private readonly IInfosecService _infosecService; + private readonly ILogger _logger; + private readonly List _allowedHeaders; - protected const string HEADER_INJECT_CDR_ARRANGEMENT_ID = "x-inject-cdr-arrangement-id"; - protected readonly IConfiguration _config; - protected readonly IDistributedCache _cache; - protected readonly IConsentsRepository _consentsRepository; - protected readonly IDataHoldersRepository _dhRepository; - protected readonly IInfosecService _infosecService; - protected readonly ILogger _logger; + protected DataSharingControllerBase( + IConfiguration config, + IDistributedCache cache, + IConsentsRepository consentsRepository, + IDataHoldersRepository dhRepository, + IInfosecService infosecService, + ILogger logger, + IHttpClientFactory clientFactory) + { + this._config = config; + this._cache = cache; + this._consentsRepository = consentsRepository; + this._dhRepository = dhRepository; + this._infosecService = infosecService; + this._logger = logger; + this._allowedHeaders = [.. this._config.GetValue(Constants.ConfigurationKeys.AllowSpecificHeaders).Split(',')]; + this._clientFactory = clientFactory; + } protected abstract string BasePath { get; } @@ -49,34 +69,21 @@ public abstract class DataSharingControllerBase : Controller protected abstract string CdsSwaggerLocation { get; } - protected List _allowedHeaders; + protected IDistributedCache Cache => this._cache; - protected DataSharingControllerBase( - IConfiguration config, - IDistributedCache cache, - IConsentsRepository consentsRepository, - IDataHoldersRepository dhRepository, - IInfosecService infosecService, - ILogger logger, - IHttpClientFactory clientFactory) - { - _config = config; - _cache = cache; - _consentsRepository = consentsRepository; - _dhRepository = dhRepository; - _infosecService = infosecService; - _logger = logger; - _allowedHeaders = [.. _config.GetValue(Constants.ConfigurationKeys.AllowSpecificHeaders).Split(',')]; - _clientFactory = clientFactory; - } + protected IConfiguration Config => this._config; + + protected IInfosecService InfosecService => this._infosecService; + + protected List AllowedHeaders => this._allowedHeaders; [HttpGet] [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult Index() { var model = new DataSharingModel(); - PopulateModel(model); - return View("DataSharing", model); + this.PopulateModel(model); + return this.View("DataSharing", model); } /// @@ -88,8 +95,8 @@ public IActionResult Index() [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Swagger() { - var sp = _config.GetSoftwareProductConfig(); - var client = _clientFactory.CreateClient(); + var sp = this._config.GetSoftwareProductConfig(); + var client = this._clientFactory.CreateClient(); Uri uri = new Uri(sp.RecipientBaseUri); // Get the CDS swagger file. @@ -99,12 +106,12 @@ public async Task Swagger() // Replace the host and base path in the CDS Banking swagger file to point to the mock data recipient proxy endpoint // in order to proxy all swagger requests via the mock data recipient. var jsonObj = JObject.Parse(cdsSwaggerJson); - jsonObj = PrepareSwaggerJson(jsonObj, uri); + jsonObj = this.PrepareSwaggerJson(jsonObj, uri); byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonObj.ToString()); // Return the updated swagger file. - Response.ContentType = "application/json"; - await Response.BodyWriter.WriteAsync(jsonBytes); + this.Response.ContentType = "application/json"; + await this.Response.BodyWriter.WriteAsync(jsonBytes); } /// @@ -115,49 +122,34 @@ public async Task Swagger() [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Proxy() { - var sp = _config.GetSoftwareProductConfig(); - - var cdrArrangement = await GetCdrArrangement(this.Request); + var cdrArrangement = await this.GetCdrArrangement(this.Request); if (cdrArrangement == null) { - Response.ContentType = "application/json"; - Response.StatusCode = StatusCodes.Status400BadRequest; + this.Response.ContentType = "application/json"; + this.Response.StatusCode = StatusCodes.Status400BadRequest; string rtnMsg = @"{""Error"":""Please select an Agreement""}"; - await Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(rtnMsg)); + await this.Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(rtnMsg)); return; } var requestPath = this.Request.Path.ToString().Replace($"/{this.BasePath}/proxy", string.Empty); - if (!IsValidRequestPath(requestPath)) + if (!this.IsValidRequestPath(requestPath)) { - Response.ContentType = "application/json"; - Response.StatusCode = StatusCodes.Status400BadRequest; + this.Response.ContentType = "application/json"; + this.Response.StatusCode = StatusCodes.Status400BadRequest; string rtnMsg = @"{""Error"":""Invalid request path""}"; - await Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(rtnMsg)); + await this.Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(rtnMsg)); return; } - var dh = await GetDataHolder(cdrArrangement); - var isPublic = IsPublic(this.Request.Path); + var dh = await this.GetDataHolder(cdrArrangement); + var isPublic = this.IsPublic(this.Request.Path); var baseUri = isPublic ? dh.EndpointDetail.PublicBaseUri : dh.EndpointDetail.ResourceBaseUri; - _logger.LogDebug("Proxying call to Data Holder: {DataHolderBrandId}. Is Public: {IsPublic}. Base Uri: {BaseUri}. Path: {Path}.", dh.DataHolderBrandId, isPublic, baseUri, this.Request.Path); - - // Build the Http Request to the data holder. - var clientHandler = new HttpClientHandler(); - var acceptAnyServerCertificate = _config.IsAcceptingAnyServerCertificate(); - if (acceptAnyServerCertificate) - { - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - } - - // Provide the data recipient's client certificate for a non-public endpoint. - if (!isPublic) - { - clientHandler.ClientCertificates.Add(sp.ClientCertificate.X509Certificate); - } + this._logger.LogDebug("Proxying call to Data Holder: {DataHolderBrandId}. Is Public: {IsPublic}. Base Uri: {BaseUri}. Path: {Path}.", dh.DataHolderBrandId, isPublic, baseUri, this.Request.Path); - var client = new HttpClient(clientHandler); + // Build the Http Request to the data holder. Use HttpClientFactory to get the name client instead of re-creating the HttpClient everytime. + var client = isPublic ? this._clientFactory.CreateClient("PublicDataHolderClient") : this._clientFactory.CreateClient("PrivateDataHolderClient"); var requestUri = string.Concat(baseUri, requestPath, this.Request.QueryString); var request = new HttpRequestMessage() @@ -177,7 +169,7 @@ public async Task Proxy() } // Don't add the host header or there will be CORS errors. This has to be added to the where. - foreach (var header in this.Request.Headers.Keys.Where(h => _allowedHeaders.Contains(h, StringComparer.OrdinalIgnoreCase))) + foreach (var header in this.Request.Headers.Keys.Where(h => this._allowedHeaders.Contains(h, StringComparer.OrdinalIgnoreCase))) { request.Headers.Add(header, this.Request.Headers[header].ToString()); } @@ -188,31 +180,32 @@ public async Task Proxy() client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", cdrArrangement.AccessToken); } - if (_config.IsEnforcingHttpsEndpoints() && !request.RequestUri.IsHttps()) + if (this._config.IsEnforcingHttpsEndpoints() && !request.RequestUri.IsHttps()) { throw new NoHttpsException(); } - _logger.LogDebug("Making request to: {RequestUri}.", request.RequestUri); + this._logger.LogDebug("Making request to: {RequestUri}.", request.RequestUri); var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); // Return the raw JSON response. - Response.ContentType = "application/json"; - Response.StatusCode = response.StatusCode.ToInt(); - await Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(body)); + this.Response.ContentType = "application/json"; + this.Response.StatusCode = response.StatusCode.ToInt(); + await this.Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes(body)); } /// /// Return the list of cdr arrangements so that a select element can be built. /// + /// A Task representing the asynchronous operation. [HttpGet] [Route("cdr-arrangements")] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task>> GetCdrArrangements() { - var consents = await GetConsents(HttpContext.User.GetUserId(), industry: this.IndustryName); + var consents = await this.GetConsents(this.HttpContext.User.GetUserId(), industry: this.IndustryName); return consents .Select(c => new KeyValuePair(c.CdrArrangementId, $"{c.CdrArrangementId} (DH Brand: {c.BrandName} {c.DataHolderBrandId})")) .ToList(); @@ -220,7 +213,7 @@ public async Task>> GetCdrArrangements() protected virtual async Task> GetConsents(string userId, string industry = null) { - return await _consentsRepository.GetConsents(string.Empty, string.Empty, userId, industry); + return await this._consentsRepository.GetConsents(string.Empty, string.Empty, userId, industry); } protected virtual void PopulateModel(DataSharingModel model) @@ -232,23 +225,25 @@ protected virtual void PopulateModel(DataSharingModel model) protected virtual async Task GetDataHolder(ConsentArrangement cdrArrangement) { - return await _dhRepository.GetDataHolderBrand(cdrArrangement.DataHolderBrandId); + return await this._dhRepository.GetDataHolderBrand(cdrArrangement.DataHolderBrandId); } protected virtual async Task GetCdrArrangement(HttpRequest request) { // Get the cdr arrangement id from the http header. +#pragma warning disable S6932 // Use model binding instead of reading raw request data if (!request.Headers.TryGetValue(HEADER_INJECT_CDR_ARRANGEMENT_ID, out var cdrArrangementId)) { return null; } +#pragma warning restore S6932 // Use model binding instead of reading raw request data if (string.IsNullOrEmpty(cdrArrangementId)) { return null; } - return await _consentsRepository.GetConsentByArrangement(cdrArrangementId); + return await this._consentsRepository.GetConsentByArrangement(cdrArrangementId); } protected abstract JObject PrepareSwaggerJson(JObject json, Uri uri); diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs index 50772ad..e03cf02 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs @@ -15,6 +15,18 @@ public class DataSharingEnergyController : DataSharingControllerBase { private const string PATH = "data-sharing-energy"; + public DataSharingEnergyController( + IConfiguration config, + IDistributedCache cache, + IConsentsRepository consentsRepository, + IDataHoldersRepository dhRepository, + IInfosecService infosecService, + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) + { + } + protected override string BasePath { get @@ -37,22 +49,10 @@ protected override string CdsSwaggerLocation { get { - return _config["ConsumerDataStandardsSwaggerEnergy"]; + return this.Config["ConsumerDataStandardsSwaggerEnergy"]; } } - public DataSharingEnergyController( - IConfiguration config, - IDistributedCache cache, - IConsentsRepository consentsRepository, - IDataHoldersRepository dhRepository, - IInfosecService infosecService, - ILogger logger, - IHttpClientFactory httpClientFactory) - : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) - { - } - protected override JObject PrepareSwaggerJson(JObject json, Uri uri) { json["servers"][0]["url"] = $"https://{uri.Host}:{uri.Port}/{PATH}/proxy/cds-au/v1"; @@ -63,7 +63,7 @@ protected override JObject PrepareSwaggerJson(JObject json, Uri uri) /// Determine if the target request is for a public endpoint, or a resource endpoint. /// /// Request Path. - /// True if the request is for the /discovery or /banking/products endpoints, otherwise false + /// True if the request is for the /discovery or /banking/products endpoints, otherwise false. protected override bool IsPublic(string requestPath) { return requestPath.Contains("/cds-au/v1/discovery", StringComparison.OrdinalIgnoreCase) diff --git a/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs b/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs index 2226730..6cb8868 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs @@ -1,4 +1,11 @@ -using CDR.DataRecipient.Repository; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Threading.Tasks; +using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK; using CDR.DataRecipient.SDK.Enum; using CDR.DataRecipient.SDK.Enumerations; @@ -16,17 +23,9 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Configuration; using Microsoft.FeatureManagement; -using Microsoft.FeatureManagement.FeatureFilters; using Microsoft.FeatureManagement.Mvc; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Net; -using System.Security.Claims; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -57,16 +56,102 @@ public DynamicClientRegistrationController( ICacheManager cacheManager, IFeatureManager featureManager) { - _config = config; - _regRepository = regRepository; - _dhRepository = dhRepository; - _ssaService = ssaService; - _dcrService = dcrService; - _regInfosecService = regInfosecService; - _dhInfosecService = dhInfosecService; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _cacheManager = cacheManager; - _featureManager = featureManager; + this._config = config; + this._regRepository = regRepository; + this._dhRepository = dhRepository; + this._ssaService = ssaService; + this._dcrService = dcrService; + this._regInfosecService = regInfosecService; + this._dhInfosecService = dhInfosecService; + this._dataHolderDiscoveryCache = dataHolderDiscoveryCache; + this._cacheManager = cacheManager; + this._featureManager = featureManager; + } + + public static (List Claims, string ErrorMessages) CreateClaimsForUpdateDCRRequest(OidcDiscovery oidcDiscovery, string ssa, SoftwareProduct softwareProductConfig) + { + string errorMessage = string.Empty; + + // Error - Unable to perform DCR as there are no mutually supported values in the mandatory claim [CLAIM_NAME] + const string ErrorMessage = "Unable to perform DCR as there are no mutually supported values in the mandatory claim"; + + var claims = new List + { + new("jti", Guid.NewGuid().ToString()), + new("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer), + new("token_endpoint_auth_signing_alg", "PS256"), + new("token_endpoint_auth_method", "private_key_jwt"), + new("application_type", "web"), + new("id_token_signed_response_alg", "PS256"), + new("request_object_signing_alg", "PS256"), + new("software_statement", ssa ?? string.Empty), + new("grant_types", "client_credentials"), + new("grant_types", "authorization_code"), + new("grant_types", "refresh_token"), + }; + + var responseTypesList = oidcDiscovery.ResponseTypesSupported.Where(x => x.ToLower().Equals("code")).ToList(); + claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypesList), JsonClaimValueTypes.JsonArray)); + + if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Length == 0) + { + // Log error message to the mandatory claim missing + errorMessage = ErrorMessage + " authorization_signed_response_alg"; + return (null, errorMessage); + } + + if (!oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) + { + // Return the error + errorMessage = ErrorMessage + " authorization_signed_response_alg"; + return (null, errorMessage); + } + + if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256")) + { + claims.Add(new Claim("authorization_signed_response_alg", "PS256")); + } + else if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) + { + claims.Add(new Claim("authorization_signed_response_alg", "ES256")); + } + + // Check if the enc is empty but a alg is specified. + if ((oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported == null || oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Length == 0) + && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256") + && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) + { + errorMessage = ErrorMessage + " authorization_encrypted_response_enc"; + return (null, errorMessage); + } + + if (oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Contains("A128CBC-HS256")) + { + claims.Add(new Claim("authorization_encrypted_response_enc", "A128CBC-HS256")); + } + else if (oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Contains("A256GCM")) + { + claims.Add(new Claim("authorization_encrypted_response_enc", "A256GCM")); + } + + // Conditional: Optional for response_type "code" if authorization_encryption_enc_values_supported is present + if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Length != 0) + { + if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256")) + { + claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP-256")); + } + else if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) + { + claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP")); + } + } + + char[] delimiters = [',', ' ']; + var redirectUrisList = softwareProductConfig.RedirectUris?.Split(delimiters).ToList(); + claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray)); + + return (claims, errorMessage); } [HttpGet] @@ -76,17 +161,17 @@ public async Task Index(string clientId = null, string dataHolder var model = new DynamicClientRegistrationModel(); if (string.IsNullOrEmpty(clientId)) { - SetViewModelDefaults(model); + this.SetViewModelDefaults(model); } else { - var client = await _regRepository.GetRegistration(clientId, dataHolderBrandId); + var client = await this._regRepository.GetRegistration(clientId, dataHolderBrandId); model.TransactionType = "Update"; - SetViewModel(model, client); + this.SetViewModel(model, client); } - await PopulateFormDetail(model); - return View(model); + await this.PopulateFormDetail(model); + return this.View(model); } [FeatureGate(nameof(Feature.AllowDynamicClientRegistration))] @@ -98,14 +183,14 @@ public async Task Index(DynamicClientRegistrationModel model) { if (string.IsNullOrEmpty(model.ClientId)) { - await Register(model); + await this.Register(model); } else { - await UpdateRegistration(model); + await this.UpdateRegistration(model); } - await PopulateFormDetail(model); + await this.PopulateFormDetail(model); } catch (Exception ex) { @@ -120,10 +205,10 @@ public async Task Index(DynamicClientRegistrationModel model) } var msg = $"Unable to {type} the Dynamic Client Registration with DataHolderBrandId: {model.DataHolderBrandId} - {ex.Message}"; - return View("Error", new ErrorViewModel { Message = msg }); + return this.View("Error", new ErrorViewModel { Message = msg }); } - return View(model); + return this.View(model); } [FeatureGate(nameof(Feature.AllowDynamicClientRegistration))] @@ -137,10 +222,10 @@ public async Task Delete(string clientId, string dataHolderBrandI try { - var deleteResponse = await DeleteRegistration(clientId, dataHolderBrandId); + var deleteResponse = await this.DeleteRegistration(clientId, dataHolderBrandId); if (deleteResponse.StatusCode.IsSuccessful()) { - return Ok(); + return this.Ok(); } regResp.StatusCode = System.Net.HttpStatusCode.OK; @@ -169,7 +254,7 @@ public async Task Get(string clientId, string dataHolderBrandId) try { - var reg = await GetRegistration(clientId, dataHolderBrandId); + var reg = await this.GetRegistration(clientId, dataHolderBrandId); if (reg.StatusCode.IsSuccessful()) { regResp.StatusCode = reg.StatusCode; @@ -199,7 +284,7 @@ public async Task UpdateDCRRegistration(string clientId, string d try { - regResp = await UpdateCurrentRegistration(clientId, dataHolderBrandId); + regResp = await this.UpdateCurrentRegistration(clientId, dataHolderBrandId); } catch (Exception ex) { @@ -226,7 +311,7 @@ public async Task GetOidcDocument(string dataHolderBrandId) try { DataHolderBrand dataHolder; - dataHolder = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); + dataHolder = await this._dhRepository.GetDataHolderBrand(dataHolderBrandId); if (dataHolder == null) { @@ -238,7 +323,7 @@ public async Task GetOidcDocument(string dataHolderBrandId) string infosecBaseUri = dataHolder.EndpointDetail.InfoSecBaseUri; configUrl = string.Concat(infosecBaseUri.TrimEnd('/'), "/.well-known/openid-configuration"); - var oidcDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); + var oidcDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); var payload = JsonConvert.SerializeObject(oidcDiscovery); response.Payload = payload; response.Messages = $"Discovery Document details loaded from {configUrl}"; @@ -257,17 +342,62 @@ public async Task GetOidcDocument(string dataHolderBrandId) }; } + private static string PopulateRegistrationRequestJwt(DynamicClientRegistrationModel model, SoftwareProduct sp, string ssa, string audience) + { + var claims = new List(); + claims.Add(new Claim("jti", Guid.NewGuid().ToString())); + claims.Add(new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer)); + claims.Add(new Claim("token_endpoint_auth_signing_alg", model.TokenEndpointAuthSigningAlg ?? string.Empty)); + claims.Add(new Claim("token_endpoint_auth_method", model.TokenEndpointAuthMethod ?? string.Empty)); + claims.Add(new Claim("application_type", model.ApplicationType ?? string.Empty)); + claims.Add(new Claim("id_token_signed_response_alg", model.IdTokenSignedResponseAlg ?? string.Empty)); + claims.Add(new Claim("request_object_signing_alg", model.RequestObjectSigningAlg ?? string.Empty)); + claims.Add(new Claim("software_statement", ssa ?? string.Empty)); + claims.Add(new Claim("authorization_signed_response_alg", model.AuthorizationSignedResponseAlg ?? string.Empty)); + claims.Add(new Claim("authorization_encrypted_response_alg", model.AuthorizationEncryptedResponseAlg ?? string.Empty)); + claims.Add(new Claim("authorization_encrypted_response_enc", model.AuthorizationEncryptedResponseEnc ?? string.Empty)); + + if (!string.IsNullOrEmpty(model.RedirectUris)) + { + char[] delimiters = { ',', ' ' }; + var redirectUrisList = model.RedirectUris.Split(delimiters).ToList(); + claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray)); + } + + foreach (var grantType in model.GrantTypes.Split(',')) + { + claims.Add(new Claim("grant_types", grantType)); + } + + if (!string.IsNullOrEmpty(model.ResponseTypes)) + { + var responseTypeList = model.ResponseTypes.Split(','); + claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypeList), JsonClaimValueTypes.JsonArray)); + } + + // algorithm to be adaptable. + var jwt = new JwtSecurityToken( + issuer: sp.SoftwareProductId, + audience: audience, + claims: claims, + expires: DateTime.UtcNow.AddMinutes(5), + signingCredentials: new X509SigningCredentials(sp.SigningCertificate.X509Certificate, SecurityAlgorithms.RsaSsaPssSha256)); + + var tokenHandler = new JwtSecurityTokenHandler(); + return tokenHandler.WriteToken(jwt); + } + private async Task Register(DynamicClientRegistrationModel model) { - var sp = _config.GetSoftwareProductConfig(); - var ssa = await GetSSA(sp, model); + var sp = this._config.GetSoftwareProductConfig(); + var ssa = await this.GetSSA(sp, model); // Construct the DCR request. - var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(model.DataHolderBrandId); + var dataHolderDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(model.DataHolderBrandId); var registrationRequestJwt = PopulateRegistrationRequestJwt(model, sp, ssa, dataHolderDiscovery.Issuer); // Request DCR to the Data Holder. - var dcrResponse = await _dcrService.Register( + var dcrResponse = await this._dcrService.Register( dataHolderDiscovery.RegistrationEndpoint, sp.ClientCertificate.X509Certificate, registrationRequestJwt); @@ -281,16 +411,16 @@ private async Task Register(DynamicClientRegistrationModel model) var registration = dcrResponse.Data; registration.DataHolderBrandId = model.DataHolderBrandId; - await _regRepository.PersistRegistration(dcrResponse.Data); + await this._regRepository.PersistRegistration(dcrResponse.Data); } } private async Task GetRegistration(string clientId, string dataHolderBrandId) { var model = new DynamicClientRegistrationModel(); - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); - var client = await _regRepository.GetRegistration(clientId, dataHolderBrandId); + var client = await this._regRepository.GetRegistration(clientId, dataHolderBrandId); if (client == null) { model.StatusCode = System.Net.HttpStatusCode.BadRequest; @@ -298,7 +428,7 @@ private async Task GetRegistration(string client return model; } - var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(client.DataHolderBrandId); + var dataHolderDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(client.DataHolderBrandId); if (dataHolderDiscovery == null) { model.StatusCode = System.Net.HttpStatusCode.BadRequest; @@ -317,7 +447,7 @@ private async Task GetRegistration(string client GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; - var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + var tokenResponse = await this._dhInfosecService.GetAccessToken(accessToken); if (!tokenResponse.IsSuccessful) { @@ -327,7 +457,7 @@ private async Task GetRegistration(string client } // Request DCR to the Data Holder. - var dcrResponse = await _dcrService.GetRegistration( + var dcrResponse = await this._dcrService.GetRegistration( dataHolderDiscovery.RegistrationEndpoint, sp.ClientCertificate.X509Certificate, tokenResponse.Data.AccessToken, @@ -344,12 +474,12 @@ private async Task UpdateCurrentRegistration(string clientId, str { var model = new ResponseModel(); - var softwareProductConfig = _config.GetSoftwareProductConfig(); - var registerConfig = _config.GetRegisterConfig(); - var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(registerConfig.OidcDiscoveryUri); + var softwareProductConfig = this._config.GetSoftwareProductConfig(); + var registerConfig = this._config.GetRegisterConfig(); + var tokenEndpoint = await this._cacheManager.GetRegisterTokenEndpoint(registerConfig.OidcDiscoveryUri); // Get an access token from the Register. - var registerTokenResponse = await _regInfosecService.GetAccessToken( + var registerTokenResponse = await this._regInfosecService.GetAccessToken( tokenEndpoint, softwareProductConfig.SoftwareProductId, softwareProductConfig.ClientCertificate.X509Certificate, @@ -363,7 +493,7 @@ private async Task UpdateCurrentRegistration(string clientId, str } // Get an SSA from the Register - var ssaResponse = await _ssaService.GetSoftwareStatementAssertion( + var ssaResponse = await this._ssaService.GetSoftwareStatementAssertion( registerConfig.MtlsBaseUri, registerConfig.GetSsaMinXv, registerTokenResponse.Data.AccessToken, @@ -381,7 +511,7 @@ private async Task UpdateCurrentRegistration(string clientId, str // Get Data Holder discovery var ssa = ssaResponse.Data; - var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); + var dataHolderDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); var accessToken = new AccessToken() { @@ -395,7 +525,7 @@ private async Task UpdateCurrentRegistration(string clientId, str }; // Get Access Token from Data Holder - var dhTokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + var dhTokenResponse = await this._dhInfosecService.GetAccessToken(accessToken); if (!dhTokenResponse.IsSuccessful) { @@ -423,7 +553,7 @@ private async Task UpdateCurrentRegistration(string clientId, str var registrationRequestJwt = tokenHandler.WriteToken(jwt); // Send Put DCR to the Data Holder. - var dcrResponse = await _dcrService.UpdateRegistration( + var dcrResponse = await this._dcrService.UpdateRegistration( dataHolderDiscovery.RegistrationEndpoint, softwareProductConfig.ClientCertificate.X509Certificate, dhTokenResponse.Data.AccessToken, @@ -438,7 +568,7 @@ private async Task UpdateCurrentRegistration(string clientId, str var registration = dcrResponse.Data; registration.DataHolderBrandId = dataHolderBrandId; - await _regRepository.UpdateRegistration(dcrResponse.Data); + await this._regRepository.UpdateRegistration(dcrResponse.Data); } return model; @@ -446,9 +576,9 @@ private async Task UpdateCurrentRegistration(string clientId, str private async Task UpdateRegistration(DynamicClientRegistrationModel model) { - var sp = _config.GetSoftwareProductConfig(); - var ssa = await GetSSA(sp, model); - var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(model.DataHolderBrandId); + var sp = this._config.GetSoftwareProductConfig(); + var ssa = await this.GetSSA(sp, model); + var dataHolderDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(model.DataHolderBrandId); model.TransactionType = "Update"; var accessToken = new AccessToken() @@ -462,7 +592,7 @@ private async Task UpdateRegistration(DynamicClientRegistrationModel model) GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; - var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + var tokenResponse = await this._dhInfosecService.GetAccessToken(accessToken); if (!tokenResponse.IsSuccessful) { @@ -475,7 +605,7 @@ private async Task UpdateRegistration(DynamicClientRegistrationModel model) var registrationRequestJwt = PopulateRegistrationRequestJwt(model, sp, ssa, dataHolderDiscovery.Issuer); // Request DCR to the Data Holder. - var dcrResponse = await _dcrService.UpdateRegistration( + var dcrResponse = await this._dcrService.UpdateRegistration( dataHolderDiscovery.RegistrationEndpoint, sp.ClientCertificate.X509Certificate, tokenResponse.Data.AccessToken, @@ -491,148 +621,17 @@ private async Task UpdateRegistration(DynamicClientRegistrationModel model) var registration = dcrResponse.Data; registration.DataHolderBrandId = model.DataHolderBrandId; - await _regRepository.UpdateRegistration(dcrResponse.Data); + await this._regRepository.UpdateRegistration(dcrResponse.Data); } } - private static string PopulateRegistrationRequestJwt(DynamicClientRegistrationModel model, SoftwareProduct sp, string ssa, string audience) - { - var claims = new List(); - claims.Add(new Claim("jti", Guid.NewGuid().ToString())); - claims.Add(new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer)); - claims.Add(new Claim("token_endpoint_auth_signing_alg", model.TokenEndpointAuthSigningAlg ?? string.Empty)); - claims.Add(new Claim("token_endpoint_auth_method", model.TokenEndpointAuthMethod ?? string.Empty)); - claims.Add(new Claim("application_type", model.ApplicationType ?? string.Empty)); - claims.Add(new Claim("id_token_signed_response_alg", model.IdTokenSignedResponseAlg ?? string.Empty)); - claims.Add(new Claim("request_object_signing_alg", model.RequestObjectSigningAlg ?? string.Empty)); - claims.Add(new Claim("software_statement", ssa ?? string.Empty)); - claims.Add(new Claim("authorization_signed_response_alg", model.AuthorizationSignedResponseAlg ?? string.Empty)); - claims.Add(new Claim("authorization_encrypted_response_alg", model.AuthorizationEncryptedResponseAlg ?? string.Empty)); - claims.Add(new Claim("authorization_encrypted_response_enc", model.AuthorizationEncryptedResponseEnc ?? string.Empty)); - - if (!string.IsNullOrEmpty(model.RedirectUris)) - { - char[] delimiters = { ',', ' ' }; - var redirectUrisList = model.RedirectUris.Split(delimiters).ToList(); - claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray)); - } - - foreach (var grantType in model.GrantTypes.Split(',')) - { - claims.Add(new Claim("grant_types", grantType)); - } - - if (!string.IsNullOrEmpty(model.ResponseTypes)) - { - var responseTypeList = model.ResponseTypes.Split(','); - claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypeList), JsonClaimValueTypes.JsonArray)); - } - - // algorithm to be adaptable. - var jwt = new JwtSecurityToken( - issuer: sp.SoftwareProductId, - audience: audience, - claims: claims, - expires: DateTime.UtcNow.AddMinutes(5), - signingCredentials: new X509SigningCredentials(sp.SigningCertificate.X509Certificate, SecurityAlgorithms.RsaSsaPssSha256)); - - var tokenHandler = new JwtSecurityTokenHandler(); - return tokenHandler.WriteToken(jwt); - } - - public static (List Claims, string ErrorMessages) CreateClaimsForUpdateDCRRequest(OidcDiscovery oidcDiscovery, string ssa, SoftwareProduct softwareProductConfig) - { - string errorMessage = string.Empty; - - // Error - Unable to perform DCR as there are no mutually supported values in the mandatory claim [CLAIM_NAME] - const string ErrorMessage = "Unable to perform DCR as there are no mutually supported values in the mandatory claim"; - - var claims = new List - { - new("jti", Guid.NewGuid().ToString()), - new("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer), - new("token_endpoint_auth_signing_alg", "PS256"), - new("token_endpoint_auth_method", "private_key_jwt"), - new("application_type", "web"), - new("id_token_signed_response_alg", "PS256"), - new("request_object_signing_alg", "PS256"), - new("software_statement", ssa ?? string.Empty), - new("grant_types", "client_credentials"), - new("grant_types", "authorization_code"), - new("grant_types", "refresh_token"), - }; - - var responseTypesList = oidcDiscovery.ResponseTypesSupported.Where(x => x.ToLower().Equals("code")).ToList(); - claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypesList), JsonClaimValueTypes.JsonArray)); - - if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Length == 0) - { - // Log error message to the mandatory claim missing - errorMessage = ErrorMessage + " authorization_signed_response_alg"; - return (null, errorMessage); - } - - if (!oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) - { - // Return the error - errorMessage = ErrorMessage + " authorization_signed_response_alg"; - return (null, errorMessage); - } - - if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256")) - { - claims.Add(new Claim("authorization_signed_response_alg", "PS256")); - } - else if (oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) - { - claims.Add(new Claim("authorization_signed_response_alg", "ES256")); - } - - // Check if the enc is empty but a alg is specified. - if ((oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported == null || oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Length == 0) - && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256") - && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) - { - errorMessage = ErrorMessage + " authorization_encrypted_response_enc"; - return (null, errorMessage); - } - - if (oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Contains("A128CBC-HS256")) - { - claims.Add(new Claim("authorization_encrypted_response_enc", "A128CBC-HS256")); - } - else if (oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported.Contains("A256GCM")) - { - claims.Add(new Claim("authorization_encrypted_response_enc", "A256GCM")); - } - - // Conditional: Optional for response_type "code" if authorization_encryption_enc_values_supported is present - if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported != null && oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Length != 0) - { - if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256")) - { - claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP-256")); - } - else if (oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) - { - claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP")); - } - } - - char[] delimiters = [',', ' ']; - var redirectUrisList = softwareProductConfig.RedirectUris?.Split(delimiters).ToList(); - claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray)); - - return (claims, errorMessage); - } - private async Task DeleteRegistration(string clientId, string dataHolderBrandId) { var model = new DynamicClientRegistrationModel(); - var client = await _regRepository.GetRegistration(clientId, dataHolderBrandId); + var client = await this._regRepository.GetRegistration(clientId, dataHolderBrandId); - var sp = _config.GetSoftwareProductConfig(); - var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(client.DataHolderBrandId); + var sp = this._config.GetSoftwareProductConfig(); + var dataHolderDiscovery = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(client.DataHolderBrandId); var accessToken = new AccessToken() { @@ -645,7 +644,7 @@ private async Task DeleteRegistration(string cli GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; - var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + var tokenResponse = await this._dhInfosecService.GetAccessToken(accessToken); if (!tokenResponse.IsSuccessful) { @@ -655,7 +654,7 @@ private async Task DeleteRegistration(string cli } // Delete client from the Data Holder. This is an optional endpoint and may not be implemented by the Data Holder. - var dcrResponse = await _dcrService.DeleteRegistration( + var dcrResponse = await this._dcrService.DeleteRegistration( dataHolderDiscovery.RegistrationEndpoint, sp.ClientCertificate.X509Certificate, tokenResponse.Data.AccessToken, @@ -666,18 +665,18 @@ private async Task DeleteRegistration(string cli model.ResponsePayload = dcrResponse.Payload; // Delete the client from the internal repository. - await _regRepository.DeleteRegistration(clientId, dataHolderBrandId); + await this._regRepository.DeleteRegistration(clientId, dataHolderBrandId); return model; } private async Task GetSSA(SoftwareProduct sp, DynamicClientRegistrationModel model) { - var reg = _config.GetRegisterConfig(); - var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); + var reg = this._config.GetRegisterConfig(); + var tokenEndpoint = await this._cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); // Get an access token from the Register. - var tokenResponse = await _regInfosecService.GetAccessToken( + var tokenResponse = await this._regInfosecService.GetAccessToken( tokenEndpoint, sp.SoftwareProductId, sp.ClientCertificate.X509Certificate, @@ -691,7 +690,7 @@ private async Task GetSSA(SoftwareProduct sp, DynamicClientRegistrationM } // Get an SSA from the Register. - var ssaResponse = await _ssaService.GetSoftwareStatementAssertion( + var ssaResponse = await this._ssaService.GetSoftwareStatementAssertion( reg.MtlsBaseUri, model.SsaVersion, tokenResponse.Data.AccessToken, @@ -712,11 +711,11 @@ private async Task GetSSA(SoftwareProduct sp, DynamicClientRegistrationM private async Task PopulateFormDetail(DynamicClientRegistrationModel model) { - var allowDynamicClientRegistration = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); + var allowDynamicClientRegistration = await this._featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); // Return any from the Registration table repository. - var registrations = await _regRepository.GetRegistrations(); - model.DataHolderBrands = (await _dhRepository.GetDataHolderBrands()) + var registrations = await this._regRepository.GetRegistrations(); + model.DataHolderBrands = (await this._dhRepository.GetDataHolderBrands()) .OrderByMockDataHolders(allowDynamicClientRegistration) .Select(d => new SelectListItem(d.BrandName, d.DataHolderBrandId)) .ToList(); @@ -747,14 +746,14 @@ private async Task PopulateFormDetail(DynamicClientRegistrationModel model) if (!allowDynamicClientRegistration) { // Return any from the DcrMessage table in the repository. - var dcrMessages = await _regRepository.GetDcrMessageRegistrations(); + var dcrMessages = await this._regRepository.GetDcrMessageRegistrations(); model.FailedDCRMessages = dcrMessages.Where(message => message.MessageState == Message.DCRFailed.ToString()).OrderBy(message => message.BrandName); } } private void SetViewModelDefaults(DynamicClientRegistrationModel model) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); model.SoftwareProductId = sp.SoftwareProductId; model.RedirectUris = sp.RedirectUris; model.Scope = sp.Scope; @@ -774,7 +773,7 @@ private void SetViewModelDefaults(DynamicClientRegistrationModel model) private void SetViewModel(DynamicClientRegistrationModel model, Registration client) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); model.DataHolderBrandId = client.DataHolderBrandId; model.SoftwareProductId = sp.SoftwareProductId; model.RedirectUris = sp.RedirectUris; diff --git a/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs b/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs index 1ebe76e..2332ccc 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs @@ -9,7 +9,7 @@ public class HealthController : Controller [HttpGet("status")] public IActionResult Index() { - return Json(new Health() { Status = "OK" }); + return this.Json(new Health() { Status = "OK" }); } } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs b/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs index 2019828..dce6105 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs @@ -1,25 +1,25 @@ -using CDR.DataRecipient.Web.Common; +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Filters; using CDR.DataRecipient.Web.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { [Authorize] public class HomeController : Controller { - private readonly IConfiguration _config; private static HttpClient client = new HttpClient(); + private readonly IConfiguration _config; public HomeController(IConfiguration config) { - _config = config; + this._config = config; } [ServiceFilter(typeof(LogActionEntryAttribute))] @@ -27,18 +27,18 @@ public async Task Index() { try { - var homePageContentUrl = _config.GetValue(Constants.Content.HomepageContentUrl); - var footerContentUrl = _config.GetValue(Constants.Content.FooterContentUrl); + var homePageContentUrl = this._config.GetValue(Constants.Content.HomepageContentUrl); + var footerContentUrl = this._config.GetValue(Constants.Content.FooterContentUrl); - ViewBag.HomepageContent = string.Empty; - ViewBag.FooterContent = string.Empty; + this.ViewBag.HomepageContent = string.Empty; + this.ViewBag.FooterContent = string.Empty; if (!string.IsNullOrEmpty(homePageContentUrl)) { var result = await client.GetAsync(homePageContentUrl); if (result.StatusCode == System.Net.HttpStatusCode.OK) { - ViewBag.HomepageContent = result.Content.ReadAsStringAsync().Result; + this.ViewBag.HomepageContent = result.Content.ReadAsStringAsync().Result; } } @@ -47,30 +47,30 @@ public async Task Index() var result = await client.GetAsync(footerContentUrl); if (result.StatusCode == System.Net.HttpStatusCode.OK) { - ViewBag.FooterContent = result.Content.ReadAsStringAsync().Result; + this.ViewBag.FooterContent = result.Content.ReadAsStringAsync().Result; } } - return View(); + return this.View(); } catch (Exception) { var msg = $"Unable to load the required content"; - return View("Error", new ErrorViewModel { Message = msg }); + return this.View("Error", new ErrorViewModel { Message = msg }); } } [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult About() { - return View(); + return this.View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult Error() { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + return this.View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier }); } } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs b/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs index 33b0a2b..2e7baef 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs @@ -1,4 +1,7 @@ -using CDR.DataRecipient.Web.Common; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; using CDR.DataRecipient.Web.Filters; using Microsoft.AspNetCore.Mvc; @@ -6,14 +9,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { - public class JwksController : Controller + [Route("")] + public class JwksController : ControllerBase { private readonly IConfiguration _config; private readonly ILogger _logger; @@ -24,9 +24,9 @@ public JwksController( ILogger logger, IMemoryCache cache) { - _config = config; - _logger = logger; - _cache = cache; + this._config = config; + this._logger = logger; + this._cache = cache; } [HttpGet] @@ -34,7 +34,7 @@ public JwksController( [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult GetJwks(int? id = 1) { - return Ok(GenerateJwks(id, false)); + return this.Ok(this.GenerateJwks(id, false)); } [HttpGet] @@ -42,7 +42,7 @@ public IActionResult GetJwks(int? id = 1) [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult GetJwksPrivateKeys(int? id = 1) { - return Ok(GenerateJwks(id, true)); + return this.Ok(this.GenerateJwks(id, true)); } /// @@ -58,18 +58,18 @@ public IActionResult GetJwksPrivateKeys(int? id = 1) /// private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKeyDetails = false) { - _logger.LogInformation($"{nameof(JwksController)}.{nameof(GenerateJwks)}"); + this._logger.LogInformation($"{nameof(JwksController)}.{nameof(this.GenerateJwks)}"); string cacheKey = $"jwks-{id}-{includePrivateKeyDetails}"; - var item = _cache.Get(cacheKey); + var item = this._cache.Get(cacheKey); if (item != null) { - _logger.LogInformation("Cache hit: {CacheKey}", cacheKey); + this._logger.LogInformation("Cache hit: {CacheKey}", cacheKey); return item; } - var cert = GetCertificate(id.Value); + var cert = this.GetCertificate(id.Value); // Get credentials from certificate var securityKey = new X509SecurityKey(cert); @@ -84,24 +84,24 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe // Make sure the kid is different for each key. FAPI 1.0 - kid needs to be unique id within the keyset. var jwkSign = new SDK.Models.JsonWebKey() { - alg = signingCredentials.Algorithm, - kid = signingCredentials.Kid, - kty = securityKey.PublicKey.KeyExchangeAlgorithm, - n = n, - e = e, - use = "sig", + Alg = signingCredentials.Algorithm, + Kid = signingCredentials.Kid, + Kty = securityKey.PublicKey.KeyExchangeAlgorithm, + N = n, + E = e, + Use = "sig", }; var jwkEncList = encryptionCredentials.Keys.Select(key => { var credential = encryptionCredentials[key]; return new SDK.Models.JsonWebKey() { - alg = credential.Enc, - kid = key, - kty = securityKey.PublicKey.KeyExchangeAlgorithm, - n = n, - e = e, - use = "enc", + Alg = credential.Enc, + Kid = key, + Kty = securityKey.PublicKey.KeyExchangeAlgorithm, + N = n, + E = e, + Use = "enc", }; }); @@ -110,21 +110,21 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe var privateKey = new RsaSecurityKey(cert.GetRSAPrivateKey()); var jwkPrivate = JsonWebKeyConverter.ConvertFromRSASecurityKey(privateKey); - jwkSign.d = jwkPrivate.D; + jwkSign.D = jwkPrivate.D; foreach (var jwk in jwkEncList) { - jwk.d = jwkPrivate.D; + jwk.D = jwkPrivate.D; } } var jwks = new List(); jwks.Add(jwkSign); jwks.AddRange(jwkEncList); - var keySet = new SDK.Models.JsonWebKeySet() { keys = jwks.ToArray() }; + var keySet = new SDK.Models.JsonWebKeySet() { Keys = jwks.ToArray() }; // Add the key Set to the cache. - _cache.Set(cacheKey, keySet); - _logger.LogInformation("JWKS added to cache"); + this._cache.Set(cacheKey, keySet); + this._logger.LogInformation("JWKS added to cache"); return keySet; } @@ -133,7 +133,7 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe /// Retrieve the certificate to use to generate the jwks. /// /// Provides the ability to switch to an alternative certificate. - /// X509Certificate2 + /// X509Certificate2. /// /// Providing the ability to switch to a different certificate allows 2 jwks to be generated from the one data recipient. /// This is not what would be used in a production scenario, however this is useful to be able to perform FAPI testing using the DR. @@ -143,7 +143,7 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe /// private X509Certificate2 GetCertificate(int id) { - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); if (id == 2) { diff --git a/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs b/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs index 88e7b60..f167f64 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs @@ -18,21 +18,33 @@ public OidcController() [HttpGet] public IActionResult RemoteError([FromQuery(Name = "error_message")] string errMsg) { - return View("Error", new ErrorViewModel { ErrorTitle = "Remote Error", Message = ProcessMessage(errMsg) }); + return this.View("Error", new ErrorViewModel { ErrorTitle = "Remote Error", Message = ProcessMessage(errMsg) }); } [Route("autherror")] [HttpGet] public IActionResult AuthError([FromQuery(Name = "error_message")] string errMsg) { - return View("Error", new ErrorViewModel { ErrorTitle = "Authentication Error", Message = ProcessMessage(errMsg) }); + return this.View("Error", new ErrorViewModel { ErrorTitle = "Authentication Error", Message = ProcessMessage(errMsg) }); } [Route("accesserror")] [HttpGet] public IActionResult AccessError([FromQuery(Name = "error_message")] string errMsg) { - return View("Error", new ErrorViewModel { ErrorTitle = "Access Error", Message = ProcessMessage(errMsg) }); + return this.View("Error", new ErrorViewModel { ErrorTitle = "Access Error", Message = ProcessMessage(errMsg) }); + } + + [Route("logout")] + [HttpGet] + public async Task Logout() + { + await this.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var prop = new AuthenticationProperties() + { + RedirectUri = "/", + }; + await this.HttpContext.SignOutAsync("OpenIdConnect", prop); } private static string ProcessMessage(string errMsg) @@ -50,17 +62,5 @@ private static string ProcessMessage(string errMsg) return msg; } - - [Route("logout")] - [HttpGet] - public async Task Logout() - { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - var prop = new AuthenticationProperties() - { - RedirectUri = "/", - }; - await HttpContext.SignOutAsync("OpenIdConnect", prop); - } } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/ParController.cs b/Source/CDR.DataRecipient.Web/Controllers/ParController.cs index 600501d..d15effd 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ParController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ParController.cs @@ -1,7 +1,10 @@ -using CDR.DataRecipient.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CDR.DataRecipient.Models; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Models; -using CDR.DataRecipient.SDK.Models.AuthorisationRequest; using CDR.DataRecipient.SDK.Services.DataHolder; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; @@ -14,10 +17,6 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -44,14 +43,14 @@ public ParController( IRegistrationsRepository registrationsRepository, ILogger logger) { - _config = config; - _cache = cache; - _dhInfoSecService = dhInfoSecService; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _consentsRepository = consentsRepository; - _dhRepository = dhRepository; - _registrationsRepository = registrationsRepository; - _logger = logger; + this._config = config; + this._cache = cache; + this._dhInfoSecService = dhInfoSecService; + this._dataHolderDiscoveryCache = dataHolderDiscoveryCache; + this._consentsRepository = consentsRepository; + this._dhRepository = dhRepository; + this._registrationsRepository = registrationsRepository; + this._logger = logger; } [HttpGet] @@ -59,8 +58,8 @@ public ParController( public async Task Index() { var model = new ParModel() { UsePkce = true, ResponseType = "code", ResponseMode = "jwt" }; - await PopulatePickers(model); - return View(model); + await this.PopulatePickers(model); + return this.View(model); } [HttpPost] @@ -71,11 +70,11 @@ public async Task Index(ParModel model) { try { - var reg = await _registrationsRepository.GetRegistration(model.ClientId, model.DataHolderBrandId); - var dhConfig = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(reg.DataHolderBrandId); - var sp = _config.GetSoftwareProductConfig(); + var reg = await this._registrationsRepository.GetRegistration(model.ClientId, model.DataHolderBrandId); + var dhConfig = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(reg.DataHolderBrandId); + var sp = this._config.GetSoftwareProductConfig(); - var infosecBaseUri = await GetInfoSecBaseUri(reg.DataHolderBrandId); + var infosecBaseUri = await this.GetInfoSecBaseUri(reg.DataHolderBrandId); if (string.IsNullOrEmpty(infosecBaseUri)) { throw new CustomException(); @@ -102,12 +101,12 @@ public async Task Index(ParModel model) if (model.UsePkce) { - authState.Pkce = _dhInfoSecService.CreatePkceData(); + authState.Pkce = this._dhInfoSecService.CreatePkceData(); } - _logger.LogDebug("saving AuthorisationState of {@AuthState} to cache", authState); + this._logger.LogDebug("saving AuthorisationState of {@AuthState} to cache", authState); - await _cache.SetAsync(stateKey, authState, DateTimeOffset.UtcNow.AddMinutes(60)); + await this._cache.SetAsync(stateKey, authState, DateTimeOffset.UtcNow.AddMinutes(60)); // Build the authentication request JWT. var authorisationRequestJwt = new AuthorisationRequestJwt() @@ -127,9 +126,9 @@ public async Task Index(ParModel model) ResponseType = model.ResponseType, }; - var authRequest = _dhInfoSecService.BuildAuthorisationRequestJwt(authorisationRequestJwt); + var authRequest = this._dhInfoSecService.BuildAuthorisationRequestJwt(authorisationRequestJwt); - var parResponse = await _dhInfoSecService.PushedAuthorisationRequest( + var parResponse = await this._dhInfoSecService.PushedAuthorisationRequest( dhConfig.PushedAuthorizationRequestEndpoint, sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate, @@ -151,7 +150,7 @@ public async Task Index(ParModel model) model.PushedAuthorisation = parResponse.Data; // Build the Authorisation URL for the Data Holder passing in the request uri returned from the PAR response. - model.AuthorisationUri = await _dhInfoSecService.BuildAuthorisationRequestUri( + model.AuthorisationUri = await this._dhInfoSecService.BuildAuthorisationRequestUri( infosecBaseUri, model.ClientId, sp.SigningCertificate.X509Certificate, @@ -168,41 +167,17 @@ public async Task Index(ParModel model) catch (CustomException) { var msg = $"The Data Holder details do not exist in the repository for ClientId: {model.ClientId}"; - return View("Error", new ErrorViewModel { Message = msg }); + return this.View("Error", new ErrorViewModel { Message = msg }); } catch (Exception ex) { var msg = $"Unable to create the Pushed Authorisation Request (PAR) with ClientId: {model.ClientId} - {ex.Message}"; - return View("Error", new ErrorViewModel { Message = msg }); - } - } - - await PopulatePickers(model); - return View(model); - } - - /// - /// With multiple supported acr values - /// Should use the the max. available Acr value spported only - /// - /// Acr values to validate - /// max available ACR value supported - private static int ValidateAcrValuesSpported(string[] acrValuesSupported) - { - var maxAcrSupported = 0; - foreach (var acrValueSupported in acrValuesSupported) - { - // Should use the max. available supported acr - // "urn:cds.au:cdr:3" - var acrSupported = acrValueSupported.Split(':')[3]; - int acrValue; - if (int.TryParse(acrSupported, out acrValue) && acrValue > maxAcrSupported) - { - maxAcrSupported = acrValue; + return this.View("Error", new ErrorViewModel { Message = msg }); } } - return maxAcrSupported; + await this.PopulatePickers(model); + return this.View(model); } [HttpPost] @@ -218,7 +193,7 @@ public async Task RegistrationDetail(string registrationId) // Return the RedirectUris for the picked item var registrationInfo = Registration.SplitRegistrationId(registrationId); - Registration registration = await _registrationsRepository.GetRegistration(registrationInfo.ClientId, registrationInfo.DataHolderBrandId); + Registration registration = await this._registrationsRepository.GetRegistration(registrationInfo.ClientId, registrationInfo.DataHolderBrandId); if (registration == null) { message = "Registration not found"; @@ -226,7 +201,7 @@ public async Task RegistrationDetail(string registrationId) } // Return the Consents(CdrArrangements) for the picked item - IEnumerable cdrArrangements = await _consentsRepository.GetConsents(registrationInfo.ClientId, registrationInfo.DataHolderBrandId, HttpContext.User.GetUserId()); + IEnumerable cdrArrangements = await this._consentsRepository.GetConsents(registrationInfo.ClientId, registrationInfo.DataHolderBrandId, this.HttpContext.User.GetUserId()); if (cdrArrangements != null && cdrArrangements.Any()) { arrangements = cdrArrangements.Select(c => new SelectListItem(c.CdrArrangementId, c.CdrArrangementId)).ToList(); @@ -241,9 +216,33 @@ public async Task RegistrationDetail(string registrationId) }); } + /// + /// With multiple supported acr values + /// Should use the the max. available Acr value spported only. + /// + /// Acr values to validate. + /// max available ACR value supported. + private static int ValidateAcrValuesSpported(string[] acrValuesSupported) + { + var maxAcrSupported = 0; + foreach (var acrValueSupported in acrValuesSupported) + { + // Should use the max. available supported acr + // "urn:cds.au:cdr:3" + var acrSupported = acrValueSupported.Split(':')[3]; + int acrValue; + if (int.TryParse(acrSupported, out acrValue) && acrValue > maxAcrSupported) + { + maxAcrSupported = acrValue; + } + } + + return maxAcrSupported; + } + private async Task PopulatePickers(ParModel model) { - model.Registrations = await _registrationsRepository.GetRegistrations(); + model.Registrations = await this._registrationsRepository.GetRegistrations(); model.RegistrationListItems = new List(); @@ -259,7 +258,7 @@ private async Task PopulatePickers(ParModel model) private async Task GetInfoSecBaseUri(string dataHolderBrandId) { - var dh = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); + var dh = await this._dhRepository.GetDataHolderBrand(dataHolderBrandId); if (dh == null) { return null; diff --git a/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs b/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs index 86ce743..205d4a0 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs @@ -1,4 +1,10 @@ -using CDR.DataRecipient.Repository; +using System; +using System.ComponentModel.DataAnnotations; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; @@ -7,16 +13,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System; -using System.ComponentModel.DataAnnotations; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; using static CDR.DataRecipient.Web.Common.Constants; namespace CDR.DataRecipient.Web.Controllers { + [Route("")] [ClientAuthorize] public class RevocationController : Controller { @@ -25,29 +26,29 @@ public class RevocationController : Controller private readonly IDataHolderDiscoveryCache _dataHolderDiscoveryCache; private readonly ILogger _logger; - private ClaimsPrincipal Client => (ClaimsPrincipal)this.HttpContext.Items[ClientAuthorizeAttribute.ClaimsPrincipalKey]; - - private string ClientBrandId => Client.Claims.FirstOrDefault(c => c.Type == "iss")?.Value; - public RevocationController( IConfiguration config, IDataHolderDiscoveryCache dataHolderDiscoveryCache, IConsentsRepository consentsRepository, ILogger logger) { - _config = config; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _consentsRepository = consentsRepository; - _logger = logger; + this._config = config; + this._dataHolderDiscoveryCache = dataHolderDiscoveryCache; + this._consentsRepository = consentsRepository; + this._logger = logger; } + private ClaimsPrincipal Client => (ClaimsPrincipal)this.HttpContext.Items[ClientAuthorizeAttribute.ClaimsPrincipalKey]; + + private string ClientBrandId => this.Client.Claims.FirstOrDefault(c => c.Type == "iss")?.Value; + [HttpPost] [Route(Urls.ClientArrangementRevokeUrl)] [MustConsume("application/x-www-form-urlencoded")] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Revoke([Required, FromForm] RevocationModel revocationModel) { - _logger.LogDebug("Revocation request received. cdr_arrangement_id: {CdrArrangementId}. cdr_arrangement_jwt: {CdrArrangementJwt}", revocationModel.CdrArrangementId, revocationModel.CdrArrangementJwt); + this._logger.LogDebug("Revocation request received. cdr_arrangement_id: {CdrArrangementId}. cdr_arrangement_jwt: {CdrArrangementJwt}", revocationModel.CdrArrangementId, revocationModel.CdrArrangementJwt); // When the Data Holder sends an arrangement revoke request to the Data Recipient: // From 31 July 2022: @@ -60,8 +61,8 @@ public async Task Revoke([Required, FromForm] RevocationModel rev // Validate that the cdr arrangement jwt parameter has been passed. if (string.IsNullOrEmpty(revocationModel.CdrArrangementJwt)) { - _logger.LogDebug("The cdr_arrangement_jwt was missing"); - return BadRequest(new ErrorListModel(ErrorCodes.MissingField, ErrorTitles.MissingField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + this._logger.LogDebug("The cdr_arrangement_jwt was missing"); + return this.BadRequest(new ErrorListModel(ErrorCodes.MissingField, ErrorTitles.MissingField, CdrArrangementRevocationRequest.CdrArrangementJwt)); } // Retrieve the cdr_arrangement_id from the JWT. @@ -70,38 +71,38 @@ public async Task Revoke([Required, FromForm] RevocationModel rev if (token == null || token.Claims == null || !token.Claims.Any()) { - _logger.LogDebug("The cdr_arrangement_jwt did not have claims"); - return BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + this._logger.LogDebug("The cdr_arrangement_jwt did not have claims"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); } // cdr_arrangement_id claim was not found in cdr_arrangement_jwt. var cdrArrangementIdClaim = token.Claims.FirstOrDefault(c => c.Type.Equals(CdrArrangementRevocationRequest.CdrArrangementId)); if (cdrArrangementIdClaim == null) { - _logger.LogDebug("The cdr_arrangement_jwt did not contain a cdr_arrangement_id claim"); - return BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + this._logger.LogDebug("The cdr_arrangement_jwt did not contain a cdr_arrangement_id claim"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); } // If a cdr_arrangement_id form parameter has also been passed, then validate the value is the same as the value in the cdr_arrangement_jwt. if (!string.IsNullOrEmpty(revocationModel.CdrArrangementId) && !revocationModel.CdrArrangementId.Equals(cdrArrangementIdClaim.Value, System.StringComparison.OrdinalIgnoreCase)) { - _logger.LogDebug("The provided cdr_arrangement_id values did not match"); - return BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementId)); + this._logger.LogDebug("The provided cdr_arrangement_id values did not match"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementId)); } - var sp = _config.GetSoftwareProductConfig(); + var sp = this._config.GetSoftwareProductConfig(); // Find the matching cdr arrangement. - var arrangement = await _consentsRepository.GetConsentByArrangement(cdrArrangementIdClaim.Value); + var arrangement = await this._consentsRepository.GetConsentByArrangement(cdrArrangementIdClaim.Value); // If the arrangement was not found or if the arrangement does not belong to the calling data holder, then return an error. // Note: The client_id in the bearer token contains the Data Holder Brand Id. if (arrangement == null - || !arrangement.DataHolderBrandId.Equals(ClientBrandId, System.StringComparison.OrdinalIgnoreCase)) + || !arrangement.DataHolderBrandId.Equals(this.ClientBrandId, System.StringComparison.OrdinalIgnoreCase)) { - _logger.LogDebug("The arrangement could not be found or was not owned by the calling data holder brand. Arrangement: {CdrArrangementId}, Data Holder Brand: {ClientBrandId}", cdrArrangementIdClaim.Value, ClientBrandId); - return UnprocessableEntity(new ErrorListModel(ErrorCodes.InvalidConsent, ErrorTitles.InvalidArrangement, cdrArrangementIdClaim.Value)); + this._logger.LogDebug("The arrangement could not be found or was not owned by the calling data holder brand. Arrangement: {CdrArrangementId}, Data Holder Brand: {ClientBrandId}", cdrArrangementIdClaim.Value, this.ClientBrandId); + return this.UnprocessableEntity(new ErrorListModel(ErrorCodes.InvalidConsent, ErrorTitles.InvalidArrangement, cdrArrangementIdClaim.Value)); } // Try and extract the "Self-Signed JWT Client Authentication" claims from the cdr_arrangement_jwt. @@ -116,7 +117,7 @@ public async Task Revoke([Required, FromForm] RevocationModel rev string validAudience = null; bool validateLifetime = false; bool fullValidationRequired = - DateTime.UtcNow > _config.AttemptValidateCdrArrangementJwtFromDate() + DateTime.UtcNow > this._config.AttemptValidateCdrArrangementJwtFromDate() && HasValue(issClaim) && HasValue(subClaim) && HasValue(audClaim) @@ -125,50 +126,58 @@ public async Task Revoke([Required, FromForm] RevocationModel rev if (fullValidationRequired) { - _logger.LogDebug("Full validation of the cdr_arrangement_jwt should occur..."); + this._logger.LogDebug("Full validation of the cdr_arrangement_jwt should occur..."); + + // Check for null or missing claims before accessing Value + if (issClaim == null || string.IsNullOrEmpty(issClaim.Value) || + subClaim == null || string.IsNullOrEmpty(subClaim.Value)) + { + this._logger.LogDebug("Missing iss or sub claim in the cdr_arrangement_jwt"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + } // iss claim and sub claim should have the same value. if (!issClaim.Value.Equals(subClaim.Value, System.StringComparison.OrdinalIgnoreCase)) { - _logger.LogDebug("The iss and sub claim values did not match in the cdr_arrangement_jwt"); - return BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + this._logger.LogDebug("The iss and sub claim values did not match in the cdr_arrangement_jwt"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); } // Set the values that will be used in cdr_arrangement_jwt token validation. - validIssuer = ClientBrandId; + validIssuer = this.ClientBrandId; validAudience = sp.RevocationUri; validateLifetime = true; } // Validate the cdr_arrangement_jwt either using "full" or "minimal" validation. - var jwksUri = await GetJwksUri(); + var jwksUri = await this.GetJwksUri(); var validated = await revocationModel.CdrArrangementJwt.ValidateToken( jwksUri, - _logger, + this._logger, validIssuer: validIssuer, validAudiences: validAudience != null ? new string[] { validAudience } : null, validateLifetime: validateLifetime, - acceptAnyServerCertificate: _config.IsAcceptingAnyServerCertificate()); + acceptAnyServerCertificate: this._config.IsAcceptingAnyServerCertificate()); if (!validated.IsValid) { - _logger.LogDebug("Token validation failed on cdr_arrangement_jwt"); - return BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); + this._logger.LogDebug("Token validation failed on cdr_arrangement_jwt"); + return this.BadRequest(new ErrorListModel(ErrorCodes.InvalidField, ErrorTitles.InvalidField, CdrArrangementRevocationRequest.CdrArrangementJwt)); } revocationModel.CdrArrangementId = cdrArrangementIdClaim.Value; - var isDeleted = await _consentsRepository.RevokeConsent(revocationModel.CdrArrangementId, ClientBrandId); + var isDeleted = await this._consentsRepository.RevokeConsent(revocationModel.CdrArrangementId, this.ClientBrandId); if (!isDeleted) { - _logger.LogDebug("An error occurred when attempting to delete the arrangement"); + this._logger.LogDebug("An error occurred when attempting to delete the arrangement"); // No matching record in the DB for the arrangement id and brand id combination or failed to delete the consent - return UnprocessableEntity(new ErrorListModel(ErrorCodes.InvalidConsent, ErrorTitles.InvalidArrangement, $"Invalid arrangement: {revocationModel.CdrArrangementId}")); + return this.UnprocessableEntity(new ErrorListModel(ErrorCodes.InvalidConsent, ErrorTitles.InvalidArrangement, $"Invalid arrangement: {revocationModel.CdrArrangementId}")); } - return NoContent(); + return this.NoContent(); } private static bool HasValue(Claim claim) @@ -179,7 +188,7 @@ private static bool HasValue(Claim claim) private async Task GetJwksUri() { // Get the current data holder details. - var dataholderDiscoveryDocument = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(this.ClientBrandId); + var dataholderDiscoveryDocument = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(this.ClientBrandId); if (dataholderDiscoveryDocument == null) { // There is no valid brand id in our DB for this issuer. diff --git a/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs b/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs index 08d644d..cc4f2aa 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs @@ -1,11 +1,11 @@ -using CDR.DataRecipient.Web.Features; +using System.Linq; +using CDR.DataRecipient.Web.Features; using CDR.DataRecipient.Web.Filters; using CDR.DataRecipient.Web.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.FeatureManagement.Mvc; -using System.Linq; namespace CDR.DataRecipient.Web.Controllers { @@ -16,7 +16,7 @@ public class SettingsController : Controller public SettingsController(IConfiguration config) { - _config = config; + this._config = config; } [FeatureGate(nameof(Feature.ShowSettings))] @@ -24,14 +24,14 @@ public SettingsController(IConfiguration config) public IActionResult Index() { var model = new SettingsModel(); - PopulateSettings(model); - return View(model); + this.PopulateSettings(model); + return this.View(model); } private void PopulateSettings(SettingsModel model) { var pattern = "MockDataRecipient:"; - var configSettings = _config.AsEnumerable().Where(c => c.Key.StartsWith(pattern) && c.Value != null).OrderBy(c => c.Key); + var configSettings = this._config.AsEnumerable().Where(c => c.Key.StartsWith(pattern) && c.Value != null).OrderBy(c => c.Key); foreach (var setting in configSettings) { model.ConfigurationSettings.Add(setting.Key.Replace(pattern, string.Empty), setting.Value.StartsWith("https://") ? $"{setting.Value}" : setting.Value); diff --git a/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs b/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs index 775e633..09a5193 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs @@ -1,4 +1,4 @@ -using CDR.DataRecipient.SDK; +using System.Threading.Tasks; using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.SDK.Services.Register; using CDR.DataRecipient.Web.Caching; @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -27,36 +26,59 @@ public SsaController( ICacheManager cacheManager, ISsaService ssaService) { - _config = config; - _infosecService = infosecService; - _cacheManager = cacheManager; - _ssaService = ssaService; + this._config = config; + this._infosecService = infosecService; + this._cacheManager = cacheManager; + this._ssaService = ssaService; } [HttpGet] public IActionResult Index() { var model = new SsaModel(); - PopulateModel(model); - return View(model); + this.PopulateModel(model); + return this.View(model); } [HttpPost] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Index(SsaModel model) { - await GetSSA(model); - return View(model); + await this.GetSSA(model); + return this.View(model); + } + + private static void SetDefaults(SsaModel model, SoftwareProduct sp) + { + if (string.IsNullOrEmpty(model.BrandId)) + { + model.BrandId = sp.BrandId; + } + + if (string.IsNullOrEmpty(model.SoftwareProductId)) + { + model.SoftwareProductId = sp.SoftwareProductId; + } + + if (string.IsNullOrEmpty(model.Version)) + { + model.Version = "3"; + } + + if (string.IsNullOrEmpty(model.Messages)) + { + model.Messages = "Waiting..."; + } } private async Task GetSSA(SsaModel model) { - var reg = _config.GetRegisterConfig(); - var sp = _config.GetSoftwareProductConfig(); - var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); + var reg = this._config.GetRegisterConfig(); + var sp = this._config.GetSoftwareProductConfig(); + var tokenEndpoint = await this._cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); // Get the access token from the Register. - var tokenResponse = await _infosecService.GetAccessToken( + var tokenResponse = await this._infosecService.GetAccessToken( tokenEndpoint, model.SoftwareProductId, sp.ClientCertificate.X509Certificate, @@ -65,11 +87,11 @@ private async Task GetSSA(SsaModel model) if (!tokenResponse.IsSuccessful) { model.Messages = $"{tokenResponse.StatusCode} - {tokenResponse.Message}"; - PopulateModel(model); + this.PopulateModel(model); return; } - var ssaResponse = await _ssaService.GetSoftwareStatementAssertion( + var ssaResponse = await this._ssaService.GetSoftwareStatementAssertion( reg.MtlsBaseUri, model.Version, tokenResponse.Data.AccessToken, @@ -81,13 +103,13 @@ private async Task GetSSA(SsaModel model) model.StatusCode = ssaResponse.StatusCode; model.Messages = $"{ssaResponse.StatusCode} - {ssaResponse.Message}"; model.SSA = ssaResponse.Data; - PopulateModel(model); + this.PopulateModel(model); } private void PopulateModel(SsaModel model) { - var reg = _config.GetRegisterConfig(); - var sp = _config.GetSoftwareProductConfig(); + var reg = this._config.GetRegisterConfig(); + var sp = this._config.GetSoftwareProductConfig(); SetDefaults(model, sp); @@ -101,28 +123,5 @@ private void PopulateModel(SsaModel model) Url = reg.GetSsaEndpoint, }; } - - private static void SetDefaults(SsaModel model, SoftwareProduct sp) - { - if (string.IsNullOrEmpty(model.BrandId)) - { - model.BrandId = sp.BrandId; - } - - if (string.IsNullOrEmpty(model.SoftwareProductId)) - { - model.SoftwareProductId = sp.SoftwareProductId; - } - - if (string.IsNullOrEmpty(model.Version)) - { - model.Version = "3"; - } - - if (string.IsNullOrEmpty(model.Messages)) - { - model.Messages = "Waiting..."; - } - } } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs b/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs index 2467ac9..14ea9bf 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs @@ -23,8 +23,8 @@ public UtilitiesController( IConfiguration config, ICacheManager cacheManager) { - _config = config; - _cacheManager = cacheManager; + this._config = config; + this._cacheManager = cacheManager; } [HttpGet] @@ -33,8 +33,8 @@ public UtilitiesController( public async Task PrivateKeyJwt() { var model = new PrivateKeyJwtModel(); - await SetDefaults(model); - return View(model); + await this.SetDefaults(model); + return this.View(model); } [HttpPost] @@ -47,14 +47,14 @@ public IActionResult PrivateKeyJwt(PrivateKeyJwtModel model) model.ClientAssertion = privateKeyJwt.Generate(model.Issuer, model.Audience, model.Jti, model.ExpiryMinutes, model.Kid); model.ClientAssertionClaims = model.ClientAssertion.GetTokenClaims(); - return View(model); + return this.View(model); } private async Task SetDefaults(PrivateKeyJwtModel model) { - var sp = _config.GetSoftwareProductConfig(); - var reg = _config.GetRegisterConfig(); - var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); + var sp = this._config.GetSoftwareProductConfig(); + var reg = this._config.GetRegisterConfig(); + var tokenEndpoint = await this._cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri); model.Issuer = sp.SoftwareProductId; model.Audience = tokenEndpoint; diff --git a/Source/CDR.DataRecipient.Web/Extensions.cs b/Source/CDR.DataRecipient.Web/Extensions.cs index e4d1c5e..7f47289 100644 --- a/Source/CDR.DataRecipient.Web/Extensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions.cs @@ -1,7 +1,8 @@ -using CDR.DataRecipient.SDK.Models; +using System; +using System.Globalization; +using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.Web.Common; using Microsoft.Extensions.Configuration; -using System; namespace CDR.DataRecipient.Web.Extensions { @@ -61,7 +62,7 @@ public static DateTime AttemptValidateCdrArrangementJwtFromDate(this IConfigurat return new DateTime(2022, 11, 15, 0, 0, 0, DateTimeKind.Utc); } - return DateTime.Parse(obligationDate); + return DateTime.Parse(obligationDate, CultureInfo.InvariantCulture); } } } diff --git a/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs index 7f66263..dee788f 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs @@ -1,6 +1,6 @@ -using Microsoft.Extensions.Caching.Distributed; -using System; +using System; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; namespace CDR.DataRecipient.Web.Extensions { diff --git a/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs index 125d00b..cc27d2a 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs @@ -1,7 +1,7 @@ -using CDR.DataRecipient.Exceptions; -using CDR.DataRecipient.Web.Common; -using System.Linq; +using System.Linq; using System.Security.Claims; +using CDR.DataRecipient.Exceptions; +using CDR.DataRecipient.Web.Common; using static CDR.DataRecipient.Web.Common.Constants; namespace CDR.DataRecipient.Web.Extensions diff --git a/Source/CDR.DataRecipient.Web/Extensions/ConfigExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/ConfigExtensions.cs index f1dd32f..d050299 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/ConfigExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/ConfigExtensions.cs @@ -13,7 +13,7 @@ public static bool IsOidcConfigured(this IConfiguration config) /// /// Turns off server certificate validation when connecting to remote endpoints. /// - /// IConfiguration + /// IConfiguration. /// Value from "AcceptAnyServerCertificate" configuration setting. Defaults to false. public static bool IsAcceptingAnyServerCertificate(this IConfiguration config) { @@ -23,7 +23,7 @@ public static bool IsAcceptingAnyServerCertificate(this IConfiguration config) /// /// When set to true will only connect to remote endpoints using https. /// - /// IConfiguration + /// IConfiguration. /// Value from "EnforceHttpsEndpoints" configuration setting. Defaults to true. public static bool IsEnforcingHttpsEndpoints(this IConfiguration config) { diff --git a/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs index 3d2f330..5fe04cf 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs @@ -5,8 +5,8 @@ public static class KeyExtensions /// /// Apply formatting to the provided private key. /// - /// Raw private key - /// string + /// Raw private key. + /// string. public static string FormatPrivateKey(this string privateKey) { return privateKey diff --git a/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs index 249dcd3..44119e7 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs @@ -1,6 +1,6 @@ -using CDR.DataRecipient.SDK.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using CDR.DataRecipient.SDK.Models; namespace CDR.DataRecipient.Web.Extensions { diff --git a/Source/CDR.DataRecipient.Web/Features/Enums.cs b/Source/CDR.DataRecipient.Web/Features/Feature.cs similarity index 100% rename from Source/CDR.DataRecipient.Web/Features/Enums.cs rename to Source/CDR.DataRecipient.Web/Features/Feature.cs diff --git a/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs b/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs index c742672..60552c6 100644 --- a/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs +++ b/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Mvc.Filters; +using System; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using Serilog.Context; -using System; namespace CDR.DataRecipient.Web.Filters { @@ -12,7 +12,7 @@ public class LogActionEntryAttribute : ActionFilterAttribute public LogActionEntryAttribute(ILogger logger) { - _logger = logger; + this._logger = logger; } public override void OnActionExecuting(ActionExecutingContext context) @@ -21,7 +21,7 @@ public override void OnActionExecuting(ActionExecutingContext context) var action = context.RouteData.Values["action"].ToString(); using (LogContext.PushProperty("MethodName", action)) { - _logger.LogInformation("Request received to {Controller}.{Action}", controller, action); + this._logger.LogInformation("Request received to {Controller}.{Action}", controller, action); } base.OnActionExecuting(context); diff --git a/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs b/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs index ede1ac0..08da8de 100644 --- a/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs +++ b/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs @@ -1,8 +1,8 @@ -using CDR.DataRecipient.SDK; +using System; +using CDR.DataRecipient.SDK; using CDR.DataRecipient.Web.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using System; namespace CDR.DataRecipient.Web.Filters { @@ -13,12 +13,12 @@ public class MustConsumeAttribute : ActionFilterAttribute public MustConsumeAttribute(string contentType) { - _contentType = contentType; + this._contentType = contentType; } public override void OnActionExecuting(ActionExecutingContext context) { - if (context.HttpContext.Request.ContentType != _contentType) + if (context.HttpContext.Request.ContentType != this._contentType) { context.Result = new BadRequestObjectResult(new ErrorListModel(Constants.ErrorCodes.InvalidHeader, Constants.ErrorTitles.InvalidHeader, string.Empty)); } diff --git a/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs b/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs index 813f968..65c1eea 100644 --- a/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs +++ b/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs @@ -1,4 +1,8 @@ -using CDR.DataRecipient.SDK.Extensions; +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; @@ -6,10 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Middleware { @@ -32,16 +32,16 @@ public ClientAuthorizationMiddleware( IDataHolderDiscoveryCache dataHolderDiscoveryCache, ILogger logger) { - _next = next; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _logger = logger; - _softwareProduct = config.GetSoftwareProductConfig(); + this._next = next; + this._dataHolderDiscoveryCache = dataHolderDiscoveryCache; + this._logger = logger; + this._softwareProduct = config.GetSoftwareProductConfig(); } public async Task Invoke(HttpContext context) { // Check if the path required client authentication. - if (_validPaths.Contains(context.Request.Path.Value)) + if (this._validPaths.Contains(context.Request.Path.Value)) { var authorisationHeader = context.Request.Headers.Authorization.FirstOrDefault(); string token = null; @@ -51,11 +51,11 @@ public async Task Invoke(HttpContext context) token = authorisationData.Length > 0 ? authorisationData[^1] : null; } - _logger.LogDebug("Validating authorization token: {Token}", token); + this._logger.LogDebug("Validating authorization token: {Token}", token); if (token != null) { - var claimsPrincipal = await ValidateTokenAsync(token); + var claimsPrincipal = await this.ValidateTokenAsync(token); if (claimsPrincipal != null) { context.Items[ClientAuthorizeAttribute.ClaimsPrincipalKey] = claimsPrincipal; @@ -63,7 +63,7 @@ public async Task Invoke(HttpContext context) } } - await _next(context); + await this._next(context); } public async Task ValidateTokenAsync(string token) @@ -71,41 +71,41 @@ public async Task ValidateTokenAsync(string token) try { var tokenJwt = token.GetJwt(); - _logger.LogDebug("tokenJwt: {TokenJwt}", tokenJwt); + this._logger.LogDebug("tokenJwt: {TokenJwt}", tokenJwt); // sub and the iss should be the same if (tokenJwt == null || tokenJwt.Issuer != tokenJwt.Subject) { - _logger.LogError("Error in {MethodName}: iss and sub should be the same value.", nameof(ValidateTokenAsync)); + this._logger.LogError("Error in {MethodName}: iss and sub should be the same value.", nameof(this.ValidateTokenAsync)); return null; } // Get the data holder details - var dataholderDiscoveryDocument = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(tokenJwt.Issuer); + var dataholderDiscoveryDocument = await this._dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(tokenJwt.Issuer); if (dataholderDiscoveryDocument == null) { // There is no valid brand id in our DB for this issuer. - _logger.LogError("Error in {MethodName}: could not find data holder discovery document.", nameof(ValidateTokenAsync)); + this._logger.LogError("Error in {MethodName}: could not find data holder discovery document.", nameof(this.ValidateTokenAsync)); return null; } - _logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); + this._logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); // Validate the token var validated = await token.ValidateToken( dataholderDiscoveryDocument.JwksUri, - _logger, + this._logger, tokenJwt.Issuer, - new[] { _softwareProduct.RevocationUri, _softwareProduct.RecipientBaseUri }, + new[] { this._softwareProduct.RevocationUri, this._softwareProduct.RecipientBaseUri }, validateLifetime: false); - _logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); + this._logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); return validated.ClaimsPrincipal; } catch (Exception ex) { - _logger.LogError(ex, "Client Authorisation Bearer token validation failed."); + this._logger.LogError(ex, "Client Authorisation Bearer token validation failed."); return null; } } diff --git a/Source/CDR.DataRecipient.Web/Models/BaseModel.cs b/Source/CDR.DataRecipient.Web/Models/BaseModel.cs index 0afad88..ec51985 100644 --- a/Source/CDR.DataRecipient.Web/Models/BaseModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/BaseModel.cs @@ -1,18 +1,20 @@ using System.Net; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { public abstract class BaseModel { + protected BaseModel() + { + this.ErrorList = new SDK.Models.ErrorList(); + } + + [JsonProperty(Required = Required.Always)] public HttpStatusCode StatusCode { get; set; } public string Messages { get; set; } public SDK.Models.ErrorList ErrorList { get; set; } - - protected BaseModel() - { - this.ErrorList = new SDK.Models.ErrorList(); - } } } diff --git a/Source/CDR.DataRecipient.Web/Models/ConsentCallbackGetModel.cs b/Source/CDR.DataRecipient.Web/Models/ConsentCallbackGetModel.cs new file mode 100644 index 0000000..653f380 --- /dev/null +++ b/Source/CDR.DataRecipient.Web/Models/ConsentCallbackGetModel.cs @@ -0,0 +1,11 @@ +using CDR.DataRecipient.SDK.Models; +using Microsoft.AspNetCore.Mvc; + +namespace CDR.DataRecipient.Web.Models +{ + public class ConsentCallbackGetModel + { + [FromQuery(Name = "response")] + public string Response { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.Web/Models/ConsentCallbackPostModel.cs b/Source/CDR.DataRecipient.Web/Models/ConsentCallbackPostModel.cs new file mode 100644 index 0000000..2c5f396 --- /dev/null +++ b/Source/CDR.DataRecipient.Web/Models/ConsentCallbackPostModel.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; + +namespace CDR.DataRecipient.Web.Models +{ + public class ConsentCallbackPostModel + { + [FromForm(Name = "code")] + public string Code { get; set; } + + [FromForm(Name = "state")] + public string State { get; set; } + + [FromForm(Name = "response")] + public string Response { get; set; } + + [FromForm(Name = "error")] + public string Error { get; set; } + + [FromForm(Name = "error_description")] + public string ErrorDescription { get; set; } + + [FromForm(Name = "error_code")] + public string ErrorCode { get; set; } + } +} diff --git a/Source/CDR.DataRecipient.Web/Models/DataHoldersModel.cs b/Source/CDR.DataRecipient.Web/Models/DataHoldersModel.cs index 8a72b8a..eb4b939 100644 --- a/Source/CDR.DataRecipient.Web/Models/DataHoldersModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/DataHoldersModel.cs @@ -1,12 +1,18 @@ -using CDR.DataRecipient.SDK.Enumerations; -using CDR.DataRecipient.SDK.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.DataRecipient.SDK.Enumerations; +using CDR.DataRecipient.SDK.Models; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { public class DataHoldersModel { + public DataHoldersModel() + { + this.DataHolders = new List(); + } + public IEnumerable DataHolders { get; set; } [Display(Name = "Version")] @@ -16,11 +22,7 @@ public class DataHoldersModel public string Messages { get; set; } + [JsonProperty(Required = Required.Always)] public Industry Industry { get; set; } - - public DataHoldersModel() - { - this.DataHolders = new List(); - } } } diff --git a/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs b/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs index 23a1999..136b3a0 100644 --- a/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs @@ -3,11 +3,18 @@ using CDR.DataRecipient.SDK.Enumerations; using CDR.DataRecipient.SDK.Models; using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { public class DynamicClientRegistrationModel : BaseModel { + public DynamicClientRegistrationModel() + { + this.Registrations = new List(); + this.TransactionType = !string.IsNullOrEmpty(this.ClientId) ? "Update" : "Create"; + } + public IEnumerable Registrations { get; set; } public IEnumerable FailedDCRMessages { get; set; } @@ -17,6 +24,7 @@ public class DynamicClientRegistrationModel : BaseModel public string ResponsePayload { get; set; } + [JsonProperty(Required = Required.Always)] public Industry Industry { get; set; } [Display(Name = "Client ID", Prompt = "Only populate for update operations")] @@ -71,11 +79,5 @@ public class DynamicClientRegistrationModel : BaseModel public List DataRecipients { get; set; } public string TransactionType { get; set; } - - public DynamicClientRegistrationModel() - { - this.Registrations = new List(); - TransactionType = !string.IsNullOrEmpty(ClientId) ? "Update" : "Create"; - } } } diff --git a/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs b/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs index db16ead..adec906 100644 --- a/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs @@ -29,7 +29,7 @@ public ErrorListModel(string errorCode, string errorTitle, string errorDetail = public bool HasErrors() { - return Errors != null && Errors.Count > 0; + return this.Errors != null && this.Errors.Count > 0; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs b/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs index 9b13122..83c71d3 100644 --- a/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs @@ -6,16 +6,16 @@ public class ErrorModel { public ErrorModel() { - Meta = null; - Detail = string.Empty; + this.Meta = null; + this.Detail = string.Empty; } public ErrorModel(string code, string title, string description) : this() { - Code = code; - Title = title; - Detail = description; + this.Code = code; + this.Title = title; + this.Detail = description; } [Required] diff --git a/Source/CDR.DataRecipient.Web/Models/ErrorViewModel.cs b/Source/CDR.DataRecipient.Web/Models/ErrorViewModel.cs index 4a957cb..cd60ec2 100644 --- a/Source/CDR.DataRecipient.Web/Models/ErrorViewModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ErrorViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace CDR.DataRecipient.Web.Models { @@ -6,7 +6,7 @@ public class ErrorViewModel { public string RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + public bool ShowRequestId => !string.IsNullOrEmpty(this.RequestId); public string Name { get; set; } @@ -14,6 +14,6 @@ public class ErrorViewModel public string Message { get; set; } - public bool ShowMessage => !string.IsNullOrEmpty(Message); + public bool ShowMessage => !string.IsNullOrEmpty(this.Message); } } diff --git a/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs b/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs index 437e2c5..db16067 100644 --- a/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { @@ -21,10 +22,13 @@ public HttpRequestModel() public IDictionary FormParameters { get; set; } + [JsonProperty(Required = Required.Always)] public bool RequiresClientCertificate { get; set; } + [JsonProperty(Required = Required.Always)] public bool RequiresAccessToken { get; set; } + [JsonProperty(Required = Required.Always)] public bool SupportsVersion { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/ParModel.cs b/Source/CDR.DataRecipient.Web/Models/ParModel.cs index b56494d..e713f31 100644 --- a/Source/CDR.DataRecipient.Web/Models/ParModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ParModel.cs @@ -3,6 +3,7 @@ using CDR.DataRecipient.Models; using CDR.DataRecipient.SDK.Models; using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { @@ -23,7 +24,7 @@ public string ClientId { get { - return Registration.SplitRegistrationId(RegistrationId).ClientId; + return Registration.SplitRegistrationId(this.RegistrationId).ClientId; } } @@ -31,7 +32,7 @@ public string DataHolderBrandId { get { - return Registration.SplitRegistrationId(RegistrationId).DataHolderBrandId; + return Registration.SplitRegistrationId(this.RegistrationId).DataHolderBrandId; } } @@ -46,6 +47,7 @@ public string DataHolderBrandId public string Scope { get; set; } [Display(Name = "Use PKCE")] + [JsonProperty(Required = Required.Always)] public bool UsePkce { get; set; } [Display(Name = "Response Type")] diff --git a/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs b/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs index 1fa02c0..c5d4cf5 100644 --- a/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Security.Claims; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { @@ -24,6 +25,7 @@ public class PrivateKeyJwtModel : BaseModel [Display(Name = "Expiry Minutes")] [Required] + [JsonProperty(Required = Required.Always)] public int ExpiryMinutes { get; set; } [Display(Name = "jti")] diff --git a/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs b/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs index 786433b..fe90878 100644 --- a/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs @@ -5,13 +5,13 @@ namespace CDR.DataRecipient.Web.Models { public class RegistrationsModel : BaseModel { - public IList Registrations { get; set; } - - public HttpRequestModel RegistrationRequest { get; set; } - public RegistrationsModel() { this.Registrations = new List(); } + + public IList Registrations { get; set; } + + public HttpRequestModel RegistrationRequest { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/SettingsModel.cs b/Source/CDR.DataRecipient.Web/Models/SettingsModel.cs index de5db6a..29251b3 100644 --- a/Source/CDR.DataRecipient.Web/Models/SettingsModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/SettingsModel.cs @@ -1,17 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; namespace CDR.DataRecipient.Web.Models { public class SettingsModel : BaseModel { - public IDictionary ConfigurationSettings { get; set; } - public SettingsModel() { this.ConfigurationSettings = new Dictionary(); } + + public IDictionary ConfigurationSettings { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/SsaModel.cs b/Source/CDR.DataRecipient.Web/Models/SsaModel.cs index 620347e..5cd9e7b 100644 --- a/Source/CDR.DataRecipient.Web/Models/SsaModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/SsaModel.cs @@ -1,5 +1,6 @@ -using CDR.DataRecipient.SDK.Enumerations; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using CDR.DataRecipient.SDK.Enumerations; +using Newtonsoft.Json; namespace CDR.DataRecipient.Web.Models { @@ -10,6 +11,7 @@ public class SsaModel : BaseModel public string SSA { get; set; } [Display(Name = "Industry")] + [JsonProperty(Required = Required.Always)] public Industry Industry { get; set; } [Display(Name = "Version")] diff --git a/Source/CDR.DataRecipient.Web/Program.cs b/Source/CDR.DataRecipient.Web/Program.cs index 8e75a45..8b037ca 100644 --- a/Source/CDR.DataRecipient.Web/Program.cs +++ b/Source/CDR.DataRecipient.Web/Program.cs @@ -1,13 +1,12 @@ -using CDR.DataRecipient.Web.Common; +using System; +using System.IO; +using System.Security.Authentication; +using CDR.DataRecipient.Web.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; using Serilog.Settings.Configuration; -using System; -using System.IO; -using System.Security.Authentication; namespace CDR.DataRecipient.Web { @@ -50,33 +49,6 @@ public static int Main(string[] args) } } - private static IConfigurationRoot BuildConfiguration(string[] args, IConfigurationBuilder builder = null) - { - var configurationCommandLine = new ConfigurationBuilder() - .AddCommandLine(args).Build(); - - builder ??= new ConfigurationBuilder(); - - builder.AddCommandLine(args) - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? configurationCommandLine.GetValue("environment")}.json", true) - .AddEnvironmentVariables(); - - var configuration = builder.Build(); - var secretVolume = configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.SecretVolumePath); - - // if the volume mount configured add this as well to the configuration to look for secrets. - if (Directory.Exists(secretVolume)) - { - var path = Path.Combine(Directory.GetCurrentDirectory(), secretVolume); - builder.AddKeyPerFile(path, optional: true, true); - configuration = builder.Build(); - } - - return configuration; - } - /// /// Configure Serilog logging. /// @@ -127,5 +99,32 @@ public static IHostBuilder CreateHostBuilder(string[] args, IConfiguration confi }); webBuilder.UseStartup(); }); + + private static IConfigurationRoot BuildConfiguration(string[] args, IConfigurationBuilder builder = null) + { + var configurationCommandLine = new ConfigurationBuilder() + .AddCommandLine(args).Build(); + + builder ??= new ConfigurationBuilder(); + + builder.AddCommandLine(args) + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? configurationCommandLine.GetValue("environment")}.json", true) + .AddEnvironmentVariables(); + + var configuration = builder.Build(); + var secretVolume = configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.SecretVolumePath); + + // if the volume mount configured add this as well to the configuration to look for secrets. + if (Directory.Exists(secretVolume)) + { + var path = Path.Combine(Directory.GetCurrentDirectory(), secretVolume); + builder.AddKeyPerFile(path, optional: true, true); + configuration = builder.Build(); + } + + return configuration; + } } } diff --git a/Source/CDR.DataRecipient.Web/Startup.cs b/Source/CDR.DataRecipient.Web/Startup.cs index bd4fc64..dba78cc 100644 --- a/Source/CDR.DataRecipient.Web/Startup.cs +++ b/Source/CDR.DataRecipient.Web/Startup.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Security.Claims; using System.Threading.Tasks; using CDR.DataRecipient.API.Logger; using CDR.DataRecipient.Infrastructure; using CDR.DataRecipient.Repository; using CDR.DataRecipient.Repository.SQL; +using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.SDK.Services.DataHolder; using CDR.DataRecipient.SDK.Services.Register; @@ -47,7 +49,7 @@ public class Startup public Startup(IConfiguration configuration) { - _configuration = configuration; + this._configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. @@ -55,8 +57,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddScoped(); - services.AddDbContext(options => options.UseSqlServer(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Default))); - var dbContext = services.BuildServiceProvider().GetService(); + services.AddDbContext(options => options.UseSqlServer(this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Default))); services.AddControllersWithViews().AddRazorRuntimeCompilation() .AddJsonOptions(options => @@ -66,18 +67,18 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(x => new ServiceConfiguration() { - AcceptAnyServerCertificate = _configuration.IsAcceptingAnyServerCertificate(), - EnforceHttpsEndpoints = _configuration.IsEnforcingHttpsEndpoints(), + AcceptAnyServerCertificate = this._configuration.IsAcceptingAnyServerCertificate(), + EnforceHttpsEndpoints = this._configuration.IsEnforcingHttpsEndpoints(), }); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddSingleton(x => new SqlDataAccess(_configuration, dbContext)); - services.AddSingleton(x => new SqlDataHoldersRepository(_configuration, dbContext)); - services.AddSingleton(x => new SqlConsentsRepository(_configuration, dbContext)); - services.AddSingleton(x => new SqlRegistrationsRepository(_configuration, dbContext)); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddSingleton(); services.AddSingleton(); @@ -85,18 +86,18 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); // if the distributed cache connection string has been set then use it, otherwise fall back to in-memory caching. - if (UseDistributedCache()) + if (this.UseDistributedCache()) { services.AddStackExchangeRedisCache(options => { - options.Configuration = _configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache); + options.Configuration = this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache); options.InstanceName = "datarecipient-cache-"; }); services.AddDataProtection() .SetApplicationName("mdh-idsvr") .PersistKeysToStackExchangeRedis( - StackExchange.Redis.ConnectionMultiplexer.Connect(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache)), + StackExchange.Redis.ConnectionMultiplexer.Connect(this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache)), "datarecipient-cache-dp-keys"); } else @@ -105,11 +106,11 @@ public void ConfigureServices(IServiceCollection services) services.AddDistributedMemoryCache(); } - string specificOrigin = _configuration.GetValue(Constants.ConfigurationKeys.AllowSpecificOrigins); + string specificOrigin = this._configuration.GetValue(Constants.ConfigurationKeys.AllowSpecificOrigins); services.AddCors(options => { options.AddPolicy( - _allowSpecificOrigins, + this._allowSpecificOrigins, builder => { builder.WithOrigins(specificOrigin) @@ -120,9 +121,9 @@ public void ConfigureServices(IServiceCollection services) }); }); - string connStr = _configuration.GetConnectionString(DbConstants.ConnectionStringNames.Logging); + string connStr = this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Logging); services.AddHealthChecks() - .AddCheck("migration", () => healthCheckMigration ? HealthCheckResult.Healthy(healthCheckMigrationMessage) : HealthCheckResult.Unhealthy(healthCheckMigrationMessage)) + .AddCheck("migration", () => this.healthCheckMigration ? HealthCheckResult.Healthy(this.healthCheckMigrationMessage) : HealthCheckResult.Unhealthy(this.healthCheckMigrationMessage)) .AddCheck("sql-connection", () => { using (var db = new SqlConnection(connStr)) @@ -140,15 +141,15 @@ public void ConfigureServices(IServiceCollection services) return HealthCheckResult.Healthy(); }); - var issuer = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.Issuer); + var issuer = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.Issuer); if (!string.IsNullOrEmpty(issuer)) { - var clientId = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientId); - var clientSecret = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientSecret); - var callbackPath = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.CallbackPath); - var responseType = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ResponseType); - var responseMode = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ResponseMode, "form_post"); - var scopes = _configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.Scope); + var clientId = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientId); + var clientSecret = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ClientSecret); + var callbackPath = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.CallbackPath); + var responseType = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ResponseType); + var responseMode = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.ResponseMode, "form_post"); + var scopes = this._configuration.GetValue(Constants.ConfigurationKeys.OidcAuthentication.Scope); services.AddAuthentication(options => { @@ -242,19 +243,37 @@ public void ConfigureServices(IServiceCollection services) services.AddFeatureManagement(); - if (_configuration.GetSection("SerilogRequestResponseLogger") != null) + if (this._configuration.GetSection("SerilogRequestResponseLogger") != null) { Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); } services.AddHttpClient(); - } - private bool UseDistributedCache() - { - var cacheConnectionString = _configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache); - return !string.IsNullOrEmpty(cacheConnectionString); + // Different HttpClient is used based on private or public endpoint + services.AddHttpClient("PublicDataHolderClient", client => { }) + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = this._configuration.IsAcceptingAnyServerCertificate() + ? HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + : null, + }); + + // Different HttpClient is used based on private or public endpoint + services.AddHttpClient("PrivateDataHolderClient", client => { }) + .ConfigurePrimaryHttpMessageHandler(() => + { + var handler = new HttpClientHandler(); + if (this._configuration.IsAcceptingAnyServerCertificate()) + { + handler.SetServerCertificateValidation(this._configuration.IsAcceptingAnyServerCertificate()); + } + + // Provide the data recipient's client certificate for a non-public endpoint. + handler.ClientCertificates.Add(this._configuration.GetSoftwareProductConfig().ClientCertificate.X509Certificate); + return handler; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -271,7 +290,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } // If an external IdP is not configured, then create a dummy mdr user. - if (!_configuration.IsOidcConfigured()) + if (!this._configuration.IsOidcConfigured()) { app.Use(async (ctx, next) => { @@ -292,12 +311,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { context.Response.Headers.Append( "Content-Security-Policy", - _configuration.GetValue(Constants.ConfigurationKeys.ContentSecurityPolicy, "default-src 'self', 'https://cdn.jsdelivr.net/';")); + this._configuration.GetValue(Constants.ConfigurationKeys.ContentSecurityPolicy, "default-src 'self', 'https://cdn.jsdelivr.net/';")); await next(); }); // Set the request host name. - var hostname = _configuration.GetValue(Constants.ConfigurationKeys.MockDataRecipient.Hostname); + var hostname = this._configuration.GetValue(Constants.ConfigurationKeys.MockDataRecipient.Hostname); if (!string.IsNullOrEmpty(hostname)) { app.Use((context, next) => @@ -312,7 +331,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); - app.UseCors(_allowSpecificOrigins); + app.UseCors(this._allowSpecificOrigins); app.UseAuthentication(); app.UseAuthorization(); app.UseSession(); @@ -365,39 +384,29 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) // Migrate the database to the latest version during application startup. using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) { - if (RunMigrations()) + if (this.RunMigrations()) { - healthCheckMigrationMessage = "Migration in progress"; + this.healthCheckMigrationMessage = "Migration in progress"; var optionsBuilder = new DbContextOptionsBuilder(); // Use DBO connection string since it has DBO rights needed to update db schema - optionsBuilder.UseSqlServer(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Migrations) + optionsBuilder.UseSqlServer(this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Migrations) ?? throw new InvalidOperationException($"Connection string '{DbConstants.ConnectionStringNames.Migrations}' not found")); using var dbContext = new RecipientDatabaseContext(optionsBuilder.Options); dbContext.Database.Migrate(); - healthCheckMigrationMessage = "Migration completed"; + this.healthCheckMigrationMessage = "Migration completed"; } - healthCheckMigration = true; + this.healthCheckMigration = true; // Reconfigure Serilog with DB - Program.ConfigureSerilog(_configuration, true); + Program.ConfigureSerilog(this._configuration, true); } } - /// - /// Determine if EF Migrations should run. - /// - private bool RunMigrations() - { - // Run migrations if the DBO connection string is set. - var dbo = _configuration.GetConnectionString(DbConstants.ConnectionStringNames.Migrations); - return !string.IsNullOrEmpty(dbo); - } - private static Task CustomResponseWriter(HttpContext context, HealthReport healthReport) { context.Response.ContentType = "application/json"; @@ -412,5 +421,21 @@ private static Task CustomResponseWriter(HttpContext context, HealthReport healt }); return context.Response.WriteAsync(result); } + + private bool UseDistributedCache() + { + var cacheConnectionString = this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Cache); + return !string.IsNullOrEmpty(cacheConnectionString); + } + + /// + /// Determine if EF Migrations should run. + /// + private bool RunMigrations() + { + // Run migrations if the DBO connection string is set. + var dbo = this._configuration.GetConnectionString(DbConstants.ConnectionStringNames.Migrations); + return !string.IsNullOrEmpty(dbo); + } } } diff --git a/Source/CDR.DiscoverDataHolders/CDR.DiscoverDataHolders.csproj b/Source/CDR.DiscoverDataHolders/CDR.DiscoverDataHolders.csproj index 5f535c5..fbc1622 100644 --- a/Source/CDR.DiscoverDataHolders/CDR.DiscoverDataHolders.csproj +++ b/Source/CDR.DiscoverDataHolders/CDR.DiscoverDataHolders.csproj @@ -16,7 +16,7 @@ - + diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index e5eca4a..dcff766 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -1,9 +1,10 @@ net8.0 - 3.0.0 + 3.0.1 true true true - + NU1901;NU1902 + \ No newline at end of file diff --git a/Source/DockerCompose/docker-compose.TestsBase.yml b/Source/DockerCompose/docker-compose.TestsBase.yml index 89ab44d..8286d4e 100644 --- a/Source/DockerCompose/docker-compose.TestsBase.yml +++ b/Source/DockerCompose/docker-compose.TestsBase.yml @@ -94,7 +94,7 @@ services: - "1433:1433" environment: - ACCEPT_EULA=Y - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 5s diff --git a/Source/DockerCompose/docker-compose.UnitTests.yml b/Source/DockerCompose/docker-compose.UnitTests.yml index a847f8b..b794518 100644 --- a/Source/DockerCompose/docker-compose.UnitTests.yml +++ b/Source/DockerCompose/docker-compose.UnitTests.yml @@ -45,7 +45,7 @@ services: image: 'mcr.microsoft.com/mssql/server:2022-latest' environment: - ACCEPT_EULA=Y - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 5s diff --git a/Source/DockerCompose/docker-compose.yml b/Source/DockerCompose/docker-compose.yml index 7d87aca..e0dd7fa 100644 --- a/Source/DockerCompose/docker-compose.yml +++ b/Source/DockerCompose/docker-compose.yml @@ -97,7 +97,7 @@ services: - ACCEPT_EULA=${ACCEPT_MSSQL_EULA:?This docker compose file utilises the Microsoft SQL Server Image from Docker Hub. The Microsoft EULA for the Microsoft SQL Server Image must be accepted to continue. Replace this unset ACCEPT_MSSQL_EULA variable with a Y if you accept the EULA. eg ACCEPT_EULA=Y. See the Microsoft SQL Server Image on Docker Hub for more information.} - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 10s diff --git a/Source/docker-compose.yml b/Source/docker-compose.yml index ebc04d3..a6be6e7 100644 --- a/Source/docker-compose.yml +++ b/Source/docker-compose.yml @@ -30,7 +30,7 @@ services: - '1433:1433' environment: - ACCEPT_EULA=Y - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 5s