From e40c2e0f090c186abe4dc73b37725be7f177045b Mon Sep 17 00:00:00 2001 From: CDR-AidenJ Date: Tue, 1 Apr 2025 15:46:27 +1100 Subject: [PATCH] Releases/3.0.0 (#83) Co-authored-by: CDR Open Source Co-authored-by: AidenJahani Co-authored-by: CDR-FarooqK --- .github/workflows/codeql-analysis.yml | 8 +- .github/workflows/dotnet.yml | 16 +- .github/workflows/test-report.yml | 6 +- CHANGELOG.md | 18 + README.md | 4 +- SECURITY.md | 3 +- Source/.editorconfig | 266 ++++++++ Source/CDR.DCR/DCR.cs | 25 +- .../CDR.DCR/Extensions/RequestExtensions.cs | 48 +- Source/CDR.DCR/Models/DcrRequest.cs | 2 +- .../CDR.DataRecipient.API.Logger.csproj | 11 +- .../IRequestResponseLogger.cs | 2 +- .../LoggerExtensions.cs | 2 +- .../RequestResponseLogger.cs | 45 +- .../RequestResponseLoggingMiddleware.cs | 38 +- Source/CDR.DataRecipient.E2ETests/BaseTest.cs | 26 +- .../Infrastructure/AccessToken.cs | 2 +- .../Pages/ConsentAndAuthorisationPages.cs | 4 +- .../Pages/DynamicClientRegistrationPage.cs | 32 +- .../Pages/ParPage.cs | 8 +- .../US23863_MDR_E2ETests.cs | 96 +-- .../US46331_MDR_E2ETests_ParFapi.cs | 19 +- .../US46352_MDR_E2ETests_AuthCodeFlowJarm.cs | 30 +- .../API2/AuthenticationCodeJwt.cs | 10 +- .../Infrastructure/API2/ClientAssertion.cs | 8 +- .../Infrastructure/AccessToken.cs | 6 +- .../Infrastructure/JWT.cs | 4 +- .../Infrastructure/PrivateKeyJwt.cs | 46 +- .../Infrastructure/PrivateKeyJwt2.cs | 4 +- ...12693_MDR_HostArrangementRevocation-JWT.cs | 2 +- .../US12964_MDR_Jwks.cs | 46 +- .../CDR.DataRecipient.Repository.SQL.csproj | 9 + .../DbConstants.cs | 2 +- .../Entities/CdrArrangement.cs | 4 +- .../Entities/DataHolderBrand.cs | 4 +- .../Entities/DcrMessage.cs | 2 +- .../Entities/LogEventsDcrService.cs | 2 +- .../Entities/Registration.cs | 4 +- .../Entities/SoftwareProduct.cs | 2 +- .../Extensions/SqlExtensions.cs | 12 +- .../ISqlDataAccess.cs | 8 +- .../RecipientDatabaseContext.cs | 11 +- ...cipientDatabaseContextDesignTimeFactory.cs | 4 +- .../Migrations/20211216230342_InitSqlDb.cs | 8 +- .../Migrations/20220315025849_DCR_Message.cs | 4 +- .../SqlConsentsRepository.cs | 46 +- .../SqlDataAccess.cs | 574 +++++++++--------- .../SqlDataHoldersRepository.cs | 24 +- .../SqlRegistrationsRepository.cs | 42 +- .../CDR.DataRecipient.SDK.csproj | 9 + Source/CDR.DataRecipient.SDK/Constants.cs | 2 +- Source/CDR.DataRecipient.SDK/Enumerations.cs | 2 +- .../Exceptions/NoHttpsException.cs | 5 +- .../Extensions/HttpExtensions.cs | 3 +- .../Extensions/StringExtensions.cs | 14 +- .../Extensions/TokenExtensions.cs | 15 +- .../Extensions/UrlExtensions.cs | 2 +- Source/CDR.DataRecipient.SDK/MessageEnum.cs | 4 +- .../Models/AccessToken.cs | 32 +- .../Models/AuthDetails.cs | 1 + .../Models/AuthorisationRequestClaims.cs | 4 +- .../Models/AuthorisationRequestJwt.cs | 16 +- .../Models/Certificate.cs | 5 +- .../Models/DataHolderBrand.cs | 8 +- .../Models/DataHolderEndpoints.cs | 7 +- .../Models/DataRecipientModel.cs | 18 + .../Models/DataRecipientViewModel.cs | 7 +- .../Models/DcrMessage.cs | 10 +- .../Models/DcrResponse.cs | 8 +- .../Models/EndpointDetail.cs | 5 + Source/CDR.DataRecipient.SDK/Models/Error.cs | 9 +- .../Models/Introspection.cs | 9 +- .../Models/JsonWebKey.cs | 6 + .../Models/LegalEntity.cs | 1 + .../Models/OidcDiscovery.cs | 359 +++++------ Source/CDR.DataRecipient.SDK/Models/Pkce.cs | 2 + .../CDR.DataRecipient.SDK/Models/Register.cs | 6 + .../Models/Registration.cs | 136 ++--- .../CDR.DataRecipient.SDK/Models/Response.cs | 6 +- .../Models/ServiceConfiguration.cs | 2 + .../Models/SoftwareProduct.cs | 4 +- .../CDR.DataRecipient.SDK/Models/UserInfo.cs | 10 +- .../Services/BaseService.cs | 5 - .../DynamicClientRegistrationService.cs | 28 +- .../Services/DataHolder/IInfosecService.cs | 6 +- .../Services/DataHolder/InfosecService.cs | 40 +- .../Services/PrivateKeyJwt.cs | 15 +- .../Services/Register/IInfosecService.cs | 1 - .../Services/Register/IMetadataService.cs | 4 +- .../Services/Register/InfosecService.cs | 9 +- .../Services/Register/MetadataService.cs | 21 +- .../Services/Register/SsaService.cs | 10 +- .../Services/Tokens/AccessTokenService.cs | 21 +- .../CDR.DataRecipient.Web.csproj | 11 +- .../Caching/CacheManager.cs | 8 +- .../CDR.DataRecipient.Web/Common/Constants.cs | 17 +- .../Common/DataHolderDiscoveryCache.cs | 36 +- .../Common/IDataHolderDiscoveryCache.cs | 3 +- .../Common/IOidcSettingsProvider.cs | 7 + .../Common/OidcSettingsProvider.cs | 33 + .../Controllers/ApiController.cs | 8 +- .../Controllers/ConsentController.cs | 153 ++--- .../Controllers/DataHoldersController.cs | 24 +- .../DataSharingBankingController.cs | 15 +- .../DataSharingCommonController.cs | 24 +- .../Controllers/DataSharingControllerBase.cs | 46 +- .../DataSharingEnergyController.cs | 9 +- .../DynamicClientRegistrationController.cs | 354 +++++++++-- .../Controllers/HealthController.cs | 2 +- .../Controllers/HomeController.cs | 10 +- .../Controllers/JwksController.cs | 12 +- .../Controllers/OidcController.cs | 20 +- .../Controllers/ParController.cs | 65 +- .../Controllers/RevocationController.cs | 14 +- .../Controllers/SettingsController.cs | 3 +- .../Controllers/SsaController.cs | 20 +- .../Controllers/UtilitiesController.cs | 40 +- Source/CDR.DataRecipient.Web/Extensions.cs | 4 +- .../Extensions/CacheExtensions.cs | 6 +- .../Extensions/ClaimsPrincipalExtensions.cs | 4 +- .../Extensions/CustomException.cs | 21 + .../Extensions/ExceptionExtensions.cs | 25 - .../Extensions/KeyExtensions.cs | 8 +- .../Extensions/LinqExtensions.cs | 2 +- .../Extensions/SerializationExtensions.cs | 6 +- .../CDR.DataRecipient.Web/Features/Enums.cs | 2 +- .../Filters/ClientAuthorizeAttribute.cs | 31 +- .../Filters/LogActionEntryAttribute.cs | 30 +- .../Filters/MustConsumeAttribute.cs | 34 +- .../ClientAuthorizationMiddleware.cs | 168 ++--- .../Models/AuthorisationState.cs | 12 +- .../CDR.DataRecipient.Web/Models/BaseModel.cs | 2 + .../Models/DataSharingModel.cs | 2 + .../Models/DynamicClientRegistrationModel.cs | 15 +- .../Models/ErrorListModel.cs | 44 +- .../Models/ErrorModel.cs | 12 +- .../Models/HttpRequestModel.cs | 7 + .../Models/HttpResponseModel.cs | 2 + .../Models/IdTokenModel.cs | 18 - .../CDR.DataRecipient.Web/Models/ParModel.cs | 12 +- .../Models/PrivateKeyJwtModel.cs | 2 +- .../Models/RegistrationsModel.cs | 3 +- .../Models/RevocationModel.cs | 14 +- Source/CDR.DataRecipient.Web/Program.cs | 85 ++- Source/CDR.DataRecipient.Web/Startup.cs | 96 +-- .../DynamicClientRegistration/Index.cshtml | 355 ++++++----- .../Views/Par/Index.cshtml | 12 +- .../Views/Shared/_Layout.cshtml | 5 - .../Views/Utilities/IdToken.cshtml | 75 --- .../appsettings.Development.json | 60 +- .../appsettings.Release.json | 60 +- Source/CDR.DataRecipient.Web/appsettings.json | 68 ++- .../CDR.DataRecipient.csproj | 11 + .../Exceptions/MissingClaimException.cs | 14 +- .../Models/ConsentArrangement.cs | 13 +- .../Repository/IConsentsRepository.cs | 7 +- .../Repository/IDataHoldersRepository.cs | 7 +- .../Repository/IRegistrationsRepository.cs | 5 + .../DiscoverDataHolders.cs | 39 +- Source/DataRecipient.sln | 1 + Source/Directory.Build.props | 5 +- Source/Dockerfile | 4 + Source/Dockerfile.mdr.integration-tests | 2 + 163 files changed, 2740 insertions(+), 2090 deletions(-) create mode 100644 Source/.editorconfig create mode 100644 Source/CDR.DataRecipient.Web/Common/IOidcSettingsProvider.cs create mode 100644 Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs create mode 100644 Source/CDR.DataRecipient.Web/Extensions/CustomException.cs delete mode 100644 Source/CDR.DataRecipient.Web/Extensions/ExceptionExtensions.cs delete mode 100644 Source/CDR.DataRecipient.Web/Models/IdTokenModel.cs delete mode 100644 Source/CDR.DataRecipient.Web/Views/Utilities/IdToken.cshtml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index eb3bd9e..ae2d76e 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@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 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@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ 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@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index abb644a..1976cd5 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout Data Recipient - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: ./mock-data-recipient @@ -143,7 +143,7 @@ jobs: # Archive unit test results - name: Archive unit test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: unit-test-results @@ -151,7 +151,7 @@ jobs: # Archive integration test results - name: Archive integration test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: integration-test-results @@ -159,7 +159,7 @@ jobs: # Archive e2e test results - name: Archive e2e test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: e2e-test-results @@ -167,7 +167,7 @@ jobs: # Archive mock data recipient logs - name: Archive mock data recipient logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: integration-test-artifacts @@ -175,7 +175,7 @@ jobs: # Archive mock data holder logs - name: Archive mock data holder logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: integration-test-artifacts @@ -183,7 +183,7 @@ jobs: # Archive mock data holder energy logs - name: Archive mock data holder energy logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: integration-test-artifacts @@ -191,7 +191,7 @@ jobs: # Archive mock register logs - name: Archive mock register logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: always() with: name: integration-test-artifacts diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml index 3414539..ba43d14 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@v2 + - uses: actions/checkout@v4 - name: Publish Unit Test Report uses: dorny/test-reporter@v1 @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Publish Integration Test Report uses: dorny/test-reporter@v1 @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Publish e2e Test Report uses: dorny/test-reporter@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c00372d..f90af45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ 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.0] - 2025-03-19 +### Changed +- Updated NuGet packages +- Fixed multiple build warnings to improve code quality and maintainability +- Fixed issue where Data Recipient could not access TLS resource APIs + +### Added +- Added ability to update Dynamic Client Registrations from UI + +### Removed +- Removed all OIDC Hybrid Flow related code and functionality +- Removed ID Token Decryption from Utilities + +## [2.2.0] - 2024-02-21 +### Added +- Ability to load appsettings from the provided volume if configured +### Changed +- Update SQL Sink configuration for Serilog ## [2.1.0] - 2024-08-16 ### Changed diff --git a/README.md b/README.md index 6a4c525..de73ab4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Consumer Data Right Logo](./cdr-logo.png?raw=true) -[![Consumer Data Standards v1.31.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.31.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction) +[![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) [![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.31.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction); +* 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); * 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/SECURITY.md b/SECURITY.md index ef53b05..d49b8d6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,8 @@ Visit our [Responsible disclosure of security vulnerabilities policy](https://ww | Version | Supported | | ------- | ------------------ | -| 2.1.x | :white_check_mark: | +| 3.0.x | :white_check_mark: | +| 2.x.x | :x: | | 1.x.x | :x: | diff --git a/Source/.editorconfig b/Source/.editorconfig new file mode 100644 index 0000000..fac3dc8 --- /dev/null +++ b/Source/.editorconfig @@ -0,0 +1,266 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +# All files +[*] +indent_style = space +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +# Code files +[*.{cs,csx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_diagnostic.SA1309.severity = none +dotnet_diagnostic.SA1310.severity = none +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +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/DCR.cs b/Source/CDR.DCR/DCR.cs index cdeb070..978d893 100644 --- a/Source/CDR.DCR/DCR.cs +++ b/Source/CDR.DCR/DCR.cs @@ -1,4 +1,4 @@ -using Azure.Storage.Queues; +using Azure.Storage.Queues; using Azure.Storage.Queues.Models; using CDR.DataRecipient.Repository.SQL; using CDR.DataRecipient.SDK; @@ -79,7 +79,7 @@ public async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "S Response tokenResponse = await GetAccessToken(); if (tokenResponse.IsSuccessful) - { + { var ssa = await GetSoftwareStatementAssertion(tokenResponse.Data.AccessToken); if (ssa.IsSuccessful) { @@ -89,13 +89,13 @@ public async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "S { // NO - DOES the DcrMessage exist? var result = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).CheckDcrMessageExistByDHBrandId(myQueueItem.DataHolderBrandId); - if (!string.IsNullOrEmpty(result.msgId)) + if (!string.IsNullOrEmpty(result.MsgId)) { // YES - UPDATE EXISTING DcrMessage (with ADDED Queue MessageId, Failed STATE and ERROR) DcrMessage dcrMsg = new() { DataHolderBrandId = Guid.Empty, - MessageId = result.msgId, + MessageId = result.MsgId, MessageState = Message.DCRFailed.ToString(), MessageError = $"{msg} - does not exist in the repo" }; @@ -107,7 +107,7 @@ public async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "S else { dataHolderBrandName = dh.BrandName; - // YES - DOES a Registration already exist for the DataHolderBrandId in the local repo? + // YES - DOES a Registration already exist for the DataHolderBrandId in the local repo? If yes, then no need to do any registration string clientId = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).GetRegByDHBrandId(dh.DataHolderBrandId); if (string.IsNullOrEmpty(clientId)) { @@ -238,11 +238,6 @@ public async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "S await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, REGISTRATION FAILED{extraMsg}", "Exception", "DCR"); } } - else - { - // YES - log this result - await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg} - is trying to be REGISTERED, but is already REGISTERED to ClientId - {clientId}", "Error", "DCR"); - } } } else @@ -328,7 +323,7 @@ private async Task> GetSoftwareStatementAssertion(string access var endpoint = $"{_options.Register_Get_SSA_Endpoint}{_options.Brand_Id}/software-products/{_options.Software_Product_Id}/ssa"; // Setup the http client. - var client = GetHttpClient( accessToken, _options.Register_Get_SSA_XV); + var client = GetHttpClient(accessToken, _options.Register_Get_SSA_XV); _logger.LogInformation("Retrieving SSA from the Register: {ssaEndpoint}", endpoint); @@ -425,7 +420,7 @@ private async Task Register( private HttpClient GetHttpClient(string accessToken = null, string version = null) { var httpClient = _httpClientFactory.CreateClient(DcrConstants.DcrHttpClientName); - + // If an access token has been provided then add to the Authorization header of the client. if (!string.IsNullOrEmpty(accessToken)) httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -440,7 +435,7 @@ private HttpClient GetHttpClient(string accessToken = null, string version = nul /// /// Insert the Message into the Queue /// - private async Task AddDeadLetterQueMsgAsync( DcrQueueMessage myQueueItem, string qName, FunctionContext context) + private async Task AddDeadLetterQueMsgAsync(DcrQueueMessage myQueueItem, string qName, FunctionContext context) { QueueClientOptions options = new() { @@ -488,7 +483,7 @@ private static async Task GetQueueCountAsync(string qConnString, string qNa /// /// Update the Log table /// - private async Task InsertLog( string DataRecipient_DB_ConnectionString, string msg, string lvl, string methodName, Exception exMsg = null) + private async Task InsertLog(string DataRecipient_DB_ConnectionString, string msg, string lvl, string methodName, Exception exMsg = null) { _logger.LogInformation("{methodName} - {message}", methodName, msg); @@ -548,4 +543,4 @@ private async Task InsertLog( string DataRecipient_DB_ConnectionString, string m db.Close(); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DCR/Extensions/RequestExtensions.cs b/Source/CDR.DCR/Extensions/RequestExtensions.cs index 135b58c..69434a8 100644 --- a/Source/CDR.DCR/Extensions/RequestExtensions.cs +++ b/Source/CDR.DCR/Extensions/RequestExtensions.cs @@ -5,6 +5,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; +using static CDR.DataRecipient.SDK.Constants; namespace CDR.DCR.Extensions { @@ -14,7 +15,7 @@ public static (List, string) CreateClaimsForDCRRequest(this DcrRequest dc { string errorMessage = string.Empty; - // Error - Unable to perform DCR as there are no mutually supported values in the mandatory claim [CLAIM_NAME] + // 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 @@ -25,58 +26,51 @@ public static (List, string) CreateClaimsForDCRRequest(this DcrRequest dc new("token_endpoint_auth_method", "private_key_jwt"), new("application_type", "web"), new("id_token_signed_response_alg", "PS256"), - new("id_token_encrypted_response_alg", "RSA-OAEP"), - new("id_token_encrypted_response_enc", "A256GCM"), new("request_object_signing_alg", "PS256"), new("software_statement", dcrRequest.Ssa ?? ""), new("grant_types", "client_credentials"), new("grant_types", "authorization_code"), - new("grant_types", "refresh_token") + new("grant_types", TokenTypes.REFRESH_TOKEN) }; - // response_types updated below "code, code id_token" both types are returned and added below + // response_types updated below "code" is returned and added below // A response type is mandatory - if (!dcrRequest.ResponseTypesSupported.Contains("code") && !dcrRequest.ResponseTypesSupported.Contains("code id_token")) + if (!dcrRequest.ResponseTypesSupported.Contains("code")) { // Return the error errorMessage = ErrorMessage + " response_types"; return (null, errorMessage); } - var responseTypesList = dcrRequest.ResponseTypesSupported.Where(x => x.ToLower().Equals("code") || x.ToLower().Equals("code id_token")).ToList(); + var responseTypesList = dcrRequest.ResponseTypesSupported.Where(x => x.ToLower().Equals("code")).ToList(); claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypesList), JsonClaimValueTypes.JsonArray)); - var isCodeFlow = dcrRequest.ResponseTypesSupported.Contains("code"); - if (isCodeFlow && dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Length==0) + if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Length == 0) { // Log error message to the mandatory claim missing errorMessage = ErrorMessage + " authorization_signed_response_alg"; return (null, errorMessage); } - // Mandatory for code flow - if (isCodeFlow) + if (!dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) { - if (!dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) - { - // Return the error - errorMessage = ErrorMessage + " authorization_signed_response_alg"; - return (null, errorMessage); - } + // Return the error + errorMessage = ErrorMessage + " authorization_signed_response_alg"; + return (null, errorMessage); + } - if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256")) - { - claims.Add(new Claim("authorization_signed_response_alg", "PS256")); - } - else if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) - { - claims.Add(new Claim("authorization_signed_response_alg", "ES256")); - } + if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256")) + { + claims.Add(new Claim("authorization_signed_response_alg", "PS256")); + } + else if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256")) + { + claims.Add(new Claim("authorization_signed_response_alg", "ES256")); } // Check if the enc is empty but a alg is specified. - if ((dcrRequest.AuthorizationEncryptionResponseEncValuesSupported == null || dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Length==0) // No enc specified + if ((dcrRequest.AuthorizationEncryptionResponseEncValuesSupported == null || dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Length == 0) // No enc specified && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256") && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) // but alg specified. { @@ -95,7 +89,7 @@ public static (List, string) CreateClaimsForDCRRequest(this DcrRequest dc } // Conditional: Optional for response_type "code" if authorization_encryption_enc_values_supported is present - if (isCodeFlow && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Length!=0) + if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Length != 0) { if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256")) { diff --git a/Source/CDR.DCR/Models/DcrRequest.cs b/Source/CDR.DCR/Models/DcrRequest.cs index 6baf39b..2fcbe23 100644 --- a/Source/CDR.DCR/Models/DcrRequest.cs +++ b/Source/CDR.DCR/Models/DcrRequest.cs @@ -4,7 +4,7 @@ namespace CDR.DCR.Models { public class DcrRequest - { + { public string SoftwareProductId { get; set; } public string RedirectUris { get; set; } public string Ssa { get; set; } 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 fc693ab..cc291dd 100644 --- a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj +++ b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj @@ -6,13 +6,22 @@ $(Version) $(Version) $(Version) + True - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs b/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs index 346c37b..a22c9cb 100644 --- a/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs +++ b/Source/CDR.DataRecipient.API.Logger/IRequestResponseLogger.cs @@ -6,4 +6,4 @@ public interface IRequestResponseLogger { ILogger Log { get; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs b/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs index 3cb33cd..0128592 100644 --- a/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs +++ b/Source/CDR.DataRecipient.API.Logger/LoggerExtensions.cs @@ -10,4 +10,4 @@ public static IServiceCollection AddRequestResponseLogging(this IServiceCollecti return services; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs b/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs index 80227bb..d6af11e 100644 --- a/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs +++ b/Source/CDR.DataRecipient.API.Logger/RequestResponseLogger.cs @@ -1,34 +1,40 @@ namespace CDR.DataRecipient.API.Logger { - using System.Diagnostics; using Microsoft.Extensions.Configuration; using Serilog; using Serilog.Core; + using Serilog.Settings.Configuration; public class RequestResponseLogger : IRequestResponseLogger, IDisposable { private readonly Logger _logger; - public ILogger Log { get { return _logger; } } + + public ILogger Log + { + get { return _logger; } + } public RequestResponseLogger(IConfiguration configuration) { _logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration, sectionName: "SerilogRequestResponseLogger") - .Enrich.WithProperty("RequestMethod", "") - .Enrich.WithProperty("RequestBody", "") - .Enrich.WithProperty("RequestHeaders", "") - .Enrich.WithProperty("RequestPath", "") - .Enrich.WithProperty("RequestQueryString", "") - .Enrich.WithProperty("StatusCode", "") - .Enrich.WithProperty("ElapsedTime", "") - .Enrich.WithProperty("ResponseHeaders", "") - .Enrich.WithProperty("ResponseBody", "") - .Enrich.WithProperty("RequestHost", "") - .Enrich.WithProperty("RequestIpAddress", "") - .Enrich.WithProperty("ClientId", "") - .Enrich.WithProperty("SoftwareId", "") - .Enrich.WithProperty("DataHolderBrandId", "") - .Enrich.WithProperty("FapiInteractionId", "") + .ReadFrom.Configuration( + configuration, + new ConfigurationReaderOptions() { SectionName = "SerilogRequestResponseLogger" }) + .Enrich.WithProperty("RequestMethod", string.Empty) + .Enrich.WithProperty("RequestBody", string.Empty) + .Enrich.WithProperty("RequestHeaders", string.Empty) + .Enrich.WithProperty("RequestPath", string.Empty) + .Enrich.WithProperty("RequestQueryString", string.Empty) + .Enrich.WithProperty("StatusCode", string.Empty) + .Enrich.WithProperty("ElapsedTime", string.Empty) + .Enrich.WithProperty("ResponseHeaders", string.Empty) + .Enrich.WithProperty("ResponseBody", string.Empty) + .Enrich.WithProperty("RequestHost", string.Empty) + .Enrich.WithProperty("RequestIpAddress", string.Empty) + .Enrich.WithProperty("ClientId", string.Empty) + .Enrich.WithProperty("SoftwareId", string.Empty) + .Enrich.WithProperty("DataHolderBrandId", string.Empty) + .Enrich.WithProperty("FapiInteractionId", string.Empty) .CreateLogger(); } @@ -44,7 +50,6 @@ protected virtual void Dispose(bool disposing) { Serilog.Log.CloseAndFlush(); } - } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs index 6a9e8e2..295d2ea 100644 --- a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs +++ b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs @@ -13,12 +13,17 @@ namespace CDR.DataRecipient.API.Logger { public class RequestResponseLoggingMiddleware { - const string httpSummaryMessageTemplate = + private const string HttpSummaryMessageTemplate = "HTTP {RequestMethod} {RequestScheme:l}://{RequestHost:l}{RequestPathBase:l}{RequestPath:l} responded {StatusCode} in {ElapsedTime:0.0000} ms."; - const string httpSummaryExceptionMessageTemplate = + private const string HttpSummaryExceptionMessageTemplate = "HTTP {RequestMethod} {RequestScheme:l}://{RequestHost:l}{RequestPathBase:l}{RequestPath:l} encountered following error {error}"; + private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; + + private readonly RequestDelegate _next; + private readonly ILogger _requestResponseLogger; + private readonly IConfiguration _configuration; private string? _requestMethod; private string? _requestBody; private string? _requestHeaders; @@ -38,11 +43,6 @@ public class RequestResponseLoggingMiddleware private string? _fapiInteractionId; private string? _dataHolderBrandId; - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; - readonly RequestDelegate _next; - private readonly ILogger _requestResponseLogger; - private readonly IConfiguration _configuration; - public RequestResponseLoggingMiddleware(RequestDelegate next, IRequestResponseLogger requestResponseLogger, IConfiguration configuration) { _requestResponseLogger = requestResponseLogger.Log.ForContext(); @@ -86,11 +86,11 @@ private void LogWithContext() if (!string.IsNullOrEmpty(_exceptionMessage)) { - logger.Error(httpSummaryExceptionMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _exceptionMessage); + logger.Error(HttpSummaryExceptionMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _exceptionMessage); } else { - logger.Write(LogEventLevel.Information, httpSummaryMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _statusCode, _elapsedTime); + logger.Write(LogEventLevel.Information, HttpSummaryMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _statusCode, _elapsedTime); } } @@ -124,7 +124,7 @@ private async Task ExtractRequestProperties(HttpContext context) } } - static class ClaimIdentifiers + public static class ClaimIdentifiers { public const string Iss = "iss"; } @@ -135,7 +135,7 @@ private static void SetIdFromJwt(string jwt, string identifierType, ref string i if (handler.CanReadToken(jwt)) { var decodedJwt = handler.ReadJwtToken(jwt); - var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? String.Empty; + var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; idToSet = id; } @@ -145,7 +145,7 @@ private void ExtractIdFromRequest(HttpRequest request) { try { - //try fetching from the JWT in the authorization header - /arrangements/revoke + // try fetching from the JWT in the authorization header - /arrangements/revoke var authorization = request.Headers[HeaderNames.Authorization]; if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue)) { @@ -154,7 +154,7 @@ private void ExtractIdFromRequest(HttpRequest request) if (scheme == JwtBearerDefaults.AuthenticationScheme && parameter != null) { - _dataHolderBrandId = String.Empty; + _dataHolderBrandId = string.Empty; SetIdFromJwt(parameter, ClaimIdentifiers.Iss, ref _dataHolderBrandId); } } @@ -163,7 +163,6 @@ private void ExtractIdFromRequest(HttpRequest request) { _exceptionMessage = ex.Message; } - } private string ReadStreamInChunks(Stream stream) @@ -180,7 +179,8 @@ private string ReadStreamInChunks(Stream stream) { readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength); textWriter.Write(readChunk, 0, readChunkLength); - } while (readChunkLength > 0); + } + while (readChunkLength > 0); return textWriter.ToString(); } catch (Exception ex) @@ -188,13 +188,11 @@ private string ReadStreamInChunks(Stream stream) _exceptionMessage = ex.Message; } - return ""; + return string.Empty; } - private async Task ExtractResponseProperties(HttpContext httpContext) { - var originalBodyStream = httpContext.Response.Body; await using var responseBody = _recyclableMemoryStreamManager.GetStream(); httpContext.Response.Body = responseBody; @@ -232,6 +230,7 @@ private async Task ExtractResponseProperties(HttpContext httpContext) await responseBody.CopyToAsync(originalBodyStream); } } + private string? GetHost(HttpRequest request) { // 1. check if the X-Forwarded-Host header has been provided -> use that @@ -259,9 +258,8 @@ private async Task ExtractResponseProperties(HttpContext httpContext) // the traffic traverses through. We get the first (and potentially only) ip address from the list as the client IP. // We also remove any port numbers that may be included on the client IP. return keys[0]? - .Split(',')[0] // Get the first IP address in the list, in case there are multiple. + .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. } - } } diff --git a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs index e3de8fd..22c6b9d 100644 --- a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs +++ b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs @@ -1,4 +1,4 @@ -#define TEST_DEBUG_MODE // Run Playwright in non-headless mode for debugging purposes (ie show a browser) +#define TEST_DEBUG_MODE // Run Playwright in non-headless mode for debugging purposes (ie show a browser) // In docker (Ubuntu container) Playwright will fail if running in non-headless mode, so we ensure TEST_DEBUG_MODE is undef'ed #if !DEBUG @@ -31,7 +31,7 @@ namespace CDR.DataRecipient.E2ETests public class BaseTest { static public bool RUNNING_IN_CONTAINER => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER")?.ToUpper() == "TRUE"; - + // Customers public const string CUSTOMERID_BANKING = "jwilson"; //public const string CUSTOMERACCOUNTS_BANKING = "Personal Loan xxx-xxx xxxxx987,Transactions and Savings Account xxx-xxx xxxxx988"; @@ -172,7 +172,7 @@ static void DeleteFile(string filename) // Setup browser context var context = await browser.NewContextAsync(new BrowserNewContextOptions { - IgnoreHTTPSErrors = true, + IgnoreHTTPSErrors = true, RecordVideoDir = CreateMedia == true ? $"{MEDIAFOLDER}" : null, ViewportSize = new ViewportSize { @@ -185,7 +185,7 @@ static void DeleteFile(string filename) var page = await context.NewPageAsync(); try { - + page.Close += async (_, page) => { // Page is closed, so save videoPath @@ -639,10 +639,10 @@ static protected async Task SSA_Get(IPage page, string industry, string version, // Create Client Registration returning DH client ID of client that was registered static protected async Task ClientRegistration_Create(IPage page, - string dhBrandId, + string dhBrandId, string? jarmSigningAlgo = null, - string responseTypes = "code,code id_token", - string? jarmEncrypAlg = null, + string responseTypes = "code", + string? jarmEncrypAlg = null, string? jarmEncryptEnc = null) { @@ -666,16 +666,10 @@ static protected async Task ClientRegistration_Create(IPage page, await dcrPage.EnterAuthorisedEncryptedResponseEnc(jarmEncryptEnc); } - if (responseTypes.Contains("id_token")) - { - await dcrPage.EnterIdTokenEncryptedResponseAlgo("RSA-OAEP"); - await dcrPage.EnterIdTokenEncryptedResponseEnc("A128CBC-HS256"); - } - await dcrPage.ClickRegister(); var registrationResponseJson = await dcrPage.GetRegistrationResponse(); - return GetClientIdFromRegistrationResponse(registrationResponseJson); + return GetClientIdFromRegistrationResponse(registrationResponseJson); } @@ -690,7 +684,7 @@ static protected async Task ConsentAndAuthorisa { ConsentAndAuthorisationPages consentAndAuthorisationPages = new ConsentAndAuthorisationPages(page); - + await consentAndAuthorisationPages.EnterCustomerId(customerId); await consentAndAuthorisationPages.ClickContinue(); @@ -788,4 +782,4 @@ public class ConsentAndAuthorisationResponse public string? CDRArrangementID { get; init; } } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs b/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs index efae57a..348906e 100644 --- a/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs +++ b/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs @@ -125,7 +125,7 @@ static string BuildContent(string scope, string grant_type, string client_id, st // Request the access token var response = await client.SendAsync(request); - if (response.StatusCode != HttpStatusCode.OK) + if (response.StatusCode != HttpStatusCode.OK) { throw new Exception($"{nameof(AccessToken)}.{nameof(GetAsync)} - Error getting access token"); } diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs b/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs index c6745bb..cdc496d 100644 --- a/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs +++ b/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs @@ -10,7 +10,7 @@ namespace CDR.DataRecipient.E2ETests.Pages internal class ConsentAndAuthorisationPages { private readonly IPage _page; - private readonly ILocator _txtCustomerId; + private readonly ILocator _txtCustomerId; private readonly ILocator _btnContinue; private readonly ILocator _txtOneTimePassword; private readonly ILocator _btnAuthorise; @@ -19,7 +19,7 @@ internal class ConsentAndAuthorisationPages public ConsentAndAuthorisationPages(IPage page) { _page = page; - _txtCustomerId = _page.Locator("id=customerId"); + _txtCustomerId = _page.Locator("id=customerId"); _btnContinue = _page.Locator("button:has-text(\"Continue\")"); _txtOneTimePassword = _page.Locator("id=otp"); _btnAuthorise = _page.Locator("text=Authorise"); diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs b/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs index ea9b03e..3461569 100644 --- a/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs +++ b/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs @@ -26,8 +26,6 @@ internal class DynamicClientRegistrationPage private readonly ILocator _txtApplicationType; private readonly ILocator _txtIdTokenSignedResponseAlgo; - private readonly ILocator _txtIdTokenEncryptedResponseAlgo; - private readonly ILocator _txtIdTokenEncryptedResponseEnc; private readonly ILocator _txtRequestSigningAlgo; private readonly ILocator _txtAuthorisedSignedResponsegAlgo; @@ -47,7 +45,7 @@ public DynamicClientRegistrationPage(IPage page, string dataRecipientBaseUrl) { _page = page; _dataRecipientBaseUrl = dataRecipientBaseUrl; - + _lnkDcrMenuItem = _page.Locator("a >> text=Dynamic Client Registration"); _hedPageHeading = _page.Locator("h2 >> text=Dynamic Client Registration"); @@ -67,8 +65,6 @@ public DynamicClientRegistrationPage(IPage page, string dataRecipientBaseUrl) _txtApplicationType = _page.Locator("id=ApplicationType"); _txtIdTokenSignedResponseAlgo = _page.Locator("id=IdTokenSignedResponseAlg"); - _txtIdTokenEncryptedResponseAlgo = _page.Locator("id=IdTokenEncryptedResponseAlg"); - _txtIdTokenEncryptedResponseEnc = _page.Locator("id=IdTokenEncryptedResponseEnc"); _txtRequestSigningAlgo = _page.Locator("id=RequestObjectSigningAlg"); _txtAuthorisedSignedResponsegAlgo = _page.Locator("id=AuthorizationSignedResponseAlg"); @@ -135,15 +131,7 @@ public async Task EnterResponseTypes(string responseTypes) public async Task EnterIdTokenIdTokenSignedResponseAlgo(string idTokenIdTokenSignedResponseAlgo) { await _txtIdTokenSignedResponseAlgo.FillAsync(idTokenIdTokenSignedResponseAlgo); - } - public async Task EnterIdTokenEncryptedResponseAlgo(string idTokenEncryptedResponseAlgo) - { - await _txtIdTokenEncryptedResponseAlgo.FillAsync(idTokenEncryptedResponseAlgo); - } - public async Task EnterIdTokenEncryptedResponseEnc(string idTokenEncryptedResponseEnc) - { - await _txtIdTokenEncryptedResponseEnc.FillAsync(idTokenEncryptedResponseEnc); - } + } public async Task EnterRequestSigningAlgo(string requestSigningAlgo) { await _txtRequestSigningAlgo.FillAsync(requestSigningAlgo); @@ -183,7 +171,7 @@ public async Task GetClientId() } public async Task GetSsaVersion() { - return await _txtSsaVersion.InputValueAsync(); + return await _txtSsaVersion.InputValueAsync(); } public async Task GetIndustry() { @@ -225,14 +213,6 @@ public async Task GetIdTokenSignedResponseAlgo() { return await _txtIdTokenSignedResponseAlgo.InputValueAsync(); } - public async Task GetIdTokenEncryptedResponseAlgo() - { - return await _txtIdTokenEncryptedResponseAlgo.InputValueAsync(); - } - public async Task GetIdTokenEncryptedResponseEnc() - { - return await _txtIdTokenEncryptedResponseEnc.InputValueAsync(); - } public async Task GetRequestSigningAlgo() { return await _txtRequestSigningAlgo.InputValueAsync(); @@ -251,7 +231,7 @@ public async Task GetAuthorisedEncryptedResponseEnc() } public async Task GetRegistrationResponse(bool includeHeading = false) { - if(includeHeading) + if (includeHeading) { return await _divRegistrationResponseWithHeading.TextContentAsync(); } @@ -259,14 +239,14 @@ public async Task GetRegistrationResponse(bool includeHeading = false) { return await _divRegistrationResponseJson.TextContentAsync(); } - + } public async Task GetViewRegistrationResponse() { return await _divViewRegistrationResponse.TextContentAsync(); } public async Task GetDiscoveryDocumentDetails(string textToSynchroniseWith = null) - { + { // Workaround to wait for text to synchonise with. // Without synchronisation, current text content is returned instead of waiting for text (page to reload) if (textToSynchroniseWith == null) diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs b/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs index b091940..17fd8c2 100644 --- a/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs +++ b/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs @@ -23,7 +23,7 @@ internal class ParPage private readonly ILocator _chkUsePkce; private readonly ILocator _divErrorMessage; private readonly ILocator _lblRequestUri; - private readonly ILocator _divRegistrationModal; + private readonly ILocator _divRegistrationModal; public ParPage(IPage page) { @@ -39,7 +39,7 @@ public ParPage(IPage page) _txtResponseMode = _page.Locator("input[name=\"ResponseMode\"]"); _btnInitiatePar = _page.Locator("div.form >> text=Initiate PAR"); _btnViewRegistration = _page.Locator("#ViewRegistration"); - _divViewRegistrationError = _page.Locator("#registrationid-validation-message"); + _divViewRegistrationError = _page.Locator("#registrationid-validation-message"); _lnkRequestUri = _page.Locator("p.results > a"); _divErrorMessage = _page.Locator(".card-footer"); _lblRequestUri = _page.Locator("dd:has-text(\"urn:\")"); @@ -58,7 +58,7 @@ public async Task CompleteParForm( string scope = null, string cdrArrangement = null, string responseType = "code", - string responseMode = "jwt", + string responseMode = "jwt", string sharingDuration = "", bool usePkce = true) { @@ -68,7 +68,7 @@ public async Task CompleteParForm( { await _selSelectArrangementId.SelectOptionAsync(new[] { cdrArrangement }); } - + await _txtSharingDuration.FillAsync(sharingDuration); if (scope != null) diff --git a/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs b/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs index e96c2c6..526d232 100644 --- a/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs +++ b/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs @@ -1,4 +1,4 @@ -using CDR.DataRecipient.E2ETests.Pages; +using CDR.DataRecipient.E2ETests.Pages; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.Data.SqlClient; @@ -17,27 +17,6 @@ public class US23863_MDR_E2ETests : BaseTest, IClassFixture private const string SWAGGER_ENERGY_IFRAME = "cds-energy/index.html"; private const string SWAGGER_COMMON_IFRAME = "cds-common/index.html"; - // Pre-generated ID Token used in IDTokenhelper test - const string IDTOKEN = @"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00iLCJraWQiOiJkYTNiYzA3OTlhMjlkODM2NWU2OWVkZjJiZmQxMzVh" + - "MDhjNGRkYmE3ZWM3ZTFiNmVhYjkzNWY5NjcyNWExZTNhIn0.afEh0qVHeHSYqu-Q-leao8EQe-cGR8m2-fszGZQOq0dBBQNGRaFwSTKn3u4KqGTXXI-w" + - "1p20YHe_JMH4k1B7_NG93YXpYsx1pfEc_qa5x7_9gYvN_X6NLESuxVQW3TJBgsmE-LMu6TjSZzXtt_HkKk0V-q5epsrWgYX-WWR4Jk-yAUEYxrqA-bGm" + - "EKLbY9rLbZaituis0E4fYP4SF9keL2H6iJ4h99F4MrEKz8K7HgsQ1SkrlFw9ApR3Wnl3NaLXsEcJJZqZcpXkHUaIA76UChG5tvZTV436Jyz_ehxbcQeK" + - "9Yx5ZhXWY2Ud9K0LZ3hQ3A0SdEGkhExGbUpiNecAGA.YtIsaDgqhifPIsRj.MHSYKFpgyc0opB0LqAQWxMaikTTc7dviOd4tSLInpQ8KwnrCHnVKJFJq" + - "2P6rFz26xorAFHtZZeeqG2RmAehMFWBxbWuSTLgmXqF1SmhVgAT6XHgPNBZE_KM4OE4FrYd2eMxbLSYVbGs1zcu8rxV_Fal74w_2qgVqiuR-8DgKLqkb" + - "dLrW0F7XXl_PtIZj4vWdwbon7SQldvk0usg5ZxqC5PeyrV2AYhu_0_VNRnwAxcbzvYdh9HxckrdYACUtcjcKi_VSPd9gw4urq58NnCSKghBD6rJvHqW1" + - "qdL32UTf-V58Ma9bRaItlUr_DOCFRp-vT7nBJwn3EDJWL_GE2QjOu6Zy-GNwv4p220Ct5GmqRVfgMXjZYOzZieBBHIU0AyXIohlF2PPC4NAXlgpFacna" + - "RKNZb3et6B9WsqEMlDhMajMeY1DWgQb0eSXrAtX4QidFucEirIhV2XwMFi60uoycuPbk2ziKCRQCdS-TiTCkrKH72GoR_8v27ddkPKF8cspqPNTmJdJi" + - "d84jyoWTgo7fMa3xf-Sa7OO9jNPuAChtpX3t9amyq-NQfoAlSRu5fUZcWgzxKcJbnHlWG71QH6ALF-4HyMML3brHlV1HoIybMyUKpCkz_qJ6uK0JQC90" + - "PMJ4f7hkFc5wZ_V4E6BOrX635SPVxk0ts4G2AVpi1nDfjajd_Ljg8ni1mE9PDuWdYap2hxRDJnmJ5XssR0FlMcWRPO3IJCjrdSmtMKxdePq3T_UEXK8C" + - "3sWs17k0URmlxnkjp6Qe6YSzAkALwgHyPCD9IqqnpFAXG88669fFWgCH7uMu9dyJI-Agwc3ufU1NAQRnknowj3DKp5DuhYL1AL-P4c8d7mlI5bpwee2c" + - "WxLQMDog4hp-FQMuir7IacY2lJ9Ocvoirf6yM-R5-lQka74poe2PGOu2QlrywvdHvJ-ExSNkT5mNAZPCPYT-eHVqdeV_aQCBu8bRD8nUeq5o6ZJMzOlr" + - "V3LDS9jyGKRVvQf7YyXHODPnTMeL78mgD_V4WSdme54-J_tnOQh-j9a58CH_7E6AlD6ahc27K1sA8hG-vZ_jRUG1QXhJfER5pQjCoPfEtWzGbJF58LYC" + - "piwR0KE15LtpJgKPBwbBFQwpBRcZDEtouBZLFyMjfNGg1cnTfdlblEtnZcE50_ZVjTfk3n1nNjRlYHceaGnd7GYINBNl87JZCd2Dn_-JmRJfKK2vnF17" + - "HA3dUOHz_rUJa6hQd77WwWDxwU7oG1bpmoRDjgjl1AxXmJVSuhkgyK6JEDgeF6SDPDsU1yym_9ACitE1FjS8VAqU3e8cgoaDD5BzASt9OmApsHQHRQur" + - "HOh05w_b63JHd3Cg_-ASojF78GjKQ2TWV-jZn65nacY5NSIU3Qxt87UbR7Cr64UZMJAhRQ0VoXmdHpG6dSjtYEP20HTgvTI7uQD8Ibw9C7jILUi76HO_" + - "-n3R2errngQrYNunYmW9q45eDdngOiA_N5HB3rS3y3-V3kYnkW8Wio-gSzuGYBoAM4OT5QFGbs7QYw0PR4wYDk7iVIkfCf5vDlVb2ehcfNQx2m5gYYuW" + - "ppVENCqtjrsUmAxmyPZ5v_Fvh8N36J-4IaKNcv9J3ic4mm0yUdCLVCRxBjpsfw.xPc7ZQDVN11iHX41af-wvg"; - static string GetClientId() { using var mdrConnection = new SqlConnection(BaseTest.DATARECIPIENT_CONNECTIONSTRING); @@ -107,7 +86,6 @@ await TestAsync($"{nameof(US23863_MDR_E2ETests)} - {nameof(AC01_HomePage)}", asy await page.Locator("span:text(\"Consumer Data Sharing\") + ul >> a:text(\"Energy\")").TextContentAsync(); await page.Locator("a >> text=PAR").TextContentAsync(); await page.Locator("span >> text=Utilities").TextContentAsync(); - await page.Locator("a >> text=ID Token Helper").TextContentAsync(); await page.Locator("a >> text=Private Key JWT Generator").TextContentAsync(); Assert.True(true); }); @@ -166,7 +144,7 @@ await TestAsync($"{nameof(US23863_MDR_E2ETests)} - {nameof(AC02_99_DiscoverDataH }); } - [Theory] + [Theory] [InlineData("BANKING", "1", DR_BRANDID, DR_SOFTWAREPRODUCTID, "NotAcceptable")] [InlineData("BANKING", "2", DR_BRANDID, DR_SOFTWAREPRODUCTID, "NotAcceptable")] [InlineData("BANKING", "3", DR_BRANDID, DR_SOFTWAREPRODUCTID, "OK - SSA Generated")] @@ -197,7 +175,7 @@ await TestAsync(testName, async (page) => { // Create a default Banking Dataholder Registration using defaults - DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL); + DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL); await dcrPage.GotoDynamicClientRegistrationPage(); await dcrPage.SelectDataHolderBrandId(DH_BRANDID); await dcrPage.ClickRegister(); @@ -252,8 +230,6 @@ await TestAsync(testName, async (page) => dcrPage.GetResponseTypes().Result.Should().Be("code"); dcrPage.GetApplicationType().Result.Should().Be("web"); dcrPage.GetIdTokenSignedResponseAlgo().Result.Should().Be("PS256"); - dcrPage.GetIdTokenEncryptedResponseAlgo().Result.Should().Be(String.Empty); - dcrPage.GetIdTokenEncryptedResponseEnc().Result.Should().Be(String.Empty); dcrPage.GetRequestSigningAlgo().Result.Should().Be("PS256"); dcrPage.GetAuthorisedSignedResponsegAlgo().Result.Should().Be("PS256"); dcrPage.GetAuthorisedEncryptedResponseAlgo().Result.Should().Be(String.Empty); @@ -261,7 +237,7 @@ await TestAsync(testName, async (page) => } }); - + } [Fact] @@ -287,7 +263,7 @@ await TestAsync(testName, async (page) => // Assert using (new AssertionScope()) { - string viewRegistrationResponse = await dcrPage.GetViewRegistrationResponse(); + string viewRegistrationResponse = await dcrPage.GetViewRegistrationResponse(); viewRegistrationResponse.Should().Contain("Registration retrieved successfully."); viewRegistrationResponse.Should().Contain($"\"client_id\": \"{dhClientId}\""); } @@ -328,8 +304,8 @@ await TestAsync(testName, async (page) => discoveryDocumentDetails.Should().Contain($"\"issuer\": \"https://{HOSTNAME_DATAHOLDER}:8001\""); } - }); - + }); + } [Fact] @@ -393,19 +369,17 @@ await TestAsync(testName, async (page) => // Assert Client Correctly Loaded dcrPage.GetClientId().Result.Should().Be(clientId); - // Modify to valid hybrid mode values - await dcrPage.EnterResponseTypes("code,code id_token"); - await dcrPage.EnterIdTokenEncryptedResponseAlgo("RSA-OAEP"); - await dcrPage.EnterIdTokenEncryptedResponseEnc("A128CBC-HS256"); + // Modify authorization encrypted response enc and alg values + await dcrPage.EnterAuthorisedEncryptedResponseEnc("A128CBC-HS256"); + await dcrPage.EnterAuthorisedEncryptedResponseAlgo("RSA-OAEP-256"); await dcrPage.ClickUpdate(); // Assert Software Product Registration Updated var registrationResponse = await dcrPage.GetRegistrationResponse(includeHeading: true); registrationResponse.Should().Contain("Registration update successful."); - registrationResponse.Should().Contain("code id_token"); - registrationResponse.Should().Contain("\"id_token_encrypted_response_alg\": \"RSA-OAEP\""); - registrationResponse.Should().Contain("\"id_token_encrypted_response_enc\": \"A128CBC-HS256\""); + registrationResponse.Should().Contain("\"authorization_encrypted_response_alg\": \"RSA-OAEP-256\""); + registrationResponse.Should().Contain("\"authorization_encrypted_response_enc\": \"A128CBC-HS256\""); }); } @@ -723,7 +697,7 @@ await iFrame.SelectOptionAsync( ); // Arrange - Click GET​/banking​/accountsGet Accounts - await iFrame.ClickAsync("text=Banking GET/banking/accountsGet AccountsGET/banking/accounts/balancesGet Bulk Ba >> [aria-label=\"get ​\\/banking​\\/accounts\"]"); + await iFrame.ClickAsync("//div[@id='operations-Banking_Accounts-listBankingAccounts']//button[1]"); // Arrange - Click Try it out await iFrame.ClickAsync("text=Try it out"); @@ -827,7 +801,7 @@ await iFrame.SelectOptionAsync( ); // Arrange - Click GET​/energy​/accountsGet Energy Accounts - await iFrame.ClickAsync("text=Energy GET/energy/plansGet Generic PlansGET/energy/plans/{planId}Get Generic Pla >> [aria-label=\"get ​\\/energy​\\/accounts\"]"); + await iFrame.ClickAsync("//div[@id='operations-Energy_Accounts-listEnergyAccounts']//button[1]"); // Arrange - Click Try it out await iFrame.ClickAsync("text=Try it out"); @@ -894,7 +868,7 @@ await iFrame.SelectOptionAsync( ); // Arrange - Click GET/discovery/statu - await iFrame.ClickAsync("text=Discovery GET/discovery/statusGet StatusGET/discovery/outagesGet Outages >> [aria-label=\"get ​\\/discovery​\\/status\"]"); + await iFrame.ClickAsync("//div[@id='operations-Data_Holder_Operations-getOutages']//button[1]"); // Arrange - Click Try it out await iFrame.ClickAsync("text=Try it out"); @@ -919,44 +893,6 @@ await CleanupAsync(async (page) => } } - [Theory] - [InlineData(IDTOKEN)] - public async Task AC09_IDTokenHelper(string encryptedToken) - { - await TestAsync($"{nameof(US23863_MDR_E2ETests)} - {nameof(AC09_IDTokenHelper)}", async (page) => - { - // Arrange - Goto home page, click menu button, check page loaded - await page.GotoAsync(WEB_URL); - await page.Locator("a >> text=ID Token Helper").ClickAsync(); - await page.Locator("h2 >> text=ID Token Helper").TextContentAsync(); - - // Arrange - Enter id token to decrypt - await page.Locator("textarea[name=\"IdTokenEncrypted\"]").FillAsync(encryptedToken); - - // Act - await page.Locator("text=Decrypt ID Token").ClickAsync(); - - // Assert - Check results - await TestResults(page, "nbf", "1687210290"); - await TestResults(page, "exp", "1687210590"); - await TestResults(page, "iss", $"https://mock-data-holder:8001"); - await TestResults(page, "aud", "549076d1-785e-46c8-b1f4-8074e859c004"); - await TestResults(page, "nonce", "998c7257-8e42-4ef9-81cc-09d08eff413f"); - await TestResults(page, "iat", "1687210290"); - await TestResults(page, "at_hash", "wvmLTnV0mNAEeqVrmgvnuw"); - await TestResults(page, "c_hash", "fRr6y32yxRN257qQ9Rzlvw"); - await TestResults(page, "auth_time", "1687210289"); - await TestResults(page, "sub", "GYFWjFhkegCHsQe0KVvFhg=="); - await TestResults(page, "name", "jwilson"); - await TestResults(page, "family_name", "Wilson"); - await TestResults(page, "given_name", "Jane"); - await TestResults(page, "updated_at", "1687210290"); - await TestResults(page, "acr", "urn:cds.au:cdr:2"); - - Assert.True(true); - }); - } - [Fact] public async Task AC10_PrivateKeyJWTGenerator() { @@ -1014,4 +950,4 @@ await TestAsync($"{nameof(US23863_MDR_E2ETests)} - {nameof(AC12_About)}", async } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.E2ETests/US46331_MDR_E2ETests_ParFapi.cs b/Source/CDR.DataRecipient.E2ETests/US46331_MDR_E2ETests_ParFapi.cs index 717d87b..a1f70b9 100644 --- a/Source/CDR.DataRecipient.E2ETests/US46331_MDR_E2ETests_ParFapi.cs +++ b/Source/CDR.DataRecipient.E2ETests/US46331_MDR_E2ETests_ParFapi.cs @@ -1,4 +1,4 @@ -using CDR.DataRecipient.E2ETests.Pages; +using CDR.DataRecipient.E2ETests.Pages; using FluentAssertions; using FluentAssertions.Execution; using System; @@ -15,13 +15,12 @@ public class US46331_MDR_E2ETests_ParFapi : BaseTest, IClassFixture public const string DH_DEFAULT_PAR_SCOPE = "openid profile common:customer.basic:read bank:accounts.basic:read bank:transactions:read cdr:registration"; [Theory] - [InlineData("Missing Response Type", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, true, "", "fragment", "ERR-GEN-008: response_type is missing")] - [InlineData("Invalid Response Type", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, true, "foo", "fragment", "response_type is not supported")] - [InlineData("Missing Response Mode for Code Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code", "", "ERR-GEN-013: response_mode is not supported")] - [InlineData("Invalid Response Mode for Code Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code", "fragment", "Invalid response_mode for response_type")] - [InlineData("Invalid Response Mode for Hybrid Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code id_token", "jwt", "Invalid response_mode for response_type")] - [InlineData("Missing Scope", DH_BRANDID, "", null, "code id_token", "fragment", "scope is missing")] - [InlineData("Invalid Scope", DH_BRANDID, "foo", null, "code id_token", "fragment", "openid scope is missing")] + [InlineData("Missing Response Type", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, true, "", "jwt", "ERR-GEN-008: response_type is missing")] + [InlineData("Invalid Response Type", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, true, "foo", "jwt", "response_type is not supported")] + [InlineData("Missing Response Mode for Code Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code", "", "ERR-PAR-009: response_mode is missing or not set to 'jwt' for response_type of 'code'")] + [InlineData("Invalid Response Mode for Code Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code", "fragment", "ERR-GEN-013: response_mode is not supported")] + [InlineData("Missing Scope", DH_BRANDID, "", null, "code", "jwt", "scope is missing")] + [InlineData("Invalid Scope", DH_BRANDID, "foo", null, "code", "jwt", "openid scope is missing")] [InlineData("Valid Response Mode for Code Flow", DH_BRANDID, DH_DEFAULT_PAR_SCOPE, null, "code", "jwt", "")] public async Task AC04_AC0_AC06_AC07_InvalidParRequests(string scenarioName, string dhBrandId, string dhScope, bool? useDefaultResponseTypeForDCR, string responseType, string responseMode, string expectedError) { @@ -32,7 +31,7 @@ public async Task AC04_AC0_AC06_AC07_InvalidParRequests(string scenarioName, str await ArrangeAsync(testName, async (page) => { - var responseTypeForRegCreation =useDefaultResponseTypeForDCR !=null && useDefaultResponseTypeForDCR == true ? "code id_token" : responseType; + var responseTypeForRegCreation = useDefaultResponseTypeForDCR != null && useDefaultResponseTypeForDCR == true ? "code" : responseType; dhClientId = await ClientRegistration_Create(page, dhBrandId, responseTypes: responseTypeForRegCreation) ?? throw new NullReferenceException(nameof(dhClientId)); @@ -189,4 +188,4 @@ await CleanupAsync(async (page) => } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.E2ETests/US46352_MDR_E2ETests_AuthCodeFlowJarm.cs b/Source/CDR.DataRecipient.E2ETests/US46352_MDR_E2ETests_AuthCodeFlowJarm.cs index 1da2077..620c3f9 100644 --- a/Source/CDR.DataRecipient.E2ETests/US46352_MDR_E2ETests_AuthCodeFlowJarm.cs +++ b/Source/CDR.DataRecipient.E2ETests/US46352_MDR_E2ETests_AuthCodeFlowJarm.cs @@ -19,7 +19,7 @@ public class US46352_MDR_E2ETests_AuthCodeFlowJarm : BaseTest, IClassFixture { - dhClientId =await CreateBankingRegistration(page); + dhClientId = await CreateBankingRegistration(page); }); await TestAsync(testName, async (page) => @@ -249,12 +249,12 @@ await CleanupAsync(async (page) => } [Theory] - [InlineData("Iss_Missing", null, "USE_VALID_AUD", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10211): Unable to validate issuer. The 'issuer' parameter is null or whitespace", "'iss' is blank in simulated callback.")] - [InlineData("Iss_Mismatch", "foo", "USE_VALID_AUD", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10205): Issuer validation failed. Issuer: 'foo'.", "'iss' is different to the expected client Id in simulated callback.")] - [InlineData("Aud_Missing", "USE_VALID_ISS", null, DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10206): Unable to validate audience. The 'audiences' parameter is empty.", "'aud' is blank in simulated callback.")] - [InlineData("Aud_Mismatch", "USE_VALID_ISS", "foo", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10214): Audience validation failed. Audiences: 'foo'.", "'aud' is different to the expected uri in simulated callback..")] - [InlineData("missing_Exp", "USE_VALID_ISS", "USE_VALID_AUD", null, "Token Validation Failed (IDX10225): Lifetime validation failed. The token is missing an Expiration Time.", "'exp' is blank in simulated callback.")] - [InlineData("Expired_Token", "USE_VALID_ISS", "USE_VALID_AUD", "-500", "Token Validation Failed (IDX10223): Lifetime validation failed. The token is expired.", "JWT has expired. (now - 500 seconds)")] + [InlineData("Iss_Missing", null, "USE_VALID_AUD", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10211): Unable to validate issuer. The 'issuer' parameter is null or whitespace", "'iss' is blank in simulated callback.")] + [InlineData("Iss_Mismatch", "foo", "USE_VALID_AUD", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10205): Issuer validation failed. Issuer: 'foo'.", "'iss' is different to the expected client Id in simulated callback.")] + [InlineData("Aud_Missing", "USE_VALID_ISS", null, DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10206): Unable to validate audience. The 'audiences' parameter is empty.", "'aud' is blank in simulated callback.")] + [InlineData("Aud_Mismatch", "USE_VALID_ISS", "foo", DEFAULT_JWT_EXP_IN_SECONDS, "Token Validation Failed (IDX10214): Audience validation failed. Audiences: 'foo'.", "'aud' is different to the expected uri in simulated callback..")] + [InlineData("missing_Exp", "USE_VALID_ISS", "USE_VALID_AUD", null, "Token Validation Failed (IDX10225): Lifetime validation failed. The token is missing an Expiration Time.", "'exp' is blank in simulated callback.")] + [InlineData("Expired_Token", "USE_VALID_ISS", "USE_VALID_AUD", "-500", "Token Validation Failed (IDX10223): Lifetime validation failed. The token is expired.", "JWT has expired. (now - 500 seconds)")] public async Task AC12_AC13_AC14_AC15_Jwt_Missing_And_Mismatch_Metadata(string testSuffix, string? issuer, string? audience, string? jwtExpiry, string expectedError, string becauseText) { try @@ -282,12 +282,12 @@ await TestAsync(testName, async (page) => { audience = audience.Replace("USE_VALID_AUD", GetDataholderClientId(DATAHOLDER_IDENTITYSERVER_CONNECTIONSTRING, requestUri)); } - if(issuer != null) + if (issuer != null) { issuer = issuer.Replace("USE_VALID_ISS", GetInfosecBaseUriFromRegister(DH_BRANDID)); - } + } - string callbackUrl = $"{WEB_URL}/consent/callback?response={GetAuthenticationCodeJwt(state: dataHolderAuthState,issuer:issuer, aud:audience, expiryTimeInSeconds: jwtExpiry )}"; + string callbackUrl = $"{WEB_URL}/consent/callback?response={GetAuthenticationCodeJwt(state: dataHolderAuthState, issuer: issuer, aud: audience, expiryTimeInSeconds: jwtExpiry)}"; await page.GotoAsync(callbackUrl); // Act/Assert - verify error message @@ -311,7 +311,7 @@ public async Task AC18_Response_JWT_Signature_Fail() { string testName = $"{nameof(US46352_MDR_E2ETests_AuthCodeFlowJarm)} - {nameof(AC18_Response_JWT_Signature_Fail)}"; string? dhClientId = null; - + await ArrangeAsync(testName, async (page) => { dhClientId = await CreateBankingRegistration(page, "code"); @@ -347,7 +347,7 @@ await CleanupAsync(async (page) => } } - private static async Task CreateBankingRegistration(IPage page, string responseType = "code id_token") + private static async Task CreateBankingRegistration(IPage page, string responseType = "code") { return await ClientRegistration_Create(page, DH_BRANDID, responseTypes: responseType); } @@ -369,7 +369,7 @@ private static string GetAuthenticationCodeJwt(string state = "", ExpiryTimeInSeconds = expiryTimeInSeconds, Iss = issuer, Aud = aud, - Kid = DEFAULT_KID, + Kid = DEFAULT_KID, State = state, Code = authCode, }.Get(); @@ -419,7 +419,7 @@ private static string GetInfosecBaseUriFromRegister(string brandId) registerDbServerConnection.Open(); using var selectCommand = new SqlCommand($"SELECT InfosecBaseUri FROM Endpoint WHERE BrandId = '{brandId}'", registerDbServerConnection); - string ? infosecBaseUri = Convert.ToString(selectCommand.ExecuteScalar()); + string? infosecBaseUri = Convert.ToString(selectCommand.ExecuteScalar()); if (String.IsNullOrEmpty(infosecBaseUri)) throw new Exception($"Could not find InfosecBaseUri for '{brandId}' in Register Endpoint Table."); diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/AuthenticationCodeJwt.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/AuthenticationCodeJwt.cs index bdbc4e7..22e4134 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/AuthenticationCodeJwt.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/AuthenticationCodeJwt.cs @@ -7,9 +7,9 @@ namespace CDR.DataRecipient.IntegrationTests.Infrastructure.API2 { public class AuthenticationCodeJwt { - public string? CertificateFilename { get; init; } - public string? CertificatePassword { get; init; } - public string? Iss { get; init; } + public string? CertificateFilename { get; init; } + public string? CertificatePassword { get; init; } + public string? Iss { get; init; } public string? Aud { get; init; } public string? Kid { get; init; } public string? Nfb { get; init; } @@ -33,7 +33,7 @@ public string Get() { "iat", new DateTimeOffset(now).ToUnixTimeSeconds() }, }; - if(ExpiryTimeInSeconds != null) + if (ExpiryTimeInSeconds != null) { var exp = now.AddSeconds(int.Parse(ExpiryTimeInSeconds)); subject.Add("exp", new DateTimeOffset(exp).ToUnixTimeSeconds()); @@ -43,7 +43,7 @@ public string Get() { subject.Add("iss", Iss.ToLower()); } - + if (Aud != null) { subject.Add("aud", Aud); diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/ClientAssertion.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/ClientAssertion.cs index af479b9..f237e82 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/ClientAssertion.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/API2/ClientAssertion.cs @@ -7,9 +7,9 @@ namespace CDR.DataRecipient.IntegrationTests.Infrastructure.API2 { public class ClientAssertion { - public string? CertificateFilename { get; init; } - public string? CertificatePassword { get; init; } - public string? Iss { get; init; } + public string? CertificateFilename { get; init; } + public string? CertificatePassword { get; init; } + public string? Iss { get; init; } public string? Aud { get; init; } public string? Kid { get; init; } @@ -34,7 +34,7 @@ public string Get() subject.Add("iss", Iss); subject.Add("sub", Iss); } - + if (Aud != null) { subject.Add("aud", Aud); diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/AccessToken.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/AccessToken.cs index d6f6baf..864b0a4 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/AccessToken.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/AccessToken.cs @@ -11,7 +11,7 @@ namespace CDR.DataRecipient.IntegrationTests.Infrastructure { -public class AccessToken + public class AccessToken { private static readonly string IDENTITYSERVER_URL = BaseTest.REGISTER_MTLS_TOKEN_URL; private static readonly string AUDIENCE = IDENTITYSERVER_URL; @@ -128,8 +128,8 @@ static string BuildContent(string scope, string grant_type, string client_id, st var response = await client.SendAsync(request); if (response.StatusCode != HttpStatusCode.OK) - { - throw new Exception($"{nameof(AccessToken)}.{nameof(GetAsync)} - Error getting access token - {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"); + { + throw new Exception($"{nameof(AccessToken)}.{nameof(GetAsync)} - Error getting access token - {response.StatusCode} - {await response.Content.ReadAsStringAsync()}"); } // Deserialize the access token from the response diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/JWT.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/JWT.cs index d2ee4de..8a36465 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/JWT.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/JWT.cs @@ -92,8 +92,8 @@ static private byte[] GetRSAPrivateKeyBytes(string certificateFilename, string c /// Get a security token descriptor /// static private SecurityTokenDescriptor GetSecurityTokenDescriptor( - byte[] keyBytes, - string? issuer, string? audience, + byte[] keyBytes, + string? issuer, string? audience, bool expired, Dictionary claims, SecurityAlgorithm securityAlgorithm) diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt.cs index ff66138..462ca7b 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt.cs @@ -104,31 +104,31 @@ public string Generate(string issuer, string audience) //using (var key = CngKey.Import(privateKeyBytes, CngKeyBlobFormat.Pkcs8PrivateBlob)) //using (var rsa = new RSACng(key)) //{ - var kid = GenerateKeyId(rsa); - var privateSecurityKey = new RsaSecurityKey(rsa) - { - KeyId = kid, - CryptoProviderFactory = new CryptoProviderFactory() - { - CacheSignatureProviders = false - } - }; - - var descriptor = new SecurityTokenDescriptor + var kid = GenerateKeyId(rsa); + var privateSecurityKey = new RsaSecurityKey(rsa) + { + KeyId = kid, + CryptoProviderFactory = new CryptoProviderFactory() { - Issuer = issuer, - Audience = audience, - Expires = DateTime.UtcNow.AddMinutes(10), - Subject = new ClaimsIdentity(new List { new Claim("sub", issuer) }), - SigningCredentials = new SigningCredentials(privateSecurityKey, SecurityAlgorithms.RsaSsaPssSha256), - NotBefore = null, - IssuedAt = null, - Claims = new Dictionary() - }; - descriptor.Claims.Add("jti", Guid.NewGuid().ToString()); + CacheSignatureProviders = false + } + }; - var tokenHandler = new JsonWebTokenHandler(); - return tokenHandler.CreateToken(descriptor); + var descriptor = new SecurityTokenDescriptor + { + Issuer = issuer, + Audience = audience, + Expires = DateTime.UtcNow.AddMinutes(10), + Subject = new ClaimsIdentity(new List { new Claim("sub", issuer) }), + SigningCredentials = new SigningCredentials(privateSecurityKey, SecurityAlgorithms.RsaSsaPssSha256), + NotBefore = null, + IssuedAt = null, + Claims = new Dictionary() + }; + descriptor.Claims.Add("jti", Guid.NewGuid().ToString()); + + var tokenHandler = new JsonWebTokenHandler(); + return tokenHandler.CreateToken(descriptor); //} } diff --git a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt2.cs b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt2.cs index 46b643e..920923e 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt2.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/Infrastructure/PrivateKeyJwt2.cs @@ -9,7 +9,7 @@ namespace CDR.DataRecipient.IntegrationTests { -class PrivateKeyJwt2 + class PrivateKeyJwt2 { public string CertificateFilename { get; set; } public string CertificatePassword { get; set; } @@ -25,7 +25,7 @@ public string Generate() new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer) }; - return Generate(claims, DateTime.UtcNow.AddMinutes(10)); + return Generate(claims, DateTime.UtcNow.AddMinutes(10)); } private string Generate(IEnumerable claims, DateTime expires) diff --git a/Source/CDR.DataRecipient.IntegrationTests/US12693_MDR_HostArrangementRevocation-JWT.cs b/Source/CDR.DataRecipient.IntegrationTests/US12693_MDR_HostArrangementRevocation-JWT.cs index 60011d2..3639745 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/US12693_MDR_HostArrangementRevocation-JWT.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/US12693_MDR_HostArrangementRevocation-JWT.cs @@ -721,7 +721,7 @@ public async Task AC11_Post_WithMinimalCDRArrangementJWT_ShouldRespondWith_204No response.StatusCode.Should().Be(HttpStatusCode.NoContent); } } - + [Fact] public async Task AC12_Post_WithInvalidClaimsValuesInJWT_ShouldRespondWith_400BadRequest_ErrorResponse() { diff --git a/Source/CDR.DataRecipient.IntegrationTests/US12964_MDR_Jwks.cs b/Source/CDR.DataRecipient.IntegrationTests/US12964_MDR_Jwks.cs index e0d1751..384536f 100644 --- a/Source/CDR.DataRecipient.IntegrationTests/US12964_MDR_Jwks.cs +++ b/Source/CDR.DataRecipient.IntegrationTests/US12964_MDR_Jwks.cs @@ -9,34 +9,34 @@ namespace CDR.DataRecipient.IntegrationTests { - public class US12964_MDR_Jwks : BaseTest - { - class Jwks_Expected - { - public class Key - { -#pragma warning disable IDE1006 - public string kty { get; set; } - public string use { get; set; } - public string kid { get; set; } - public string e { get; set; } + public class US12964_MDR_Jwks : BaseTest + { + class Jwks_Expected + { + public class Key + { +#pragma warning disable IDE1006 + public string kty { get; set; } + public string use { get; set; } + public string kid { get; set; } + public string e { get; set; } public string n { get; set; } public string alg { get; set; } #pragma warning restore IDE1006 } - public Key[] Keys { get; set; } - } + public Key[] Keys { get; set; } + } - [Fact] - public async Task AC01_Get_ShouldRespondWith_200OK_ValidJWKS() - { - // Arrange - var apiCall = new Infrastructure.API - { - HttpMethod = HttpMethod.Get, - URL = $"https://{HOSTNAME_DATARECIPIENT}:9001/jwks", - }; + [Fact] + public async Task AC01_Get_ShouldRespondWith_200OK_ValidJWKS() + { + // Arrange + var apiCall = new Infrastructure.API + { + HttpMethod = HttpMethod.Get, + URL = $"https://{HOSTNAME_DATARECIPIENT}:9001/jwks", + }; // Act var response = await apiCall.SendAsync(); @@ -62,5 +62,5 @@ public async Task AC01_Get_ShouldRespondWith_200OK_ValidJWKS() encKeyOaep256.Should().NotBeNull(); } } - } + } } 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 4d5fdef..933f5c6 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj +++ b/Source/CDR.DataRecipient.Repository.SQL/CDR.DataRecipient.Repository.SQL.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + True @@ -19,6 +20,14 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.DataRecipient.Repository.SQL/DbConstants.cs b/Source/CDR.DataRecipient.Repository.SQL/DbConstants.cs index 65e0746..d9a494b 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/DbConstants.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/DbConstants.cs @@ -10,4 +10,4 @@ public static class ConnectionStringNames public const string Cache = "DataRecipient_Cache"; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/CdrArrangement.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/CdrArrangement.cs index 30820a6..9548019 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/CdrArrangement.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/CdrArrangement.cs @@ -8,7 +8,7 @@ public class CdrArrangement [Key] [MaxLength(100)] public string CdrArrangementId { get; set; } - + [MaxLength(100)] public string ClientId { get; set; } @@ -16,4 +16,4 @@ public class CdrArrangement public string JsonDocument { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/DataHolderBrand.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/DataHolderBrand.cs index 51f957e..af357d5 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/DataHolderBrand.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/DataHolderBrand.cs @@ -7,7 +7,9 @@ public class DataHolderBrand { [Key] public Guid DataHolderBrandId { get; set; } + public string JsonDocument { get; set; } + public DateTime? LastUpdated { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/DcrMessage.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/DcrMessage.cs index e4fa8aa..c06cfc9 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/DcrMessage.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/DcrMessage.cs @@ -31,4 +31,4 @@ public class DcrMessage public DateTime LastUpdated { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/LogEventsDcrService.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/LogEventsDcrService.cs index e2fd4c0..f47d830 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/LogEventsDcrService.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/LogEventsDcrService.cs @@ -34,4 +34,4 @@ public class LogEventsDcrService [MaxLength(100)] public string SourceContext { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/Registration.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/Registration.cs index c31f1c1..db66270 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/Registration.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/Registration.cs @@ -8,8 +8,10 @@ public class Registration [Required] [MaxLength(100)] public string ClientId { get; set; } + [Required] public Guid DataHolderBrandId { get; set; } + public string JsonDocument { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Entities/SoftwareProduct.cs b/Source/CDR.DataRecipient.Repository.SQL/Entities/SoftwareProduct.cs index 886a425..c107caa 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Entities/SoftwareProduct.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Entities/SoftwareProduct.cs @@ -34,4 +34,4 @@ public class SoftwareProduct [MaxLength(25)] public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs b/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs index 560d396..eebed1b 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Extensions/SqlExtensions.cs @@ -1,14 +1,14 @@ -using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient; using System; namespace CDR.DataRecipient.Repository.SQL.Extensions { - static public class SqLExtensions + public static class SqLExtensions { /// - /// Execute scalar command and return result as Int32. Throw error if no results or conversion error + /// Execute scalar command and return result as Int32. Throw error if no results or conversion error. /// - static public Int32 ExecuteScalarInt32(this SqlCommand command) + public static int ExecuteScalarInt32(this SqlCommand command) { var res = command.ExecuteScalar(); @@ -21,9 +21,9 @@ static public Int32 ExecuteScalarInt32(this SqlCommand command) } /// - /// Execute scalar command and return result as string. Throw error if no results or conversion error + /// Execute scalar command and return result as string. Throw error if no results or conversion error. /// - static public string ExecuteScalarString(this SqlCommand command) + 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 5f2fa55..86cbcbf 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/ISqlDataAccess.cs @@ -7,9 +7,13 @@ namespace CDR.DataRecipient.Repository.SQL public interface ISqlDataAccess { Task DeleteCdrArrangementData(); + Task DeleteCdrArrangementData(string clientId); - Task DeleteRegistrationData(); + + Task DeleteRegistrationData(); + Task GetDataHolderBrand(string brandId); + Task> GetDataHolderBrands(); } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContext.cs b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContext.cs index 8953b7d..e2e64fb 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContext.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContext.cs @@ -7,20 +7,23 @@ public class RecipientDatabaseContext : DbContext { public RecipientDatabaseContext() { - } - public RecipientDatabaseContext(DbContextOptions options) : base(options) + public RecipientDatabaseContext(DbContextOptions options) + : base(options) { - } public DbSet CdrArrangements { get; set; } + public DbSet DataHolderBrands { get; set; } + public DbSet SoftwareProducts { get; set; } + public DbSet Registrations { get; set; } public DbSet DcrMessage { get; set; } + public DbSet LogEvents_DCRService { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -32,4 +35,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasKey(nameof(Registration.ClientId), nameof(Registration.DataHolderBrandId)); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs index ae01fec..12f0db3 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Infrastructure/RecipientDatabaseContextDesignTimeFactory.cs @@ -20,5 +20,5 @@ public RecipientDatabaseContext CreateDbContext(string[] args) return new RecipientDatabaseContext(options); } - } -} \ No newline at end of file + } +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/Migrations/20211216230342_InitSqlDb.cs b/Source/CDR.DataRecipient.Repository.SQL/Migrations/20211216230342_InitSqlDb.cs index b910541..9352049 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Migrations/20211216230342_InitSqlDb.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Migrations/20211216230342_InitSqlDb.cs @@ -13,7 +13,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { CdrArrangementId = table.Column(type: "uniqueidentifier", nullable: false), ClientId = table.Column(type: "uniqueidentifier", nullable: false), - JsonDocument = table.Column(type: "nvarchar(max)", nullable: true) + JsonDocument = table.Column(type: "nvarchar(max)", nullable: true), }, constraints: table => { @@ -25,7 +25,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { DataHolderBrandId = table.Column(type: "uniqueidentifier", nullable: false), - JsonDocument = table.Column(type: "nvarchar(max)", nullable: true) + JsonDocument = table.Column(type: "nvarchar(max)", nullable: true), }, constraints: table => { @@ -37,7 +37,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { ClientId = table.Column(type: "uniqueidentifier", nullable: false), - JsonDocument = table.Column(type: "nvarchar(max)", nullable: true) + JsonDocument = table.Column(type: "nvarchar(max)", nullable: true), }, constraints: table => { @@ -57,7 +57,7 @@ protected override void Up(MigrationBuilder migrationBuilder) RedirectUri = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), JwksUri = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), Scope = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), - Status = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: true) + Status = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: true), }, constraints: table => { diff --git a/Source/CDR.DataRecipient.Repository.SQL/Migrations/20220315025849_DCR_Message.cs b/Source/CDR.DataRecipient.Repository.SQL/Migrations/20220315025849_DCR_Message.cs index 3738cbd..efde66b 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/Migrations/20220315025849_DCR_Message.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/Migrations/20220315025849_DCR_Message.cs @@ -18,7 +18,7 @@ protected override void Up(MigrationBuilder migrationBuilder) MessageId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), MessageState = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: true), MessageError = table.Column(type: "nvarchar(max)", nullable: true), - LastUpdated = table.Column(type: "datetime", nullable: false) + LastUpdated = table.Column(type: "datetime", nullable: false), }, constraints: table => { @@ -39,7 +39,7 @@ protected override void Up(MigrationBuilder migrationBuilder) ProcessName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), ThreadId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), MethodName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - SourceContext = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true) + SourceContext = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), }, constraints: table => { diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs index d129b98..74424a0 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlConsentsRepository.cs @@ -11,69 +11,71 @@ namespace CDR.DataRecipient.Repository.SQL public class SqlConsentsRepository : IConsentsRepository { protected readonly IConfiguration _config; - public SqlDataAccess _sqlDataAccess { get; } + + public SqlDataAccess SqlDataAccess { get; } public SqlConsentsRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { _config = config; - _sqlDataAccess = new SqlDataAccess(_config, recipientDatabaseContext); + SqlDataAccess = new SqlDataAccess(_config, recipientDatabaseContext); } public async Task GetConsentByArrangement(string cdrArrangementId) - { - return await _sqlDataAccess.GetConsentByArrangement(cdrArrangementId); + { + return await 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 SqlDataAccess.GetConsents(clientId, dataHolderBrandId, userId); return cdrArrangements.OrderByDescending(x => x.CreatedOn); } public async Task PersistConsent(ConsentArrangement consentArrangement) - { + { var existingArrangement = await GetConsentByArrangement(consentArrangement.CdrArrangementId); if (existingArrangement == null) { - await _sqlDataAccess.InsertCdrArrangement(consentArrangement); + await SqlDataAccess.InsertCdrArrangement(consentArrangement); return; } - - await _sqlDataAccess.UpdateCdrArrangement(consentArrangement); + + await SqlDataAccess.UpdateCdrArrangement(consentArrangement); } public async Task UpdateTokens(string cdrArrangementId, string idToken, string accessToken, string refreshToken) - { + { var consent = await GetConsentByArrangement(cdrArrangementId); consent.IdToken = idToken; consent.AccessToken = accessToken; consent.RefreshToken = refreshToken; - - await _sqlDataAccess.UpdateCdrArrangement(consent); + + await SqlDataAccess.UpdateCdrArrangement(consent); } public async Task DeleteConsent(string cdrArrangementId) - { + { var consent = await GetConsentByArrangement(cdrArrangementId); if (!string.IsNullOrEmpty(consent?.CdrArrangementId)) { - await _sqlDataAccess.DeleteConsent(cdrArrangementId); - } + await SqlDataAccess.DeleteConsent(cdrArrangementId); + } } public async Task RevokeConsent(string cdrArrangementId, string dataHolderBrandId) - { + { var consent = await GetConsentByArrangement(cdrArrangementId); - - if (!string.IsNullOrEmpty(consent?.CdrArrangementId) && - string.Equals(consent?.DataHolderBrandId, dataHolderBrandId, StringComparison.OrdinalIgnoreCase)) + + if (!string.IsNullOrEmpty(consent?.CdrArrangementId) && + string.Equals(consent.DataHolderBrandId, dataHolderBrandId, StringComparison.OrdinalIgnoreCase)) { - await _sqlDataAccess.DeleteConsent(cdrArrangementId); + await SqlDataAccess.DeleteConsent(cdrArrangementId); return true; } + return false; - } + } } } diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs index 64891b2..d104b9d 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlDataAccess.cs @@ -16,20 +16,19 @@ namespace CDR.DataRecipient.Repository.SQL { public class SqlDataAccess : ISqlDataAccess { - public IConfiguration _config { get; } - public string _dbConn { get; set; } - protected readonly RecipientDatabaseContext _mdrDatabaseContext; + public IConfiguration Config { get; } + + public string DbConn { get; set; } public SqlDataAccess(IConfiguration configuration, RecipientDatabaseContext recipientDatabaseContext) { - _config = configuration; - _dbConn = _config.GetConnectionString(DbConstants.ConnectionStringNames.Default); - _mdrDatabaseContext = recipientDatabaseContext; + Config = configuration; + DbConn = Config.GetConnectionString(DbConstants.ConnectionStringNames.Default); } public SqlDataAccess(string connString) { - _dbConn = connString; + DbConn = connString; } #region CdrArragements @@ -37,9 +36,9 @@ public async Task GetConsentByArrangement(string cdrArrangem { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT JsonDocument FROM dbo.CdrArrangement WHERE CdrArrangementId = @id", db); sqlCommand.Parameters.AddWithValue("@id", cdrArrangementId); @@ -50,7 +49,7 @@ public async Task GetConsentByArrangement(string cdrArrangem var jsonDocument = Convert.ToString(res); var consentArrangement = JsonConvert.DeserializeObject(jsonDocument, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); - db.Close(); + await db.CloseAsync(); return consentArrangement; } @@ -60,15 +59,16 @@ public async Task GetConsentByArrangement(string cdrArrangem { return null; } + return null; } public async Task> GetConsents(string clientId, string dataHolderBrandId, string userId) { List cdrArrangements = new(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = new StringBuilder(); sqlQuery.Append("SELECT [CdrArrangementId], [ClientId], [JsonDocument], UserId FROM [CdrArrangement]"); @@ -88,7 +88,7 @@ public async Task> GetConsents(string clientId, } SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); - while (reader.Read()) + while (await reader.ReadAsync()) { var cdrArrangement = new ConsentArrangement(); var jsonDocument = Convert.ToString(reader.GetString(2)); @@ -100,7 +100,8 @@ public async Task> GetConsents(string clientId, cdrArrangements.Add(cdrArrangement); } } - db.Close(); + + await db.CloseAsync(); if (cdrArrangements.Count > 0) { @@ -109,51 +110,57 @@ public async Task> GetConsents(string clientId, arr.BrandName = await GetDataHolderBrandName(arr.DataHolderBrandId); } } + return cdrArrangements; } } public async Task InsertCdrArrangement(ConsentArrangement consentArrangement) { - using (SqlConnection db = new(_dbConn)) - { - db.Open(); + using SqlConnection db = new(DbConn); + await db.OpenAsync(); - var jsonDocument = JsonConvert.SerializeObject(consentArrangement); + var jsonDocument = JsonConvert.SerializeObject(consentArrangement); - //special case for CdrArrangements - jsonDocument = jsonDocument.Replace(@"""CreatedOn"":""0001-01-01T00:00:00""", @"""CreatedOn"": null"); - jsonDocument = jsonDocument.Replace(@"""ExpiresIn"":0", @"""ExpiresIn"": null"); + // special case for CdrArrangements + jsonDocument = jsonDocument.Replace(@"""CreatedOn"":""0001-01-01T00:00:00""", @"""CreatedOn"": null"); + jsonDocument = jsonDocument.Replace(@"""ExpiresIn"":0", @"""ExpiresIn"": null"); - var sqlQuery = ""; - if (string.IsNullOrEmpty(consentArrangement.UserId)) - sqlQuery = "INSERT INTO dbo.CdrArrangement(CdrArrangementId, ClientId, JsonDocument) VALUES(@arrangementId, @clientId, @jsonDocument)"; - else - sqlQuery = "INSERT INTO dbo.CdrArrangement(CdrArrangementId, ClientId, JsonDocument, UserId) VALUES(@arrangementId, @clientId, @jsonDocument, @userId)"; - - using var sqlCommand = new SqlCommand(sqlQuery, db); - sqlCommand.Parameters.AddWithValue("@arrangementId", consentArrangement.CdrArrangementId); - sqlCommand.Parameters.AddWithValue("@clientId", consentArrangement.ClientId); - sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); - - if (!string.IsNullOrEmpty(consentArrangement.UserId)) - sqlCommand.Parameters.AddWithValue("@userId", consentArrangement.UserId); - - await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + var sqlQuery = string.Empty; + if (string.IsNullOrEmpty(consentArrangement.UserId)) + { + sqlQuery = "INSERT INTO dbo.CdrArrangement(CdrArrangementId, ClientId, JsonDocument) VALUES(@arrangementId, @clientId, @jsonDocument)"; + } + else + { + sqlQuery = "INSERT INTO dbo.CdrArrangement(CdrArrangementId, ClientId, JsonDocument, UserId) VALUES(@arrangementId, @clientId, @jsonDocument, @userId)"; } + + using var sqlCommand = new SqlCommand(sqlQuery, db); + sqlCommand.Parameters.AddWithValue("@arrangementId", consentArrangement.CdrArrangementId); + sqlCommand.Parameters.AddWithValue("@clientId", consentArrangement.ClientId); + sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); + + if (!string.IsNullOrEmpty(consentArrangement.UserId)) + { + sqlCommand.Parameters.AddWithValue("@userId", consentArrangement.UserId); + } + + await sqlCommand.ExecuteNonQueryAsync(); + await db.CloseAsync(); } public async Task UpdateCdrArrangement(ConsentArrangement consentArrangement) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var jsonDocument = JsonConvert.SerializeObject(consentArrangement, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Include }); - //special case for CdrArrangements + + // special case for CdrArrangements jsonDocument = jsonDocument.Replace(@"""CreatedOn"":""0001-01-01T00:00:00""", @"""CreatedOn"": null"); - jsonDocument = jsonDocument.Replace(@"""ExpiresIn"":0", @"""ExpiresIn"": null"); + jsonDocument = jsonDocument.Replace(@"""ExpiresIn"":0", @"""ExpiresIn"": null"); var sqlQuery = "UPDATE dbo.CdrArrangement SET JsonDocument=@jsonDocument WHERE CdrArrangementId=@id"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -161,142 +168,120 @@ public async Task UpdateCdrArrangement(ConsentArrangement consentArrangement) sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } public async Task DeleteCdrArrangementData() { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement"; using var sqlCommand = new SqlCommand(sqlQuery, db); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } public async Task DeleteCdrArrangementData(string clientId) { - using (SqlConnection db = new SqlConnection(_dbConn)) + using (SqlConnection db = new SqlConnection(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement WHERE ClientId = @clientId"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("clientId", clientId); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } public async Task DeleteRegistrationData() { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.Registration"; using var sqlCommand = new SqlCommand(sqlQuery, db); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } public async Task DeleteConsent(string cdrArrangementId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.CdrArrangement WHERE CdrArrangementId=@id"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@id", cdrArrangementId); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } #endregion - #region CrdRegistrations + #region CrdRegistrations /// - /// Get the Registrations for this Data Holders + /// Get the Registrations for this Data Holders. /// - /// The STRING DataHolderBrandId + /// The STRING DataHolderBrandId. /// - /// This is called from Azure DiscoverDataHolders Function, it is used to add the DCR message to the queue + /// This is called from Azure DiscoverDataHolders Function, it is used to add the DCR message to the queue. /// - /// [true|false] + /// [true|false]. public async Task CheckRegistrationExist(string dhBrandId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT [DataHolderBrandId] FROM [Registration] WHERE [DataHolderBrandId] = @id", db); sqlCommand.Parameters.AddWithValue("@id", dhBrandId); var res = await sqlCommand.ExecuteScalarAsync(); - db.Close(); + await db.CloseAsync(); if (!string.IsNullOrEmpty(Convert.ToString(res))) + { return true; + } else + { return false; + } } } /// - /// Check if the Data Holder Brands are Registered - (Sandbox Mode) + /// Check if the Data Holder Brands are Registered - (Sandbox Mode). /// - /// List of Discovered Data Holders + /// List of Discovered Data Holders. /// - /// This is called from Azure DiscoverDataHolders Function, it is used to process the Insert and Update list + /// This is called from Azure DiscoverDataHolders Function, it is used to process the Insert list /// of data holders for use in performing the DCR. /// - /// Lists if data holders to be Registered - public async Task<(IList, IList)> CheckRegistrationsExist(IList newDhBrands) + /// Lists if data holders to be Registered. + public async Task> CheckRegistrationsExist(IList newDhBrands) { List dhBrandsIns = new List(); - List dhBrandsUpd = new List(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { foreach (var dh in newDhBrands) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT [JsonDocument] FROM [Registration] WHERE [DataHolderBrandId] = @id", db); sqlCommand.Parameters.AddWithValue("@id", dh.DataHolderBrandId.ToString()); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); - if (reader.HasRows) - { - bool update = false; - while (reader.Read() && !update) - { - var jsonDocument = Convert.ToString(reader.GetString(0)); - var reg = System.Text.Json.JsonSerializer.Deserialize(jsonDocument); - - if (!string.Equals(dh.BrandName, reg.BrandName)) - update = true; - } - if (update) - { - dhBrandsUpd.Add(new DataHolderBrand - { - DataHolderBrandId = dh.DataHolderBrandId.ToString(), - BrandName = dh.BrandName, - LastUpdated = dh.LastUpdated, - EndpointDetail = new EndpointDetail - { - InfoSecBaseUri = dh.EndpointDetail.InfoSecBaseUri - } - }); - } - } - else + if (!reader.HasRows) { dhBrandsIns.Add(new DataHolderBrand { @@ -305,22 +290,23 @@ public async Task CheckRegistrationExist(string dhBrandId) LastUpdated = dh.LastUpdated, EndpointDetail = new EndpointDetail { - InfoSecBaseUri = dh.EndpointDetail.InfoSecBaseUri - } + InfoSecBaseUri = dh.EndpointDetail.InfoSecBaseUri, + }, }); } - db.Close(); + await db.CloseAsync(); } } - return (dhBrandsIns, dhBrandsUpd); + + return dhBrandsIns; } public async Task GetRegistration(string clientId, string dataHolderBrandId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT [JsonDocument] FROM [Registration] WHERE [ClientId] = @clientId AND [DataHolderBrandId] = @dataHolderBrandId", db); sqlCommand.Parameters.AddWithValue("@clientId", clientId); @@ -332,62 +318,65 @@ public async Task GetRegistration(string clientId, string dataHold { var jsonDocument = Convert.ToString(res); var registration = System.Text.Json.JsonSerializer.Deserialize(jsonDocument); - db.Close(); + await db.CloseAsync(); return registration; } } + return null; } /// - /// Return the Registration detail from the local repo + /// Return the Registration detail from the local repo. /// - /// The registered DataHolderBrandId + /// The registered DataHolderBrandId. /// /// This is called from Azure DCR Function. /// - /// [true|false] + /// [true|false]. /// This needs to be string? public async Task GetRegByDHBrandId(string dhBrandId) { var clientId = string.Empty; - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT [ClientId], [DataHolderBrandId] FROM [Registration] WHERE [DataHolderBrandId] = @id", db); sqlCommand.Parameters.AddWithValue("@id", dhBrandId); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { clientId = reader.GetString(0); } } - db.Close(); + await db.CloseAsync(); } + return clientId; } public async Task> GetRegistrations() { List registrations = new(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT ClientId, JsonDocument FROM dbo.Registration", db); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); - while (reader.Read()) + while (await reader.ReadAsync()) { var jsonDocument = Convert.ToString(reader.GetString(1)); var registration = System.Text.Json.JsonSerializer.Deserialize(jsonDocument); registrations.Add(registration); } - db.Close(); + + await db.CloseAsync(); if (registrations.Count > 0) { @@ -396,15 +385,16 @@ public async Task> GetRegistrations() reg.BrandName = await GetDataHolderBrandName(reg.DataHolderBrandId); } } + return registrations; } } public async Task DeleteRegistration(string clientId, string dataHolderBrandId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlCommand = "DELETE FROM dbo.Registration WHERE [ClientId] = @clientId AND [DataHolderBrandId] = @dataHolderBrandId"; using var command = new SqlCommand(sqlCommand, db); @@ -412,7 +402,7 @@ public async Task DeleteRegistration(string clientId, string dataHolderBrandId) command.Parameters.AddWithValue("@dataHolderBrandId", dataHolderBrandId); await command.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } @@ -420,9 +410,9 @@ public async Task InsertRegistration(Registration registration) { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var dhBrandId = new Guid(registration.DataHolderBrandId); var jsonDocument = System.Text.Json.JsonSerializer.Serialize(registration); var sqlCommand = "INSERT INTO [Registration] ([ClientId], [DataHolderBrandId], [JsonDocument]) VALUES(@clientId, @dhBrndId, @jsonDoc)"; @@ -431,8 +421,9 @@ public async Task InsertRegistration(Registration registration) cmd.Parameters.AddWithValue("@dhBrndId", dhBrandId); cmd.Parameters.AddWithValue("@jsonDoc", jsonDocument); await cmd.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } + return true; } catch (Exception) @@ -442,59 +433,60 @@ public async Task InsertRegistration(Registration registration) } /// - /// Return a list of Registrations from the DcrMessage table in the local repo + /// Return a list of Registrations from the DcrMessage table in the local repo. /// /// /// This is used in the DCR View using the table data populated from the Azure DCR Function. /// - /// The list of registrations + /// The list of registrations. public async Task> GetDcrMessageRegistrations() { List registrations = new(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "SELECT [ClientId],[DataHolderBrandId],[BrandName],[MessageState],[LastUpdated] FROM [dbo].[DcrMessage] WHERE [MessageState] != @msgState"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@msgState", Message.Pending.ToString()); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { var registration = new Registration { - ClientId = reader.IsDBNull(0) ? "" : reader.GetString(0), + ClientId = reader.IsDBNull(0) ? string.Empty : reader.GetString(0), DataHolderBrandId = Convert.ToString(reader.GetGuid(1)), - BrandName = reader.IsDBNull(2) ? "" : reader.GetString(2), + BrandName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2), MessageState = reader.GetString(3), - LastUpdated = reader.GetDateTime(4) + LastUpdated = reader.GetDateTime(4), }; registrations.Add(registration); } } - db.Close(); + + await db.CloseAsync(); return registrations; } } /// - /// Insert the Registration details into the local repo + /// Insert the Registration details into the local repo. /// - /// ClientId as returned from the Register - /// The DataHolderBrandId being registered - /// The response as a json string + /// ClientId as returned from the Register. + /// The DataHolderBrandId being registered. + /// The response as a json string. /// /// This is called from Azure DCR Function, it updates the local repo after performing the DCR in the Register. /// - /// [true|false] + /// [true|false]. public async Task InsertDcrRegistration(string regClientId, string dcrDHBrandId, string jsonDocument) { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var dhBrandId = new Guid(dcrDHBrandId); var sqlQuery = "INSERT INTO [Registration] ([ClientId], [DataHolderBrandId], [JsonDocument]) VALUES(@clientId, @dhBrndId, @jsonDoc)"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -502,8 +494,9 @@ public async Task InsertDcrRegistration(string regClientId, string dcrDHBr sqlCommand.Parameters.AddWithValue("@dhBrndId", dhBrandId); sqlCommand.Parameters.AddWithValue("@jsonDoc", jsonDocument); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } + return true; } catch (Exception) @@ -514,20 +507,20 @@ public async Task InsertDcrRegistration(string regClientId, string dcrDHBr public async Task UpdateRegistration(Registration registration) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); - var jsonDocument = System.Text.Json.JsonSerializer.Serialize(registration); + var jsonDocument = System.Text.Json.JsonSerializer.Serialize(registration); var sqlQuery = "UPDATE dbo.Registration SET JsonDocument=@jsonDocument WHERE [ClientId] = @clientId AND [DataHolderBrandId] = @dataHolderBrandId"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@clientId", registration.ClientId); sqlCommand.Parameters.AddWithValue("@dataHolderBrandId", registration.DataHolderBrandId); - sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); + sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } @@ -537,9 +530,9 @@ public async Task UpdateRegistration(Registration registration) public async Task GetDataHolderBrand(string brandId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT JsonDocument FROM dbo.DataHolderBrand WHERE DataHolderBrandId = @id", db); sqlCommand.Parameters.AddWithValue("@id", brandId); @@ -551,18 +544,19 @@ public async Task GetDataHolderBrand(string brandId) var jsonDocument = Convert.ToString(res); var dataholderbrand = System.Text.Json.JsonSerializer.Deserialize(jsonDocument); - db.Close(); + await db.CloseAsync(); return dataholderbrand; } } + return null; } public async Task GetDataHolderBrandName(string brandId) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT JsonDocument FROM dbo.DataHolderBrand WHERE DataHolderBrandId = @id", db); sqlCommand.Parameters.AddWithValue("@id", brandId); @@ -573,25 +567,26 @@ public async Task GetDataHolderBrandName(string brandId) var jsonDocument = Convert.ToString(res); var dataholderbrand = System.Text.Json.JsonSerializer.Deserialize(jsonDocument); - db.Close(); + await db.CloseAsync(); return dataholderbrand.BrandName; } } + return null; } public async Task> GetDataHolderBrands() { List dataHolderBrands = new(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT DataHolderBrandId, JsonDocument FROM dbo.DataHolderBrand", db); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { var dataHolderBrandjson = reader.GetString(1); var jsonDocument = Convert.ToString(dataHolderBrandjson); @@ -599,7 +594,7 @@ public async Task> GetDataHolderBrands() } } - db.Close(); + await db.CloseAsync(); return dataHolderBrands .OrderBy(x => x.LegalEntity.LegalEntityName) @@ -610,38 +605,38 @@ public async Task> GetDataHolderBrands() public async Task DataHolderBrandsDelete() { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM dbo.DataHolderBrand"; using var sqlCommand = new SqlCommand(sqlQuery, db); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } /// - /// Aggregate the Data Holders - updates the repo (Mock Mode) + /// Aggregate the Data Holders - updates the repo (Mock Mode). /// - /// List of Discovered Data Holders - /// Count of data holders to be inserted and updated - public async Task<(int, int)> AggregateDataHolderBrands(IList dhBrandsNew) + /// List of Discovered Data Holders. + /// Count of data holders to be inserted and updated. + public async Task<(int DhBrandsInserted, int DhBrandsUpdated)> AggregateDataHolderBrands(IList dhBrandsNew) { - List dhBrandsIns = new List(); - List dhBrandsUpd = new List(); + List dhBrandsIns = []; + List dhBrandsUpd = []; - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); List dataHolderBrandsOrig = new List(); using var sqlCommand = new SqlCommand("SELECT [DataHolderBrandId], [JsonDocument], [LastUpdated] FROM [DataHolderBrand]", db); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { // NB: LastUpdated populated from jsonDocument when deserialised through entity // this is used below to compare with received data to build updated list where LastUpdated is newer @@ -650,7 +645,7 @@ public async Task DataHolderBrandsDelete() } } - db.Close(); + await db.CloseAsync(); if (dataHolderBrandsOrig.Count > 0) { @@ -669,6 +664,7 @@ public async Task DataHolderBrandsDelete() { dhBrandsIns.ToList().ForEach(async dataholder => await InsertDataHolder(dataholder)); } + if (dhBrandsUpd.Count > 0) { dhBrandsUpd.ToList().ForEach(async dataholder => await UpdateDataHolder(dataholder)); @@ -684,33 +680,35 @@ public async Task DataHolderBrandsDelete() } } } + return (dhBrandsIns.Count, dhBrandsUpd.Count); } /// - /// Insert the Data Holder into the repo + /// Insert the Data Holder into the repo. /// - /// The Data Holder to be inserted + /// The Data Holder to be inserted. /// /// This is called from above to Insert the Data Holder into the repo. /// This is also called from Azure DiscoverDataHolders Function, it is used to Insert the data holder after performing the DCR. /// - /// Boolean status only consumed in the Azure Functions DiscoverDataHolders + /// Boolean status only consumed in the Azure Functions DiscoverDataHolders. public async Task InsertDataHolder(DataHolderBrand dataholder) { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var jsonDocument = System.Text.Json.JsonSerializer.Serialize(dataholder); var sqlQuery = "INSERT INTO [DataHolderBrand] ([DataHolderBrandId], [JsonDocument], [LastUpdated]) VALUES (@dhBrndId, @jsonDoc, GETUTCDATE())"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@dhBrndId", dataholder.DataHolderBrandId); sqlCommand.Parameters.AddWithValue("@jsonDoc", jsonDocument); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } + return true; } catch (Exception) @@ -720,31 +718,32 @@ public async Task InsertDataHolder(DataHolderBrand dataholder) } /// - /// Update the Data Holder + /// Update the Data Holder. /// - /// The Data Holder to be updated + /// The Data Holder to be updated. /// /// This is called from above to Update the Data Holder in the repo. /// This is also called from Azure DiscoverDataHolders Function, it is used to Upate the data holder after performing the DCR. /// - /// Boolean status only consumed in the Azure Functions DiscoverDataHolders + /// Boolean status only consumed in the Azure Functions DiscoverDataHolders. public async Task UpdateDataHolder(DataHolderBrand dataholder) { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); - var jsonDocument = System.Text.Json.JsonSerializer.Serialize(dataholder); + var jsonDocument = System.Text.Json.JsonSerializer.Serialize(dataholder); var sqlQuery = "UPDATE [DataHolderBrand] SET [JsonDocument] = @jsonDocument, [LastUpdated] = GETUTCDATE() WHERE [DataHolderBrandId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@id", dataholder.DataHolderBrandId); sqlCommand.Parameters.AddWithValue("@jsonDocument", jsonDocument); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } + return true; } catch (Exception) @@ -754,56 +753,56 @@ public async Task UpdateDataHolder(DataHolderBrand dataholder) } /// - /// Delete the Data Holder into the repo + /// Delete the Data Holder into the repo. /// - /// The Data Holder to be deleted + /// The Data Holder to be deleted. /// /// This is called from above to delete the Data Holder into the repo. /// This is also called from Azure DiscoverDataHolders Function, it is used to delete the data holder after performing the DCR. /// - /// Boolean status only consumed in the Azure Functions DiscoverDataHolders + /// Boolean status only consumed in the Azure Functions DiscoverDataHolders. public async Task DeleteDataHolder(string dataholderBrndId) { try { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); - - var sqlQuery = "SELECT [ClientId] FROM [dbo].[DcrMessage] WHERE [DataHolderBrandId] = @dcrBrandId"; + await db.OpenAsync(); + + var sqlQuery = "SELECT [ClientId] FROM [dbo].[DcrMessage] WHERE [DataHolderBrandId] = @dcrBrandId"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@dcrBrandId", dataholderBrndId); - + var dcrClientId = await sqlCommand.ExecuteScalarAsync(); - if (!String.IsNullOrEmpty(dcrClientId.ToString())) + if (!string.IsNullOrEmpty(dcrClientId.ToString())) { - //Remove cdrArrangements + // Remove cdrArrangements sqlQuery = "DELETE FROM [CdrArrangement] WHERE [ClientId] = @cdrClientId"; using var sqlCdrCommand = new SqlCommand(sqlQuery, db); sqlCdrCommand.Parameters.AddWithValue("@cdrClientId", dcrClientId.ToString()); await sqlCdrCommand.ExecuteNonQueryAsync(); - //Remove Registrations + // Remove Registrations sqlQuery = "DELETE FROM [Registration] WHERE [ClientId] = @regClientId"; using var sqlRegCommand = new SqlCommand(sqlQuery, db); sqlRegCommand.Parameters.AddWithValue("@regClientId", dcrClientId.ToString()); await sqlRegCommand.ExecuteNonQueryAsync(); } - //Remove DcrMessage + // Remove DcrMessage sqlQuery = "DELETE FROM [dbo].[DcrMessage] WHERE [DataHolderBrandId] = @dcrDataHolderBrandId"; using var sqldcrDhBrandQuery = new SqlCommand(sqlQuery, db); sqldcrDhBrandQuery.Parameters.AddWithValue("@dcrDataHolderBrandId", dataholderBrndId); await sqldcrDhBrandQuery.ExecuteNonQueryAsync(); - //Remove DataHolder brands + // Remove DataHolder brands sqlQuery = "DELETE FROM [DataHolderBrand] WHERE [DataHolderBrandId] = @dhBrandId"; using var sqldhBrandQuery = new SqlCommand(sqlQuery, db); sqldhBrandQuery.Parameters.AddWithValue("@dhBrandId", dataholderBrndId); await sqldhBrandQuery.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } return true; @@ -816,17 +815,19 @@ public async Task DeleteDataHolder(string dataholderBrndId) public async Task PersistDataHolderBrands(IEnumerable dataHolderBrands) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM DataHolderBrand"; using var sqlCommand = new SqlCommand(sqlQuery, db); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); if (dataHolderBrands.Any()) + { dataHolderBrands.ToList().ForEach(async dataholder => await InsertDataHolder(dataholder)); + } } } @@ -834,15 +835,15 @@ public async Task> GetSoftwareProducts() { List rtnList = new(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); using var sqlCommand = new SqlCommand("SELECT SoftwareProductId, SoftwareProductName FROM SoftwareProduct", db); SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { var swProductId = reader.GetGuid(0); var swProductName = reader.GetString(1); @@ -850,12 +851,12 @@ public async Task> GetSoftwareProducts() rtnList.Add(new DataRecipientViewModel { SoftwareProductId = swProductId.ToString(), - SoftwareProductName = swProductName + SoftwareProductName = swProductName, }); } } - db.Close(); + await db.CloseAsync(); return rtnList; } } @@ -864,13 +865,13 @@ public async Task PersistSoftwareProducts(IEnumerable dataRe { if (dataRecipients.Any()) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "DELETE FROM SoftwareProduct"; using var sqlCommand = new SqlCommand(sqlQuery, db); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); dataRecipients.ToList().ForEach(async dataRecipient => await InsertSoftwareProduct(dataRecipient.DataRecipientBrands)); } @@ -885,15 +886,15 @@ private async Task InsertSoftwareProduct(IEnumerable drBrands) return; } - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + 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); @@ -910,24 +911,25 @@ private async Task InsertSoftwareProduct(IEnumerable drBrands) } } } - db.Close(); + + await db.CloseAsync(); } } /// - /// Check if the DataHolderBrandId exist + /// Check if the DataHolderBrandId exist. /// - /// The DataHolderBrandId + /// The DataHolderBrandId. /// - /// This is called from Azure DiscoverDataHolders and DCR Functions, to prevent multiple queue entries for the same DataHolderBrandId + /// This is called from Azure DiscoverDataHolders and DCR Functions, to prevent multiple queue entries for the same DataHolderBrandId. /// - /// The DataHolderBrandId and BrandName + /// The DataHolderBrandId and BrandName. public async Task GetDHBrandById(string dhBrandId) { DataHolderBrand dh = new DataHolderBrand(); - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "SELECT [DataHolderBrandId], [BrandName], [InfosecBaseUri] FROM [DcrMessage] WHERE [DataHolderBrandId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -935,37 +937,38 @@ public async Task GetDHBrandById(string dhBrandId) SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { dh.DataHolderBrandId = dhBrandId; dh.BrandName = reader.GetString(1); dh.EndpointDetail = new EndpointDetail { - InfoSecBaseUri = reader.GetString(2) + InfoSecBaseUri = reader.GetString(2), }; } } - db.Close(); + await db.CloseAsync(); } + return dh; } /// - /// Check if the queue message exists by DataHolderBrandId + /// Check if the queue message exists by DataHolderBrandId. /// - /// The DataHolderBrandId + /// The DataHolderBrandId. /// - /// This is called from Azure DiscoverDataHolders and DCR Functions, to prevent multiple queue entries for the same DataHolderBrandId + /// This is called from Azure DiscoverDataHolders and DCR Functions, to prevent multiple queue entries for the same DataHolderBrandId. /// - /// The MessageId and the MessageState - public async Task<(string msgId, string msgState)> CheckDcrMessageExistByDHBrandId(string dhBrandId) + /// The MessageId and the MessageState. + public async Task<(string MsgId, string MsgState)> CheckDcrMessageExistByDHBrandId(string dhBrandId) { - string msgId = ""; - string msgState = ""; - using (SqlConnection db = new(_dbConn)) + string msgId = string.Empty; + string msgState = string.Empty; + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "SELECT [MessageId], [MessageState] FROM [DcrMessage] WHERE [DataHolderBrandId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -973,33 +976,34 @@ public async Task GetDHBrandById(string dhBrandId) SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { msgId = reader.GetString(0); msgState = reader.GetString(1); } } - db.Close(); + await db.CloseAsync(); } + return (msgId, msgState); } /// - /// Check if the queue message exists by the Queue MessageId + /// Check if the queue message exists by the Queue MessageId. /// - /// The message object to be inserted + /// The message object to be inserted. /// - /// This is called from Azure Functions DiscoverDataHolders, to prevent multiple queue entries for the same DataHolderBrandId + /// This is called from Azure Functions DiscoverDataHolders, to prevent multiple queue entries for the same DataHolderBrandId. /// - /// The MessageId and the MessageState - public async Task<(string, string)> CheckDcrMessageExistByMessageId(string dhMessageId) + /// The MessageId and the MessageState. + public async Task<(string MessageId, string MessageState)> CheckDcrMessageExistByMessageId(string dhMessageId) { - string msgId = ""; - string msgState = ""; - using (SqlConnection db = new(_dbConn)) + string msgId = string.Empty; + string msgState = string.Empty; + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "SELECT [MessageId], [MessageState] FROM [DcrMessage] WHERE [MessageId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); @@ -1007,45 +1011,45 @@ public async Task GetDHBrandById(string dhBrandId) SqlDataReader reader = await sqlCommand.ExecuteReaderAsync(); if (reader.HasRows) { - while (reader.Read()) + while (await reader.ReadAsync()) { msgId = reader.GetString(0); msgState = reader.GetString(1); } } - db.Close(); + await db.CloseAsync(); } + return (msgId, msgState); } /// - /// Insert the queue message status + /// Insert the queue message status. /// - /// The message object to be inserted + /// The message object to be inserted. /// - /// This is called from Azure DiscoverDataHolders Function + /// This is called from Azure DiscoverDataHolders Function. /// - /// [true|false] + /// [true|false]. public async Task InsertDcrMessage(DcrMessage dcrMessage) { try { - using (SqlConnection db = new(_dbConn)) - { - db.Open(); - 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); - sqlCommand.Parameters.AddWithValue("@dhBrandId", dcrMessage.DataHolderBrandId); - sqlCommand.Parameters.AddWithValue("@msgId", dcrMessage.MessageId); - sqlCommand.Parameters.AddWithValue("@msgState", dcrMessage.MessageState); - sqlCommand.Parameters.AddWithValue("@msgErr", string.IsNullOrEmpty(dcrMessage.MessageError) ? DBNull.Value : dcrMessage.MessageError); - sqlCommand.Parameters.AddWithValue("@clientId", string.IsNullOrEmpty(dcrMessage.ClientId) ? DBNull.Value : dcrMessage.ClientId); - sqlCommand.Parameters.AddWithValue("@brandName", string.IsNullOrEmpty(dcrMessage.BrandName) ? DBNull.Value : dcrMessage.BrandName); - sqlCommand.Parameters.AddWithValue("@infosecBaseUri", dcrMessage.InfosecBaseUri); - await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); - } + using SqlConnection db = new(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); + sqlCommand.Parameters.AddWithValue("@dhBrandId", dcrMessage.DataHolderBrandId); + sqlCommand.Parameters.AddWithValue("@msgId", dcrMessage.MessageId); + sqlCommand.Parameters.AddWithValue("@msgState", dcrMessage.MessageState); + sqlCommand.Parameters.AddWithValue("@msgErr", string.IsNullOrEmpty(dcrMessage.MessageError) ? DBNull.Value : dcrMessage.MessageError); + sqlCommand.Parameters.AddWithValue("@clientId", string.IsNullOrEmpty(dcrMessage.ClientId) ? DBNull.Value : dcrMessage.ClientId); + sqlCommand.Parameters.AddWithValue("@brandName", string.IsNullOrEmpty(dcrMessage.BrandName) ? DBNull.Value : dcrMessage.BrandName); + sqlCommand.Parameters.AddWithValue("@infosecBaseUri", dcrMessage.InfosecBaseUri); + await sqlCommand.ExecuteNonQueryAsync(); + await db.CloseAsync(); + return true; } catch (Exception) @@ -1055,20 +1059,20 @@ public async Task InsertDcrMessage(DcrMessage dcrMessage) } /// - /// Update the DcrMessage MessageState and MessageError by DataHolderBrandId + /// Update the DcrMessage MessageState and MessageError by DataHolderBrandId. /// - /// The message object to be updated + /// The message object to be updated. /// - /// This is called from Azure Functions DCR + /// This is called from Azure Functions DCR. /// public async Task UpdateDcrMsgByDHBrandId(DcrMessage dcrMessage) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [BrandName] = @brandName, [InfosecBaseUri] = @infosecBaseUri, [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE(), [ClientId] = @clientId WHERE [DataHolderBrandId] = @id"; - + using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@id", dcrMessage.DataHolderBrandId); sqlCommand.Parameters.AddWithValue("@msgState", dcrMessage.MessageState); @@ -1077,45 +1081,46 @@ public async Task UpdateDcrMsgByDHBrandId(DcrMessage dcrMessage) sqlCommand.Parameters.AddWithValue("@brandName", string.IsNullOrEmpty(dcrMessage.BrandName) ? DBNull.Value : dcrMessage.BrandName); sqlCommand.Parameters.AddWithValue("@infosecBaseUri", string.IsNullOrEmpty(dcrMessage.InfosecBaseUri) ? DBNull.Value : dcrMessage.InfosecBaseUri); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } /// - /// Update the DcrMessage MessageState and MessageError by the Queue MessageId + /// Update the DcrMessage MessageState and MessageError by the Queue MessageId. /// - /// The message object to be updated + /// The message object to be updated. /// msgState Abandoned brand name not available and can be ignored /// - /// This is called from Azure Functions DCR + /// This is called from Azure Functions DCR. /// public async Task UpdateDcrMsgByMessageId(DcrMessage dcrMessage) { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@id", dcrMessage.MessageId); sqlCommand.Parameters.AddWithValue("@msgState", dcrMessage.MessageState); sqlCommand.Parameters.AddWithValue("@msgErr", string.IsNullOrEmpty(dcrMessage.MessageError) ? DBNull.Value : dcrMessage.MessageError); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } /// - /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId + /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId. /// - /// The message object to be updated + /// The message object to be updated. /// This is called from Azure DCR Functions - /// BrandName not available when DCR + /// + /// BrandName not available when DCR. /// public async Task UpdateDcrMsgReplaceMessageIdWithoutBrand(DcrMessage dcrMessage, string replacementMsgId = "") { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageId] = @replaceMsgId, [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@replaceMsgId", replacementMsgId); @@ -1123,24 +1128,23 @@ public async Task UpdateDcrMsgReplaceMessageIdWithoutBrand(DcrMessage dcrMessage sqlCommand.Parameters.AddWithValue("@msgErr", string.IsNullOrEmpty(dcrMessage.MessageError) ? DBNull.Value : dcrMessage.MessageError); sqlCommand.Parameters.AddWithValue("@id", dcrMessage.MessageId); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } - /// - /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId + /// Update the DcrMessage MessageId (new added queue item id), MessageState and MessageError by the Queue MessageId. /// - /// The message object to be updated + /// The message object to be updated. /// Update BrandName for Discovery Data Holder /// - /// This is called from Azure DiscoverDataHolders + /// This is called from Azure DiscoverDataHolders. /// public async Task UpdateDcrMsgReplaceMessageId(DcrMessage dcrMessage, string replacementMsgId = "") { - using (SqlConnection db = new(_dbConn)) + using (SqlConnection db = new(DbConn)) { - db.Open(); + await db.OpenAsync(); var sqlQuery = "UPDATE [DcrMessage] SET [MessageId] = @replaceMsgId, [BrandName] = @brandName, [InfosecBaseUri] = @infosecBaseUri, [MessageState] = @msgState, [MessageError] = @msgErr, [LastUpdated] = GETUTCDATE() WHERE [MessageId] = @id"; using var sqlCommand = new SqlCommand(sqlQuery, db); sqlCommand.Parameters.AddWithValue("@replaceMsgId", replacementMsgId); @@ -1148,13 +1152,13 @@ public async Task UpdateDcrMsgReplaceMessageId(DcrMessage dcrMessage, string rep sqlCommand.Parameters.AddWithValue("@msgErr", string.IsNullOrEmpty(dcrMessage.MessageError) ? DBNull.Value : dcrMessage.MessageError); sqlCommand.Parameters.AddWithValue("@id", dcrMessage.MessageId); sqlCommand.Parameters.AddWithValue("@brandName", string.IsNullOrEmpty(dcrMessage.BrandName) ? DBNull.Value : dcrMessage.BrandName); - sqlCommand.Parameters.AddWithValue("@infosecBaseUri", string.IsNullOrEmpty(dcrMessage.InfosecBaseUri) ? DBNull.Value : dcrMessage.InfosecBaseUri); + sqlCommand.Parameters.AddWithValue("@infosecBaseUri", string.IsNullOrEmpty(dcrMessage.InfosecBaseUri) ? DBNull.Value : dcrMessage.InfosecBaseUri); await sqlCommand.ExecuteNonQueryAsync(); - db.Close(); + await db.CloseAsync(); } } #endregion } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs index 2c14aed..5817944 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlDataHoldersRepository.cs @@ -8,48 +8,46 @@ namespace CDR.DataRecipient.Repository.SQL { public class SqlDataHoldersRepository : IDataHoldersRepository { - protected readonly IConfiguration _config; - public SqlDataAccess _sqlDataAccess { get; } + public SqlDataAccess SqlDataAccess { get; } public SqlDataHoldersRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { - _config = config; - _sqlDataAccess = new SqlDataAccess(_config, recipientDatabaseContext); + SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); } public async Task GetDataHolderBrand(string brandId) - { - var dataHolderBrand = await _sqlDataAccess.GetDataHolderBrand(brandId); + { + var dataHolderBrand = await SqlDataAccess.GetDataHolderBrand(brandId); return dataHolderBrand; } public async Task GetDHBrandById(string brandId) { - var dataHolderBrand = await _sqlDataAccess.GetDHBrandById(brandId); + var dataHolderBrand = await SqlDataAccess.GetDHBrandById(brandId); return dataHolderBrand; } public async Task> GetDataHolderBrands() - { - return await _sqlDataAccess.GetDataHolderBrands(); + { + return await SqlDataAccess.GetDataHolderBrands(); } public async Task DataHolderBrandsDelete() { // Delete existing data first then add all data - await _sqlDataAccess.DataHolderBrandsDelete(); + await SqlDataAccess.DataHolderBrandsDelete(); } public async Task<(int, int)> AggregateDataHolderBrands(IList dataHolderBrands) { // Aggregate Old with New data - return await _sqlDataAccess.AggregateDataHolderBrands(dataHolderBrands); + return await SqlDataAccess.AggregateDataHolderBrands(dataHolderBrands); } public async Task PersistDataHolderBrands(IEnumerable dataHolderBrands) { // Delete existing data first then add all data - await _sqlDataAccess.PersistDataHolderBrands(dataHolderBrands); + await SqlDataAccess.PersistDataHolderBrands(dataHolderBrands); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs b/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs index 767e91b..5b6b504 100644 --- a/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs +++ b/Source/CDR.DataRecipient.Repository.SQL/SqlRegistrationsRepository.cs @@ -9,61 +9,59 @@ namespace CDR.DataRecipient.Repository.SQL { public class SqlRegistrationsRepository : IRegistrationsRepository { - protected readonly IConfiguration _config; - public SqlDataAccess _sqlDataAccess { get; } + public SqlDataAccess SqlDataAccess { get; } - public SqlRegistrationsRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) + public SqlRegistrationsRepository(IConfiguration config, RecipientDatabaseContext recipientDatabaseContext) { - _config = config; - _sqlDataAccess = new SqlDataAccess(_config, recipientDatabaseContext); + SqlDataAccess = new SqlDataAccess(config, recipientDatabaseContext); } public async Task GetRegistration(string clientId, string dataHolderBrandId) - { - return await _sqlDataAccess.GetRegistration(clientId, dataHolderBrandId); + { + return await SqlDataAccess.GetRegistration(clientId, dataHolderBrandId); } public async Task> GetRegistrations() - { - return await _sqlDataAccess.GetRegistrations(); + { + return await SqlDataAccess.GetRegistrations(); } public async Task> GetDcrMessageRegistrations() { - return await _sqlDataAccess.GetDcrMessageRegistrations(); + return await SqlDataAccess.GetDcrMessageRegistrations(); } public async Task DeleteRegistration(string clientId, string dataHolderBrandId) - { + { var registration = await GetRegistration(clientId, dataHolderBrandId); - //Delete existing data. + // Delete existing data. if (!string.IsNullOrEmpty(registration?.ClientId)) { - await _sqlDataAccess.DeleteRegistration(clientId, dataHolderBrandId); - await _sqlDataAccess.DeleteCdrArrangementData(clientId); + await SqlDataAccess.DeleteRegistration(clientId, dataHolderBrandId); + await SqlDataAccess.DeleteCdrArrangementData(clientId); } } - //Check is DH id is present + // Check is DH id is present public async Task PersistRegistration(Registration registration) - { + { var existingRegistration = await GetRegistration(registration.ClientId, registration.DataHolderBrandId); if (string.IsNullOrEmpty(existingRegistration?.ClientId)) { - await _sqlDataAccess.InsertRegistration(registration); + await SqlDataAccess.InsertRegistration(registration); } } public async Task UpdateRegistration(Registration registration) - { - var _registration = await GetRegistration(registration.ClientId, registration.DataHolderBrandId); + { + var currentRegstration = await GetRegistration(registration.ClientId, registration.DataHolderBrandId); - //Update existing data. - if (!string.IsNullOrEmpty(_registration?.ClientId)) + // Update existing data. + if (!string.IsNullOrEmpty(currentRegstration?.ClientId)) { - await _sqlDataAccess.UpdateRegistration(registration); + await SqlDataAccess.UpdateRegistration(registration); } } } diff --git a/Source/CDR.DataRecipient.SDK/CDR.DataRecipient.SDK.csproj b/Source/CDR.DataRecipient.SDK/CDR.DataRecipient.SDK.csproj index bd7173e..862401d 100644 --- a/Source/CDR.DataRecipient.SDK/CDR.DataRecipient.SDK.csproj +++ b/Source/CDR.DataRecipient.SDK/CDR.DataRecipient.SDK.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + True @@ -12,6 +13,14 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.DataRecipient.SDK/Constants.cs b/Source/CDR.DataRecipient.SDK/Constants.cs index 1bea063..5f19420 100644 --- a/Source/CDR.DataRecipient.SDK/Constants.cs +++ b/Source/CDR.DataRecipient.SDK/Constants.cs @@ -45,4 +45,4 @@ public static class ErrorTitles public const string InvalidHeader = "Invalid Header"; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Enumerations.cs b/Source/CDR.DataRecipient.SDK/Enumerations.cs index 591bdc5..bc4c7bb 100644 --- a/Source/CDR.DataRecipient.SDK/Enumerations.cs +++ b/Source/CDR.DataRecipient.SDK/Enumerations.cs @@ -5,6 +5,6 @@ public enum Industry ALL, BANKING, ENERGY, - TELCO + TELCO, } } diff --git a/Source/CDR.DataRecipient.SDK/Exceptions/NoHttpsException.cs b/Source/CDR.DataRecipient.SDK/Exceptions/NoHttpsException.cs index 245a1f3..78c636f 100644 --- a/Source/CDR.DataRecipient.SDK/Exceptions/NoHttpsException.cs +++ b/Source/CDR.DataRecipient.SDK/Exceptions/NoHttpsException.cs @@ -9,6 +9,9 @@ namespace CDR.DataRecipient.SDK.Exceptions { public class NoHttpsException : SecurityException { - public NoHttpsException() : base("A non-https endpoint has been encountered and blocked") { } + public NoHttpsException() + : base("A non-https endpoint has been encountered and blocked") + { + } } } diff --git a/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs index f2c1cc6..ebe17d8 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/HttpExtensions.cs @@ -12,7 +12,6 @@ namespace CDR.DataRecipient.SDK.Extensions { public static class HttpExtensions { - public static void SetClientCertificate(this HttpClientHandler clientHandler, string certificateFileName, string certificatePassword) { clientHandler.ClientCertificates.Add(new X509Certificate2(certificateFileName, certificatePassword, X509KeyStorageFlags.Exportable)); @@ -25,7 +24,7 @@ public static bool IsSuccessful(this HttpStatusCode statusCode) public static int ToInt(this HttpStatusCode statusCode) { - return ((int)statusCode); + return (int)statusCode; } public static async Task SendPrivateKeyJwtRequest( diff --git a/Source/CDR.DataRecipient.SDK/Extensions/StringExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/StringExtensions.cs index 10873a8..bcce808 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/StringExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/StringExtensions.cs @@ -8,38 +8,40 @@ public static class StringExtensions { public static string Sha256(this string value) { - // ComputeHash - returns byte array + // ComputeHash - returns byte array byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(value)); - // Convert byte array to a string + // Convert byte array to a string var builder = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { builder.Append(bytes[i].ToString("x2")); } + return builder.ToString(); } public static string Sha1(this string value) { - // ComputeHash - returns byte array + // ComputeHash - returns byte array byte[] bytes = SHA1.HashData(Encoding.UTF8.GetBytes(value)); - // Convert byte array to a string + // Convert byte array to a string var builder = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { builder.Append(bytes[i].ToString("x2")); } + return builder.ToString(); } - public static (string errorCode, string errorTitle, string errorDescription) ParseErrorString(this string value, string defaultErrorTitle = "error", string defaultErrorCode = "error", string defaultErrorDescription = "An error has occured") + public static (string ErrorCode, string ErrorTitle, string ErrorDescription) ParseErrorString(this string value, string defaultErrorTitle = "error", string defaultErrorCode = "error", string defaultErrorDescription = "An error has occured") { int charLocation = value.IndexOf(':', StringComparison.Ordinal); var errorCode = charLocation > 0 ? value.Substring(0, charLocation) : defaultErrorCode; var errorTitle = defaultErrorTitle; - var errorDescription= charLocation > 0 ? value.Substring(charLocation + 1) : defaultErrorDescription; + var errorDescription = charLocation > 0 ? value.Substring(charLocation + 1) : defaultErrorDescription; return (errorCode, errorTitle, errorDescription); } diff --git a/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs index cd3dbf4..41e25ef 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/TokenExtensions.cs @@ -14,7 +14,7 @@ namespace CDR.DataRecipient.SDK.Extensions { public static class TokenExtensions { - public static async Task<(bool IsValid, JwtSecurityToken ValidatedToken, ClaimsPrincipal ClaimsPrincipal, Models.Error validationError)> ValidateToken( + public static async Task<(bool IsValid, JwtSecurityToken ValidatedToken, ClaimsPrincipal ClaimsPrincipal, Models.Error ValidationError)> ValidateToken( this string jwt, string jwksUri, ILogger logger, @@ -28,11 +28,11 @@ public static class TokenExtensions var jwks = await jwksUri.GetJwks(acceptAnyServerCertificate, enforceHttpsEndpoint); if (jwks == null || jwks.Keys.Count == 0) { - logger.LogDebug("Keys not found in JWKS: {jwksUri}", jwksUri); + logger.LogDebug("Keys not found in JWKS: {JwksUri}", jwksUri); return (false, null, null, new Models.Error("ERR-JWT-003", "keys_not_found", $"Keys not found in JWKS: {jwksUri}")); } - logger.LogDebug("Keys found in JWKS: {keys}", string.Join(',', jwks.Keys.Select(k => k.Kid).ToArray())); + logger.LogDebug("Keys found in JWKS: {Keys}", string.Join(',', jwks.Keys.Select(k => k.Kid).ToArray())); var tokenValidationParameters = new TokenValidationParameters { @@ -45,8 +45,8 @@ public static class TokenExtensions ValidateAudience = validAudiences != null && validAudiences.Length > 0, RequireSignedTokens = true, ValidateLifetime = validateLifetime, - }; - logger.LogDebug("Validating token: {jwt}", jwt); + }; + logger.LogDebug("Validating token: {Jwt}", jwt); var errorCode = string.Empty; var errorTitle = string.Empty; @@ -111,8 +111,7 @@ public static string GenerateJwt( claimsCollection: claims, expires: DateTime.UtcNow.AddSeconds(expirySeconds), issuedAt: DateTime.UtcNow, - notBefore: DateTime.UtcNow - ); + notBefore: DateTime.UtcNow); var jwt = new JwtSecurityToken(jwtHeader, jwtPayload); var tokenHandler = new JwtSecurityTokenHandler(); @@ -132,4 +131,4 @@ public static IEnumerable GetTokenClaims(this string token) return jwt.Payload.Claims; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs b/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs index e468f39..a503f9c 100644 --- a/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs +++ b/Source/CDR.DataRecipient.SDK/Extensions/UrlExtensions.cs @@ -42,7 +42,7 @@ public static string ValidateEndpoint(this string endpoint, bool enforceHttpsEnd var uri = new Uri(endpoint); uri.ValidateEndpoint(enforceHttpsEndpoint); - return endpoint; + return endpoint; } public static Uri ValidateEndpoint(this Uri uri, bool enforceHttpsEndpoint) diff --git a/Source/CDR.DataRecipient.SDK/MessageEnum.cs b/Source/CDR.DataRecipient.SDK/MessageEnum.cs index bd4f823..8f67e0a 100644 --- a/Source/CDR.DataRecipient.SDK/MessageEnum.cs +++ b/Source/CDR.DataRecipient.SDK/MessageEnum.cs @@ -5,6 +5,6 @@ public enum Message Abandoned, DCRComplete, DCRFailed, - Pending + Pending, } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/AccessToken.cs b/Source/CDR.DataRecipient.SDK/Models/AccessToken.cs index a83141c..4778946 100644 --- a/Source/CDR.DataRecipient.SDK/Models/AccessToken.cs +++ b/Source/CDR.DataRecipient.SDK/Models/AccessToken.cs @@ -3,15 +3,23 @@ namespace CDR.DataRecipient.SDK.Models { public class AccessToken - { - public string TokenEndpoint { get; set; } - public string ClientId { get; set; } - public X509Certificate2 ClientCertificate { get; set; } - public X509Certificate2 SigningCertificate { get; set; } - public string Scope { get; set; } = "cdr:registration"; - public string RedirectUri { get; set; } = null; - public string Code { get; set; } = null; - public string GrantType { get; set; } = Constants.GrantTypes.CLIENT_CREDENTIALS; - public Pkce Pkce { get; set; } = null; - } -} \ No newline at end of file + { + public string TokenEndpoint { get; set; } + + public string ClientId { get; set; } + + public X509Certificate2 ClientCertificate { get; set; } + + public X509Certificate2 SigningCertificate { get; set; } + + public string Scope { get; set; } = "cdr:registration"; + + public string RedirectUri { get; set; } = null; + + public string Code { get; set; } = null; + + public string GrantType { get; set; } = Constants.GrantTypes.CLIENT_CREDENTIALS; + + public Pkce Pkce { get; set; } = null; + } +} diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs b/Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs index 33fd50e..4297625 100644 --- a/Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs +++ b/Source/CDR.DataRecipient.SDK/Models/AuthDetails.cs @@ -3,6 +3,7 @@ public class AuthDetail { public string RegisterUType { get; set; } + public string JwksEndpoint { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs index bd98aab..c5e8620 100644 --- a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs +++ b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestClaims.cs @@ -33,9 +33,9 @@ public class UserInfo } public class IdToken - { + { public IdToken(int supportedAcr) - { + { this.acr = new Acr() { essential = true, values = new string[] { $"urn:cds.au:cdr:{supportedAcr}" } }; } diff --git a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestJwt.cs b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestJwt.cs index 7657199..bea2f27 100644 --- a/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestJwt.cs +++ b/Source/CDR.DataRecipient.SDK/Models/AuthorisationRequestJwt.cs @@ -3,23 +3,31 @@ namespace CDR.DataRecipient.SDK.Models { public class AuthorisationRequestJwt - { + { public string InfosecBaseUri { get; set; } + public string ClientId { get; set; } + public string RedirectUri { get; set; } + public string Scope { get; set; } + public string State { get; set; } + public string Nonce { get; set; } public X509Certificate2 SigningCertificate { get; set; } + public int? SharingDuration { get; set; } = 0; public string CdrArrangementId { get; set; } = null; - public string ResponseMode { get; set; } = "form_post"; + + public string ResponseMode { get; set; } public Pkce Pkce { get; set; } = null; + public int AcrValueSupported { get; set; } = 0; - public string ResponseType { get; set; } = "code id_token"; + public string ResponseType { get; set; } = "code"; } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/Certificate.cs b/Source/CDR.DataRecipient.SDK/Models/Certificate.cs index 5924f6f..69e82e9 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Certificate.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Certificate.cs @@ -10,8 +10,11 @@ public class Certificate private X509Certificate2 _certificate; public string Path { get; set; } + public string Url { get; set; } + public string Password { get; set; } + public X509Certificate2 X509Certificate { get @@ -46,4 +49,4 @@ private static byte[] DownloadData(string url) } } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DataHolderBrand.cs b/Source/CDR.DataRecipient.SDK/Models/DataHolderBrand.cs index a810635..6fe3955 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataHolderBrand.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataHolderBrand.cs @@ -6,11 +6,17 @@ namespace CDR.DataRecipient.SDK.Models public class DataHolderBrand { public string DataHolderBrandId { get; set; } + public string BrandName { get; set; } + public LegalEntity LegalEntity { get; set; } + public string Status { get; set; } + public EndpointDetail EndpointDetail { get; set; } + public IList AuthDetails { get; set; } + public DateTime LastUpdated { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DataHolderEndpoints.cs b/Source/CDR.DataRecipient.SDK/Models/DataHolderEndpoints.cs index 92c281f..887cbea 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataHolderEndpoints.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataHolderEndpoints.cs @@ -3,10 +3,15 @@ public class DataHolderEndpoints { public string BrandId { get; set; } + public string InfosecBaseUri { get; set; } + public string ResourceBaseUri { get; set; } + public string PublicBaseUri { get; set; } + public string OidcDiscoveryUri { get; set; } + public string RegistrationEndpoint { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs b/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs index a1fd599..ae87858 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataRecipientModel.cs @@ -6,33 +6,51 @@ namespace CDR.DataRecipient.SDK.Models public class DataRecipientModel { public string LegalEntityId { get; set; } + public string LegalEntityName { get; set; } + public string Industry { get; set; } + public string LogoUri { get; set; } + public string Status { get; set; } + public List DataRecipientBrands { get; set; } + 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 24c18a8..df76629 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DataRecipientViewModel.cs @@ -3,15 +3,20 @@ public class DataRecipientViewModel { public string SoftwareProductId { get; set; } + 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; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DcrMessage.cs b/Source/CDR.DataRecipient.SDK/Models/DcrMessage.cs index 546c24d..29045cb 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DcrMessage.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DcrMessage.cs @@ -5,13 +5,21 @@ namespace CDR.DataRecipient.SDK.Models public class DcrMessage { public int DcrMessageId { get; set; } + public string ClientId { get; set; } + public Guid DataHolderBrandId { get; set; } + public string BrandName { get; set; } + public string InfosecBaseUri { get; set; } + public string MessageId { get; set; } + public string MessageState { get; set; } + public string MessageError { get; set; } + public DateTime LastUpdatedByMessageDate { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs b/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs index 7c826f6..90757ff 100644 --- a/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs +++ b/Source/CDR.DataRecipient.SDK/Models/DcrResponse.cs @@ -5,7 +5,7 @@ public class DcrResponse : Response private string _payload; public string ClientId - { + { get { if (this.Data == null) @@ -17,18 +17,18 @@ public string ClientId } } - public string Payload - { + public string Payload + { get { return _payload; } + set { _payload = value; this.Data = Newtonsoft.Json.JsonConvert.DeserializeObject(_payload); } } - } } diff --git a/Source/CDR.DataRecipient.SDK/Models/EndpointDetail.cs b/Source/CDR.DataRecipient.SDK/Models/EndpointDetail.cs index b11c720..1d9316a 100644 --- a/Source/CDR.DataRecipient.SDK/Models/EndpointDetail.cs +++ b/Source/CDR.DataRecipient.SDK/Models/EndpointDetail.cs @@ -3,10 +3,15 @@ public class EndpointDetail { public string Version { get; set; } + public string PublicBaseUri { get; set; } + public string ResourceBaseUri { get; set; } + public string InfoSecBaseUri { get; set; } + public string ExtensionBaseUri { get; set; } + public string WebsiteUri { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Error.cs b/Source/CDR.DataRecipient.SDK/Models/Error.cs index ac06ddf..b94f098 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Error.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Error.cs @@ -7,7 +7,8 @@ public Error() this.Meta = new object(); } - public Error(string code, string title, string detail) : this() + public Error(string code, string title, string detail) + : this() { this.Code = code; this.Title = title; @@ -15,17 +16,17 @@ public Error(string code, string title, string detail) : this() } /// - /// Error code + /// Error code. /// public string Code { get; set; } /// - /// Error title + /// Error title. /// public string Title { get; set; } /// - /// Error detail + /// Error detail. /// public string Detail { get; set; } diff --git a/Source/CDR.DataRecipient.SDK/Models/Introspection.cs b/Source/CDR.DataRecipient.SDK/Models/Introspection.cs index 52b0c71..045237b 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Introspection.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Introspection.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.Serialization; namespace CDR.DataRecipient.SDK.Models { - [Serializable] public class Introspection : Dictionary { - public Introspection() : base() { } - - protected Introspection(SerializationInfo info, StreamingContext context) : base(info, context) + public Introspection() + : base() { } } diff --git a/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs b/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs index f4d1ae8..0321d3b 100644 --- a/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs +++ b/Source/CDR.DataRecipient.SDK/Models/JsonWebKey.cs @@ -3,11 +3,17 @@ public class JsonWebKey { public string alg { get; set; } + public string e { get; set; } + public string use { get; set; } + public string kid { get; set; } + public string kty { get; set; } + public string n { get; set; } + public string d { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/LegalEntity.cs b/Source/CDR.DataRecipient.SDK/Models/LegalEntity.cs index 0018742..69dc19f 100644 --- a/Source/CDR.DataRecipient.SDK/Models/LegalEntity.cs +++ b/Source/CDR.DataRecipient.SDK/Models/LegalEntity.cs @@ -9,6 +9,7 @@ namespace CDR.DataRecipient.SDK.Models public class LegalEntity { public string LegalEntityId { get; set; } + public string LegalEntityName { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs b/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs index 3a5624e..01e5ffe 100644 --- a/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs +++ b/Source/CDR.DataRecipient.SDK/Models/OidcDiscovery.cs @@ -1,156 +1,177 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace CDR.DataRecipient.SDK.Models { - public class OidcDiscovery - { - [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)) - { - return MtlsEndpointAliases?.TokenEndpoint; - } - return _tokenEndpoint; - } - set { _tokenEndpoint = value; } - } - - private string _introspectionEndpoint; - [JsonProperty("introspection_endpoint")] - public string IntrospectionEndpoint - { - get - { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.IntrospectionEndpoint)) - { - return MtlsEndpointAliases?.IntrospectionEndpoint; - } - return _introspectionEndpoint; - } - set { _introspectionEndpoint = value; } - } - - private string _revocationEndpoint; - [JsonProperty("revocation_endpoint")] - public string RevocationEndpoint - { - get - { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RevocationEndpoint)) - { - return MtlsEndpointAliases?.RevocationEndpoint; - } - return _revocationEndpoint; - } - set { _revocationEndpoint = value; } - } - - private string _userInfoEndpoint; - [JsonProperty("userinfo_endpoint")] - public string UserInfoEndpoint - { - get - { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.UserInfoEndpoint)) - { - return MtlsEndpointAliases?.UserInfoEndpoint; - } - - return _userInfoEndpoint; - } - set { _userInfoEndpoint = value; } - } - - private string _registrationEndpoint; - [JsonProperty("registration_endpoint")] - public string RegistrationEndpoint - { - get - { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RegistrationEndpoint)) - { - return MtlsEndpointAliases?.RegistrationEndpoint; - } - return _registrationEndpoint; - } - set { _registrationEndpoint = value; } - } - - private string _pushedAuthorizationRequestEndpoint; - [JsonProperty("pushed_authorization_request_endpoint")] - public string PushedAuthorizationRequestEndpoint - { - get - { - if (!string.IsNullOrEmpty(MtlsEndpointAliases?.PushedAuthorizationRequestEndpoint)) - { - return MtlsEndpointAliases?.PushedAuthorizationRequestEndpoint; - } - return _pushedAuthorizationRequestEndpoint; - } - set { _pushedAuthorizationRequestEndpoint = value; } - } - - [JsonProperty("jwks_uri")] - public string JwksUri { get; set; } - - [JsonProperty("scopes_supported")] - public string[] ScopesSupported { get; set; } - - [JsonProperty("response_types_supported")] - public string[] ResponseTypesSupported { get; set; } - - [JsonProperty("response_modes_supported")] - public string[] ResponseModesSupported { get; set; } - - [JsonProperty("grant_types_supported")] - public string[] GrantTypesSupported { get; set; } - - [JsonProperty("acr_values_supported")] - public string[] AcrValuesSupported { get; set; } - - [JsonProperty("subject_types_supported")] - public string[] SubjectTypesSupported { get; set; } - - [JsonProperty("id_token_signing_alg_values_supported")] - public string[] IdTokenSigningAlgValuesSupported { get; set; } - - [JsonProperty("id_token_encryption_alg_values_supported")] - public string[] IdTokenEncryptionAlgValuesSupported { get; set; } - - [JsonProperty("id_token_encryption_enc_values_supported")] - public string[] IdTokenEncryptionEncValuesSupported { get; set; } - - [JsonProperty("cdr_arrangement_revocation_endpoint")] - public string CdrArrangementRevocationEndpoint { get; set; } - - [JsonProperty("request_object_signing_alg_values_supported")] - public string[] RequestObjectSigningAlgValuesSupported { get; set; } - - [JsonProperty("token_endpoint_auth_methods_supported")] - public string[] TokenEndpointAuthMethodsSupported { get; set; } - - [JsonProperty("tls_client_certificate_bound_access_tokens")] - public bool TlsClientCertificateBoundAccessTokens { get; set; } - - [JsonProperty("authorization_signing_alg_values_supported")] + public class OidcDiscovery + { + [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)) + { + return MtlsEndpointAliases.TokenEndpoint; + } + + return _tokenEndpoint; + } + + set + { + _tokenEndpoint = value; + } + } + + private string _introspectionEndpoint; + + [JsonProperty("introspection_endpoint")] + public string IntrospectionEndpoint + { + get + { + if (!string.IsNullOrEmpty(MtlsEndpointAliases?.IntrospectionEndpoint)) + { + return MtlsEndpointAliases.IntrospectionEndpoint; + } + + return _introspectionEndpoint; + } + + set + { + _introspectionEndpoint = value; + } + } + + private string _revocationEndpoint; + + [JsonProperty("revocation_endpoint")] + public string RevocationEndpoint + { + get + { + if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RevocationEndpoint)) + { + return MtlsEndpointAliases.RevocationEndpoint; + } + + return _revocationEndpoint; + } + + set => _revocationEndpoint = value; + } + + private string _userInfoEndpoint; + + [JsonProperty("userinfo_endpoint")] + public string UserInfoEndpoint + { + get + { + if (!string.IsNullOrEmpty(MtlsEndpointAliases?.UserInfoEndpoint)) + { + return MtlsEndpointAliases.UserInfoEndpoint; + } + + return _userInfoEndpoint; + } + + set + { + _userInfoEndpoint = value; + } + } + + private string _registrationEndpoint; + + [JsonProperty("registration_endpoint")] + public string RegistrationEndpoint + { + get + { + if (!string.IsNullOrEmpty(MtlsEndpointAliases?.RegistrationEndpoint)) + { + return MtlsEndpointAliases.RegistrationEndpoint; + } + + return _registrationEndpoint; + } + + set + { + _registrationEndpoint = value; + } + } + + private string _pushedAuthorizationRequestEndpoint; + + [JsonProperty("pushed_authorization_request_endpoint")] + public string PushedAuthorizationRequestEndpoint + { + get + { + if (!string.IsNullOrEmpty(MtlsEndpointAliases?.PushedAuthorizationRequestEndpoint)) + { + return MtlsEndpointAliases.PushedAuthorizationRequestEndpoint; + } + + return _pushedAuthorizationRequestEndpoint; + } + + set + { + _pushedAuthorizationRequestEndpoint = value; + } + } + + [JsonProperty("jwks_uri")] + public string JwksUri { get; set; } + + [JsonProperty("scopes_supported")] + public string[] ScopesSupported { get; set; } + + [JsonProperty("response_types_supported")] + public string[] ResponseTypesSupported { get; set; } + + [JsonProperty("response_modes_supported")] + public string[] ResponseModesSupported { get; set; } + + [JsonProperty("grant_types_supported")] + public string[] GrantTypesSupported { get; set; } + + [JsonProperty("acr_values_supported")] + public string[] AcrValuesSupported { get; set; } + + [JsonProperty("subject_types_supported")] + public string[] SubjectTypesSupported { get; set; } + + [JsonProperty("id_token_signing_alg_values_supported")] + public string[] IdTokenSigningAlgValuesSupported { get; set; } + + [JsonProperty("cdr_arrangement_revocation_endpoint")] + public string CdrArrangementRevocationEndpoint { get; set; } + + [JsonProperty("request_object_signing_alg_values_supported")] + public string[] RequestObjectSigningAlgValuesSupported { get; set; } + + [JsonProperty("token_endpoint_auth_methods_supported")] + public string[] TokenEndpointAuthMethodsSupported { get; set; } + + [JsonProperty("tls_client_certificate_bound_access_tokens")] + public bool TlsClientCertificateBoundAccessTokens { get; set; } + + [JsonProperty("authorization_signing_alg_values_supported")] public string[] AuthorizationSigningResponseAlgValuesSupported { get; set; } - + [JsonProperty("authorization_encryption_enc_values_supported")] public string[] AuthorizationEncryptionResponseEncValuesSupported { get; set; } @@ -158,30 +179,30 @@ public string PushedAuthorizationRequestEndpoint public string[] AuthorizationEncryptionResponseAlgValuesSupported { get; set; } [JsonProperty("claims_supported")] - public string[] ClaimsSupported { get; set; } + public string[] ClaimsSupported { get; set; } + + [JsonProperty("mtls_endpoint_aliases")] + public MtlsAliases MtlsEndpointAliases { get; set; } + } - [JsonProperty("mtls_endpoint_aliases")] - public MtlsAliases MtlsEndpointAliases { get; set; } - } + public class MtlsAliases + { + [JsonProperty("token_endpoint")] + public string TokenEndpoint { get; set; } - public class MtlsAliases - { - [JsonProperty("token_endpoint")] - public string TokenEndpoint { get; set; } + [JsonProperty("revocation_endpoint")] + public string RevocationEndpoint { get; set; } - [JsonProperty("revocation_endpoint")] - public string RevocationEndpoint { get; set; } + [JsonProperty("introspection_endpoint")] + public string IntrospectionEndpoint { get; set; } - [JsonProperty("introspection_endpoint")] - public string IntrospectionEndpoint { get; set; } + [JsonProperty("userinfo_endpoint")] + public string UserInfoEndpoint { get; set; } - [JsonProperty("userinfo_endpoint")] - public string UserInfoEndpoint { get; set; } - - [JsonProperty("registration_endpoint")] - public string RegistrationEndpoint { get; set; } + [JsonProperty("registration_endpoint")] + public string RegistrationEndpoint { get; set; } - [JsonProperty("pushed_authorization_request_endpoint")] - public string PushedAuthorizationRequestEndpoint { 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 e87e98e..a57a8c2 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Pkce.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Pkce.cs @@ -9,7 +9,9 @@ namespace CDR.DataRecipient.SDK.Models public class Pkce { public string CodeVerifier { get; set; } + public string CodeChallenge { get; set; } + public string CodeChallengeMethod { get; private set; } public Pkce() diff --git a/Source/CDR.DataRecipient.SDK/Models/Register.cs b/Source/CDR.DataRecipient.SDK/Models/Register.cs index 82ffaad..fdf138c 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Register.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Register.cs @@ -3,9 +3,15 @@ public class Register { public string TlsBaseUri { get; set; } + public string MtlsBaseUri { get; set; } + public string OidcDiscoveryUri { get; set; } + public string GetSsaEndpoint { get; set; } + public string GetDataHolderBrandsEndpoint { get; set; } + + public string GetSsaMinXv { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Registration.cs b/Source/CDR.DataRecipient.SDK/Models/Registration.cs index c8efcce..70f6648 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Registration.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Registration.cs @@ -3,80 +3,74 @@ namespace CDR.DataRecipient.SDK.Models { - public class Registration - { - public const string IdDelimeter = "|||"; + public class Registration + { + public const string IdDelimeter = "|||"; - public string DataHolderBrandId { get; set; } + public string DataHolderBrandId { get; set; } - public string BrandName { get; set; } + public string BrandName { get; set; } - public string MessageState { get; set; } + public string MessageState { get; set; } - public DateTime LastUpdated { get; set; } + public DateTime LastUpdated { get; set; } - [JsonProperty("client_id")] - public string ClientId { get; set; } + [JsonProperty("client_id")] + public string ClientId { get; set; } - [JsonProperty("client_id_issued_at")] - public int ClientIdIssuedAt { get; set; } + [JsonProperty("client_id_issued_at")] + public int ClientIdIssuedAt { get; set; } - [JsonProperty("client_description")] - public string ClientDescription { get; set; } + [JsonProperty("client_description")] + public string ClientDescription { get; set; } - [JsonProperty("client_uri")] - public string ClientUri { get; set; } + [JsonProperty("client_uri")] + public string ClientUri { get; set; } - [JsonProperty("org_id")] - public string OrgId { get; set; } + [JsonProperty("org_id")] + public string OrgId { get; set; } - [JsonProperty("org_name")] - public string OrgName { get; set; } + [JsonProperty("org_name")] + public string OrgName { get; set; } - [JsonProperty("redirect_uris")] - public string[] RedirectUris { get; set; } + [JsonProperty("redirect_uris")] + public string[] RedirectUris { get; set; } - [JsonProperty("logo_uri")] - public string LogoUri { get; set; } + [JsonProperty("logo_uri")] + public string LogoUri { get; set; } - [JsonProperty("tos_uri")] - public string TosUri { get; set; } + [JsonProperty("tos_uri")] + public string TosUri { get; set; } - [JsonProperty("policy_uri")] - public string PolicyUri { get; set; } + [JsonProperty("policy_uri")] + public string PolicyUri { get; set; } - [JsonProperty("jwks_uri")] - public string JwksUri { get; set; } + [JsonProperty("jwks_uri")] + public string JwksUri { get; set; } - [JsonProperty("revocation_uri")] - public string RevocationUri { get; set; } + [JsonProperty("revocation_uri")] + public string RevocationUri { get; set; } - [JsonProperty("recipient_base_uri")] - public string RecipientBaseUri { get; set; } + [JsonProperty("recipient_base_uri")] + public string RecipientBaseUri { get; set; } - [JsonProperty("token_endpoint_auth_signing_alg")] - public string TokenEndpointAuthSigningAlg { get; set; } + [JsonProperty("token_endpoint_auth_signing_alg")] + public string TokenEndpointAuthSigningAlg { get; set; } - [JsonProperty("token_endpoint_auth_method")] - public string TokenEndpointAuthMethod { get; set; } + [JsonProperty("token_endpoint_auth_method")] + public string TokenEndpointAuthMethod { get; set; } - [JsonProperty("grant_types")] - public string[] GrantTypes { get; set; } + [JsonProperty("grant_types")] + public string[] GrantTypes { get; set; } - [JsonProperty("response_types")] - public string[] ResponseTypes { get; set; } + [JsonProperty("response_types")] + public string[] ResponseTypes { get; set; } - [JsonProperty("application_type")] - public string ApplicationType { get; set; } + [JsonProperty("application_type")] + public string ApplicationType { get; set; } - [JsonProperty("id_token_signed_response_alg")] - public string IdTokenSignedResponseAlg { get; set; } - - [JsonProperty("id_token_encrypted_response_alg")] - public string IdTokenEncryptedResponseAlg { get; set; } - - [JsonProperty("id_token_encrypted_response_enc")] - public string IdTokenEncryptedResponseEnc { get; set; } + [JsonProperty("id_token_signed_response_alg")] + public string IdTokenSignedResponseAlg { get; set; } [JsonProperty("authorization_signed_response_alg")] public string AuthorizationSignedResponseAlg { get; set; } @@ -88,36 +82,36 @@ public class Registration public string AuthorizationEncryptedResponseEnc { get; set; } [JsonProperty("request_object_signing_alg")] - public string RequestObjectSigningAlg { get; set; } + public string RequestObjectSigningAlg { get; set; } - [JsonProperty("software_statement")] - public string SoftwareStatement { get; set; } + [JsonProperty("software_statement")] + public string SoftwareStatement { get; set; } - [JsonProperty("software_id")] - public string SoftwareId { get; set; } + [JsonProperty("software_id")] + public string SoftwareId { get; set; } - [JsonProperty("scope")] - public string Scope { get; set; } + [JsonProperty("scope")] + public string Scope { get; set; } + public string GetRegistrationId() + { + return $"{ClientId}{IdDelimeter}{DataHolderBrandId}"; + } - public string GetRegistrationId() - { - return $"{ClientId}{IdDelimeter}{DataHolderBrandId}"; - } - public static (string ClientId, string DataHolderBrandId) SplitRegistrationId(string id) - { - var idParts = id?.Split(IdDelimeter); + public static (string ClientId, string DataHolderBrandId) SplitRegistrationId(string id) + { + var idParts = id?.Split(IdDelimeter); if (idParts == null) { return (null, null); } if (idParts.Length != 2) - { - return (null, null); - } + { + return (null, null); + } - return (idParts[0], idParts[1]); - } - } + return (idParts[0], idParts[1]); + } + } } diff --git a/Source/CDR.DataRecipient.SDK/Models/Response.cs b/Source/CDR.DataRecipient.SDK/Models/Response.cs index b63b7ff..68d4aa9 100644 --- a/Source/CDR.DataRecipient.SDK/Models/Response.cs +++ b/Source/CDR.DataRecipient.SDK/Models/Response.cs @@ -9,11 +9,11 @@ public Response() this.Errors = new ErrorList(); } - public bool IsSuccessful - { + public bool IsSuccessful + { get { - return ((int) this.StatusCode) < 400; + return ((int)this.StatusCode) < 400; } } diff --git a/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs b/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs index 5aa9d68..f8be610 100644 --- a/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs +++ b/Source/CDR.DataRecipient.SDK/Models/ServiceConfiguration.cs @@ -3,6 +3,7 @@ public class ServiceConfiguration : IServiceConfiguration { public bool AcceptAnyServerCertificate { get; set; } + public bool EnforceHttpsEndpoints { get; set; } public ServiceConfiguration() @@ -16,6 +17,7 @@ public ServiceConfiguration() public interface IServiceConfiguration { bool AcceptAnyServerCertificate { get; set; } + bool EnforceHttpsEndpoints { get; set; } } } diff --git a/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs b/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs index d34d880..9362359 100644 --- a/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs +++ b/Source/CDR.DataRecipient.SDK/Models/SoftwareProduct.cs @@ -16,7 +16,7 @@ public string RedirectUri { get { - return (string.IsNullOrEmpty(this.RedirectUris) ? null : this.RedirectUris.Split(',')[0]); + return string.IsNullOrEmpty(this.RedirectUris) ? null : this.RedirectUris.Split(',')[0]; } } @@ -30,4 +30,4 @@ public string RedirectUri public Certificate SigningCertificate { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.SDK/Models/UserInfo.cs b/Source/CDR.DataRecipient.SDK/Models/UserInfo.cs index 30b0cce..73c4cca 100644 --- a/Source/CDR.DataRecipient.SDK/Models/UserInfo.cs +++ b/Source/CDR.DataRecipient.SDK/Models/UserInfo.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Collections.Generic; namespace CDR.DataRecipient.SDK.Models { - [Serializable] public class UserInfo : Dictionary { - public UserInfo() : base() { } - - protected UserInfo(SerializationInfo info, StreamingContext context) : base(info, context) + public UserInfo() + : base() { } } diff --git a/Source/CDR.DataRecipient.SDK/Services/BaseService.cs b/Source/CDR.DataRecipient.SDK/Services/BaseService.cs index b18fd51..205d58f 100644 --- a/Source/CDR.DataRecipient.SDK/Services/BaseService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/BaseService.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; @@ -19,7 +15,6 @@ public abstract class BaseService protected readonly ILogger _logger; protected readonly IServiceConfiguration _serviceConfiguration; - protected BaseService( IConfiguration config, ILogger logger, diff --git a/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs b/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs index def5d08..ac77021 100644 --- a/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/DataHolder/DynamicClientRegistrationService.cs @@ -11,11 +11,11 @@ namespace CDR.DataRecipient.SDK.Services.DataHolder { public class DynamicClientRegistrationService : BaseService, IDynamicClientRegistrationService { - public DynamicClientRegistrationService( IConfiguration config, ILogger logger, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { } @@ -30,20 +30,20 @@ public async Task DeleteRegistration( // Setup the http client. var client = GetHttpClient(clientCertificate, accessToken); - _logger.LogDebug("Deleting registration from Data Holder: {registrationEndpoint}. Client ID: {clientId}. Client Certificate: {thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); + _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 body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {statusCode}. Body: {body}", response.StatusCode, body); + _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse { StatusCode = response.StatusCode, Payload = body, - Message = response.IsSuccessStatusCode ? "Registration deleted." : $"Failed to delete registration: {body}" + Message = response.IsSuccessStatusCode ? "Registration deleted." : $"Failed to delete registration: {body}", }; } @@ -63,19 +63,19 @@ public async Task GetRegistration( var response = await client.GetAsync(EnsureValidEndpoint(uri)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {statusCode}. Body: {body}", response.StatusCode, body); + _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { StatusCode = response.StatusCode, Payload = body, - Message = response.IsSuccessStatusCode ? "Registration retrieved successfully." : $"Failed to retrieve registration: {body}" + Message = response.IsSuccessStatusCode ? "Registration retrieved successfully." : $"Failed to retrieve registration: {body}", }; } public async Task Register( - string registrationEndpoint, - X509Certificate2 clientCertificate, + string registrationEndpoint, + X509Certificate2 clientCertificate, string payload) { _logger.LogDebug($"Request received to {nameof(DynamicClientRegistrationService)}.{nameof(Register)}."); @@ -83,7 +83,7 @@ public async Task Register( // Setup the http client. var client = GetHttpClient(clientCertificate); - _logger.LogDebug("Registering with Data Holder: {registrationEndpoint}. Client Certificate: {thumbprint}", registrationEndpoint, clientCertificate.Thumbprint); + _logger.LogDebug("Registering with Data Holder: {RegistrationEndpoint}. Client Certificate: {Thumbprint}", registrationEndpoint, clientCertificate.Thumbprint); // Create the post content. var content = new StringContent(payload); @@ -93,14 +93,14 @@ public async Task Register( var response = await client.PostAsync(EnsureValidEndpoint(registrationEndpoint), content); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {statusCode}. Body: {body}", response.StatusCode, body); + _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { Data = JsonConvert.DeserializeObject(body), StatusCode = response.StatusCode, Message = response.IsSuccessStatusCode ? "Registration successful." : $"Failed to register: {body}", - Payload = body + Payload = body, }; } @@ -116,7 +116,7 @@ public async Task UpdateRegistration( // Setup the http client. var client = GetHttpClient(clientCertificate, accessToken); - _logger.LogDebug("Updating registration with Data Holder: {registrationEndpoint}. Client ID: {clientId}. Client Certificate: {thumbprint}", registrationEndpoint, clientId, clientCertificate.Thumbprint); + _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); @@ -127,7 +127,7 @@ public async Task UpdateRegistration( var response = await client.PutAsync(EnsureValidEndpoint(uri), content); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Response: {statusCode}. Body: {body}", response.StatusCode, body); + _logger.LogDebug("Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return new DcrResponse() { diff --git a/Source/CDR.DataRecipient.SDK/Services/DataHolder/IInfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/DataHolder/IInfosecService.cs index 53c607d..3fbe25e 100644 --- a/Source/CDR.DataRecipient.SDK/Services/DataHolder/IInfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/DataHolder/IInfosecService.cs @@ -28,7 +28,7 @@ Task RevokeToken( string token); Task> Introspect( - string introspectionEndpoint, + string introspectionEndpoint, X509Certificate2 clientCertificate, X509Certificate2 signingCertificate, string clientId, @@ -52,7 +52,6 @@ Task> PushedAuthorisationRequest( X509Certificate2 signingCertificate, string clientId, string request); - Task BuildAuthorisationRequestUri( string infosecBaseUri, @@ -60,11 +59,10 @@ Task BuildAuthorisationRequestUri( X509Certificate2 signingCertificate, string requestUri, string scope, - string responseType = "code id_token"); + string responseType = "code"); string BuildAuthorisationRequestJwt(AuthorisationRequestJwt authorisationRequestJwt); Pkce CreatePkceData(); - } } diff --git a/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs index 3975da1..47ac255 100644 --- a/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/DataHolder/InfosecService.cs @@ -11,19 +11,20 @@ using Jose; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static CDR.DataRecipient.SDK.Constants; namespace CDR.DataRecipient.SDK.Services.DataHolder { public class InfosecService : BaseService, IInfosecService { - private readonly IAccessTokenService _accessTokenService; public InfosecService( IConfiguration config, ILogger logger, IAccessTokenService accessTokenService, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { _accessTokenService = accessTokenService; } @@ -95,16 +96,18 @@ public string BuildAuthorisationRequestJwt(AuthorisationRequestJwt authorisation // Build the list of claims to include in the authorisation request jwt. var authorisationRequestClaims = new Dictionary - { + { { "response_type", authorisationRequestJwt.ResponseType }, { "client_id", authorisationRequestJwt.ClientId }, { "redirect_uri", authorisationRequestJwt.RedirectUri }, - { "response_mode", authorisationRequestJwt.ResponseMode}, + { "response_mode", authorisationRequestJwt.ResponseMode }, { "scope", authorisationRequestJwt.Scope }, { "state", authorisationRequestJwt.State }, { "nonce", authorisationRequestJwt.Nonce }, - { "claims", JsonSerializer.SerializeToElement(new AuthorisationRequestClaims(authorisationRequestJwt.AcrValueSupported) - { sharing_duration = authorisationRequestJwt.SharingDuration, cdr_arrangement_id = authorisationRequestJwt.CdrArrangementId } )} + { + "claims", JsonSerializer.SerializeToElement(new AuthorisationRequestClaims(authorisationRequestJwt.AcrValueSupported) + { sharing_duration = authorisationRequestJwt.SharingDuration, cdr_arrangement_id = authorisationRequestJwt.CdrArrangementId }) + }, }; if (authorisationRequestJwt.Pkce != null) @@ -115,15 +118,14 @@ public string BuildAuthorisationRequestJwt(AuthorisationRequestJwt authorisation return authorisationRequestClaims.GenerateJwt(authorisationRequestJwt.ClientId, authorisationRequestJwt.InfosecBaseUri, authorisationRequestJwt.SigningCertificate); } - public async Task BuildAuthorisationRequestUri( string infosecBaseUri, string clientId, X509Certificate2 signingCertificate, string requestUri, - string scope, - string responseType = "code id_token") + string scope, + string responseType = "code") { _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(BuildAuthorisationRequestUri)}."); @@ -171,7 +173,7 @@ public async Task> RefreshAccessToken( issuer: clientId, clientId: clientId, scope: scope, - grantType: "refresh_token", + grantType: TokenTypes.REFRESH_TOKEN, additionalFormFields: formFields, enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); @@ -214,7 +216,7 @@ public async Task RevokeToken( signingCertificate, issuer: clientId, clientId: clientId, - scope: "", + scope: string.Empty, additionalFormFields: formFields, enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); @@ -246,14 +248,14 @@ public async Task> Introspect( var formFields = new Dictionary(); formFields.Add("token", refreshToken); - formFields.Add("token_type_hint", "refresh_token"); + formFields.Add("token_type_hint", TokenTypes.REFRESH_TOKEN); var response = await client.SendPrivateKeyJwtRequest( introspectionEndpoint, signingCertificate, issuer: clientId, clientId: clientId, - scope: "", + scope: string.Empty, additionalFormFields: formFields, enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); @@ -274,8 +276,8 @@ public async Task> Introspect( } public async Task> UserInfo( - string userInfoEndpoint, - X509Certificate2 clientCertificate, + string userInfoEndpoint, + X509Certificate2 clientCertificate, string accessToken) { var userInfoResponse = new Response(); @@ -324,7 +326,7 @@ public async Task RevokeCdrArrangement( signingCertificate, issuer: clientId, clientId: clientId, - scope: "", + scope: string.Empty, additionalFormFields: formFields, enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); @@ -341,8 +343,8 @@ public async Task RevokeCdrArrangement( } public async Task> PushedAuthorizationRequest( - string parEndpoint, - X509Certificate2 clientCertificate, + string parEndpoint, + X509Certificate2 clientCertificate, string accessToken) { var parResponse = new Response(); @@ -373,7 +375,7 @@ public Pkce CreatePkceData() { var pkce = new Pkce { - CodeVerifier = string.Concat(System.Guid.NewGuid().ToString(), '-', System.Guid.NewGuid().ToString()) + CodeVerifier = string.Concat(System.Guid.NewGuid().ToString(), '-', System.Guid.NewGuid().ToString()), }; var challengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(pkce.CodeVerifier)); diff --git a/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs b/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs index 5f136f1..36ed67f 100644 --- a/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs +++ b/Source/CDR.DataRecipient.SDK/Services/PrivateKeyJwt.cs @@ -21,7 +21,8 @@ public class PrivateKeyJwt /// /// The path to the certificate. /// The password of the certificate. - public PrivateKeyJwt(string certFilePath, string pwd) : this(new X509Certificate2(certFilePath, pwd, X509KeyStorageFlags.Exportable)) + public PrivateKeyJwt(string certFilePath, string pwd) + : this(new X509Certificate2(certFilePath, pwd, X509KeyStorageFlags.Exportable)) { } @@ -55,7 +56,7 @@ public PrivateKeyJwt(string privateKey) /// The audience of the JWT, usually set to the target token endpoint /// A base64 encoded JWT public string Generate( - string issuer, + string issuer, string audience, string jti = null, int expiryMinutes = 10, @@ -77,11 +78,11 @@ public string Generate( } var expiry = DateTime.UtcNow.AddMinutes(expiryMinutes); - var claims = new List - { - new Claim("sub", issuer), - new Claim("jti", jti ?? Guid.NewGuid().ToString()), - new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer) + var claims = new List + { + new Claim("sub", issuer), + new Claim("jti", jti ?? Guid.NewGuid().ToString()), + new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer), }; var jwt = new JwtSecurityToken(issuer, audience, claims, expires: expiry, signingCredentials: this.SigningCredentials); var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/IInfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/IInfosecService.cs index 21de162..68e9383 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/IInfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/IInfosecService.cs @@ -16,6 +16,5 @@ Task> GetAccessToken( Task> GetOidcDiscovery(string registerOidcConfigEndpoint); Task GetTokenEndpoint(string registerOidcConfigEndpoint); - } } diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs index dec4d13..0cbd540 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/IMetadataService.cs @@ -6,7 +6,7 @@ namespace CDR.DataRecipient.SDK.Services.Register { public interface IMetadataService { - Task<(string, System.Net.HttpStatusCode, string)> GetDataHolderBrands( + Task<(string RespBody, System.Net.HttpStatusCode StatusCode, string Reason)> GetDataHolderBrands( string registerMtlsBaseUri, string version, string accessToken, @@ -16,7 +16,7 @@ public interface IMetadataService int? page = null, int? pageSize = null); - Task<(string, System.Net.HttpStatusCode, string)> GetDataRecipients( + Task<(string RespBody, System.Net.HttpStatusCode StatusCode, string Reason)> GetDataRecipients( string registerTlsBaseUri, string version, Industry industry); diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs index b6aa276..f108bc9 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/InfosecService.cs @@ -15,7 +15,8 @@ public InfosecService( IConfiguration config, ILogger logger, IAccessTokenService accessTokenService, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { _accessTokenService = accessTokenService; } @@ -29,13 +30,13 @@ public async Task> GetAccessToken( { _logger.LogDebug($"Request received to {nameof(InfosecService)}.{nameof(GetAccessToken)}."); - var accessToken = new AccessToken() + var accessToken = new AccessToken() { - TokenEndpoint = tokenEndpoint, + TokenEndpoint = tokenEndpoint, ClientId = clientId, ClientCertificate = clientCertificate, SigningCertificate = signingCertificate, - Scope = scope + Scope = scope, }; return await _accessTokenService.GetAccessToken(accessToken); diff --git a/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs b/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs index ad737b3..a99397a 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/MetadataService.cs @@ -1,22 +1,21 @@ -using CDR.DataRecipient.SDK.Enumerations; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Enumerations; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace CDR.DataRecipient.SDK.Services.Register { public class MetadataService : BaseService, IMetadataService { public MetadataService( - IConfiguration config, + IConfiguration config, ILogger logger, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { - } public async Task<(string, System.Net.HttpStatusCode, string)> GetDataHolderBrands( @@ -37,7 +36,7 @@ public MetadataService( // Setup the http client. var client = GetHttpClient(clientCertificate, accessToken, version); - _logger.LogDebug("Requesting data holder brands from Register: {endpoint}. Client Certificate: {thumbprint}", endpoint, clientCertificate.Thumbprint); + _logger.LogDebug("Requesting data holder brands from Register: {Endpoint}. Client Certificate: {Thumbprint}", endpoint, clientCertificate.Thumbprint); // Add the query parameters. if (page.HasValue) @@ -54,7 +53,7 @@ public MetadataService( var response = await client.GetAsync(EnsureValidEndpoint(endpoint)); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get Data Holder Brands Response: {statusCode}. Body: {body}", response.StatusCode, body); + _logger.LogDebug("Get Data Holder Brands Response: {StatusCode}. Body: {Body}", response.StatusCode, body); return (body, response.StatusCode, response.ReasonPhrase.ToString()); } @@ -72,13 +71,13 @@ public MetadataService( // Setup the http client. var client = GetHttpClient(version: version); - _logger.LogDebug("Requesting data recipients from Register: {endpoint}.", endpoint); + _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 body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get Data Recipients Response: {statusCode}. Body: {body}", response.StatusCode, body); + _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 1ff8eb2..d749fd4 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Register/SsaService.cs @@ -12,16 +12,16 @@ namespace CDR.DataRecipient.SDK.Services.Register { public class SsaService : BaseService, ISsaService { - public SsaService( IConfiguration config, ILogger logger, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { } public async Task> GetSoftwareStatementAssertion( - string mtlsBaseUri, + string mtlsBaseUri, string version, string accessToken, X509Certificate2 clientCertificate, @@ -39,13 +39,13 @@ public async Task> GetSoftwareStatementAssertion( // Setup the http client. var client = GetHttpClient(clientCertificate, accessToken, version); - _logger.LogDebug("Requesting SSA from Register: {ssaEndpoint}. Client Certificate: {thumbprint}", ssaEndpoint, clientCertificate.Thumbprint); + _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 body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Get SSA Response: {statusCode}. Body: {body}", response.StatusCode, body); + _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 ec3b6dc..264292c 100644 --- a/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs +++ b/Source/CDR.DataRecipient.SDK/Services/Tokens/AccessTokenService.cs @@ -1,6 +1,4 @@ -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; +using System.Threading.Tasks; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using Microsoft.Extensions.Configuration; @@ -11,15 +9,17 @@ namespace CDR.DataRecipient.SDK.Services.Tokens public class AccessTokenService : BaseService, IAccessTokenService { private AccessToken AccessToken { get; set; } + public AccessTokenService( IConfiguration config, ILogger logger, - IServiceConfiguration serviceConfiguration) : base(config, logger, serviceConfiguration) + IServiceConfiguration serviceConfiguration) + : base(config, logger, serviceConfiguration) { // LEGACY CODE AccessToken = new AccessToken() { Scope = string.Empty }; } - + public async Task> GetAccessToken(AccessToken accessToken) { var tokenResponse = new Response(); @@ -31,8 +31,11 @@ public async Task> GetAccessToken(AccessToken accessToken) // Setup the http client. var client = GetHttpClient(AccessToken.ClientCertificate); - _logger.LogDebug("Requesting access token from: {tokenEndpoint}. Software Product ID: {clientId}. Client Certificate: {thumbprint}", - AccessToken.TokenEndpoint, AccessToken.ClientId, AccessToken.ClientCertificate.Thumbprint); + _logger.LogDebug( + "Requesting access token from: {TokenEndpoint}. Software Product ID: {ClientId}. Client Certificate: {Thumbprint}", + AccessToken.TokenEndpoint, + AccessToken.ClientId, + AccessToken.ClientCertificate.Thumbprint); // Make the request to the token endpoint. var response = await client.SendPrivateKeyJwtRequest( @@ -44,12 +47,12 @@ public async Task> GetAccessToken(AccessToken accessToken) AccessToken.RedirectUri, AccessToken.Code, AccessToken.GrantType, - pkce: AccessToken.Pkce, + pkce: AccessToken.Pkce, enforceHttpsEndpoint: _serviceConfiguration.EnforceHttpsEndpoints); var body = await response.Content.ReadAsStringAsync(); - _logger.LogDebug("Access Token Response: {statusCode}. Body: {body}", response.StatusCode, body); + _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 8ed13f9..f521114 100644 --- a/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj +++ b/Source/CDR.DataRecipient.Web/CDR.DataRecipient.Web.csproj @@ -7,6 +7,7 @@ $(Version) $(Version) $(Version) + True @@ -28,7 +29,7 @@ - + @@ -42,6 +43,14 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + 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 73ee601..5bb08f2 100644 --- a/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs +++ b/Source/CDR.DataRecipient.Web/Caching/CacheManager.cs @@ -7,7 +7,7 @@ namespace CDR.DataRecipient.Web.Caching { public class CacheManager : ICacheManager - { + { private readonly IMemoryCache _memCache; private readonly ILogger _logger; private readonly IInfosecService _infosecService; @@ -16,7 +16,7 @@ public CacheManager( IMemoryCache memCache, ILogger logger, IInfosecService infosecService) - { + { _memCache = memCache; _logger = logger; _infosecService = infosecService; @@ -28,14 +28,14 @@ public async Task GetRegisterTokenEndpoint(string oidcDiscoveryUri) var item = _memCache.Get(key); if (!string.IsNullOrEmpty(item)) { - _logger.LogInformation("Cache hit: {key}", key); + _logger.LogInformation("Cache hit: {Key}", key); return item; } var tokenEndpoint = await _infosecService.GetTokenEndpoint(oidcDiscoveryUri); _memCache.Set(key, tokenEndpoint); - _logger.LogInformation("Adding item to cache: {key}", key); + _logger.LogInformation("Adding item to cache: {Key}", key); return tokenEndpoint; } diff --git a/Source/CDR.DataRecipient.Web/Common/Constants.cs b/Source/CDR.DataRecipient.Web/Common/Constants.cs index 8281ed6..aff195d 100644 --- a/Source/CDR.DataRecipient.Web/Common/Constants.cs +++ b/Source/CDR.DataRecipient.Web/Common/Constants.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Components; -using System.Net.NetworkInformation; - -namespace CDR.DataRecipient.Web.Common +namespace CDR.DataRecipient.Web.Common { public static class Constants { @@ -12,7 +9,6 @@ public static class MockDataRecipient public const string Hostname = "MockDataRecipient:Hostname"; public const string DefaultPageSize = "MockDataRecipient:Paging:DefaultPageSize"; public const string AttemptValidateCdrArrangementJwtFromDate = "MockDataRecipient:Arrangement:AttemptValidateJwtFromDate"; - public static class SoftwareProduct { @@ -41,6 +37,8 @@ public static class OidcAuthentication public const string ResponseType = "oidc:response_type"; public const string ResponseMode = "oidc:response_mode"; public const string Scope = "oidc:scope"; + public const string SecretVolumePath = "oidc:secretVolumePath"; + public const string MountedSecretName = "oidc:mountedSecretName"; } public static class Register @@ -109,10 +107,10 @@ public static class CdrArrangementRevocationRequest public const string CdrArrangementJwt = "cdr_arrangement_jwt"; } - public static class ErrorDescription - { + public static class ErrorDescription + { public const string MissingAuthCode = "authCode is missing"; - public const string MissingState= "state is missing"; + public const string MissingState = "state is missing"; public const string MissingAuthState = "authState is missing"; public const string MissingDiscoveryDocument = "data holder document not found"; public const string MissingResponse = "response is missing"; @@ -123,6 +121,7 @@ public static class Defaults { public const string DefaultUserName = "unknown"; } + public const string DEFAULT_KEY_ID = "7EFA85C18FDE857949BC2EAA21C25E49627D4865"; public const string DEFAULT_PRIVATE_KEY = @@ -156,4 +155,4 @@ public static class Defaults -----END PRIVATE KEY----- "; } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs b/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs index e0baffb..7d51bd0 100644 --- a/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs +++ b/Source/CDR.DataRecipient.Web/Common/DataHolderDiscoveryCache.cs @@ -1,36 +1,33 @@ using System; using System.Threading.Tasks; using CDR.DataRecipient.Repository; -using CDR.DataRecipient.SDK.Services.DataHolder; using CDR.DataRecipient.SDK.Models; +using CDR.DataRecipient.SDK.Services.DataHolder; using CDR.DataRecipient.Web.Extensions; +using CDR.DataRecipient.Web.Features; using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; -using CDR.DataRecipient.Web.Features; +using Microsoft.Extensions.Logging; using Microsoft.FeatureManagement; namespace CDR.DataRecipient.Web.Common { public class DataHolderDiscoveryCache : IDataHolderDiscoveryCache - { + { private readonly IDistributedCache _cache; private readonly IInfosecService _dhInfosecService; private readonly IDataHoldersRepository _dhRepository; - private readonly IFeatureManager _featureManager; private readonly ILogger _logger; - public DataHolderDiscoveryCache( + public DataHolderDiscoveryCache( IDistributedCache cache, IInfosecService dhInfosecService, IDataHoldersRepository dhRepository, - IFeatureManager featureManager, ILogger logger) - { + { _cache = cache; _dhInfosecService = dhInfosecService; _dhRepository = dhRepository; - _featureManager = featureManager; _logger = logger; } @@ -40,29 +37,27 @@ public async Task GetOidcDiscoveryByBrandId(string dataHolderBran var oidcDiscovery = await _cache.GetAsync(key); if (oidcDiscovery == null) { - _logger.LogDebug("Discovery document for {dataHolderBrandId} not found in cache. Retrieving...", dataHolderBrandId); + _logger.LogDebug("Discovery document for {DataHolderBrandId} not found in cache. Retrieving...", dataHolderBrandId); DataHolderBrand dataHolder; - var allowDynamicClientRegistration = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); - if (allowDynamicClientRegistration) - dataHolder = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); - else - dataHolder = await _dhRepository.GetDHBrandById(dataHolderBrandId); + dataHolder = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); if (dataHolder == null) { - _logger.LogError("Data Holder {dataHolderBrandId} not found.", dataHolderBrandId); + _logger.LogError("Data Holder {DataHolderBrandId} not found.", dataHolderBrandId); return null; } + string infosecBaseUri = dataHolder.EndpointDetail.InfoSecBaseUri; oidcDiscovery = (await _dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; if (oidcDiscovery != null) { - _logger.LogDebug("Data Holder {dataHolderBrandId} discovery document added to cache.", dataHolderBrandId); + _logger.LogDebug("Data Holder {DataHolderBrandId} discovery document added to cache.", dataHolderBrandId); await _cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); } } + return oidcDiscovery; } @@ -72,16 +67,17 @@ public async Task GetOidcDiscoveryByInfoSecBaseUri(string infosec var oidcDiscovery = await _cache.GetAsync(key); if (oidcDiscovery == null) { - _logger.LogDebug("Discovery document for {infosecBaseUri} not found in cache. Retrieving...", infosecBaseUri); + _logger.LogDebug("Discovery document for {InfosecBaseUri} not found in cache. Retrieving...", infosecBaseUri); oidcDiscovery = (await _dhInfosecService.GetOidcDiscovery(infosecBaseUri)).Data; if (oidcDiscovery != null) { - _logger.LogDebug("Discovery document for {infosecBaseUri} added to cache.", infosecBaseUri); + _logger.LogDebug("Discovery document for {InfosecBaseUri} added to cache.", infosecBaseUri); await _cache.SetAsync(key, oidcDiscovery, DateTimeOffset.UtcNow.AddMinutes(5)); } } + return oidcDiscovery; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs b/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs index 3822a93..427fced 100644 --- a/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs +++ b/Source/CDR.DataRecipient.Web/Common/IDataHolderDiscoveryCache.cs @@ -6,6 +6,7 @@ namespace CDR.DataRecipient.Web.Common public interface IDataHolderDiscoveryCache { Task GetOidcDiscoveryByBrandId(string dataHolderBrandId); + Task GetOidcDiscoveryByInfoSecBaseUri(string infosecBaseUri); } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Common/IOidcSettingsProvider.cs b/Source/CDR.DataRecipient.Web/Common/IOidcSettingsProvider.cs new file mode 100644 index 0000000..03151ec --- /dev/null +++ b/Source/CDR.DataRecipient.Web/Common/IOidcSettingsProvider.cs @@ -0,0 +1,7 @@ +namespace CDR.DataRecipient.Web.Common +{ + public interface IOidcSettingsProvider + { + string GetSecret(); + } +} diff --git a/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs b/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs new file mode 100644 index 0000000..2962d93 --- /dev/null +++ b/Source/CDR.DataRecipient.Web/Common/OidcSettingsProvider.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CDR.DataRecipient.Web.Common +{ + public class OidcSettingsProvider : IOidcSettingsProvider + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public OidcSettingsProvider(IConfiguration configuration, ILogger logger) + { + _configuration = configuration; + _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); + + // 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]; + } + + return secret; + } + } +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs b/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs index 5b9933f..2bec49b 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ApiController.cs @@ -24,6 +24,7 @@ public ApiController( } [Route("generate-client-assertion")] + [HttpPost] public async Task GenerateClientAssertion( [FromQuery] string iss = null, [FromQuery] string aud = null, @@ -37,12 +38,11 @@ public async Task GenerateClientAssertion( var privateKeyJwt = new PrivateKeyJwt(privateKeyFormatted); return privateKeyJwt.Generate( - iss ?? sp.SoftwareProductId, - aud ?? await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri), + iss ?? sp.SoftwareProductId, + aud ?? await _cacheManager.GetRegisterTokenEndpoint(reg.OidcDiscoveryUri), jti ?? System.Guid.NewGuid().ToString(), - exp ?? 10, + 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 b072cfc..a29781b 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ConsentController.cs @@ -40,16 +40,16 @@ public class ConsentController : Controller private readonly IDistributedCache _cache; private readonly IInfosecService _dhInfosecService; private readonly IRegistrationsRepository _registrationsRepository; - private readonly IConsentsRepository _consentsRepository; + private readonly IConsentsRepository _consentsRepository; private readonly IDataHolderDiscoveryCache _dataHolderDiscoveryCache; - protected readonly ILogger _logger; + protected readonly ILogger _logger; public ConsentController( IConfiguration config, IDistributedCache cache, IInfosecService dhInfosecService, IRegistrationsRepository registrationsRepository, - IConsentsRepository consentsRepository, + IConsentsRepository consentsRepository, IDataHolderDiscoveryCache dataHolderDiscoveryCache, ILogger logger) { @@ -57,22 +57,20 @@ public ConsentController( _cache = cache; _dhInfosecService = dhInfosecService; _registrationsRepository = registrationsRepository; - _consentsRepository = consentsRepository; + _consentsRepository = consentsRepository; _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _logger = logger; + _logger = logger; } - - [HttpPost] [Route("registration/detail")] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task RegistrationDetail(string registrationId) { // Return the software product detail. - string message = ""; - string redirectUris = ""; - string scope = ""; + string message = string.Empty; + string redirectUris = string.Empty; + string scope = string.Empty; var registrationInfo = Registration.SplitRegistrationId(registrationId); Registration myResponse = await _registrationsRepository.GetRegistration(registrationInfo.ClientId, registrationInfo.DataHolderBrandId); @@ -88,22 +86,24 @@ public async Task RegistrationDetail(string registrationId) sb.Append(item); sb.Append(' '); } + redirectUris = sb.ToString().Trim(); scope = myResponse.Scope; } + return new JsonResult(new { message, redirectUris, scope }) { }; } - [HttpGet] + [HttpGet] [HttpPost] - [Route("callback")] + [Route("callback")] [ServiceFilter(typeof(LogActionEntryAttribute))] [AllowAnonymous] public async Task Callback() { var model = new TokenModel(); var sp = _config.GetSoftwareProductConfig(); - + (bool isvalid, string authCode, AuthorisationState authState, ErrorList errorList) = await ValidateCallback(this.Request); if (errorList != null && errorList.Errors.Count > 0) @@ -111,9 +111,9 @@ public async Task Callback() model.Messages = "An error has occurred."; model.ErrorList.Errors.AddRange(errorList.Errors); } - + if (isvalid) - { + { // Request a token from the data holder. var tokenEndpoint = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri)).TokenEndpoint; @@ -123,11 +123,11 @@ public async Task Callback() ClientId = authState.ClientId, ClientCertificate = sp.ClientCertificate.X509Certificate, SigningCertificate = sp.SigningCertificate.X509Certificate, - Scope = "", + Scope = string.Empty, RedirectUri = authState.RedirectUri, Code = authCode, GrantType = "authorization_code", - Pkce = authState.Pkce + Pkce = authState.Pkce, }; model.TokenResponse = await _dhInfosecService.GetAccessToken(accessToken); @@ -141,14 +141,14 @@ public async Task Callback() DataHolderBrandId = authState.DataHolderBrandId, ClientId = authState.ClientId, SharingDuration = authState.SharingDuration, - CdrArrangementId = model.TokenResponse.Data.CdrArrangementId ?? String.Empty, + CdrArrangementId = model.TokenResponse.Data.CdrArrangementId ?? string.Empty, IdToken = model.TokenResponse.Data.IdToken, - AccessToken = model.TokenResponse.Data.AccessToken ?? String.Empty, - RefreshToken = model.TokenResponse.Data.RefreshToken ?? String.Empty, + AccessToken = model.TokenResponse.Data.AccessToken ?? string.Empty, + RefreshToken = model.TokenResponse.Data.RefreshToken ?? string.Empty, ExpiresIn = model.TokenResponse.Data.ExpiresIn, Scope = model.TokenResponse.Data.Scope, TokenType = model.TokenResponse.Data.TokenType, - CreatedOn = DateTime.UtcNow + CreatedOn = DateTime.UtcNow, }; await _consentsRepository.PersistConsent(consentArrangement); @@ -163,13 +163,13 @@ public async Task Callback() if (!string.IsNullOrEmpty(qs["title"])) { model.ErrorList.Errors.Add(new SDK.Models.Error(qs["code"], qs["title"], qs["detail"])); - } + } } return View(model); } - private async Task<(bool isvalid, string authCode, AuthorisationState authState, ErrorList errorList)> ValidateCallback(HttpRequest request) + private async Task<(bool Isvalid, string AuthCode, AuthorisationState AuthState, ErrorList ErrorList)> ValidateCallback(HttpRequest request) { bool isValid = false; string authCode = string.Empty; @@ -177,38 +177,39 @@ public async Task Callback() AuthorisationState authState = null; ErrorList errorList = new ErrorList(); - //GET Request + // GET Request if (request.Method.Equals("get", StringComparison.OrdinalIgnoreCase)) - { - //code, jwt + { + // 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: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); return (isValid, authCode, authState, errorList); } (isValid, authCode, authState, errorList) = await ValidateJARMToken(responseToken); } } - //code id_token, form_post - //code, form_post.jwt + + // code id_token, form_post + // code, form_post.jwt else if (request.Method.Equals("post", StringComparison.OrdinalIgnoreCase) && request.Form != null) { - //form_post - if (request.Form.ContainsKey("id_token")) + // form_post + if (request.Form.ContainsKey(SDK.Constants.TokenTypes.ID_TOKEN)) { authCode = request.Form["code"].ToString(); state = request.Form["state"].ToString(); - + if (string.IsNullOrEmpty(state)) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingState)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingState)); return (isValid, authCode, authState, errorList); } @@ -217,28 +218,29 @@ public async Task Callback() if (authState == null) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthState)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthState)); return (isValid, authCode, authState, errorList); } if (string.IsNullOrEmpty(authCode)) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthCode)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthCode)); return (isValid, authCode, authState, errorList); } isValid = true; } - //form_post.jwt is JARM callback + + // form_post.jwt is JARM callback else if (request.Form.ContainsKey("response")) { var responseToken = request.Form["response"].ToString(); - + if (string.IsNullOrEmpty(responseToken)) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingResponse)); return (isValid, authCode, authState, errorList); } @@ -251,7 +253,7 @@ public async Task Callback() var errorDescription = request.Form["error_description"].ToString(); var errorCode = request.Form["error_code"].ToString(); - //error and error_description + // error and error_description if (!string.IsNullOrEmpty(error)) { errorList.Errors.Add(new Error(code: errorCode, title: error, detail: errorDescription)); @@ -264,8 +266,8 @@ public async Task Callback() return (isValid, authCode, authState, errorList); } - //Validating JARM token - private async Task<(bool isvalid, string authCode, AuthorisationState authState, ErrorList errorList)> ValidateJARMToken(string responseToken) + // Validating JARM token + private async Task<(bool Isvalid, string AuthCode, AuthorisationState AuthState, ErrorList ErrorList)> ValidateJARMToken(string responseToken) { bool isValid = false; string authCode = string.Empty; @@ -283,7 +285,8 @@ public async Task Callback() // Check if the token is encrypted if (!string.IsNullOrEmpty(token.Header.Enc)) { - var failedDecryptionError = new Error(code: "", title: ErrorTitles.InvalidResponse, detail: ErrorDescription.FailedDecryption); + 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 encryptionKeys = sp.SigningCertificate.X509Certificate.GetEncryptionCredentials(); @@ -307,11 +310,11 @@ public async Task Callback() token = handler.ReadJwtToken(responseToken); } - state = token.Claims.FirstOrDefault(x => x.Type == "state")?.Value ?? ""; + state = token.Claims.FirstOrDefault(x => x.Type == "state")?.Value ?? string.Empty; if (string.IsNullOrEmpty(state)) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingState)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingState)); return (isValid, authCode, authState, errorList); } @@ -320,22 +323,22 @@ public async Task Callback() if (authState == null) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthState)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthState)); return (isValid, authCode, authState, errorList); } - //Validate token against JWKS of the Data holder - var dataholderDiscoveryDocument = (await _dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri)); + // Validate token against JWKS of the Data holder + var dataholderDiscoveryDocument = await _dataHolderDiscoveryCache.GetOidcDiscoveryByInfoSecBaseUri(authState.DataHolderInfosecBaseUri); if (dataholderDiscoveryDocument == null) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingDiscoveryDocument)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingDiscoveryDocument)); return (isValid, authCode, authState, errorList); } - _logger.LogDebug("Validating token against {jwksUri}.", dataholderDiscoveryDocument.JwksUri); + _logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); - //Validate the token + // Validate the token var validated = await responseToken.ValidateToken( dataholderDiscoveryDocument.JwksUri, _logger, @@ -344,14 +347,14 @@ public async Task Callback() validateLifetime: true, acceptAnyServerCertificate: _config.IsAcceptingAnyServerCertificate()); - _logger.LogDebug("Validated token: {isValid}.", validated.IsValid); + _logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); isValid = validated.IsValid; - var errorTitle = token.Claims.FirstOrDefault(x => x.Type == "error")?.Value ?? validated.validationError?.Title ?? ""; - var errorDescription = token.Claims.FirstOrDefault(x => x.Type == "error_description")?.Value ?? validated.validationError?.Detail ?? ""; - var errorCode = validated.validationError?.Code; + var errorTitle = token.Claims.FirstOrDefault(x => x.Type == "error")?.Value ?? validated.ValidationError?.Title ?? string.Empty; + var errorDescription = token.Claims.FirstOrDefault(x => x.Type == "error_description")?.Value ?? validated.ValidationError?.Detail ?? string.Empty; + var errorCode = validated.ValidationError?.Code; - //Error description + // Error description if (!string.IsNullOrEmpty(errorTitle)) { errorList.Errors.Add(new Error(code: errorCode, title: errorTitle, detail: errorDescription)); @@ -359,19 +362,19 @@ public async Task Callback() return (isValid, authCode, authState, errorList); } - authCode = token.Claims.FirstOrDefault(x => x.Type == "code")?.Value ?? ""; + authCode = token.Claims.FirstOrDefault(x => x.Type == "code")?.Value ?? string.Empty; if (string.IsNullOrEmpty(authCode)) { isValid = false; - errorList.Errors.Add(new Error(code: "", title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthCode)); + errorList.Errors.Add(new Error(code: string.Empty, title: ErrorTitles.MissingField, detail: ErrorDescription.MissingAuthCode)); return (isValid, authCode, authState, errorList); } } - catch(Exception ex) + catch (Exception ex) { _logger.LogError(ex, "An error occurred validating the JARM token"); - var (errorCode, errorTitle, errorDescription)= ex.Message.ParseErrorString("Token Validation Error", "error", ex.Message); + var (errorCode, errorTitle, errorDescription) = ex.Message.ParseErrorString("Token Validation Error", "error", ex.Message); errorList.Errors.Add(new Error(code: errorCode, title: errorTitle, detail: errorDescription)); return (false, authCode, authState, errorList); @@ -387,7 +390,7 @@ public async Task Callback() public async Task Consents() { var model = new ConsentsModel(); - model.ConsentArrangements = await _consentsRepository.GetConsents("", "", HttpContext.User.GetUserId(), ""); + model.ConsentArrangements = await _consentsRepository.GetConsents(string.Empty, string.Empty, HttpContext.User.GetUserId(), string.Empty); return View(model); } @@ -401,12 +404,12 @@ public async Task UserInfo(string cdrArrangementId) { StatusCode = reg.StatusCode, Messages = reg.Messages, - Payload = reg.Payload + Payload = reg.Payload, }; return new JsonResult(response) { - StatusCode = 200 + StatusCode = 200, }; } @@ -420,12 +423,12 @@ public async Task Introspection(string cdrArrangementId) { StatusCode = reg.StatusCode, Messages = reg.Messages, - Payload = reg.Payload + Payload = reg.Payload, }; return new JsonResult(response) { - StatusCode = 200 + StatusCode = 200, }; } @@ -439,12 +442,12 @@ public async Task Revoke(string cdrArrangementId) { StatusCode = reg.StatusCode, Messages = reg.Messages, - Payload = reg.Payload + Payload = reg.Payload, }; return new JsonResult(response) { - StatusCode = 200 + StatusCode = 200, }; } @@ -458,12 +461,12 @@ public async Task Revoke(string cdrArrangementId, [FromQuery] str { StatusCode = reg.StatusCode, Messages = reg.Messages, - Payload = reg.Payload + Payload = reg.Payload, }; return new JsonResult(response) { - StatusCode = 200 + StatusCode = 200, }; } @@ -477,12 +480,12 @@ public async Task Refresh(string cdrArrangementId) { StatusCode = reg.StatusCode, Messages = reg.Messages, - Payload = reg.Payload + Payload = reg.Payload, }; return new JsonResult(response) { - StatusCode = 200 + StatusCode = 200, }; } @@ -618,7 +621,7 @@ private async Task GetIntrospection(string cdrArrangementId) { StatusCode = introspection == null ? System.Net.HttpStatusCode.NotFound : System.Net.HttpStatusCode.OK, Messages = introspection == null ? "Failed to retrieve introspection details." : "Introspection details found.", - Payload = introspection == null ? null : JsonConvert.SerializeObject(introspection) + Payload = introspection == null ? null : JsonConvert.SerializeObject(introspection), }; } @@ -657,10 +660,10 @@ private async Task GetUserInfo(string cdrArrangementId) { StatusCode = userInfo == null ? System.Net.HttpStatusCode.NotFound : System.Net.HttpStatusCode.OK, Messages = userInfo == null ? "Failed to retrieve userinfo." : "userinfo details found.", - Payload = userInfo == null ? null : JsonConvert.SerializeObject(userInfo) + Payload = userInfo == null ? null : JsonConvert.SerializeObject(userInfo), }; } - + private async Task RevokeToken(string cdrArrangementId, string tokenType) { var sp = _config.GetSoftwareProductConfig(); @@ -693,4 +696,4 @@ private async Task RevokeToken(string cdrArrangementId, string to }; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs index 1d94696..78370ca 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataHoldersController.cs @@ -27,7 +27,7 @@ public class DataHoldersController : Controller private readonly IMetadataService _metadataService; private readonly IDataHoldersRepository _repository; private readonly IFeatureManager _featureManager; - + public DataHoldersController( IConfiguration config, IInfosecService infosecService, @@ -71,9 +71,9 @@ private async Task GetDataHolderBrands(DataHoldersModel model) // Get the access token from the Register. var tokenResponse = await _infosecService.GetAccessToken( - tokenEndpoint, - sp.SoftwareProductId, - sp.ClientCertificate.X509Certificate, + tokenEndpoint, + sp.SoftwareProductId, + sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate); if (!tokenResponse.IsSuccessful) @@ -84,12 +84,12 @@ 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( - reg.MtlsBaseUri, - model.Version, - tokenResponse.Data.AccessToken, - sp.ClientCertificate.X509Certificate, - sp.SoftwareProductId, - model.Industry, + reg.MtlsBaseUri, + model.Version, + tokenResponse.Data.AccessToken, + sp.ClientCertificate.X509Certificate, + sp.SoftwareProductId, + model.Industry, pageSize: _config.GetDefaultPageSize()); if (statusCode != System.Net.HttpStatusCode.OK) @@ -137,7 +137,7 @@ private async Task PopulateModel(DataHoldersModel model) Url = reg.GetDataHolderBrandsEndpoint, RequiresClientCertificate = true, RequiresAccessToken = true, - SupportsVersion = true + SupportsVersion = true, }; SetDefaults(model); } @@ -156,4 +156,4 @@ private void SetDefaults(DataHoldersModel model) } } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs index 47cc664..92ed345 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingBankingController.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Services.DataHolder; using Microsoft.AspNetCore.Mvc; @@ -29,8 +30,9 @@ protected override string IndustryName return "Banking"; } } + protected override string ApiGroupName => this.IndustryName; - + protected override string CdsSwaggerLocation { get @@ -45,9 +47,11 @@ public DataSharingBankingController( IConsentsRepository consentsRepository, IDataHoldersRepository dhRepository, IInfosecService infosecService, - ILogger logger) : base(config, cache, consentsRepository, dhRepository, infosecService, logger) + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) { - } + } protected override JObject PrepareSwaggerJson(JObject json, Uri uri) { @@ -58,13 +62,12 @@ 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 + /// Request Path. + /// 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) || requestPath.Contains("/cds-au/v1/banking/products", StringComparison.OrdinalIgnoreCase); } - } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs index 2acc0aa..c8987b7 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingCommonController.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Net.Http; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Services.DataHolder; using Microsoft.AspNetCore.Mvc; @@ -29,6 +31,7 @@ protected override string IndustryName return string.Empty; } } + protected override string ApiGroupName => "Common"; protected override string CdsSwaggerLocation @@ -45,25 +48,38 @@ public DataSharingCommonController( IConsentsRepository consentsRepository, IDataHoldersRepository dhRepository, IInfosecService infosecService, - ILogger logger) : base(config, cache, consentsRepository, dhRepository, infosecService, logger) + 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"; + + // Remove TLS Servers section from endpoints + var paths = json["paths"].Children().Select(p => (JObject)p.Value); + foreach (var path in paths) + { + var getsets = path.Children().Select(p => (JObject)p.Value); + foreach (var getset in getsets) + { + getset.Property("servers")?.Remove(); + } + } + return json; } /// /// 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 + /// Request Path. + /// 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/DataSharingControllerBase.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs index 12ac920..49fbad9 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingControllerBase.cs @@ -1,4 +1,11 @@ -using CDR.DataRecipient.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using CDR.DataRecipient.Models; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Exceptions; using CDR.DataRecipient.SDK.Extensions; @@ -15,20 +22,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Security; -using System.Text; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { [Authorize] public abstract class DataSharingControllerBase : Controller { + private readonly IHttpClientFactory _clientFactory; + protected const string HEADER_INJECT_CDR_ARRANGEMENT_ID = "x-inject-cdr-arrangement-id"; protected readonly IConfiguration _config; protected readonly IDistributedCache _cache; @@ -38,11 +39,14 @@ public abstract class DataSharingControllerBase : Controller protected readonly ILogger _logger; protected abstract string BasePath { get; } + protected abstract string IndustryName { get; } + /// /// This is the group name of the APIs according to the standards. e.g. "Banking" API, "Common" API. /// protected abstract string ApiGroupName { get; } + protected abstract string CdsSwaggerLocation { get; } protected List _allowedHeaders; @@ -53,7 +57,8 @@ protected DataSharingControllerBase( IConsentsRepository consentsRepository, IDataHoldersRepository dhRepository, IInfosecService infosecService, - ILogger logger) + ILogger logger, + IHttpClientFactory clientFactory) { _config = config; _cache = cache; @@ -61,7 +66,8 @@ protected DataSharingControllerBase( _dhRepository = dhRepository; _infosecService = infosecService; _logger = logger; - _allowedHeaders = _config.GetValue(Constants.ConfigurationKeys.AllowSpecificHeaders).Split(',').ToList(); + _allowedHeaders = [.. _config.GetValue(Constants.ConfigurationKeys.AllowSpecificHeaders).Split(',')]; + _clientFactory = clientFactory; } [HttpGet] @@ -76,14 +82,14 @@ public IActionResult Index() /// /// Endpoint that outputs a customised version of the CDS swagger in order to utilise the SwaggerUI. /// - /// + /// Task. [HttpGet] [Route("swagger")] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Swagger() { var sp = _config.GetSoftwareProductConfig(); - var client = new HttpClient(); + var client = _clientFactory.CreateClient(); Uri uri = new Uri(sp.RecipientBaseUri); // Get the CDS swagger file. @@ -104,7 +110,7 @@ public async Task Swagger() /// /// The Proxy action captures requests from the SwaggerUI and forwards them on to the appropriate data holder. /// - /// + /// Task. [Route("proxy/{**path}")] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task Proxy() @@ -121,7 +127,7 @@ public async Task Proxy() return; } - var requestPath = this.Request.Path.ToString().Replace($"/{this.BasePath}/proxy", ""); + var requestPath = this.Request.Path.ToString().Replace($"/{this.BasePath}/proxy", string.Empty); if (!IsValidRequestPath(requestPath)) { Response.ContentType = "application/json"; @@ -135,7 +141,7 @@ public async Task Proxy() var isPublic = 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); + _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(); @@ -152,11 +158,12 @@ public async Task Proxy() } var client = new HttpClient(clientHandler); - var requestUri = String.Concat(baseUri, requestPath, this.Request.QueryString); + + var requestUri = string.Concat(baseUri, requestPath, this.Request.QueryString); var request = new HttpRequestMessage() { Method = this.Request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) ? HttpMethod.Get : HttpMethod.Post, - RequestUri = new Uri(requestUri) + RequestUri = new Uri(requestUri), }; // Add the body to the request for POST requests. @@ -213,7 +220,7 @@ public async Task>> GetCdrArrangements() protected virtual async Task> GetConsents(string userId, string industry = null) { - return await _consentsRepository.GetConsents("", "", userId, industry); + return await _consentsRepository.GetConsents(string.Empty, string.Empty, userId, industry); } protected virtual void PopulateModel(DataSharingModel model) @@ -245,6 +252,7 @@ protected virtual async Task GetCdrArrangement(HttpRequest r } protected abstract JObject PrepareSwaggerJson(JObject json, Uri uri); + protected abstract bool IsPublic(string requestPath); protected virtual bool IsValidRequestPath(string requestPath) diff --git a/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs b/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs index d25acd6..50772ad 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DataSharingEnergyController.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK.Services.DataHolder; using Microsoft.AspNetCore.Mvc; @@ -29,6 +30,7 @@ protected override string IndustryName return "Energy"; } } + protected override string ApiGroupName => this.IndustryName; protected override string CdsSwaggerLocation @@ -45,7 +47,9 @@ public DataSharingEnergyController( IConsentsRepository consentsRepository, IDataHoldersRepository dhRepository, IInfosecService infosecService, - ILogger logger) : base(config, cache, consentsRepository, dhRepository, infosecService, logger) + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(config, cache, consentsRepository, dhRepository, infosecService, logger, httpClientFactory) { } @@ -58,13 +62,12 @@ protected override JObject PrepareSwaggerJson(JObject json, Uri uri) /// /// Determine if the target request is for a public endpoint, or a resource endpoint. /// - /// Request Path + /// Request Path. /// 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) || requestPath.Contains("/cds-au/v1/energy/plans", StringComparison.OrdinalIgnoreCase); } - } } diff --git a/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs b/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs index 3c1cb90..2226730 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/DynamicClientRegistrationController.cs @@ -1,5 +1,7 @@ using CDR.DataRecipient.Repository; using CDR.DataRecipient.SDK; +using CDR.DataRecipient.SDK.Enum; +using CDR.DataRecipient.SDK.Enumerations; using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Models; using CDR.DataRecipient.SDK.Services.DataHolder; @@ -14,6 +16,7 @@ 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; @@ -94,23 +97,32 @@ public async Task Index(DynamicClientRegistrationModel model) try { if (string.IsNullOrEmpty(model.ClientId)) + { await Register(model); + } else + { await UpdateRegistration(model); + } await PopulateFormDetail(model); } catch (Exception ex) { - var type = ""; + var type = string.Empty; if (string.IsNullOrEmpty(model.ClientId)) + { type = $"create"; + } else + { type = "update"; + } var msg = $"Unable to {type} the Dynamic Client Registration with DataHolderBrandId: {model.DataHolderBrandId} - {ex.Message}"; return View("Error", new ErrorViewModel { Message = msg }); } + return View(model); } @@ -130,6 +142,7 @@ public async Task Delete(string clientId, string dataHolderBrandI { return Ok(); } + regResp.StatusCode = System.Net.HttpStatusCode.OK; regResp.Messages = deleteResponse.Messages; regResp.Payload = deleteResponse.ResponsePayload; @@ -140,9 +153,10 @@ public async Task Delete(string clientId, string dataHolderBrandI regResp.StatusCode = System.Net.HttpStatusCode.BadRequest; regResp.Messages = msg; } + return new JsonResult(regResp) { - StatusCode = Convert.ToInt32(regResp.StatusCode) + StatusCode = Convert.ToInt32(regResp.StatusCode), }; } @@ -161,7 +175,7 @@ public async Task Get(string clientId, string dataHolderBrandId) regResp.StatusCode = reg.StatusCode; regResp.Messages = reg.Messages; regResp.Payload = reg.ResponsePayload; - } + } } catch (Exception ex) { @@ -169,12 +183,36 @@ public async Task Get(string clientId, string dataHolderBrandId) regResp.StatusCode = System.Net.HttpStatusCode.BadRequest; regResp.Messages = msg; } + return new JsonResult(regResp) { - StatusCode = Convert.ToInt32(regResp.StatusCode) + StatusCode = Convert.ToInt32(regResp.StatusCode), }; } + [HttpPut] + [Route("registrations/{clientId}/{dataHolderBrandId}")] + [ServiceFilter(typeof(LogActionEntryAttribute))] + public async Task UpdateDCRRegistration(string clientId, string dataHolderBrandId) + { + var regResp = new ResponseModel(); + + try + { + regResp = await UpdateCurrentRegistration(clientId, dataHolderBrandId); + } + catch (Exception ex) + { + var msg = $"Unable to view the DCR details for ClientId: {clientId}, DataHolderBrandId:{dataHolderBrandId} - {ex.Message}"; + regResp.StatusCode = System.Net.HttpStatusCode.BadRequest; + regResp.Messages = msg; + } + + return new JsonResult(regResp) + { + StatusCode = Convert.ToInt32(regResp.StatusCode), + }; + } [FeatureGate(nameof(Feature.AllowDynamicClientRegistration))] [HttpGet] @@ -182,11 +220,11 @@ public async Task Get(string clientId, string dataHolderBrandId) [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetOidcDocument(string dataHolderBrandId) { - var response = new ResponseModel(); - string configUrl = String.Empty; + var response = new ResponseModel(); + string configUrl = string.Empty; try - { + { DataHolderBrand dataHolder; dataHolder = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); @@ -197,7 +235,7 @@ public async Task GetOidcDocument(string dataHolderBrandId) } else { - string infosecBaseUri = dataHolder.EndpointDetail.InfoSecBaseUri; + string infosecBaseUri = dataHolder.EndpointDetail.InfoSecBaseUri; configUrl = string.Concat(infosecBaseUri.TrimEnd('/'), "/.well-known/openid-configuration"); var oidcDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); @@ -205,20 +243,20 @@ public async Task GetOidcDocument(string dataHolderBrandId) response.Payload = payload; response.Messages = $"Discovery Document details loaded from {configUrl}"; response.StatusCode = HttpStatusCode.OK; - } + } } catch (Exception) - { + { response.Messages = $"Unable to load Discovery Document from {configUrl}"; response.StatusCode = HttpStatusCode.BadRequest; } + return new JsonResult(response) { - StatusCode = Convert.ToInt32(response.StatusCode) + StatusCode = Convert.ToInt32(response.StatusCode), }; } - private async Task Register(DynamicClientRegistrationModel model) { var sp = _config.GetSoftwareProductConfig(); @@ -268,7 +306,7 @@ private async Task GetRegistration(string client return model; } - var accessToken = new AccessToken() + var accessToken = new AccessToken() { TokenEndpoint = dataHolderDiscovery.TokenEndpoint, ClientId = clientId, @@ -276,7 +314,7 @@ private async Task GetRegistration(string client SigningCertificate = sp.SigningCertificate.X509Certificate, Scope = Constants.Scopes.CDR_DYNAMIC_CLIENT_REGISTRATION, RedirectUri = sp.RedirectUri, - GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS + GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); @@ -302,14 +340,118 @@ private async Task GetRegistration(string client return model; } + private async Task UpdateCurrentRegistration(string clientId, string dataHolderBrandId) + { + var model = new ResponseModel(); + + var softwareProductConfig = _config.GetSoftwareProductConfig(); + var registerConfig = _config.GetRegisterConfig(); + var tokenEndpoint = await _cacheManager.GetRegisterTokenEndpoint(registerConfig.OidcDiscoveryUri); + + // Get an access token from the Register. + var registerTokenResponse = await _regInfosecService.GetAccessToken( + tokenEndpoint, + softwareProductConfig.SoftwareProductId, + softwareProductConfig.ClientCertificate.X509Certificate, + softwareProductConfig.SigningCertificate.X509Certificate); + + if (!registerTokenResponse.IsSuccessful) + { + model.StatusCode = registerTokenResponse.StatusCode; + model.Messages = $"{registerTokenResponse.StatusCode} - {registerTokenResponse.Message}"; + return model; + } + + // Get an SSA from the Register + var ssaResponse = await _ssaService.GetSoftwareStatementAssertion( + registerConfig.MtlsBaseUri, + registerConfig.GetSsaMinXv, + registerTokenResponse.Data.AccessToken, + softwareProductConfig.ClientCertificate.X509Certificate, + softwareProductConfig.BrandId, + softwareProductConfig.SoftwareProductId, + Industry.ALL); + + if (!ssaResponse.IsSuccessful) + { + model.StatusCode = ssaResponse.StatusCode; + model.Messages = $"{ssaResponse.StatusCode} - {ssaResponse.Message}"; + return model; + } + + // Get Data Holder discovery + var ssa = ssaResponse.Data; + var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(dataHolderBrandId); + + var accessToken = new AccessToken() + { + TokenEndpoint = dataHolderDiscovery.TokenEndpoint, + ClientId = clientId, + ClientCertificate = softwareProductConfig.ClientCertificate.X509Certificate, + SigningCertificate = softwareProductConfig.SigningCertificate.X509Certificate, + Scope = Constants.Scopes.CDR_DYNAMIC_CLIENT_REGISTRATION, + RedirectUri = softwareProductConfig.RedirectUri, + GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, + }; + + // Get Access Token from Data Holder + var dhTokenResponse = await _dhInfosecService.GetAccessToken(accessToken); + + if (!dhTokenResponse.IsSuccessful) + { + model.StatusCode = dhTokenResponse.StatusCode; + model.Messages = $"{dhTokenResponse.StatusCode} - {dhTokenResponse.Message}"; + return model; + } + + // create DCR Request Jwt + var (claims, errorMessage) = CreateClaimsForUpdateDCRRequest(dataHolderDiscovery, ssa, softwareProductConfig); + if (claims == null) + { + model.Messages = errorMessage; + return model; + } + + var jwt = new JwtSecurityToken( + issuer: softwareProductConfig.SoftwareProductId, + audience: dataHolderDiscovery.Issuer, + claims: claims, + expires: DateTime.UtcNow.AddMinutes(5), + signingCredentials: new X509SigningCredentials(softwareProductConfig.SigningCertificate.X509Certificate, SecurityAlgorithms.RsaSsaPssSha256)); + + var tokenHandler = new JwtSecurityTokenHandler(); + var registrationRequestJwt = tokenHandler.WriteToken(jwt); + + // Send Put DCR to the Data Holder. + var dcrResponse = await _dcrService.UpdateRegistration( + dataHolderDiscovery.RegistrationEndpoint, + softwareProductConfig.ClientCertificate.X509Certificate, + dhTokenResponse.Data.AccessToken, + clientId, + registrationRequestJwt); + + model.StatusCode = dcrResponse.StatusCode; + model.Messages = dcrResponse.Message; + + if (dcrResponse.IsSuccessful) + { + var registration = dcrResponse.Data; + registration.DataHolderBrandId = dataHolderBrandId; + + await _regRepository.UpdateRegistration(dcrResponse.Data); + } + + return model; + } + private async Task UpdateRegistration(DynamicClientRegistrationModel model) - { + { var sp = _config.GetSoftwareProductConfig(); var ssa = await GetSSA(sp, model); var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(model.DataHolderBrandId); model.TransactionType = "Update"; - var accessToken = new AccessToken() + var accessToken = new AccessToken() { TokenEndpoint = dataHolderDiscovery.TokenEndpoint, ClientId = model.ClientId, @@ -317,7 +459,7 @@ private async Task UpdateRegistration(DynamicClientRegistrationModel model) SigningCertificate = sp.SigningCertificate.X509Certificate, Scope = Constants.Scopes.CDR_DYNAMIC_CLIENT_REGISTRATION, RedirectUri = sp.RedirectUri, - GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS + GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); @@ -358,21 +500,19 @@ private static string PopulateRegistrationRequestJwt(DynamicClientRegistrationMo 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 ?? "")); - claims.Add(new Claim("token_endpoint_auth_method", model.TokenEndpointAuthMethod ?? "")); - claims.Add(new Claim("application_type", model.ApplicationType ?? "")); - claims.Add(new Claim("id_token_signed_response_alg", model.IdTokenSignedResponseAlg ?? "")); - claims.Add(new Claim("id_token_encrypted_response_alg", model.IdTokenEncryptedResponseAlg ?? "")); - claims.Add(new Claim("id_token_encrypted_response_enc", model.IdTokenEncryptedResponseEnc ?? "")); - claims.Add(new Claim("request_object_signing_alg", model.RequestObjectSigningAlg ?? "")); - claims.Add(new Claim("software_statement", ssa ?? "")); - claims.Add(new Claim("authorization_signed_response_alg", model.AuthorizationSignedResponseAlg ?? "")); - claims.Add(new Claim("authorization_encrypted_response_alg", model.AuthorizationEncryptedResponseAlg ?? "")); - claims.Add(new Claim("authorization_encrypted_response_enc", model.AuthorizationEncryptedResponseEnc ?? "")); + 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 = { ',', ' '}; + char[] delimiters = { ',', ' ' }; var redirectUrisList = model.RedirectUris.Split(delimiters).ToList(); claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray)); } @@ -387,7 +527,6 @@ private static string PopulateRegistrationRequestJwt(DynamicClientRegistrationMo var responseTypeList = model.ResponseTypes.Split(','); claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypeList), JsonClaimValueTypes.JsonArray)); } - // algorithm to be adaptable. var jwt = new JwtSecurityToken( @@ -401,6 +540,92 @@ private static string PopulateRegistrationRequestJwt(DynamicClientRegistrationMo 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(); @@ -409,7 +634,7 @@ private async Task DeleteRegistration(string cli var sp = _config.GetSoftwareProductConfig(); var dataHolderDiscovery = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(client.DataHolderBrandId); - var accessToken = new AccessToken() + var accessToken = new AccessToken() { TokenEndpoint = dataHolderDiscovery.TokenEndpoint, ClientId = clientId, @@ -417,7 +642,7 @@ private async Task DeleteRegistration(string cli SigningCertificate = sp.SigningCertificate.X509Certificate, Scope = Constants.Scopes.CDR_DYNAMIC_CLIENT_REGISTRATION, RedirectUri = sp.RedirectUri, - GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS + GrantType = Constants.GrantTypes.CLIENT_CREDENTIALS, }; var tokenResponse = await _dhInfosecService.GetAccessToken(accessToken); @@ -453,9 +678,9 @@ private async Task GetSSA(SoftwareProduct sp, DynamicClientRegistrationM // Get an access token from the Register. var tokenResponse = await _regInfosecService.GetAccessToken( - tokenEndpoint, - sp.SoftwareProductId, - sp.ClientCertificate.X509Certificate, + tokenEndpoint, + sp.SoftwareProductId, + sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate); if (!tokenResponse.IsSuccessful) @@ -488,29 +713,29 @@ private async Task GetSSA(SoftwareProduct sp, DynamicClientRegistrationM private async Task PopulateFormDetail(DynamicClientRegistrationModel model) { var allowDynamicClientRegistration = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); - if (allowDynamicClientRegistration) - { - // Return any from the Registration table repository. - var registrations = await _regRepository.GetRegistrations(); - model.DataHolderBrands = (await _dhRepository.GetDataHolderBrands()) - .OrderByMockDataHolders(allowDynamicClientRegistration) - .Select(d => new SelectListItem(d.BrandName, d.DataHolderBrandId)) - .ToList(); - // Fill the brand name - if (model.DataHolderBrands != null && model.DataHolderBrands.Count > 0) + // Return any from the Registration table repository. + var registrations = await _regRepository.GetRegistrations(); + model.DataHolderBrands = (await _dhRepository.GetDataHolderBrands()) + .OrderByMockDataHolders(allowDynamicClientRegistration) + .Select(d => new SelectListItem(d.BrandName, d.DataHolderBrandId)) + .ToList(); + + // Fill the brand name + if (model.DataHolderBrands != null && model.DataHolderBrands.Count > 0) + { + var brandsDictionary = model.DataHolderBrands.ToDictionary(brand => brand.Value); + foreach (var registration in registrations) { - var brandsDictionary = model.DataHolderBrands.ToDictionary(brand => brand.Value); - foreach (var registration in registrations) - { - registration.BrandName = brandsDictionary.TryGetValue(registration.DataHolderBrandId, out var brandValue) ? - brandValue.Text : string.Empty; - } + registration.BrandName = brandsDictionary.TryGetValue(registration.DataHolderBrandId, out var brandValue) ? + brandValue.Text : string.Empty; } - model.Registrations = registrations; + } - // Set Selected item in picker - if (model.DataHolderBrands.Count > 0 && !string.IsNullOrEmpty(model.DataHolderBrandId)) + model.Registrations = registrations.OrderBy(reg => reg.BrandName); + + // Set Selected item in picker + if (model.DataHolderBrands.Count > 0 && !string.IsNullOrEmpty(model.DataHolderBrandId)) { var selected = model.DataHolderBrands.Find(d => d.Value.Equals(model.DataHolderBrandId, StringComparison.OrdinalIgnoreCase)); if (selected != null) @@ -518,11 +743,12 @@ private async Task PopulateFormDetail(DynamicClientRegistrationModel model) selected.Selected = true; } } - } - else + + if (!allowDynamicClientRegistration) { // Return any from the DcrMessage table in the repository. - model.Registrations = await _regRepository.GetDcrMessageRegistrations(); + var dcrMessages = await _regRepository.GetDcrMessageRegistrations(); + model.FailedDCRMessages = dcrMessages.Where(message => message.MessageState == Message.DCRFailed.ToString()).OrderBy(message => message.BrandName); } } @@ -538,14 +764,12 @@ private void SetViewModelDefaults(DynamicClientRegistrationModel model) model.ResponseTypes = "code"; model.ApplicationType = "web"; model.IdTokenSignedResponseAlg = sp.DefaultSigningAlgorithm; - model.IdTokenEncryptedResponseAlg = ""; - model.IdTokenEncryptedResponseEnc = ""; model.RequestObjectSigningAlg = sp.DefaultSigningAlgorithm; model.AuthorizationSignedResponseAlg = sp.DefaultSigningAlgorithm; - model.AuthorizationEncryptedResponseAlg = ""; - model.AuthorizationEncryptedResponseEnc = ""; + model.AuthorizationEncryptedResponseAlg = string.Empty; + model.AuthorizationEncryptedResponseEnc = string.Empty; model.SsaVersion = "3"; - model.Messages = ""; + model.Messages = string.Empty; } private void SetViewModel(DynamicClientRegistrationModel model, Registration client) @@ -558,11 +782,9 @@ private void SetViewModel(DynamicClientRegistrationModel model, Registration cli model.TokenEndpointAuthSigningAlg = sp.DefaultSigningAlgorithm; model.TokenEndpointAuthMethod = "private_key_jwt"; model.GrantTypes = "client_credentials,authorization_code,refresh_token"; - model.ResponseTypes = string.Join(",", client.ResponseTypes); + model.ResponseTypes = string.Join(",", client.ResponseTypes); model.ApplicationType = "web"; model.IdTokenSignedResponseAlg = sp.DefaultSigningAlgorithm; - model.IdTokenEncryptedResponseAlg = ""; - model.IdTokenEncryptedResponseEnc = ""; model.RequestObjectSigningAlg = sp.DefaultSigningAlgorithm; model.AuthorizationSignedResponseAlg = client.AuthorizationSignedResponseAlg; model.AuthorizationEncryptedResponseAlg = client.AuthorizationEncryptedResponseAlg; @@ -571,4 +793,4 @@ private void SetViewModel(DynamicClientRegistrationModel model, Registration cli model.Messages = "Waiting..."; } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs b/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs index efdbae8..1ebe76e 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/HealthController.cs @@ -12,4 +12,4 @@ public IActionResult Index() return Json(new Health() { Status = "OK" }); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs b/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs index 3c4b6df..2019828 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/HomeController.cs @@ -30,21 +30,25 @@ public async Task Index() var homePageContentUrl = _config.GetValue(Constants.Content.HomepageContentUrl); var footerContentUrl = _config.GetValue(Constants.Content.FooterContentUrl); - ViewBag.HomepageContent = ""; - ViewBag.FooterContent = ""; + ViewBag.HomepageContent = string.Empty; + 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; + } } if (!string.IsNullOrEmpty(footerContentUrl)) { var result = await client.GetAsync(footerContentUrl); if (result.StatusCode == System.Net.HttpStatusCode.OK) + { ViewBag.FooterContent = result.Content.ReadAsStringAsync().Result; + } } return View(); @@ -69,4 +73,4 @@ public IActionResult Error() return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs b/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs index 2d64cad..33b0a2b 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/JwksController.cs @@ -50,7 +50,7 @@ public IActionResult GetJwksPrivateKeys(int? id = 1) /// /// ID to control the certificate to use to generate the JWKS. Can be 1 or 2. /// Whether private key details should be included in the JWKS. - /// JsonWebKeySet + /// JsonWebKeySet. /// /// In a production scenario the private key details would never be included in the output. /// However, for FAPI testing the private key is required to be included in the JWKS when configuring the test plan. @@ -65,11 +65,12 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe if (item != null) { - _logger.LogInformation("Cache hit: {cacheKey}", cacheKey); + _logger.LogInformation("Cache hit: {CacheKey}", cacheKey); return item; } var cert = GetCertificate(id.Value); + // Get credentials from certificate var securityKey = new X509SecurityKey(cert); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); @@ -81,7 +82,6 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe // Create JWKs for sig and enc purposes. // 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, @@ -89,7 +89,7 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe kty = securityKey.PublicKey.KeyExchangeAlgorithm, n = n, e = e, - use = "sig" + use = "sig", }; var jwkEncList = encryptionCredentials.Keys.Select(key => { @@ -101,7 +101,7 @@ private SDK.Models.JsonWebKeySet GenerateJwks(int? id = 1, bool includePrivateKe kty = securityKey.PublicKey.KeyExchangeAlgorithm, n = n, e = e, - use = "enc" + use = "enc", }; }); @@ -132,7 +132,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 + /// Provides the ability to switch to an alternative certificate. /// X509Certificate2 /// /// Providing the ability to switch to a different certificate allows 2 jwks to be generated from the one data recipient. diff --git a/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs b/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs index 57cacae..88e7b60 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/OidcController.cs @@ -1,25 +1,24 @@ -using CDR.DataRecipient.Web.Models; +using System; +using System.Threading.Tasks; +using CDR.DataRecipient.Web.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using System.Linq; -using System; namespace CDR.DataRecipient.Web.Controllers { [Route("oidc")] public class OidcController : Controller - { + { public OidcController() - { + { } [Route("remoteerror")] [HttpGet] public IActionResult RemoteError([FromQuery(Name = "error_message")] string errMsg) { - return View("Error", new ErrorViewModel { ErrorTitle = "Remote Error", Message = ProcessMessage(errMsg) }); + return View("Error", new ErrorViewModel { ErrorTitle = "Remote Error", Message = ProcessMessage(errMsg) }); } [Route("autherror")] @@ -38,7 +37,7 @@ public IActionResult AccessError([FromQuery(Name = "error_message")] string errM private static string ProcessMessage(string errMsg) { - var msg = ""; + var msg = string.Empty; if (!string.IsNullOrEmpty(errMsg)) { var msgParts = errMsg.Split("|"); @@ -48,6 +47,7 @@ private static string ProcessMessage(string errMsg) msg = errorMessage; } } + return msg; } @@ -58,9 +58,9 @@ public async Task Logout() await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); var prop = new AuthenticationProperties() { - RedirectUri = "/" + RedirectUri = "/", }; await HttpContext.SignOutAsync("OpenIdConnect", prop); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Controllers/ParController.cs b/Source/CDR.DataRecipient.Web/Controllers/ParController.cs index cbceed3..600501d 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/ParController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/ParController.cs @@ -1,6 +1,7 @@ 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; @@ -50,7 +51,7 @@ public ParController( _consentsRepository = consentsRepository; _dhRepository = dhRepository; _registrationsRepository = registrationsRepository; - _logger=logger; + _logger = logger; } [HttpGet] @@ -67,16 +68,18 @@ public async Task Index() public async Task Index(ParModel model) { if (!string.IsNullOrEmpty(model.ClientId)) - { + { try - { + { var reg = await _registrationsRepository.GetRegistration(model.ClientId, model.DataHolderBrandId); var dhConfig = await _dataHolderDiscoveryCache.GetOidcDiscoveryByBrandId(reg.DataHolderBrandId); var sp = _config.GetSoftwareProductConfig(); var infosecBaseUri = await GetInfoSecBaseUri(reg.DataHolderBrandId); if (string.IsNullOrEmpty(infosecBaseUri)) + { throw new CustomException(); + } var stateKey = Guid.NewGuid().ToString(); var nonce = Guid.NewGuid().ToString(); @@ -94,13 +97,15 @@ public async Task Index(ParModel model) DataHolderBrandId = reg.DataHolderBrandId, DataHolderInfosecBaseUri = infosecBaseUri, RedirectUri = redirectUri, - UserId = this.HttpContext.User.GetUserId() + UserId = this.HttpContext.User.GetUserId(), }; if (model.UsePkce) + { authState.Pkce = _dhInfoSecService.CreatePkceData(); + } - _logger.LogDebug("saving AuthorisationState of {@authState} to cache", authState); + _logger.LogDebug("saving AuthorisationState of {@AuthState} to cache", authState); await _cache.SetAsync(stateKey, authState, DateTimeOffset.UtcNow.AddMinutes(60)); @@ -119,7 +124,7 @@ public async Task Index(ParModel model) ResponseMode = model.ResponseMode, Pkce = authState.Pkce, AcrValueSupported = acrValueSupported, - ResponseType = model.ResponseType + ResponseType = model.ResponseType, }; var authRequest = _dhInfoSecService.BuildAuthorisationRequestJwt(authorisationRequestJwt); @@ -136,16 +141,24 @@ public async Task Index(ParModel model) if (parResponse.IsSuccessful) { - model.PushedAuthorisation = parResponse.Data; + if (model.ResponseType != "code") + { + // Hybrid flow is no longer supported. MDR allows PAR calls to be redirected to DHs for Hybrid flow but no Authorization uri is displayed to the user. + model.AcfOnlyErrorMessage = $"This Data Recipient cannot progress further using a response type of \"{model.ResponseType}\". Only Authorisation Code Flow (response type of \"code\") is supported.\r\n "; + } + else + { + 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( - infosecBaseUri, - model.ClientId, - sp.SigningCertificate.X509Certificate, - model.PushedAuthorisation.RequestUri, - model.Scope, - model.ResponseType); + // Build the Authorisation URL for the Data Holder passing in the request uri returned from the PAR response. + model.AuthorisationUri = await _dhInfoSecService.BuildAuthorisationRequestUri( + infosecBaseUri, + model.ClientId, + sp.SigningCertificate.X509Certificate, + model.PushedAuthorisation.RequestUri, + model.Scope, + model.ResponseType); + } } else { @@ -172,7 +185,7 @@ public async Task Index(ParModel 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) { @@ -198,9 +211,9 @@ private static int ValidateAcrValuesSpported(string[] acrValuesSupported) public async Task RegistrationDetail(string registrationId) { // Return the software product detail. - string message = ""; - string redirectUris = ""; - string scope = ""; + string message = string.Empty; + string redirectUris = string.Empty; + string scope = string.Empty; List arrangements = new(); // Return the RedirectUris for the picked item @@ -219,12 +232,12 @@ public async Task RegistrationDetail(string registrationId) arrangements = cdrArrangements.Select(c => new SelectListItem(c.CdrArrangementId, c.CdrArrangementId)).ToList(); } - return new JsonResult(new - { - message, - arrangements, - redirectUris = string.Join(' ', registration.RedirectUris), - scope = registration.Scope + return new JsonResult(new + { + message, + arrangements, + redirectUris = string.Join(' ', registration.RedirectUris), + scope = registration.Scope, }); } @@ -248,7 +261,9 @@ private async Task GetInfoSecBaseUri(string dataHolderBrandId) { var dh = await _dhRepository.GetDataHolderBrand(dataHolderBrandId); if (dh == null) + { return null; + } return dh.EndpointDetail.InfoSecBaseUri; } diff --git a/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs b/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs index beea82c..86ce743 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/RevocationController.cs @@ -26,6 +26,7 @@ public class RevocationController : Controller 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( @@ -99,7 +100,7 @@ public async Task Revoke([Required, FromForm] RevocationModel rev if (arrangement == null || !arrangement.DataHolderBrandId.Equals(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); + _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)); } @@ -114,12 +115,12 @@ public async Task Revoke([Required, FromForm] RevocationModel rev string validIssuer = null; string validAudience = null; bool validateLifetime = false; - bool fullValidationRequired = + bool fullValidationRequired = DateTime.UtcNow > _config.AttemptValidateCdrArrangementJwtFromDate() - && HasValue(issClaim) - && HasValue(subClaim) - && HasValue(audClaim) - && HasValue(jtiClaim) + && HasValue(issClaim) + && HasValue(subClaim) + && HasValue(audClaim) + && HasValue(jtiClaim) && HasValue(expClaim); if (fullValidationRequired) @@ -142,7 +143,6 @@ public async Task Revoke([Required, FromForm] RevocationModel rev // Validate the cdr_arrangement_jwt either using "full" or "minimal" validation. var jwksUri = await GetJwksUri(); - var validated = await revocationModel.CdrArrangementJwt.ValidateToken( jwksUri, _logger, diff --git a/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs b/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs index 627c3d7..08d644d 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/SettingsController.cs @@ -12,7 +12,6 @@ namespace CDR.DataRecipient.Web.Controllers [Authorize] public class SettingsController : Controller { - private readonly IConfiguration _config; public SettingsController(IConfiguration config) @@ -35,7 +34,7 @@ private void PopulateSettings(SettingsModel model) var configSettings = _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, ""), setting.Value.StartsWith("https://") ? $"{setting.Value}" : setting.Value); + 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 b9ff00e..775e633 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/SsaController.cs @@ -57,9 +57,9 @@ private async Task GetSSA(SsaModel model) // Get the access token from the Register. var tokenResponse = await _infosecService.GetAccessToken( - tokenEndpoint, - model.SoftwareProductId, - sp.ClientCertificate.X509Certificate, + tokenEndpoint, + model.SoftwareProductId, + sp.ClientCertificate.X509Certificate, sp.SigningCertificate.X509Certificate); if (!tokenResponse.IsSuccessful) @@ -70,12 +70,12 @@ private async Task GetSSA(SsaModel model) } var ssaResponse = await _ssaService.GetSoftwareStatementAssertion( - reg.MtlsBaseUri, - model.Version, - tokenResponse.Data.AccessToken, - sp.ClientCertificate.X509Certificate, - model.BrandId, - model.SoftwareProductId, + reg.MtlsBaseUri, + model.Version, + tokenResponse.Data.AccessToken, + sp.ClientCertificate.X509Certificate, + model.BrandId, + model.SoftwareProductId, model.Industry); model.StatusCode = ssaResponse.StatusCode; @@ -98,7 +98,7 @@ private void PopulateModel(SsaModel model) RequiresAccessToken = true, RequiresClientCertificate = true, SupportsVersion = true, - Url = reg.GetSsaEndpoint + Url = reg.GetSsaEndpoint, }; } diff --git a/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs b/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs index 75f069e..2467ac9 100644 --- a/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs +++ b/Source/CDR.DataRecipient.Web/Controllers/UtilitiesController.cs @@ -1,6 +1,6 @@ -using CDR.DataRecipient.SDK.Extensions; +using System.Threading.Tasks; +using CDR.DataRecipient.SDK.Extensions; using CDR.DataRecipient.SDK.Register; -using CDR.DataRecipient.SDK.Services.Register; using CDR.DataRecipient.Web.Caching; using CDR.DataRecipient.Web.Common; using CDR.DataRecipient.Web.Extensions; @@ -9,8 +9,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Controllers { @@ -29,40 +27,6 @@ public UtilitiesController( _cacheManager = cacheManager; } - [HttpGet] - [Route("id-token")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public IActionResult IdToken() - { - var model = new IdTokenModel(); - return View(model); - } - - [HttpPost] - [Route("id-token")] - [ServiceFilter(typeof(LogActionEntryAttribute))] - public IActionResult IdToken(IdTokenModel model) - { - var sp = _config.GetSoftwareProductConfig(); - - if (string.IsNullOrEmpty(model.IdTokenEncrypted)) - { - return View(model); - } - - try - { - model.IdTokenDecrypted = model.IdTokenEncrypted.DecryptToken(sp.SigningCertificate.X509Certificate); - model.IdTokenClaims = model.IdTokenDecrypted.GetTokenClaims(); - } - catch (Exception) - { - model.Messages = "Invalid ID Token"; - } - - return View(model); - } - [HttpGet] [Route("private-key-jwt")] [ServiceFilter(typeof(LogActionEntryAttribute))] diff --git a/Source/CDR.DataRecipient.Web/Extensions.cs b/Source/CDR.DataRecipient.Web/Extensions.cs index 4d5150a..e4d1c5e 100644 --- a/Source/CDR.DataRecipient.Web/Extensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions.cs @@ -34,6 +34,7 @@ public static SoftwareProduct GetSoftwareProductConfig(this IConfiguration confi config.GetSection(key).Bind(sp); return sp; } + public static DataHolderEndpoints GetDefaultDataHolderConfig(this IConfiguration config, string key = Common.Constants.ConfigurationKeys.MockDataRecipient.DefaultDataHolder.Root) { var dh = new DataHolderEndpoints(); @@ -62,6 +63,5 @@ public static DateTime AttemptValidateCdrArrangementJwtFromDate(this IConfigurat return DateTime.Parse(obligationDate); } - } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs index a10ff7b..7f66263 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/CacheExtensions.cs @@ -6,7 +6,8 @@ namespace CDR.DataRecipient.Web.Extensions { public static class CacheExtensions { - public async static Task GetAsync(this IDistributedCache cache, string key) where T : class + public static async Task GetAsync(this IDistributedCache cache, string key) + where T : class { var bytes = await cache.GetAsync(key); if (bytes == null) @@ -17,7 +18,8 @@ public async static Task GetAsync(this IDistributedCache cache, string key return bytes.FromByteArray(); } - public async static Task SetAsync(this IDistributedCache cache, string key, T data, DateTimeOffset absoluteExpiry) where T : class + public static async Task SetAsync(this IDistributedCache cache, string key, T data, DateTimeOffset absoluteExpiry) + where T : class { await cache.SetAsync(key, data.ToByteArray(), new DistributedCacheEntryOptions() { AbsoluteExpiration = absoluteExpiry }); } diff --git a/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs index 7e76215..125d00b 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/ClaimsPrincipalExtensions.cs @@ -15,6 +15,7 @@ public static string GetUserId(this ClaimsPrincipal user) { throw new MissingClaimException(Constants.Claims.UserId); } + return userIdClaim.Value; } @@ -25,6 +26,7 @@ public static string GetUserName(this ClaimsPrincipal user) { throw new MissingClaimException(Constants.Claims.Name); } + return userNameClaim.Value; } @@ -38,4 +40,4 @@ public static bool IsUserNameUnknown(this ClaimsPrincipal user) return user.GetUserName().Equals(Defaults.DefaultUserName, System.StringComparison.OrdinalIgnoreCase); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Extensions/CustomException.cs b/Source/CDR.DataRecipient.Web/Extensions/CustomException.cs new file mode 100644 index 0000000..bc2fcd9 --- /dev/null +++ b/Source/CDR.DataRecipient.Web/Extensions/CustomException.cs @@ -0,0 +1,21 @@ +using System; + +namespace CDR.DataRecipient.Web.Extensions +{ + public class CustomException : Exception + { + public CustomException() + { + } + + public CustomException(string message) + : base(message) + { + } + + public CustomException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/CDR.DataRecipient.Web/Extensions/ExceptionExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/ExceptionExtensions.cs deleted file mode 100644 index 1b94edb..0000000 --- a/Source/CDR.DataRecipient.Web/Extensions/ExceptionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace CDR.DataRecipient.Web.Extensions -{ - [Serializable] - public class CustomException : Exception - { - public CustomException() - { - } - - public CustomException(string message) : base(message) - { - } - - public CustomException(string message, Exception innerException) : base(message, innerException) - { - } - - protected CustomException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs index 7ac1b8c..3d2f330 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/KeyExtensions.cs @@ -6,13 +6,13 @@ public static class KeyExtensions /// Apply formatting to the provided private key. /// /// Raw private key - /// + /// string public static string FormatPrivateKey(this string privateKey) { return privateKey - .Replace("-----BEGIN PRIVATE KEY-----", "") - .Replace("-----END PRIVATE KEY-----", "") - .Replace("\r\n", "") + .Replace("-----BEGIN PRIVATE KEY-----", string.Empty) + .Replace("-----END PRIVATE KEY-----", string.Empty) + .Replace("\r\n", string.Empty) .Trim(); } } diff --git a/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs index 16f3432..249dcd3 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/LinqExtensions.cs @@ -7,7 +7,7 @@ namespace CDR.DataRecipient.Web.Extensions public static class LinqExtensions { public static IEnumerable OrderByMockDataHolders( - this IEnumerable dataHolderBrands, + this IEnumerable dataHolderBrands, bool mockDataHoldersFirst) { if (!mockDataHoldersFirst) diff --git a/Source/CDR.DataRecipient.Web/Extensions/SerializationExtensions.cs b/Source/CDR.DataRecipient.Web/Extensions/SerializationExtensions.cs index eb78cae..a29ce82 100644 --- a/Source/CDR.DataRecipient.Web/Extensions/SerializationExtensions.cs +++ b/Source/CDR.DataRecipient.Web/Extensions/SerializationExtensions.cs @@ -8,14 +8,15 @@ public static byte[] ToByteArray(this object obj) { if (obj == null) { - return null; + return []; } var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj); return Encoding.UTF8.GetBytes(json); } - public static T FromByteArray(this byte[] byteArray) where T : class + public static T FromByteArray(this byte[] byteArray) + where T : class { if (byteArray == null) { @@ -25,6 +26,5 @@ public static T FromByteArray(this byte[] byteArray) where T : class var json = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length); return Newtonsoft.Json.JsonConvert.DeserializeObject(json); } - } } diff --git a/Source/CDR.DataRecipient.Web/Features/Enums.cs b/Source/CDR.DataRecipient.Web/Features/Enums.cs index ea15f0c..9c8d3f7 100644 --- a/Source/CDR.DataRecipient.Web/Features/Enums.cs +++ b/Source/CDR.DataRecipient.Web/Features/Enums.cs @@ -6,6 +6,6 @@ public enum Feature AllowDynamicClientRegistration, ShowSettings, LinkToExternalJwt, - EnforceHttpsEndpoints + EnforceHttpsEndpoints, } } diff --git a/Source/CDR.DataRecipient.Web/Filters/ClientAuthorizeAttribute.cs b/Source/CDR.DataRecipient.Web/Filters/ClientAuthorizeAttribute.cs index 48579f4..d36201f 100644 --- a/Source/CDR.DataRecipient.Web/Filters/ClientAuthorizeAttribute.cs +++ b/Source/CDR.DataRecipient.Web/Filters/ClientAuthorizeAttribute.cs @@ -7,19 +7,20 @@ namespace CDR.DataRecipient.Web.Filters { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class ClientAuthorizeAttribute : Attribute, IAuthorizationFilter - { - public const string ClaimsPrincipalKey = "Client"; - public void OnAuthorization(AuthorizationFilterContext context) - { - var client = context.HttpContext.Items[ClaimsPrincipalKey] as ClaimsPrincipal; - if (client == null) - { - // Invalid JWT - context.Result = new JsonResult(new { error = "invalid_token" }) { StatusCode = StatusCodes.Status401Unauthorized }; - context.HttpContext.Response.Headers.Append(HeaderNames.WWWAuthenticate, "Bearer error=\"invalid_token\""); - } - } - } + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class ClientAuthorizeAttribute : Attribute, IAuthorizationFilter + { + public const string ClaimsPrincipalKey = "Client"; + + public void OnAuthorization(AuthorizationFilterContext context) + { + var client = context.HttpContext.Items[ClaimsPrincipalKey] as ClaimsPrincipal; + if (client == null) + { + // Invalid JWT + context.Result = new JsonResult(new { error = "invalid_token" }) { StatusCode = StatusCodes.Status401Unauthorized }; + context.HttpContext.Response.Headers.Append(HeaderNames.WWWAuthenticate, "Bearer error=\"invalid_token\""); + } + } + } } diff --git a/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs b/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs index 711edbe..c742672 100644 --- a/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs +++ b/Source/CDR.DataRecipient.Web/Filters/LogActionEntryAttribute.cs @@ -5,26 +5,26 @@ namespace CDR.DataRecipient.Web.Filters { - [AttributeUsage(AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Method)] public class LogActionEntryAttribute : ActionFilterAttribute { - private readonly ILogger _logger; + private readonly ILogger _logger; public LogActionEntryAttribute(ILogger logger) { - _logger = logger; - } + _logger = logger; + } - public override void OnActionExecuting(ActionExecutingContext context) - { - var controller = context.RouteData.Values["controller"].ToString(); - var action = context.RouteData.Values["action"].ToString(); - using (LogContext.PushProperty("MethodName", action)) - { - _logger.LogInformation("Request received to {controller}.{action}", controller, action); - } + public override void OnActionExecuting(ActionExecutingContext context) + { + var controller = context.RouteData.Values["controller"].ToString(); + var action = context.RouteData.Values["action"].ToString(); + using (LogContext.PushProperty("MethodName", action)) + { + _logger.LogInformation("Request received to {Controller}.{Action}", controller, action); + } - base.OnActionExecuting(context); - } - } + base.OnActionExecuting(context); + } + } } diff --git a/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs b/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs index eacd44d..ede1ac0 100644 --- a/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs +++ b/Source/CDR.DataRecipient.Web/Filters/MustConsumeAttribute.cs @@ -6,24 +6,24 @@ namespace CDR.DataRecipient.Web.Filters { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] - public class MustConsumeAttribute : ActionFilterAttribute - { - private readonly string _contentType; + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class MustConsumeAttribute : ActionFilterAttribute + { + private readonly string _contentType; - public MustConsumeAttribute(string contentType) - { - _contentType = contentType; - } + public MustConsumeAttribute(string contentType) + { + _contentType = contentType; + } - public override void OnActionExecuting(ActionExecutingContext context) - { - if (context.HttpContext.Request.ContentType != _contentType) - { - context.Result = new BadRequestObjectResult(new ErrorListModel(Constants.ErrorCodes.InvalidHeader, Constants.ErrorTitles.InvalidHeader, string.Empty)); - } + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context.HttpContext.Request.ContentType != _contentType) + { + context.Result = new BadRequestObjectResult(new ErrorListModel(Constants.ErrorCodes.InvalidHeader, Constants.ErrorTitles.InvalidHeader, string.Empty)); + } - base.OnActionExecuting(context); - } - } + base.OnActionExecuting(context); + } + } } diff --git a/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs b/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs index 820c659..813f968 100644 --- a/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs +++ b/Source/CDR.DataRecipient.Web/Middleware/ClientAuthorizationMiddleware.cs @@ -14,98 +14,100 @@ namespace CDR.DataRecipient.Web.Middleware { public class ClientAuthorizationMiddleware - { - private readonly RequestDelegate _next; - private readonly IDataHolderDiscoveryCache _dataHolderDiscoveryCache; - private readonly ILogger _logger; - private readonly SoftwareProduct _softwareProduct; + { + private readonly RequestDelegate _next; + private readonly IDataHolderDiscoveryCache _dataHolderDiscoveryCache; + private readonly ILogger _logger; + private readonly SoftwareProduct _softwareProduct; - // Any path that requires a client authorisaton should be listed here. - private readonly string[] _validPaths = new string[] - { - $"/{Common.Constants.Urls.ClientArrangementRevokeUrl}" - }; + // Any path that requires a client authorisaton should be listed here. + private readonly string[] _validPaths = new string[] + { + $"/{Common.Constants.Urls.ClientArrangementRevokeUrl}", + }; - public ClientAuthorizationMiddleware(RequestDelegate next, - IConfiguration config, - IDataHolderDiscoveryCache dataHolderDiscoveryCache, - ILogger logger) - { - _next = next; - _dataHolderDiscoveryCache = dataHolderDiscoveryCache; - _logger = logger; - _softwareProduct = config.GetSoftwareProductConfig(); - } + public ClientAuthorizationMiddleware( + RequestDelegate next, + IConfiguration config, + IDataHolderDiscoveryCache dataHolderDiscoveryCache, + ILogger logger) + { + _next = next; + _dataHolderDiscoveryCache = dataHolderDiscoveryCache; + _logger = logger; + _softwareProduct = config.GetSoftwareProductConfig(); + } - public async Task Invoke(HttpContext context) - { - // Check if the path required client authentication. - if (_validPaths.Contains(context.Request.Path.Value)) - { - var authorisationHeader = context.Request.Headers.Authorization.FirstOrDefault(); - string token = null; - if (authorisationHeader != null) - { - var authorisationData = authorisationHeader.Split(" "); - token = authorisationData.Length > 0 ? authorisationData[^1] : null; - } - _logger.LogDebug("Validating authorization token: {token}", token); + public async Task Invoke(HttpContext context) + { + // Check if the path required client authentication. + if (_validPaths.Contains(context.Request.Path.Value)) + { + var authorisationHeader = context.Request.Headers.Authorization.FirstOrDefault(); + string token = null; + if (authorisationHeader != null) + { + var authorisationData = authorisationHeader.Split(" "); + token = authorisationData.Length > 0 ? authorisationData[^1] : null; + } - if (token != null) - { - var claimsPrincipal = await ValidateTokenAsync(token); - if (claimsPrincipal != null) - { - context.Items[ClientAuthorizeAttribute.ClaimsPrincipalKey] = claimsPrincipal; - } - } - } + _logger.LogDebug("Validating authorization token: {Token}", token); - await _next(context); - } + if (token != null) + { + var claimsPrincipal = await ValidateTokenAsync(token); + if (claimsPrincipal != null) + { + context.Items[ClientAuthorizeAttribute.ClaimsPrincipalKey] = claimsPrincipal; + } + } + } - public async Task ValidateTokenAsync(string token) - { - try - { - var tokenJwt = token.GetJwt(); - _logger.LogDebug("tokenJwt: {tokenJwt}", tokenJwt); + await _next(context); + } - // 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)); - return null; - } + public async Task ValidateTokenAsync(string token) + { + try + { + var tokenJwt = token.GetJwt(); + _logger.LogDebug("tokenJwt: {TokenJwt}", tokenJwt); - // Get the data holder details - var dataholderDiscoveryDocument = await _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)); - return null; - } + // 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)); + return null; + } - _logger.LogDebug("Validating token against {jwksUri}.", dataholderDiscoveryDocument.JwksUri); + // Get the data holder details + var dataholderDiscoveryDocument = await _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)); + return null; + } - // Validate the token - var validated = await token.ValidateToken( - dataholderDiscoveryDocument.JwksUri, - _logger, - tokenJwt.Issuer, - new[] { _softwareProduct.RevocationUri, _softwareProduct.RecipientBaseUri }, - validateLifetime: false); + _logger.LogDebug("Validating token against {JwksUri}.", dataholderDiscoveryDocument.JwksUri); - _logger.LogDebug("Validated token: {isValid}.", validated.IsValid); + // Validate the token + var validated = await token.ValidateToken( + dataholderDiscoveryDocument.JwksUri, + _logger, + tokenJwt.Issuer, + new[] { _softwareProduct.RevocationUri, _softwareProduct.RecipientBaseUri }, + validateLifetime: false); - return validated.ClaimsPrincipal; - } - catch (Exception ex) - { - _logger.LogError(ex, "Client Authorisation Bearer token validation failed."); - return null; - } - } - } -} \ No newline at end of file + _logger.LogDebug("Validated token: {IsValid}.", validated.IsValid); + + return validated.ClaimsPrincipal; + } + catch (Exception ex) + { + _logger.LogError(ex, "Client Authorisation Bearer token validation failed."); + return null; + } + } + } +} diff --git a/Source/CDR.DataRecipient.Web/Models/AuthorisationState.cs b/Source/CDR.DataRecipient.Web/Models/AuthorisationState.cs index 6ac0748..828001c 100644 --- a/Source/CDR.DataRecipient.Web/Models/AuthorisationState.cs +++ b/Source/CDR.DataRecipient.Web/Models/AuthorisationState.cs @@ -1,21 +1,25 @@ using CDR.DataRecipient.SDK.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web.Models { public class AuthorisationState { public string StateKey { get; set; } + public string DataHolderInfosecBaseUri { get; set; } + public string DataHolderBrandId { get; set; } + public string ClientId { get; set; } + public string Scope { get; set; } + public int? SharingDuration { get; set; } + public string RedirectUri { get; set; } + public string UserId { get; set; } + public Pkce Pkce { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/BaseModel.cs b/Source/CDR.DataRecipient.Web/Models/BaseModel.cs index 063bf40..0afad88 100644 --- a/Source/CDR.DataRecipient.Web/Models/BaseModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/BaseModel.cs @@ -5,7 +5,9 @@ namespace CDR.DataRecipient.Web.Models public abstract class BaseModel { public HttpStatusCode StatusCode { get; set; } + public string Messages { get; set; } + public SDK.Models.ErrorList ErrorList { get; set; } protected BaseModel() diff --git a/Source/CDR.DataRecipient.Web/Models/DataSharingModel.cs b/Source/CDR.DataRecipient.Web/Models/DataSharingModel.cs index 17bf78c..207beb5 100644 --- a/Source/CDR.DataRecipient.Web/Models/DataSharingModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/DataSharingModel.cs @@ -3,10 +3,12 @@ public class DataSharingModel { public string CdsSwaggerLocation { get; set; } + /// /// This is the group name of the APIs according to the standards. e.g. "Banking" API, "Common" API. /// public string ApiGroupName { get; set; } + public string BasePath { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs b/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs index 6b66f68..23a1999 100644 --- a/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/DynamicClientRegistrationModel.cs @@ -10,6 +10,8 @@ public class DynamicClientRegistrationModel : BaseModel { public IEnumerable Registrations { get; set; } + public IEnumerable FailedDCRMessages { get; set; } + [Display(Name = "SSA Version")] public string SsaVersion { get; set; } @@ -52,28 +54,27 @@ public class DynamicClientRegistrationModel : BaseModel [Display(Name = "Id Token Signed Response Alg")] public string IdTokenSignedResponseAlg { get; set; } - [Display(Name = "Id Token Encrypted Response Alg")] - public string IdTokenEncryptedResponseAlg { get; set; } - - [Display(Name = "Id Token Encrypted Response Enc")] - public string IdTokenEncryptedResponseEnc { get; set; } - [Display(Name = "Request Object Signing Alg")] public string RequestObjectSigningAlg { get; set; } [Display(Name = "Authorization Signed Response Alg")] public string AuthorizationSignedResponseAlg { get; set; } + [Display(Name = "Authorization Encrypted Response Alg")] public string AuthorizationEncryptedResponseAlg { get; set; } + [Display(Name = "Authorization Encrypted Response Enc")] public string AuthorizationEncryptedResponseEnc { get; set; } public List DataHolderBrands { get; set; } + public List DataRecipients { get; set; } + public string TransactionType { get; set; } + public DynamicClientRegistrationModel() { - this.Registrations = new List(); + 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 ce4dda7..db16ead 100644 --- a/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ErrorListModel.cs @@ -6,30 +6,30 @@ namespace CDR.DataRecipient.Web.Models { - public class ErrorListModel - { - public ErrorListModel() - { - this.Errors = new List(); - } + public class ErrorListModel + { + public ErrorListModel() + { + this.Errors = new List(); + } - public ErrorListModel(ErrorModel error) - { - this.Errors = new List() { error }; - } + public ErrorListModel(ErrorModel error) + { + this.Errors = new List() { error }; + } - public ErrorListModel(string errorCode, string errorTitle, string errorDetail = null) - { - var error = new ErrorModel(errorCode, errorTitle, errorDetail); - this.Errors = new List() { error }; - } + public ErrorListModel(string errorCode, string errorTitle, string errorDetail = null) + { + var error = new ErrorModel(errorCode, errorTitle, errorDetail); + this.Errors = new List() { error }; + } - [Required] - public List Errors { get; set; } + [Required] + public List Errors { get; set; } - public bool HasErrors() - { - return Errors != null && Errors.Count > 0; - } - } + public bool HasErrors() + { + return Errors != null && Errors.Count > 0; + } + } } diff --git a/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs b/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs index a51b4dd..9b13122 100644 --- a/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ErrorModel.cs @@ -2,15 +2,16 @@ namespace CDR.DataRecipient.Web.Models { - public class ErrorModel - { + public class ErrorModel + { public ErrorModel() { Meta = null; - Detail = ""; + Detail = string.Empty; } - public ErrorModel(string code, string title, string description) : this() + public ErrorModel(string code, string title, string description) + : this() { Code = code; Title = title; @@ -19,9 +20,12 @@ public ErrorModel(string code, string title, string description) : this() [Required] public string Code { get; set; } + [Required] public string Title { get; set; } + public string Detail { get; set; } + public object Meta { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs b/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs index 4ef3c33..437e2c5 100644 --- a/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/HttpRequestModel.cs @@ -12,12 +12,19 @@ public HttpRequestModel() } public string Method { get; set; } + public string Url { get; set; } + public IDictionary Headers { get; set; } + public IDictionary QueryParameters { get; set; } + public IDictionary FormParameters { get; set; } + public bool RequiresClientCertificate { get; set; } + public bool RequiresAccessToken { get; set; } + public bool SupportsVersion { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/HttpResponseModel.cs b/Source/CDR.DataRecipient.Web/Models/HttpResponseModel.cs index b85645f..6304c48 100644 --- a/Source/CDR.DataRecipient.Web/Models/HttpResponseModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/HttpResponseModel.cs @@ -5,7 +5,9 @@ namespace CDR.DataRecipient.Web.Models public class HttpResponseModel { public string StatusCode { get; set; } + public IDictionary Headers { get; set; } + public string Body { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/IdTokenModel.cs b/Source/CDR.DataRecipient.Web/Models/IdTokenModel.cs deleted file mode 100644 index fa55c0f..0000000 --- a/Source/CDR.DataRecipient.Web/Models/IdTokenModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Security.Claims; - -namespace CDR.DataRecipient.Web.Models -{ - public class IdTokenModel : BaseModel - { - [Display(Name = "ID Token Encrypted")] - public string IdTokenEncrypted { get; set; } - - [Display(Name = "ID Token Decrypted")] - public string IdTokenDecrypted { get; set; } - - [Display(Name = "ID Token Claims")] - public IEnumerable IdTokenClaims { get; set; } - } -} diff --git a/Source/CDR.DataRecipient.Web/Models/ParModel.cs b/Source/CDR.DataRecipient.Web/Models/ParModel.cs index 2cf2d0a..b56494d 100644 --- a/Source/CDR.DataRecipient.Web/Models/ParModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/ParModel.cs @@ -13,11 +13,12 @@ public class ParModel : BaseModel public IEnumerable ConsentArrangements { get; set; } /// - /// This has the format of {ClientId}|||{DataHolderBrandId} - /// - [Display(Name = "Registration")] + /// This has the format of {ClientId}|||{DataHolderBrandId}. + /// + [Display(Name = "Registration")] [Required(ErrorMessage = "Please select a registration")] public string RegistrationId { get; set; } + public string ClientId { get @@ -25,6 +26,7 @@ public string ClientId return Registration.SplitRegistrationId(RegistrationId).ClientId; } } + public string DataHolderBrandId { get @@ -51,7 +53,7 @@ public string DataHolderBrandId [Display(Name = "Response Mode")] public string ResponseMode { get; set; } - + public IEnumerable RegistrationListItems { get; set; } public IEnumerable ConsentArrangementListItems { get; set; } @@ -59,5 +61,7 @@ public string DataHolderBrandId public string AuthorisationUri { get; set; } public PushedAuthorisation PushedAuthorisation { get; set; } + + public string AcfOnlyErrorMessage { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs b/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs index 5634f99..1fa02c0 100644 --- a/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/PrivateKeyJwtModel.cs @@ -30,7 +30,7 @@ public class PrivateKeyJwtModel : BaseModel public string Jti { get; set; } public string ClientAssertion { get; set; } - public IEnumerable ClientAssertionClaims { get; set; } + public IEnumerable ClientAssertionClaims { get; set; } } } diff --git a/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs b/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs index 6dc592b..786433b 100644 --- a/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/RegistrationsModel.cs @@ -6,6 +6,7 @@ namespace CDR.DataRecipient.Web.Models public class RegistrationsModel : BaseModel { public IList Registrations { get; set; } + public HttpRequestModel RegistrationRequest { get; set; } public RegistrationsModel() @@ -13,4 +14,4 @@ public RegistrationsModel() this.Registrations = new List(); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Models/RevocationModel.cs b/Source/CDR.DataRecipient.Web/Models/RevocationModel.cs index 5d151e7..ba61ea8 100644 --- a/Source/CDR.DataRecipient.Web/Models/RevocationModel.cs +++ b/Source/CDR.DataRecipient.Web/Models/RevocationModel.cs @@ -3,12 +3,12 @@ namespace CDR.DataRecipient.Web.Models { - public class RevocationModel : BaseModel - { - [FromForm(Name = "cdr_arrangement_id")] - public string CdrArrangementId { get; set; } + public class RevocationModel : BaseModel + { + [FromForm(Name = "cdr_arrangement_id")] + public string CdrArrangementId { get; set; } - [FromForm(Name = "cdr_arrangement_jwt")] - public string CdrArrangementJwt { get; set; } - } + [FromForm(Name = "cdr_arrangement_jwt")] + public string CdrArrangementJwt { get; set; } + } } diff --git a/Source/CDR.DataRecipient.Web/Program.cs b/Source/CDR.DataRecipient.Web/Program.cs index da6b29d..8e75a45 100644 --- a/Source/CDR.DataRecipient.Web/Program.cs +++ b/Source/CDR.DataRecipient.Web/Program.cs @@ -1,8 +1,10 @@ +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; @@ -13,36 +15,18 @@ public static class Program { public static int Main(string[] args) { - var configurationCommandLine = new ConfigurationBuilder() - .AddCommandLine(args).Build(); + var configuration = BuildConfiguration(args); - var configuration = new ConfigurationBuilder() - .AddCommandLine(args) - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") - .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? configurationCommandLine.GetValue("environment")}.json", true) - .AddEnvironmentVariables() - .Build(); - try { - Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) - .Enrich.FromLogContext() - .Enrich.WithProcessId() - .Enrich.WithProcessName() - .Enrich.WithThreadId() - .Enrich.WithThreadName() - .Enrich.WithProperty("Environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")) - .CreateLogger(); - + ConfigureSerilog(configuration); Serilog.Debugging.SelfLog.Enable(msg => Log.Logger.Debug(msg)); } - catch (Exception) + catch (Exception) { // Catch and handle exception here if required. } - + try { Log.Information("Starting web host"); @@ -66,9 +50,66 @@ 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. + /// + /// App configuration. + /// Set to True if the database is ready and the MSSqlServer sink will be configured. + public static void ConfigureSerilog(IConfiguration configuration, bool isDatabaseReady = false) + { + var loggerConfiguration = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext() + .Enrich.WithProcessId() + .Enrich.WithProcessName() + .Enrich.WithThreadId() + .Enrich.WithThreadName() + .Enrich.WithProperty("Environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); + + // If the database is ready, configure the SQL Server sink + if (isDatabaseReady) + { + loggerConfiguration.ReadFrom.Configuration(configuration, new ConfigurationReaderOptions() { SectionName = "SerilogMSSqlServerWriteTo" }); + } + + Log.Logger = loggerConfiguration.CreateLogger(); + } + public static IHostBuilder CreateHostBuilder(string[] args, IConfiguration configuration) => Host.CreateDefaultBuilder(args) .UseSerilog() + .ConfigureAppConfiguration(builder => + { + builder.Sources.Clear(); + _ = BuildConfiguration(args, builder); + }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseKestrel((context, serverOptions) => diff --git a/Source/CDR.DataRecipient.Web/Startup.cs b/Source/CDR.DataRecipient.Web/Startup.cs index 51e8fac..bd4fc64 100644 --- a/Source/CDR.DataRecipient.Web/Startup.cs +++ b/Source/CDR.DataRecipient.Web/Startup.cs @@ -1,3 +1,9 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; using CDR.DataRecipient.API.Logger; using CDR.DataRecipient.Infrastructure; using CDR.DataRecipient.Repository; @@ -28,23 +34,17 @@ using Microsoft.FeatureManagement; using Newtonsoft.Json; using Serilog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; namespace CDR.DataRecipient.Web { public class Startup { + private readonly IConfiguration _configuration; + private readonly string _allowSpecificOrigins = "_allowSpecificOrigins"; + private bool healthCheckMigration = false; private string healthCheckMigrationMessage = null; - private readonly IConfiguration _configuration; - private readonly string AllowSpecificOrigins = "_allowSpecificOrigins"; - public Startup(IConfiguration configuration) { _configuration = configuration; @@ -53,6 +53,8 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddScoped(); + services.AddDbContext(options => options.UseSqlServer(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Default))); var dbContext = services.BuildServiceProvider().GetService(); @@ -62,9 +64,10 @@ public void ConfigureServices(IServiceCollection services) options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull; }); - services.AddSingleton(x => new ServiceConfiguration() { + services.AddSingleton(x => new ServiceConfiguration() + { AcceptAnyServerCertificate = _configuration.IsAcceptingAnyServerCertificate(), - EnforceHttpsEndpoints = _configuration.IsEnforcingHttpsEndpoints() + EnforceHttpsEndpoints = _configuration.IsEnforcingHttpsEndpoints(), }); services.AddTransient(); services.AddTransient(); @@ -105,15 +108,16 @@ public void ConfigureServices(IServiceCollection services) string specificOrigin = _configuration.GetValue(Constants.ConfigurationKeys.AllowSpecificOrigins); services.AddCors(options => { - options.AddPolicy(AllowSpecificOrigins, - builder => - { - builder.WithOrigins(specificOrigin) - .SetPreflightMaxAge(TimeSpan.FromSeconds(600)) - .WithMethods("GET") - .AllowAnyHeader() - .AllowAnyMethod(); - }); + options.AddPolicy( + _allowSpecificOrigins, + builder => + { + builder.WithOrigins(specificOrigin) + .SetPreflightMaxAge(TimeSpan.FromSeconds(600)) + .WithMethods("GET") + .AllowAnyHeader() + .AllowAnyMethod(); + }); }); string connStr = _configuration.GetConnectionString(DbConstants.ConnectionStringNames.Logging); @@ -132,6 +136,7 @@ public void ConfigureServices(IServiceCollection services) return HealthCheckResult.Unhealthy(); } } + return HealthCheckResult.Healthy(); }); @@ -160,9 +165,18 @@ public void ConfigureServices(IServiceCollection services) o.ResponseMode = responseMode; o.CallbackPath = callbackPath; o.GetClaimsFromUserInfoEndpoint = false; + o.Events.OnRedirectToIdentityProvider = context => + { + context.Response.Headers.CacheControl = "no-cache,no-store"; + + // Set the client secret dynamically here + var clientSecretProvider = context.HttpContext.RequestServices.GetRequiredService(); + context.Options.ClientSecret = clientSecretProvider.GetSecret(); + return Task.CompletedTask; + }; o.Events.OnRemoteFailure = context => { - string errMessage = context.Failure == null ? "" : context.Failure.Message; + string errMessage = context.Failure == null ? string.Empty : context.Failure.Message; string innerErrorMessage = string.Empty; string redirectError = string.Format("?error_message={0}", errMessage); if (context.Failure.InnerException != null) @@ -170,6 +184,7 @@ public void ConfigureServices(IServiceCollection services) innerErrorMessage = context.Failure.InnerException.Message; redirectError = string.Format("{0}&inner_error={1}", redirectError, innerErrorMessage); } + redirectError = redirectError.Replace("\r\n", "|"); string rtnMessage = "oidc/remoteerror"; @@ -188,6 +203,7 @@ public void ConfigureServices(IServiceCollection services) innerErrorMessage = context.Exception.InnerException.Message; redirectError = string.Format("{0}&inner_error={1}", redirectError, innerErrorMessage); } + redirectError = redirectError.Replace("\r\n", "|"); string rtnMessage = "oidc/autherror"; @@ -196,8 +212,9 @@ public void ConfigureServices(IServiceCollection services) context.HandleResponse(); return Task.CompletedTask; }; - o.Events.OnAccessDenied = context => { - string errMessage = context.Result == null ? "" : context.Result.Failure.Message; + o.Events.OnAccessDenied = context => + { + string errMessage = context.Result == null ? string.Empty : context.Result.Failure.Message; string redirectError = string.Format("?error_message={0}", errMessage); redirectError = redirectError.Replace("\r\n", "|"); @@ -230,6 +247,8 @@ public void ConfigureServices(IServiceCollection services) Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); } + + services.AddHttpClient(); } private bool UseDistributedCache() @@ -256,11 +275,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async (ctx, next) => { - var claims = new List() { + var claims = new List() + { new Claim(Constants.Claims.UserId, Constants.LocalAuthentication.UserId), new Claim(ClaimTypes.GivenName, Constants.LocalAuthentication.GivenName), new Claim(ClaimTypes.Surname, Constants.LocalAuthentication.Surname), - new Claim(Constants.Claims.Name, string.Concat(Constants.LocalAuthentication.GivenName, " ", Constants.LocalAuthentication.Surname)) + new Claim(Constants.Claims.Name, string.Concat(Constants.LocalAuthentication.GivenName, " ", Constants.LocalAuthentication.Surname)), }; ctx.User = new ClaimsPrincipal(new ClaimsIdentity(claims, Constants.LocalAuthentication.AuthenticationType)); await next(); @@ -271,7 +291,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.Use(async (context, next) => { context.Response.Headers.Append( - "Content-Security-Policy", + "Content-Security-Policy", _configuration.GetValue(Constants.ConfigurationKeys.ContentSecurityPolicy, "default-src 'self', 'https://cdn.jsdelivr.net/';")); await next(); }); @@ -292,12 +312,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); - app.UseCors(AllowSpecificOrigins); + app.UseCors(_allowSpecificOrigins); app.UseAuthentication(); app.UseAuthorization(); app.UseSession(); - // Custom Client autorise middleware + // Custom Client autorise middleware app.UseMiddleware(); // Common swagger. @@ -336,10 +356,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); - + app.UseHealthChecks("/health", new HealthCheckOptions() { - ResponseWriter = CustomResponseWriter + ResponseWriter = CustomResponseWriter, }); // Migrate the database to the latest version during application startup. @@ -353,15 +373,19 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) // Use DBO connection string since it has DBO rights needed to update db schema optionsBuilder.UseSqlServer(_configuration.GetConnectionString(DbConstants.ConnectionStringNames.Migrations) - ?? throw new Exception($"Connection string '{DbConstants.ConnectionStringNames.Migrations}' not found")); + ?? throw new InvalidOperationException($"Connection string '{DbConstants.ConnectionStringNames.Migrations}' not found")); using var dbContext = new RecipientDatabaseContext(optionsBuilder.Options); dbContext.Database.Migrate(); healthCheckMigrationMessage = "Migration completed"; } - healthCheckMigration = true; - } + + healthCheckMigration = true; + + // Reconfigure Serilog with DB + Program.ConfigureSerilog(_configuration, true); + } } /// @@ -383,10 +407,10 @@ private static Task CustomResponseWriter(HttpContext context, HealthReport healt errors = healthReport.Entries.Select(e => new { key = e.Key, - value = e.Value.Status.ToString() - }) + value = e.Value.Status.ToString(), + }), }); return context.Response.WriteAsync(result); } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient.Web/Views/DynamicClientRegistration/Index.cshtml b/Source/CDR.DataRecipient.Web/Views/DynamicClientRegistration/Index.cshtml index 8ec55b7..d0df929 100644 --- a/Source/CDR.DataRecipient.Web/Views/DynamicClientRegistration/Index.cshtml +++ b/Source/CDR.DataRecipient.Web/Views/DynamicClientRegistration/Index.cshtml @@ -1,7 +1,7 @@ @{ ViewData["Title"] = "Dynamic Client Registration"; - var allowDynamicClientRegistration = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); - + var allowDynamicClientRegistration = await _featureManager.IsEnabledAsync(nameof(Feature.AllowDynamicClientRegistration)); + var mdrDisplayName = "Mock Data Recipient"; @if (!Context.User.IsLocal()) @@ -27,19 +27,20 @@

The page allows you to perform Dynamic Client Registration (DCR) with a Data Holder Brand that is currently stored in the Mock Data Recipient's memory - see the Discover Data Holders page for more detail.

} else -{ -

This page shows Data Holders for which this @(mdrDisplayName) has attempted a Dynamic Client Registration flow and the last response received.

-

This @(mdrDisplayName) will periodically attempt to perform Dynamic Client Registration with new Data Holder Brand Ids, or retry Dynamic Client Registration with Data Holder Brand Ids that were previously unsuccessful.

+{ +

This page shows Data Holders for which this @(mdrDisplayName) has attempted a Dynamic Client Registration (DCR).

+

This @(mdrDisplayName) will periodically attempt to perform DCR with new Data Holder Brand Ids, or retry DCR with Data Holder Brand Ids that were previously unsuccessful.

+

Successful DCR’s can be viewed or updated in the Current Dynamic Client Registrations table. Unsuccessful DCR attempts are also listed in the Failed Client Registrations table.

} @if (Context.User.IsLocal()) {

If you would like to DCR to a CDR Mock Data Holder, firstly ensure the relevant solution is running then select either Mock Data Holder (Banking) or Mock Data Holder (Energy) from the 'DH Brand Name' dropdown.

Alternatively, you can select your own data holder solution if you have added it to the Mock Register and have refreshed the data holders held in memory by the Mock Data Recipient.

- +

Note:.

  • The dummy data holder brands displayed here are for other testing purposes and they should not be used.
  • -
  • As per CDS FAPI 1.0 Migration 4, OIDC Hybrid Flow may be retired and is recommended to use Authorisation Code Flow.
  • +
  • As per CDS v1.33.0, OIDC Hybrid Flow is now retired and Authorisation Code Flow must be supported.
} @@ -53,7 +54,7 @@ else
@(allowDynamicClientRegistration ? $"{Model.TransactionType} Client Registration" : "Dynamic Client Registrations")
-
+
@using (Html.BeginForm("Index", "dcr", FormMethod.Post, new { id = "clientRegistrationFrom" })) { @Html.HiddenFor(m => m.DataRecipientBrandId) @@ -81,7 +82,7 @@ else
-
+
@Html.LabelFor(m => m.SsaVersion, new { @class = "col-sm-3 col-form-label" })
@Html.TextBoxFor(m => m.SsaVersion, new { @class = "form-control" }) @@ -159,20 +160,6 @@ else
-
- @Html.LabelFor(m => m.IdTokenEncryptedResponseAlg, new { @class = "col-sm-3 col-form-label" }) -
- @Html.TextBoxFor(m => m.IdTokenEncryptedResponseAlg, new { @class = "form-control" }) -
-
- -
- @Html.LabelFor(m => m.IdTokenEncryptedResponseEnc, new { @class = "col-sm-3 col-form-label" }) -
- @Html.TextBoxFor(m => m.IdTokenEncryptedResponseEnc, new { @class = "form-control" }) -
-
-
@Html.LabelFor(m => m.RequestObjectSigningAlg, new { @class = "col-sm-3 col-form-label" })
@@ -204,7 +191,7 @@ else
-
+
@if (Model.TransactionType == "Create") { @@ -231,7 +218,7 @@ else
@json
- } + }
@@ -240,7 +227,7 @@ else
-
Current Registrations
+
Current Dynamic Client Registrations

@if (@Model.Registrations.Count() > 0) @@ -251,36 +238,27 @@ else Client ID DH Brand Name - @if (!allowDynamicClientRegistration) - { - Status - Last Update - } Actions @foreach (var reg in Model.Registrations) { - + @reg.ClientId @reg.BrandName (@reg.DataHolderBrandId) - @if (!allowDynamicClientRegistration) - { - @reg.MessageState - @reg.LastUpdated.ToString("yyyy-MM-dd HH:mm:ss") - } @if (allowDynamicClientRegistration) { View Edit - Delete + Delete } else if (!string.IsNullOrEmpty(@reg.ClientId)) { - View + View + Update } @@ -314,9 +292,54 @@ else

-
- } +
+ }
+ @if (!allowDynamicClientRegistration) + { +
+
+
+
Failed Dynamic Client Registrations
+
+

+ @if (@Model.FailedDCRMessages.Count() > 0) + { + + + + + + + + + + + @foreach (var reg in Model.FailedDCRMessages) + { + + + + + + + } +
CDR Failed Registrations
Client IDDH Brand NameStatusLast Update
@reg.ClientId@reg.BrandName (@reg.DataHolderBrandId)@reg.MessageState@reg.LastUpdated.ToString("yyyy-MM-dd HH:mm:ss")
+ } + else + { +

No existing registrations found.
+ } +

+
+ +
+ } +
@@ -338,135 +361,181 @@ else
+ + @section Scripts { - + + if ('@allowDynamicClientRegistration' == 'False') { + $('#registrations .update').click(function () { + // Get the registration - display in modal view + $('#dcr-spinner').show(); + $.ajax({ + url: this.href, + type: 'PUT', + success: function (result) { + updateModal.show(); + $('#update-modal-messages').html(result.messages); + $('#dcr-spinner').hide(); + }, + error: function (xhr, textStatus, errorThrown) { + updateModal.show(); + $('#update-modal-messages').html(xhr.responseJSON.messages); + $('#dcr-spinner').hide(); + } + }); + + return false; + }); + } + + + if ('@allowDynamicClientRegistration' == 'True') { + $('#registrations .delete').click(function () { + var clientId = $(this).data('id'); + + // Post -> Delete the registration + $('#dcr-spinner').show(); + $.ajax({ + url: this.href, + type: 'DELETE', + statusCode: { + 204: function () { + window.location = "/dcr"; + } + }, + success: function (result) { + window.location = "/dcr"; + $('#dcr-spinner').hide(); + }, + error: function (xhr, textStatus, errorThrown) { + alert(xhr.responseJSON.messages); + $('#dcr-spinner').hide(); + } + }); + + return false; + }); + } + }); + + setSidebarMenuItem('dcr'); + } diff --git a/Source/CDR.DataRecipient.Web/Views/Par/Index.cshtml b/Source/CDR.DataRecipient.Web/Views/Par/Index.cshtml index 5cd2858..a6e0710 100644 --- a/Source/CDR.DataRecipient.Web/Views/Par/Index.cshtml +++ b/Source/CDR.DataRecipient.Web/Views/Par/Index.cshtml @@ -23,7 +23,7 @@
@Html.DropDownListFor(m => m.RegistrationId, Model.RegistrationListItems, "Select registration...", new { @class = "form-control" })
- @Html.ValidationMessageFor(model => model.RegistrationId, "", new {@class = "text-danger"}) + @Html.ValidationMessageFor(model => model.RegistrationId, "", new {@class = "text-danger"})
@@ -58,7 +58,7 @@ -
+
@Html.CheckBoxFor(m => m.UsePkce, new { @class = "form-check-input" }) @@ -71,7 +71,7 @@ @Html.LabelFor(m => m.ResponseType, new { @class = "col-sm-2 col-form-label" })
@Html.TextBoxFor(m => m.ResponseType, new { @class = "form-control" }) - * As per CDS FAPI 1.0 Migration 4, OIDC Hybrid Flow may be retired and is recommended to use Authorisation Code Flow. + * As per CDS v1.33.0, OIDC Hybrid Flow is now retired and Authorisation Code Flow must be supported.
@@ -93,6 +93,12 @@

+ @if (!string.IsNullOrEmpty(Model.AcfOnlyErrorMessage)) + { +
+

@Model.AcfOnlyErrorMessage

+ } + @if (!string.IsNullOrEmpty(Model.AuthorisationUri)) { @Model.AuthorisationUri diff --git a/Source/CDR.DataRecipient.Web/Views/Shared/_Layout.cshtml b/Source/CDR.DataRecipient.Web/Views/Shared/_Layout.cshtml index b523087..8f5b1d8 100644 --- a/Source/CDR.DataRecipient.Web/Views/Shared/_Layout.cshtml +++ b/Source/CDR.DataRecipient.Web/Views/Shared/_Layout.cshtml @@ -102,11 +102,6 @@
  • Utilities
      -
    • - - ID Token Helper - -
    • Private Key JWT Generator diff --git a/Source/CDR.DataRecipient.Web/Views/Utilities/IdToken.cshtml b/Source/CDR.DataRecipient.Web/Views/Utilities/IdToken.cshtml deleted file mode 100644 index 040afa4..0000000 --- a/Source/CDR.DataRecipient.Web/Views/Utilities/IdToken.cshtml +++ /dev/null @@ -1,75 +0,0 @@ -@using CDR.DataRecipient.Web.Features -@using Microsoft.FeatureManagement -@{ - ViewData["Title"] = "ID Token Helper"; - var linkToExternalJwt = await _featureManager.IsEnabledAsync(nameof(Feature.LinkToExternalJwt)); -} -@model IdTokenModel -@inject IFeatureManager _featureManager - -

      ID Token Helper

      - -

      - This page allows for an encrypted ID Token to be decrypted and decoded. -

      - -
      - -@section Scripts { - -} \ No newline at end of file diff --git a/Source/CDR.DataRecipient.Web/appsettings.Development.json b/Source/CDR.DataRecipient.Web/appsettings.Development.json index 997190f..bb13ebf 100644 --- a/Source/CDR.DataRecipient.Web/appsettings.Development.json +++ b/Source/CDR.DataRecipient.Web/appsettings.Development.json @@ -10,6 +10,7 @@ "Register": { "tlsBaseUri": "https://localhost:7000", "mtlsBaseUri": "https://localhost:7001", + "getSsaMinXv": "3", "oidcDiscoveryUri": "https://localhost:7000/idp/.well-known/openid-configuration" }, "SoftwareProduct": { @@ -22,7 +23,7 @@ } }, "Serilog": { - "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.MSSqlServer" ], + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "MinimumLevel": "Debug", "WriteTo": [ { @@ -37,63 +38,6 @@ "path": "c:\\cdr\\Logs\\cdr-mdr-web.log", "outputTemplate": "{Timestamp:dd/MM/yyyy HH:mm:ss.fff zzz} {Level} [{SourceContext}] {Message}{NewLine}{Exception}" } - }, - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "DataRecipient_Logging_DB", - "sinkOptionsSection": { - "tableName": "LogEvents-Web", - "autoCreateSqlTable": true - }, - "restrictedToMinimumLevel": "Verbose", - "batchPostingLimit": 1000, - "period": "0.00:00:10", - "columnOptionsSection": { - "disableTriggers": true, - "clusteredColumnstoreIndex": false, - "primaryKeyColumnName": "Id", - "removeStandardColumns": [ "MessageTemplate", "Properties" ], - "additionalColumns": [ - { - "ColumnName": "Environment", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ProcessId", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ProcessName", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ThreadId", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "MethodName", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "SourceContext", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 100 - } - ] - } - } } ] }, diff --git a/Source/CDR.DataRecipient.Web/appsettings.Release.json b/Source/CDR.DataRecipient.Web/appsettings.Release.json index 96e4d89..b2076ca 100644 --- a/Source/CDR.DataRecipient.Web/appsettings.Release.json +++ b/Source/CDR.DataRecipient.Web/appsettings.Release.json @@ -9,6 +9,7 @@ "Register": { "tlsBaseUri": "https://mock-register:7000", "mtlsBaseUri": "https://mock-register:7001", + "getSsaMinXv": "3", "oidcDiscoveryUri": "https://mock-register:7000/idp/.well-known/openid-configuration" }, "SoftwareProduct": { @@ -18,7 +19,7 @@ } }, "Serilog": { - "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.MSSqlServer" ], + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File"], "MinimumLevel": "Debug", "WriteTo": [ { @@ -33,63 +34,6 @@ "path": "/tmp/cdr-mdr-web.log", "outputTemplate": "{Timestamp:dd/MM/yyyy HH:mm:ss.fff zzz} {Level} [{SourceContext}] {Message}{NewLine}{Exception}" } - }, - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "DataRecipient_Logging_DB", - "sinkOptionsSection": { - "tableName": "LogEvents-Web", - "autoCreateSqlTable": true - }, - "restrictedToMinimumLevel": "Verbose", - "batchPostingLimit": 1000, - "period": "0.00:00:10", - "columnOptionsSection": { - "disableTriggers": true, - "clusteredColumnstoreIndex": false, - "primaryKeyColumnName": "Id", - "removeStandardColumns": [ "MessageTemplate", "Properties" ], - "additionalColumns": [ - { - "ColumnName": "Environment", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ProcessId", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ProcessName", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "ThreadId", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "MethodName", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 50 - }, - { - "ColumnName": "SourceContext", - "DataType": "nvarchar", - "AllowNull": true, - "DataLength": 100 - } - ] - } - } } ] }, diff --git a/Source/CDR.DataRecipient.Web/appsettings.json b/Source/CDR.DataRecipient.Web/appsettings.json index 4a523e4..8f2fb5f 100644 --- a/Source/CDR.DataRecipient.Web/appsettings.json +++ b/Source/CDR.DataRecipient.Web/appsettings.json @@ -57,7 +57,9 @@ "callback_path": "", "response_type": "", "response_mode": "", - "scope": "" + "scope": "", + "secretVolumePath": "", + "mountedSecretName": "" }, "AllowSpecificHeaders": "x-v,x-min-v,x-fapi-interaction-id,x-fapi-auth-date,x-fapi-customer-ip-address,x-cds-client-headers", "AllowSpecificOrigins": "http://localhost:3000", @@ -66,5 +68,67 @@ "ContentSecurityPolicy": "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com;", "ApplicationName": "Mock Data Recipient", "HomepageOverrideContentUrl": "", - "FooterOverrideContentUrl": "" + "FooterOverrideContentUrl": "", + "SerilogMSSqlServerWriteTo": { + "Using": [ "Serilog.Sinks.MSSqlServer" ], + "WriteTo": [ + { + "Name": "MSSqlServer", + "Args": { + "connectionString": "DataRecipient_Logging_DB", + "sinkOptionsSection": { + "tableName": "LogEvents-Web", + "autoCreateSqlTable": true + }, + "restrictedToMinimumLevel": "Verbose", + "batchPostingLimit": 1000, + "period": "0.00:00:10", + "columnOptionsSection": { + "disableTriggers": true, + "clusteredColumnstoreIndex": false, + "primaryKeyColumnName": "Id", + "removeStandardColumns": [ "MessageTemplate", "Properties" ], + "additionalColumns": [ + { + "ColumnName": "Environment", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 50 + }, + { + "ColumnName": "ProcessId", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 50 + }, + { + "ColumnName": "ProcessName", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 50 + }, + { + "ColumnName": "ThreadId", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 50 + }, + { + "ColumnName": "MethodName", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 50 + }, + { + "ColumnName": "SourceContext", + "DataType": "nvarchar", + "AllowNull": true, + "DataLength": 100 + } + ] + } + } + } + ] + } } \ No newline at end of file diff --git a/Source/CDR.DataRecipient/CDR.DataRecipient.csproj b/Source/CDR.DataRecipient/CDR.DataRecipient.csproj index b5a6646..6eeb89e 100644 --- a/Source/CDR.DataRecipient/CDR.DataRecipient.csproj +++ b/Source/CDR.DataRecipient/CDR.DataRecipient.csproj @@ -4,7 +4,18 @@ $(Version) $(Version) $(Version) + True + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Source/CDR.DataRecipient/Exceptions/MissingClaimException.cs b/Source/CDR.DataRecipient/Exceptions/MissingClaimException.cs index 63b6c4b..8956b72 100644 --- a/Source/CDR.DataRecipient/Exceptions/MissingClaimException.cs +++ b/Source/CDR.DataRecipient/Exceptions/MissingClaimException.cs @@ -1,25 +1,21 @@ using System; -using System.Runtime.Serialization; namespace CDR.DataRecipient.Exceptions { - [Serializable] public class MissingClaimException : Exception { public MissingClaimException() { } - public MissingClaimException(string claimName) : base($"{claimName} not found for user") + public MissingClaimException(string claimName) + : base($"{claimName} not found for user") { } - public MissingClaimException(string claimName, Exception innerException) : base($"{claimName} not found for user", innerException) - { - } - - protected MissingClaimException(SerializationInfo info, StreamingContext context) : base(info, context) + public MissingClaimException(string claimName, Exception innerException) + : base($"{claimName} not found for user", innerException) { } } -} \ No newline at end of file +} diff --git a/Source/CDR.DataRecipient/Models/ConsentArrangement.cs b/Source/CDR.DataRecipient/Models/ConsentArrangement.cs index 6666d31..ff95f05 100644 --- a/Source/CDR.DataRecipient/Models/ConsentArrangement.cs +++ b/Source/CDR.DataRecipient/Models/ConsentArrangement.cs @@ -5,18 +5,29 @@ namespace CDR.DataRecipient.Models public class ConsentArrangement { public string UserId { get; set; } + public string DataHolderBrandId { get; set; } + public string BrandName { get; set; } + public string ClientId { get; set; } + public int? SharingDuration { get; set; } + public string CdrArrangementId { get; set; } + public string IdToken { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + public int ExpiresIn { get; set; } + public string Scope { get; set; } + public string TokenType { get; set; } + public DateTime CreatedOn { get; set; } - } } diff --git a/Source/CDR.DataRecipient/Repository/IConsentsRepository.cs b/Source/CDR.DataRecipient/Repository/IConsentsRepository.cs index 1ec8fd1..1ebb800 100644 --- a/Source/CDR.DataRecipient/Repository/IConsentsRepository.cs +++ b/Source/CDR.DataRecipient/Repository/IConsentsRepository.cs @@ -4,13 +4,18 @@ namespace CDR.DataRecipient.Repository { - public interface IConsentsRepository + public interface IConsentsRepository { Task> GetConsents(string clientId, string dataHolderBrandId, string userId, string industry = null); + Task GetConsentByArrangement(string cdrArrangementId); + Task PersistConsent(ConsentArrangement consentArrangement); + Task DeleteConsent(string cdrArrangementId); + Task UpdateTokens(string cdrArrangementId, string idToken, string accessToken, string refreshToken); + Task RevokeConsent(string cdrArrangementId, string dataHolderBrandId); } } diff --git a/Source/CDR.DataRecipient/Repository/IDataHoldersRepository.cs b/Source/CDR.DataRecipient/Repository/IDataHoldersRepository.cs index 4cb0c7c..0648230 100644 --- a/Source/CDR.DataRecipient/Repository/IDataHoldersRepository.cs +++ b/Source/CDR.DataRecipient/Repository/IDataHoldersRepository.cs @@ -7,10 +7,15 @@ namespace CDR.DataRecipient.Repository public interface IDataHoldersRepository { Task> GetDataHolderBrands(); + Task GetDataHolderBrand(string brandId); + Task GetDHBrandById(string brandId); + Task DataHolderBrandsDelete(); - Task<(int, int)> AggregateDataHolderBrands(IList dataHolderBrands); + + Task<(int DhBrandsInserted, int DhBrandsUpdated)> AggregateDataHolderBrands(IList dataHolderBrands); + Task PersistDataHolderBrands(IEnumerable dataHolderBrands); } } diff --git a/Source/CDR.DataRecipient/Repository/IRegistrationsRepository.cs b/Source/CDR.DataRecipient/Repository/IRegistrationsRepository.cs index e8cf59a..e456926 100644 --- a/Source/CDR.DataRecipient/Repository/IRegistrationsRepository.cs +++ b/Source/CDR.DataRecipient/Repository/IRegistrationsRepository.cs @@ -7,10 +7,15 @@ namespace CDR.DataRecipient.Repository public interface IRegistrationsRepository { Task> GetRegistrations(); + Task> GetDcrMessageRegistrations(); + Task GetRegistration(string clientId, string dataHolderBrandId); + Task PersistRegistration(Registration registration); + Task DeleteRegistration(string clientId, string dataHolderBrandId); + Task UpdateRegistration(Registration registration); } } diff --git a/Source/CDR.DiscoverDataHolders/DiscoverDataHolders.cs b/Source/CDR.DiscoverDataHolders/DiscoverDataHolders.cs index f003dd4..1445ff0 100644 --- a/Source/CDR.DiscoverDataHolders/DiscoverDataHolders.cs +++ b/Source/CDR.DiscoverDataHolders/DiscoverDataHolders.cs @@ -1,4 +1,4 @@ -using Azure.Storage.Queues; +using Azure.Storage.Queues; using Azure.Storage.Queues.Models; using CDR.DataRecipient.Repository.SQL; using CDR.DataRecipient.SDK; @@ -42,7 +42,7 @@ public DiscoverDataHoldersFunction(ILogger logger, byte[] signCertBytes = Convert.FromBase64String(_options.Signing_Certificate); _signCertificate = new(signCertBytes, _options.Signing_Certificate_Password, X509KeyStorageFlags.MachineKeySet); _logger.LogInformation("Signing certificate loaded: {thumbprint}", _signCertificate.Thumbprint); - _httpClientFactory=httpClientFactory; + _httpClientFactory = httpClientFactory; } /// @@ -58,11 +58,10 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) int qCount = await GetQueueCountAsync(_options.StorageConnectionString, _options.QueueName); _logger.LogInformation("qCount = {qCount}", qCount); _logger.LogInformation("Loading the client certificate..."); - + string msg = $"DHBRANDS"; int inserted = 0; - int updated = 0; int pendingReg = 0; Response tokenRes = await GetAccessToken(); @@ -74,7 +73,7 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) Response> dhResponse = JsonConvert.DeserializeObject>>(dataHolderBrandsResult.body); if (dhResponse.Data.Count == 0) { - await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, Unable to get the DHBrands from: {_options.Register_Get_DH_Brands}, Ver: {_options.Register_Get_DH_Brands_XV}", "Error", "DHBRANDS"); + await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, Unable to get the DHBrands from: {_options.Register_Get_DH_Brands}, Ver: {_options.Register_Get_DH_Brands_XV}", "Error", "DHBRANDS"); return; } @@ -84,23 +83,8 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) await SynchroniseDataHolderBrandsMetadata(dhResponse.Data, _options.DataRecipient_DB_ConnectionString, _logger); // RETURN a list of ALL DataHolderBrands that are NOT REGISTERED - (IList dhBrandsInsert, IList dhBrandsUpd) = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).CheckRegistrationsExist(dhResponse.Data); - _logger.LogInformation("{ins} data holder brands inserted. {upd} data holder brands updated.", dhBrandsInsert.Count, dhBrandsUpd.Count); - - // UPDATE DataHolderBrands - if (dhBrandsUpd.Count > 0) - { - foreach (var dh in dhBrandsUpd) - { - bool result = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).CheckRegistrationExist(dh.DataHolderBrandId); - if (!result) - { - var qMsgId = await AddQueueMessageAsync(_logger, _options.StorageConnectionString, _options.QueueName, dh.DataHolderBrandId, "UPDATE QUEUED"); - await AddDcrMessage(dh.DataHolderBrandId, dh.BrandName, dh.EndpointDetail.InfoSecBaseUri, qMsgId, "UPDATE DcrMessage"); - updated++; - } - } - } + IList dhBrandsInsert = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).CheckRegistrationsExist(dhResponse.Data); + _logger.LogInformation("{ins} data holder brands inserted.", dhBrandsInsert.Count); // ALL DataHolderBrands that are TO REGISTER if (dhBrandsInsert.Count > 0) @@ -117,7 +101,7 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) var proc = (qCount == 0) ? "NO REG (ADD to EMPTY QUEUE)" : "NO REG (ADD to QUEUE)"; // ADD to QUEUE and DcrMessage table - var qMsgId = await AddQueueMessageAsync(_logger, _options.StorageConnectionString, _options.QueueName, dh.DataHolderBrandId, proc); + var qMsgId = await AddQueueMessageAsync(_logger, _options.StorageConnectionString, _options.QueueName, dh.DataHolderBrandId, proc); await AddDcrMessage(dh.DataHolderBrandId, dh.BrandName, dh.EndpointDetail.InfoSecBaseUri, qMsgId, "ADD to DcrMessage table"); pendingReg++; } @@ -143,7 +127,7 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) } } - if (inserted == 0 && updated == 0 && pendingReg == 0) + if (inserted == 0 && pendingReg == 0) { msg += $" - no additional data holder brands added."; } @@ -152,9 +136,6 @@ public async Task DHBRANDS([TimerTrigger("%Schedule%")] TimerInfo myTimer) if (inserted > 0) msg += $" - {inserted} new data holder brands loaded."; - if (updated > 0) - msg += $" - {updated} existing data holder brands updated."; - if (pendingReg > 0) msg += $" - {pendingReg} existing data holder brands queued to be registered."; } @@ -401,7 +382,7 @@ private static async Task GetQueueCountAsync(string storageConnectionString private async Task InsertLog(string dataRecipient_DB_ConnectionString, string msg, string lvl, string methodName, Exception ex = null) { string exMessage = ""; - + if (ex != null) { @@ -463,4 +444,4 @@ private async Task InsertLog(string dataRecipient_DB_ConnectionString, string ms db.Close(); } } -} \ No newline at end of file +} diff --git a/Source/DataRecipient.sln b/Source/DataRecipient.sln index 39b10e1..adb00dc 100644 --- a/Source/DataRecipient.sln +++ b/Source/DataRecipient.sln @@ -14,6 +14,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{56CFBFDE-5522-4AC1-9592-C74FB04EADF6}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore + .editorconfig = .editorconfig docker-compose.E2ETests.yml = docker-compose.E2ETests.yml docker-compose.IntegrationTests.yml = docker-compose.IntegrationTests.yml docker-compose.UnitTests.yml = docker-compose.UnitTests.yml diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index 94f22ca..e5eca4a 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -1,6 +1,9 @@ net8.0 - 2.0.0 + 3.0.0 + true + true + true \ No newline at end of file diff --git a/Source/Dockerfile b/Source/Dockerfile index fd72cee..12be9ad 100644 --- a/Source/Dockerfile +++ b/Source/Dockerfile @@ -5,6 +5,9 @@ EXPOSE 9001 # Default ASPNETCORE_ENVIRONMENT to Release ENV ASPNETCORE_ENVIRONMENT=Release +# https://liupeirong.github.io/dynamicReloadConfigK8s/ +ENV DOTNET_USE_POLLING_FILE_WATCHER true + FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src @@ -13,6 +16,7 @@ COPY . ./ FROM build AS publish COPY ./Directory.Build.props /app/Directory.Build.props +COPY ./.editorconfig /app/.editorconfig COPY ./CDR.DataRecipient.Web/. /app/CDR.DataRecipient.Web COPY ./CDR.DataRecipient.Repository.SQL/. /app/CDR.DataRecipient.Repository.SQL diff --git a/Source/Dockerfile.mdr.integration-tests b/Source/Dockerfile.mdr.integration-tests index b4c55f3..62f548a 100644 --- a/Source/Dockerfile.mdr.integration-tests +++ b/Source/Dockerfile.mdr.integration-tests @@ -13,7 +13,9 @@ COPY . ./ FROM build AS publish COPY ./Directory.Build.props /app/Directory.Build.props +COPY ./.editorconfig /app/.editorconfig +COPY ./CDR.DataRecipient.API.Logger/. /app/CDR.DataRecipient.API.Logger COPY ./CDR.DataRecipient.Web/. /app/CDR.DataRecipient.Web COPY ./CDR.DataRecipient.Repository.SQL/. /app/CDR.DataRecipient.Repository.SQL COPY ./CDR.DataRecipient/. /app/CDR.DataRecipient