diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml index 5256325..851bc65 100644 --- a/.azuredevops/pipelines/build-v2.yml +++ b/.azuredevops/pipelines/build-v2.yml @@ -173,8 +173,8 @@ steps: BasePath: '$(BasePath)' PublicHostName: '$(CtsPublicHostNameUrl)' SecureHostName: '$(CtsSecureHostNameUrl)' - Certificate__CertThumbprintNameHttpHeaderName: 'X-SSLClientCertThumbprint' - Certificate__CertCommonNameHttpHeaderName: 'X-SSLClientCertCN' + Certificate__CertThumbprintNameHttpHeaderName: '$(CtsSettings__CertThumbprintNameHttpHeaderName)' + Certificate__CertCommonNameHttpHeaderName: '$(CtsSettings_CertCommonNameHttpHeaderName)' # Run trx formatter to output .MD and .CSV - script: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5870a61..2f1a244 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 18c1240..1dcea1f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout Register - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: ./mock-register @@ -84,7 +84,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 @@ -92,7 +92,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 @@ -100,7 +100,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/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index 35a8131..516447c 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -37,7 +37,7 @@ jobs: with: java-version: 17 distribution: 'zulu' # Alternative distribution options are available. - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ env.sonarSecret != 0 }} # Only run scan if secret use is allowed - requires secrets to run successfully with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml index e9ddb8c..ced2e2a 100644 --- a/.github/workflows/test-report.yml +++ b/.github/workflows/test-report.yml @@ -12,7 +12,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 @@ -26,7 +26,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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fab27f..ff9afa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.0] - 2025-03-19 +### Changed +- Updated NuGet packages +- Fixed multiple build warnings to improve code quality and maintainability + ## [2.1.0] - 2024-08-16 ### Changed - Updated NuGet packages diff --git a/README.md b/README.md index a681fbb..c124a03 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-register)](./LICENSE) @@ -12,7 +12,7 @@ This project includes source code, documentation and instructions for the Consum The ACCC operates the CDR Register within the CDR ecosystem. This repository contains a mock implementation of the CDR Register and is offered to help the community in the development and testing of their CDR solutions. ## Mock Register - Alignment -The Mock Register 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). +The Mock Register 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). ## Getting Started There are a number of ways that the artefacts within this project can be used: diff --git a/SECURITY.md b/SECURITY.md index b434d53..a55db80 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,7 @@ Visit our [Responsible disclosure of security vulnerabilities policy](https://ww | Version | Supported | | ------- | ------------------ | -| 2.1.x | :white_check_mark: | +| 2.2.x | :white_check_mark: | | 1.x.x | :x: | ## Reporting a Vulnerability diff --git a/Source/.editorconfig b/Source/.editorconfig new file mode 100644 index 0000000..6487a11 --- /dev/null +++ b/Source/.editorconfig @@ -0,0 +1,286 @@ +# 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 +dotnet_style_namespace_match_folder = true:suggestion +# 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 + +#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 + +[*.cs] +# Disable xUnit1013 warning +dotnet_diagnostic.xUnit1013.severity = none +csharp_prefer_system_threading_lock = true:suggestion +#region should not be located within a code element +dotnet_diagnostic.SA1123.severity = none +dotnet_diagnostic.SA1601.severity = none +dotnet_diagnostic.SA1616.severity = none +dotnet_diagnostic.SA1108.severity = none + +# A multi-line initializer in a C# code file should use a comma on the last line. +dotnet_diagnostic.SA1413.severity = none + +# Ignore rules for Testing Projects +[/*Tests*/**] +# SA1025: Code should not contain multiple whitespace in a row.(This is usefull for InlineData documentation.) +dotnet_diagnostic.SA1025.severity = none +# A C# code file contains more than one unique type. +dotnet_diagnostic.SA1402.severity = none +# A constant field is placed beneath a non-constant field. +dotnet_diagnostic.SA1203.severity = none + +# Ignore rules for Controllers +[**/Controllers/**] +# This controller has multiple responsibilities and could be split into 2 smaller controllers. (https://rules.sonarsource.com/csharp/RSPEC-6960) +dotnet_diagnostic.S6960.severity = none +# Use model binding instead of accessing the raw request data (https://rules.sonarsource.com/csharp/RSPEC-6932) +dotnet_diagnostic.S6932.severity = none +# In the context of ASP.NET Core MVC web applications, both model binding and model validation are processes that take place prior to the execution of a controller action. +dotnet_diagnostic.S6967.severity = none + + +[DiscoveryDocument.cs] +# A closing square bracket within a C# statement is not spaced correctly.(There is an issue with identifying nullable array properties.) +dotnet_diagnostic.SA1011.severity = none + +[JwtSecurityTokenExtensions.cs] +# A closing square bracket within a C# statement is not spaced correctly.(There is an issue with identifying nullable array properties.) +dotnet_diagnostic.SA1011.severity = none + +[AccessToken.cs] +# The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line. (Test methods with many parameters) +dotnet_diagnostic.SA1117.severity = none + +[JsonWebKey.cs] +# The name of a C# element does not begin with an upper-case letter. (Json property mapping) +dotnet_diagnostic.SA1300.severity = none + +[JsonWebKeySet.cs] +# The name of a C# element does not begin with an upper-case letter. (Json property mapping) +dotnet_diagnostic.SA1300.severity = none + +[ClientAssertionRequest.cs] +# The name of a C# element does not begin with an upper-case letter. (Json property mapping) +dotnet_diagnostic.SA1300.severity = none + +[AccessToken.cs] +# The name of a C# element does not begin with an upper-case letter. (Json property mapping) +dotnet_diagnostic.SA1300.severity = none + +[SoftwareStatementAssertionModel.cs] +# The name of a C# element does not begin with an upper-case letter. (Json property mapping) +dotnet_diagnostic.SA1300.severity = none + +[DataRecipientStatus.cs] +# A C# code file contains more than one unique type. +dotnet_diagnostic.SA1402.severity = none + +[ExpectedApiErrors.cs] +# Remove this empty class, write its code or make it an "interface". +dotnet_diagnostic.S2094.severity = none + +[Program.cs] +# Using platform dependent API on a component makes the code no longer work across all platforms. +dotnet_diagnostic.CA1416.severity = none + + diff --git a/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj b/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj index c301e8e..04ecd02 100644 --- a/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj +++ b/Source/CDR.Register.API.Gateway.TLS/CDR.Register.API.Gateway.TLS.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + true @@ -19,14 +20,23 @@ - - - + + + + - + - + + + 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.Register.API.Gateway.TLS/Program.cs b/Source/CDR.Register.API.Gateway.TLS/Program.cs index 3d0e612..4fccbed 100644 --- a/Source/CDR.Register.API.Gateway.TLS/Program.cs +++ b/Source/CDR.Register.API.Gateway.TLS/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Security.Authentication; using Microsoft.AspNetCore.Builder; @@ -6,7 +6,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; namespace CDR.Register.API.Gateway.TLS { @@ -22,7 +21,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj b/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj index 143d4f0..8e98bcb 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj +++ b/Source/CDR.Register.API.Gateway.mTLS/CDR.Register.API.Gateway.mTLS.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + True @@ -28,16 +29,25 @@ - - - + + + + - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Gateway.mTLS/Program.cs b/Source/CDR.Register.API.Gateway.mTLS/Program.cs index cea2a9f..d506ace 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/Program.cs +++ b/Source/CDR.Register.API.Gateway.mTLS/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net.Security; using System.Security.Authentication; @@ -8,13 +8,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; -namespace CDR.Register.API.Gateway.mTLS +namespace CDR.Register.API.Gateway.Mtls { public static class Program { - public static int Main(string[] args) { var configuration = new ConfigurationBuilder() @@ -25,7 +23,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() @@ -87,13 +85,13 @@ public static IHostBuilder CreateHostBuilder(string[] args) => listenOptions.OnAuthenticate = (context, sslOptions) => { // Set the cipher suites dictated by the CDS. - sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy( - new[] { + sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(new[] + { TlsCipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TlsCipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - }); + }); }; } }); diff --git a/Source/CDR.Register.API.Gateway.mTLS/Startup.cs b/Source/CDR.Register.API.Gateway.mTLS/Startup.cs index a08c311..e5a6eaf 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/Startup.cs +++ b/Source/CDR.Register.API.Gateway.mTLS/Startup.cs @@ -1,5 +1,4 @@ -using System.Configuration; -using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Exceptions; @@ -18,7 +17,7 @@ using Serilog; using static System.Net.Mime.MediaTypeNames; -namespace CDR.Register.API.Gateway.mTLS +namespace CDR.Register.API.Gateway.Mtls { public class Startup { @@ -61,6 +60,7 @@ public void ConfigureServices(IServiceCollection services) } }; }) + // Adding an ICertificateValidationCache results in certificate auth caching the results. // The default implementation uses a memory cache. .AddCertificateCache(); @@ -90,7 +90,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { context.Response.StatusCode = StatusCodes.Status502BadGateway; } - + context.Response.ContentType = Text.Plain; await context.Response.WriteAsync($"An error occurred handling the request: {ex?.Message}"); }); diff --git a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj index 5571ba6..8c867b3 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/CDR.Register.API.Infrastructure.Tests.UnitTests.csproj @@ -5,11 +5,13 @@ $(Version) $(Version) $(Version) + True - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -19,6 +21,14 @@ all + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs index 23d0d01..725c2d9 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Security.Cryptography.X509Certificates; using CDR.Register.API.Infrastructure.Exceptions; @@ -17,11 +16,12 @@ public class CertificateValidatorTests [Fact] public void IsValid_ValidCertificate_ShouldReturnTrue() { - // Arrange. + // Arrange. var logger = Substitute.For>(); var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); - var inMemorySettings = new Dictionary { - {"RootCACertificatePath", rootCaPath} + var inMemorySettings = new Dictionary + { + { "RootCACertificatePath", rootCaPath } }; IConfiguration configuration = new ConfigurationBuilder() @@ -45,15 +45,14 @@ public void IsValid_NullCertificate_ShouldThrowException() // Arrange. var logger = Substitute.For>(); var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); - var inMemorySettings = new Dictionary { - {"RootCACertificatePath", rootCaPath} + var inMemorySettings = new Dictionary + { + { "RootCACertificatePath", rootCaPath } }; IConfiguration configuration = new ConfigurationBuilder() .AddInMemoryCollection(inMemorySettings) .Build(); - var clientCertPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "client.pfx"); - var goodClientCert = new X509Certificate2(clientCertPath, "#M0ckDataRecipient#"); var validator = new CertificateValidator(logger, configuration); // Act. @@ -65,11 +64,12 @@ public void IsValid_NullCertificate_ShouldThrowException() [Fact] public void IsValid_SelfSignedCertificate_ShouldReturnFalse() { - // Arrange. + // Arrange. var logger = Substitute.For>(); var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); - var inMemorySettings = new Dictionary { - {"RootCACertificatePath", rootCaPath} + var inMemorySettings = new Dictionary + { + { "RootCACertificatePath", rootCaPath } }; IConfiguration configuration = new ConfigurationBuilder() @@ -86,11 +86,12 @@ public void IsValid_SelfSignedCertificate_ShouldReturnFalse() [Fact] public void IsValid_FakeMockCDRCACertificate_ShouldReturnFalse() { - // Arrange. + // Arrange. var logger = Substitute.For>(); var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); - var inMemorySettings = new Dictionary { - {"RootCACertificatePath", rootCaPath} + var inMemorySettings = new Dictionary + { + { "RootCACertificatePath", rootCaPath } }; IConfiguration configuration = new ConfigurationBuilder() @@ -109,11 +110,12 @@ public void IsValid_FakeMockCDRCACertificate_ShouldReturnFalse() [Fact] public void IsValid_NonMockCDRCACertificate_ShouldReturnFalse() { - // Arrange. + // Arrange. var logger = Substitute.For>(); var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); - var inMemorySettings = new Dictionary { - {"RootCACertificatePath", rootCaPath} + var inMemorySettings = new Dictionary + { + { "RootCACertificatePath", rootCaPath } }; IConfiguration configuration = new ConfigurationBuilder() @@ -128,6 +130,5 @@ public void IsValid_NonMockCDRCACertificate_ShouldReturnFalse() // Assert. } - } } diff --git a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs index 9efecaa..4ca5982 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs @@ -1,4 +1,4 @@ -using CDR.Register.API.Infrastructure.Versioning; +using CDR.Register.API.Infrastructure.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using NSubstitute; @@ -11,7 +11,6 @@ namespace CDR.Register.API.Infrastructure.Tests.UnitTests.Versioning [Trait("Category", "UnitTests")] public partial class CdrVersionReaderTests { - [Fact] public void Read_DataHolderStatus_NoXvHeader_ShouldReturn1() { @@ -37,7 +36,7 @@ public void Read_DataHolderStatus_EmptyXvHeader_ShouldReturn1() var expectedVersion = "1"; var mockHttpHeaders = new HeaderDictionary(new Dictionary(StringComparer.OrdinalIgnoreCase)); - mockHttpHeaders.Add("x-v", new StringValues("")); + mockHttpHeaders.Add("x-v", new StringValues(string.Empty)); var mockHttpRequest = Substitute.For(); mockHttpRequest.Path.Returns(new PathString("/cdr-register/v1/all/data-holders/status")); @@ -74,7 +73,7 @@ public void Read_EmptyXvHeader_ShouldReturnMissingVersion() var apiVersionReader = new CdrVersionReader(new Models.CdrApiOptions()); var mockHttpHeaders = new HeaderDictionary(new Dictionary(StringComparer.OrdinalIgnoreCase)); - mockHttpHeaders.Add("x-v", new StringValues("")); + mockHttpHeaders.Add("x-v", new StringValues(string.Empty)); var mockHttpRequest = Substitute.For(); mockHttpRequest.Path.Returns(new PathString("/cdr-register/v1/all/data-holders/brands")); @@ -95,7 +94,7 @@ public void Read_NullXvHeader_ShouldReturnMissingVersion() var apiVersionReader = new CdrVersionReader(new Models.CdrApiOptions()); var mockHttpHeaders = new HeaderDictionary(new Dictionary(StringComparer.OrdinalIgnoreCase)); - mockHttpHeaders.Add("x-v", new StringValues()); + mockHttpHeaders.Add("x-v", default); var mockHttpRequest = Substitute.For(); mockHttpRequest.Path.Returns(new PathString("/cdr-register/v1/all/data-holders/brands")); @@ -363,4 +362,4 @@ public void Read_ValidXvInvalidXminV_ShouldReturnInvalidVersion() Assert.Equal(expectedVersion, actualVersion); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicies.cs b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicies.cs index e56990d..345a994 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicies.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicies.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; -using System; -using Microsoft.OpenApi.Extensions; +using System; +using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Extensions; namespace CDR.Register.API.Infrastructure.Authorization { diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs index cd1ec0a..75649ce 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs @@ -15,9 +15,13 @@ public AuthorisationPolicyAttribute(string name, string? scopeRequirement, bool } public string Name { get; private set; } + public string? ScopeRequirement { get; private set; } + public bool HasMtlsRequirement { get; private set; } + public bool HasHolderOfKeyRequirement { get; private set; } + public bool HasAccessTokenRequirement { get; private set; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs index 196cad4..ca6e4a5 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs @@ -32,6 +32,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. Access token is missing 'client_id' claim for issuer '{Issuer}'.", requirement.Issuer); } + return Task.CompletedTask; } @@ -42,6 +43,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. Access token is missing 'client_id' claim."); } + return Task.CompletedTask; } @@ -54,6 +56,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. Access token client_id '{AccessTokenClientId}' does not match request softwareProductId '{RequestDataRecipientProductId}'", accessTokenClientId, requestDataRecipientProductId); } + return Task.CompletedTask; } @@ -62,4 +65,4 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs index f61aa43..eef6041 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs @@ -5,8 +5,6 @@ using Microsoft.Extensions.Primitives; using Newtonsoft.Json.Linq; using Serilog.Context; -using System.Configuration; -using System.Linq; using System.Threading.Tasks; namespace CDR.Register.API.Infrastructure.Authorization @@ -21,7 +19,7 @@ public MtlsHandler(IHttpContextAccessor httpContextAccessor, ILogger(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; @@ -50,6 +46,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. Request header 'X-TlsClientCertThumbprint' is missing."); } + return Task.CompletedTask; } @@ -67,6 +64,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. cnf:x5t#S256 claim is missing from access token."); } + return Task.CompletedTask; } @@ -76,6 +74,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. X-TlsClientCertThumbprint request header value '{RequestHeaderClientCertThumprint}' does not match access token cnf:x5t#S256 claim value '{AccessTokenClientCertThumbprint}'", requestHeaderClientCertThumprint, accessTokenClientCertThumbprint); } + return Task.CompletedTask; } @@ -84,4 +83,4 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs b/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs index 3b25665..34f9d3f 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs @@ -1,5 +1,4 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.Domain.Models; +using CDR.Register.Domain.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; @@ -13,11 +12,11 @@ namespace CDR.Register.API.Infrastructure.Authorization [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class PolicyAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter { - public readonly RegisterAuthorisationPolicy policy; + public RegisterAuthorisationPolicy RegisterAuthorisationPolicy { get; private set; } public PolicyAuthorizeAttribute(RegisterAuthorisationPolicy policy) { - this.policy = policy; + this.RegisterAuthorisationPolicy = policy; } public async Task OnAuthorizationAsync(AuthorizationFilterContext context) @@ -25,14 +24,16 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context) var authorizationService = (IAuthorizationService?)context.HttpContext.RequestServices.GetService(typeof(IAuthorizationService)); if (authorizationService == null) - { - return; + { + return; } - var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, policy.ToString()); + var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, RegisterAuthorisationPolicy.ToString()); if (authorizationResult.Succeeded) + { return; + } if (authorizationResult.Failure.FailedRequirements.Any(r => r.GetType() == typeof(MtlsRequirement))) { @@ -43,4 +44,4 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context) context.Result = new RegisterForbidResult(new ResponseErrorList(StatusCodes.Status403Forbidden.ToString(), HttpStatusCode.Forbidden.ToString(), "You do not have permission to access this resource.")); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs index 7324949..0914518 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs @@ -4,7 +4,7 @@ namespace CDR.Register.API.Infrastructure.Authorization { public enum RegisterAuthorisationPolicy { - [AuthorisationPolicy("DataHolderBrandsApiMultiIndustry", CdsRegistrationScopes.Read, true, false,false)] + [AuthorisationPolicy("DataHolderBrandsApiMultiIndustry", CdsRegistrationScopes.Read, true, false, false)] DataHolderBrandsApiMultiIndustry, [AuthorisationPolicy("GetSSAMultiIndustry", CdsRegistrationScopes.Read, true, false, false)] GetSSAMultiIndustry diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterForbidResult.cs b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterForbidResult.cs index 0251492..71135b0 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterForbidResult.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterForbidResult.cs @@ -1,5 +1,4 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.Domain.Models; +using CDR.Register.Domain.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -7,12 +6,14 @@ namespace CDR.Register.API.Infrastructure.Authorization { public class RegisterForbidResult : ObjectResult { - public RegisterForbidResult(ResponseErrorList errorList) : base(errorList) + public RegisterForbidResult(ResponseErrorList errorList) + : base(errorList) { this.StatusCode = StatusCodes.Status403Forbidden; } - public RegisterForbidResult(Error error) : this(new ResponseErrorList(error)) + public RegisterForbidResult(Error error) + : this(new ResponseErrorList(error)) { } } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterUnauthorizedResult.cs b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterUnauthorizedResult.cs index 6b82300..4f8f8d3 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterUnauthorizedResult.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterUnauthorizedResult.cs @@ -1,5 +1,4 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.Domain.Models; +using CDR.Register.Domain.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -7,12 +6,14 @@ namespace CDR.Register.API.Infrastructure.Authorization { public class RegisterUnauthorizedResult : ObjectResult { - public RegisterUnauthorizedResult(ResponseErrorList errorList) : base(errorList) + public RegisterUnauthorizedResult(ResponseErrorList errorList) + : base(errorList) { this.StatusCode = StatusCodes.Status401Unauthorized; } - public RegisterUnauthorizedResult(Error error) : this(new ResponseErrorList(error)) + public RegisterUnauthorizedResult(Error error) + : this(new ResponseErrorList(error)) { } } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs index c7868f3..ae33ef4 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs @@ -30,6 +30,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { _logger.LogError("Unauthorized request. Access token is missing 'scope' claim."); } + return Task.CompletedTask; } @@ -40,7 +41,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte // The space character is used to seperate the scopes as this is in line with CDS specifications. string[] requiredScopes = requirement.Scope.Split(' '); - if (userClaimScopes!=null && userClaimScopes.Intersect(requiredScopes).Any()) + if (userClaimScopes != null && userClaimScopes.Intersect(requiredScopes).Any()) { context.Succeed(requirement); } @@ -48,4 +49,4 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs index 9dde007..cf4bf5c 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs @@ -11,5 +11,5 @@ public ScopeRequirement(string? scope) { Scope = scope ?? throw new ArgumentNullException(nameof(scope)); } - } -} \ No newline at end of file + } +} diff --git a/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj b/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj index a332dc1..e525df7 100644 --- a/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj +++ b/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj @@ -5,13 +5,15 @@ $(Version) $(Version) enable + True - + - - + + + @@ -23,6 +25,14 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs b/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs index 11eb189..7f2ab1f 100644 --- a/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs +++ b/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs @@ -22,14 +22,14 @@ public CertificateValidator(ILogger logger, IConfiguration public void ValidateClientCertificate(X509Certificate2 clientCert) { - _logger.LogInformation("Validating certificate within the {Name}",nameof(CertificateValidator)); + _logger.LogInformation("Validating certificate within the {Name}", nameof(CertificateValidator)); if (clientCert == null) { throw new ArgumentNullException(nameof(clientCert)); } - var rootCAPath = _config.GetValue("RootCACertificatePath")??throw new ClientCertificateException("Root CA Certificate path not configured"); + var rootCAPath = _config.GetValue("RootCACertificatePath") ?? throw new ClientCertificateException("Root CA Certificate path not configured"); // Validate that the certificate has been issued by the Mock CDR CA. var rootCACertificate = new X509Certificate2(rootCAPath); diff --git a/Source/CDR.Register.API.Infrastructure/Constants.cs b/Source/CDR.Register.API.Infrastructure/Constants.cs index 7a2b03a..b2081d7 100644 --- a/Source/CDR.Register.API.Infrastructure/Constants.cs +++ b/Source/CDR.Register.API.Infrastructure/Constants.cs @@ -13,7 +13,7 @@ public static class Headers public static class ConfigurationKeys { public const string BasePath = "BasePath"; - public const string BasePathExpression = "BasePathExpression"; + public const string BasePathExpression = "BasePathExpression"; public const string PublicHostName = "PublicHostName"; public const string SecureHostName = "SecureHostName"; public const string OidcMetadataAddress = "OidcMetadataAddress"; @@ -28,6 +28,5 @@ public static class Versioning { public const string GroupNameFormat = "'v'VVV"; } - } } diff --git a/Source/CDR.Register.API.Infrastructure/Exceptions/ClientCertificateException.cs b/Source/CDR.Register.API.Infrastructure/Exceptions/ClientCertificateException.cs index 73f001d..a0c6e25 100644 --- a/Source/CDR.Register.API.Infrastructure/Exceptions/ClientCertificateException.cs +++ b/Source/CDR.Register.API.Infrastructure/Exceptions/ClientCertificateException.cs @@ -4,11 +4,13 @@ namespace CDR.Register.API.Infrastructure.Exceptions { public class ClientCertificateException : Exception { - public ClientCertificateException(string message) : base($"An error occurred validating the client certificate: {message}") + public ClientCertificateException(string message) + : base($"An error occurred validating the client certificate: {message}") { } - public ClientCertificateException(string message, Exception ex) : base($"An error occurred validating the client certificate: {message}", ex) + public ClientCertificateException(string message, Exception ex) + : base($"An error occurred validating the client certificate: {message}", ex) { } } diff --git a/Source/CDR.Register.API.Infrastructure/Extensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions.cs index efd4ed7..5868be0 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions.cs @@ -37,7 +37,7 @@ public static class Extensions public static string GetInfosecBaseUrl(this IConfiguration configuration, HttpContext? context, bool isSecure = false) { var basePath = string.Empty; - if (context.Request != null && context.Request.PathBase.HasValue) + if (context?.Request != null && context.Request.PathBase.HasValue) { basePath = context.Request.PathBase.ToString(); } @@ -50,10 +50,8 @@ public static string GetInfosecBaseUrl(this IConfiguration configuration, HttpCo } /// - /// CTS conformance ids must be validated - /// - /// - /// + /// CTS conformance ids must be validated. + /// public static bool ValidateIssuer(this HttpContext context) { if (context.Request != null && context.Request.PathBase.HasValue) @@ -65,7 +63,7 @@ public static bool ValidateIssuer(this HttpContext context) return false; } - // For a stronger match validating dynamic base path with an conformance ID instead of confromanceId only + // For a stronger match validating dynamic base path with an conformance ID instead of confromanceId only return issuer?.Contains(context.Request.PathBase) ?? false; } @@ -79,7 +77,7 @@ public static void UseBasePathOrExpression(this IApplicationBuilder app, IConfig { app.UsePathBase(basePath); } - + // A dynamic base path can be set by the Mock Register:BasePathExpression app setting. // This allows a regular expression to be set and matched rather than a static base path. var basePathExpression = configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); @@ -88,12 +86,12 @@ public static void UseBasePathOrExpression(this IApplicationBuilder app, IConfig app.Use((context, next) => { var matches = Regex.Matches(context.Request.Path, basePathExpression, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase, matchTimeout: TimeSpan.FromMilliseconds(500)); - if (matches.Count!=0) + if (matches.Count != 0) { var path = matches[0].Groups[0].Value; var remainder = matches[0].Groups[1].Value; context.Request.Path = $"/{remainder}"; - context.Request.PathBase = path.Replace(remainder, "").TrimEnd('/'); + context.Request.PathBase = path.Replace(remainder, string.Empty).TrimEnd('/'); } return next(context); @@ -125,6 +123,7 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic { var metadataAddress = configuration.GetValue(Constants.ConfigurationKeys.OidcMetadataAddress); var jwks = Task.Run(() => LoadJwks($"{metadataAddress}/jwks", configuration)).Result; + // Default 2 mins* var clockSkew = configuration.GetValue(Constants.ConfigurationKeys.ClockSkewSeconds, 120); @@ -162,9 +161,9 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic services.AddMvcCore().AddAuthorization(options => { var allAuthPolicies = AuthorisationPolicies.GetAllPolicies(); - - //Apply all listed policities from a single source of truth that is also used for self-documentation - foreach(var pol in allAuthPolicies) + + // Apply all listed policities from a single source of truth that is also used for self-documentation + foreach (var pol in allAuthPolicies) { options.AddPolicy(pol.Name, policy => { @@ -179,10 +178,9 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - } - static async Task LoadJwks(string jwksUri, IConfiguration configuration) + private static async Task LoadJwks(string jwksUri, IConfiguration configuration) { var handler = new HttpClientHandler(); handler.SetServerCertificateValidation(configuration); @@ -195,7 +193,7 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic public static string GetHostName(this string url) { - return url.Replace("https://", "").Replace("http://", "").Split('/')[0]; + return url.Replace("https://", string.Empty).Replace("http://", string.Empty).Split('/')[0]; } public static LinksPaginated GetPaginated( @@ -230,6 +228,7 @@ public static LinksPaginated GetPaginated( { links.Prev = controller.GetPageUri(routeName, updatedSince, currentPage - 1, pageSize, hostName); } + if (currentPage >= totalPages) { links.Next = null; @@ -264,19 +263,20 @@ public static string GetHostNameAsUri(this ControllerBase controller, IConfigura { if (controller.Request.Headers.TryGetValue("X-Forwarded-Host", out StringValues forwardedHosts)) { - hostNameToUse = forwardedHosts[0]; + hostNameToUse = forwardedHosts[0]; } else { hostNameToUse = hostName; } - if (hostNameToUse!= null && !hostNameToUse.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + if (hostNameToUse != null && !hostNameToUse.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { - hostNameToUse = "https://" + hostNameToUse; + hostNameToUse = "https://" + hostNameToUse; } } - return hostNameToUse ?? ""; + + return hostNameToUse ?? string.Empty; } public static Uri? GetPageUri( @@ -323,13 +323,13 @@ public static string GetHostNameAsUri(this ControllerBase controller, IConfigura return new Uri(url.Replace(currentHost, newHostName)); } - private static Uri ReplaceUriHost(string url, string newHost = null) + private static Uri ReplaceUriHost(string url, string? newHost = null) { - Uri originalUri = new(url); - Uri replaceUri = new(newHost); + Uri originalUri = new Uri(url); + Uri replaceUri = new Uri(newHost ?? string.Empty); // Update the Uri components - UriBuilder modifiedUriBuilder = new(originalUri) + UriBuilder modifiedUriBuilder = new UriBuilder(originalUri) { Host = replaceUri.Host, Port = replaceUri.IsDefaultPort ? -1 : replaceUri.Port, @@ -341,9 +341,13 @@ private static Uri ReplaceUriHost(string url, string newHost = null) public static Industry ToIndustry(this string industry) { if (Enum.IsDefined(typeof(Industry), industry.ToUpper())) + { return (Industry)Enum.Parse(typeof(Industry), industry, true); + } else + { throw new NotSupportedException($"Invalid industry: {industry}"); + } } public static IEnumerable GetValueAsList(this IConfiguration configuration, string key, string delimiter) @@ -361,12 +365,12 @@ public static string GetClientCertificateThumbprint(this HttpContext context, IC { var certThumbprintNameHttpHeaderName = configuration.GetValue(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; - if (context.Request.Headers.TryGetValue(certThumbprintNameHttpHeaderName, out StringValues headerThumbprints) && headerThumbprints.Count > 0) + if (context.Request.Headers.TryGetValue(certThumbprintNameHttpHeaderName, out StringValues headerThumbprints) && headerThumbprints.Count > 0) { - return headerThumbprints[0] ?? ""; + return headerThumbprints[0] ?? string.Empty; } - return ""; + return string.Empty; } public static string GetClientCertificateCommonName(this HttpContext context, ILogger logger, IConfiguration configuration) @@ -376,7 +380,7 @@ public static string GetClientCertificateCommonName(this HttpContext context, IL if (context.Request.Headers.TryGetValue(certCommonNameHttpHeaderName, out StringValues headerCommonNames) && headerCommonNames.Count > 0) { - headerCommonName = headerCommonNames[0] ?? ""; + headerCommonName = headerCommonNames[0] ?? string.Empty; } else { @@ -425,11 +429,10 @@ public static string GetCommonNameFromDistinguishedName(this string distinguishe { try { - X500DistinguishedName dn = new(distinguishedName); + X500DistinguishedName dn = new X500DistinguishedName(distinguishedName); var cnAttribute = Array.Find( - dn.Decode(X500DistinguishedNameFlags.UseNewLines).Split('\n'), + dn.Decode(X500DistinguishedNameFlags.UseNewLines).Split('\n'), attr => attr.Trim().StartsWith("CN=")); - if (cnAttribute != null) { @@ -442,7 +445,6 @@ public static string GetCommonNameFromDistinguishedName(this string distinguishe { return string.Empty; } - } public static IServiceCollection AddCdrSwaggerGen(this IServiceCollection services, Action configureRegisterSwaggerOptions, bool isVersioned = true) @@ -456,12 +458,11 @@ public static IServiceCollection AddCdrSwaggerGen(this IServiceCollection servic { services.AddTransient, ConfigureSwaggerOptions>(); - //Required for our Swagger setup to work when endpoints have been versioned + // Required for our Swagger setup to work when endpoints have been versioned services.AddVersionedApiExplorer(opt => { opt.GroupNameFormat = options.VersionedApiGroupNameFormat; }); - } else { diff --git a/Source/CDR.Register.API.Infrastructure/Extensions/AttributeExtensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions/AttributeExtensions.cs index c5a5eca..4b1a2c3 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions/AttributeExtensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions/AttributeExtensions.cs @@ -11,12 +11,13 @@ public static IEnumerable GetAttributes(List types, MethodInfo inf { var actionAttributes = info.GetCustomAttributes(inherit); - IEnumerable controllerAttributes = []; + IEnumerable controllerAttributes = []; if (info.DeclaringType != null) { controllerAttributes = info.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit); } + var actionAndControllerAttributes = actionAttributes.Union(controllerAttributes); return actionAndControllerAttributes.Where(attr => types.Contains(attr.GetType())); @@ -26,12 +27,13 @@ public static IEnumerable GetAttributes(List types, MethodInfo inf { var actionAttributes = info.GetCustomAttributes(inherit); - IEnumerable controllerAttributes = []; + IEnumerable controllerAttributes = []; if (info.DeclaringType != null) { controllerAttributes = info.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit); } + var actionAndControllerAttributes = actionAttributes.Union(controllerAttributes); return (T?)actionAndControllerAttributes.SingleOrDefault(attr => attr.GetType() == typeof(T)); @@ -41,12 +43,13 @@ public static bool HasAttribute(MethodInfo info, Type type, bool inherit) { var actionAttributes = info.GetCustomAttributes(inherit); - IEnumerable controllerAttributes = []; + IEnumerable controllerAttributes = []; if (info.DeclaringType != null) { controllerAttributes = info.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit); } + var actionAndControllerAttributes = actionAttributes.Union(controllerAttributes); return actionAndControllerAttributes.Any(attr => attr.GetType() == type); diff --git a/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs index f1abdb0..2fadc6a 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs @@ -44,8 +44,8 @@ public static IApplicationBuilder UseCdrSwagger(this IApplicationBuilder builder string swaggerJsonBasePath = string.IsNullOrWhiteSpace(options.RoutePrefix) ? "." : ".."; options.SwaggerEndpoint( - $"{swaggerJsonBasePath}/swagger/v1/swagger.json", - name); + $"{swaggerJsonBasePath}/swagger/v1/swagger.json", + name); }); return builder; diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckDate.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs similarity index 83% rename from Source/CDR.Register.API.Infrastructure/Filters/CheckDate.cs rename to Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs index a1cc67e..0a24a95 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckDate.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Globalization; using CDR.Register.Domain.Models; using Newtonsoft.Json; @@ -10,7 +11,7 @@ public class CheckDateAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext? validationContext) { - if (!DateTime.TryParse(value?.ToString(), out DateTime output)) + if (!DateTime.TryParse(value?.ToString(), CultureInfo.InvariantCulture, out _)) { return new ValidationResult(JsonConvert.SerializeObject(ResponseErrorList.InvalidDateTime())); } diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs index 338d18e..660a9df 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs @@ -7,7 +7,7 @@ namespace CDR.Register.API.Infrastructure.Filters { /// - /// Checks the industry parameter is supported, if not then responds with BadRequest and appropriate ResponseErrorList + /// Checks the industry parameter is supported, if not then responds with BadRequest and appropriate ResponseErrorList. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] public class CheckIndustryAttribute : ActionFilterAttribute @@ -16,7 +16,7 @@ public class CheckIndustryAttribute : ActionFilterAttribute public CheckIndustryAttribute() { - _industryRestriction = ""; + _industryRestriction = string.Empty; } public CheckIndustryAttribute(Industry industryRestriction) diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckPage.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckPageAttribute.cs similarity index 100% rename from Source/CDR.Register.API.Infrastructure/Filters/CheckPage.cs rename to Source/CDR.Register.API.Infrastructure/Filters/CheckPageAttribute.cs diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSize.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs similarity index 100% rename from Source/CDR.Register.API.Infrastructure/Filters/CheckPageSize.cs rename to Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs diff --git a/Source/CDR.Register.API.Infrastructure/Filters/ETagAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/ETagAttribute.cs index 53f29c4..1d71043 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/ETagAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/ETagAttribute.cs @@ -40,7 +40,7 @@ public override void OnActionExecuted(ActionExecutedContext context) } } - // Add ETag response header + // Add ETag response header response.Headers.Append(HeaderNames.ETag, $"\"{etag}\""); } @@ -49,14 +49,16 @@ public override void OnActionExecuted(ActionExecutedContext context) private static string GenerateETag(string response) { - if (String.IsNullOrEmpty(response)) - return String.Empty; + if (string.IsNullOrEmpty(response)) + { + return string.Empty; + } using (var sha = SHA256.Create()) { byte[] textData = System.Text.Encoding.UTF8.GetBytes(response); byte[] hash = sha.ComputeHash(textData); - return BitConverter.ToString(hash).Replace("-", String.Empty); + return BitConverter.ToString(hash).Replace("-", string.Empty); } } } diff --git a/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs index 95ac3ee..268768e 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs @@ -5,26 +5,26 @@ namespace CDR.Register.API.Infrastructure.Filters { - [AttributeUsage(AttributeTargets.Method)] - public class LogActionEntryAttribute : ActionFilterAttribute - { - private readonly ILogger _logger; + [AttributeUsage(AttributeTargets.Method)] + public class LogActionEntryAttribute : ActionFilterAttribute + { + private readonly ILogger _logger; - public LogActionEntryAttribute(ILogger logger) - { - _logger = logger; - } + public LogActionEntryAttribute(ILogger 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.Register.API.Infrastructure/Filters/CheckXVAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs similarity index 86% rename from Source/CDR.Register.API.Infrastructure/Filters/CheckXVAttribute.cs rename to Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs index 73bc378..09e067a 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckXVAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs @@ -1,14 +1,10 @@ using System; -using System.Net; -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.Repository.Infrastructure; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace CDR.Register.API.Infrastructure.Filters { /// - /// Checks the x-v header field is a supported version, if not then responds with BadRequest and appropriate ResponseErrorList + /// Checks the x-v header field is a supported version, if not then responds with BadRequest and appropriate ResponseErrorList. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] public class ReturnXVAttribute : ActionFilterAttribute @@ -28,4 +24,4 @@ public override void OnActionExecuted(ActionExecutedContext context) base.OnActionExecuted(context); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs b/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs index 07a9844..d060e3e 100644 --- a/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs @@ -1,11 +1,9 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.API.Infrastructure.Versioning; +using CDR.Register.API.Infrastructure.Versioning; using CDR.Register.Domain.Models; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System.Linq; using System.Net; using System.Threading.Tasks; using static CDR.Register.API.Infrastructure.Constants; @@ -14,7 +12,7 @@ namespace CDR.Register.API.Infrastructure.Middleware { public static class ApiExceptionHandler { - public async static Task Handle(HttpContext context) + public static async Task Handle(HttpContext context) { var exceptionDetails = context.Features.Get(); var ex = exceptionDetails?.Error; @@ -45,7 +43,7 @@ public async static Task Handle(HttpContext context) handledError = JsonConvert.SerializeObject(new ResponseErrorList().AddInvalidXVUnsupportedVersion(), jsonSerializerSettings); } - if (ex is MissingRequiredHeaderException) + if (ex.GetType() == typeof(MissingRequiredHeaderException)) { var missingRequiredHeaderException = ex as MissingRequiredHeaderException; if (missingRequiredHeaderException?.HeaderName == Headers.X_V) diff --git a/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs b/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs index 0ebc291..78002e3 100644 --- a/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs +++ b/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs @@ -40,7 +40,6 @@ public static IActionResult ExecuteResult(ActionContext context) } } } - } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs b/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs index 024c813..2a743c4 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs @@ -2,21 +2,22 @@ { public class CdrApiEndpointVersionOptions { - public readonly string Path; - public readonly bool IsVersioned; - public readonly bool IsXVHeaderMandatory; - public readonly int? CurrentMinVersion; - public readonly int? CurrentMaxVersion; - public readonly int? MinVerForResponseErrorListV2; //any supported versions earlier than this number will use ResponseErrorList (v1) as per the CDS standards + public string Path { get; } + + public bool IsVersioned { get; } + + public bool IsXVHeaderMandatory { get; } + + public int? CurrentMinVersion { get; } + + public int? CurrentMaxVersion { get; } + + public int? MinVerForResponseErrorListV2 { get; } // any supported versions earlier than this number will use ResponseErrorList (v1) as per the CDS standard {get;}. /// - /// Constructs an option set where multiple versions of the endpoint are supported + /// Initializes a new instance of the class. + /// Constructs an option set where multiple versions of the endpoint are supported. /// - /// - /// - /// - /// - /// public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int minVersion, int maxVersion, int minVersionForErrorListV2) { Path = path; @@ -28,11 +29,9 @@ public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int minVers } /// - /// Constructs an option set where only one version of the endpoint is supported + /// Initializes a new instance of the class. + /// Constructs an option set where only one version of the endpoint is supported. /// - /// - /// - /// public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int version) { Path = path; @@ -44,9 +43,9 @@ public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int version } /// - /// Constructs an option set where the endpoint is unversioned + /// Initializes a new instance of the class. + /// Constructs an option set where the endpoint is unversioned. /// - /// public CdrApiEndpointVersionOptions(string path) { Path = path; diff --git a/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs b/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs index e6a20b6..54fc001 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs @@ -8,20 +8,20 @@ public class CdrApiOptions { private static readonly List _supportedApiVersions = new List { - //(?i) is to make the regex case insensitive - //(%7B)* and (%7D)* represent the possibility of a { and } in the path, to cover the value '{industry}' for OAS generation purposes + // (?i) is to make the regex case insensitive + // (%7B)* and (%7D)* represent the possibility of a { and } in the path, to cover the value '{industry}' for OAS generation purposes - //Register + // Register new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-holders\/brands", true, 2), - new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-holders\/status",false,1), - new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients",true,3), - new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/status",true,2), - new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/brands\/software-products\/status",true,2), - new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/brands\/[A-Za-z0-9\-]*\/software-products\/[A-Za-z0-9\-]*\/ssa",true,3), + new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-holders\/status", false, 1), + new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients", true, 3), + new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/status", true, 2), + new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/brands\/software-products\/status", true, 2), + new CdrApiEndpointVersionOptions(@"(?i)\/cdr-register\/v1\/(%7B)*[A-Za-z]*(%7D)*\/data-recipients\/brands\/[A-Za-z0-9\-]*\/software-products\/[A-Za-z0-9\-]*\/ssa", true, 3), - //Admin - new CdrApiEndpointVersionOptions(@"(?i)\/admin\/metadata\/data-holders",true,1), - new CdrApiEndpointVersionOptions(@"(?i)\/admin\/metadata\/data-recipients",true,1), + // Admin + new CdrApiEndpointVersionOptions(@"(?i)\/admin\/metadata\/data-holders", true, 1), + new CdrApiEndpointVersionOptions(@"(?i)\/admin\/metadata\/data-recipients", true, 1), }; public List EndpointVersionOptions { get; } = _supportedApiVersions; diff --git a/Source/CDR.Register.API.Infrastructure/Models/CdrSwaggerOptions.cs b/Source/CDR.Register.API.Infrastructure/Models/CdrSwaggerOptions.cs index 7f73362..7a0901b 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/CdrSwaggerOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/CdrSwaggerOptions.cs @@ -6,6 +6,6 @@ public class CdrSwaggerOptions public bool IncludeAuthentication { get; set; } - public string VersionedApiGroupNameFormat { get; set; } = Constants.Versioning.GroupNameFormat; //default for group name format + public string VersionedApiGroupNameFormat { get; set; } = Constants.Versioning.GroupNameFormat; // default for group name format } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs index 0e4fb54..fb05509 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs @@ -5,13 +5,21 @@ namespace CDR.Register.API.Infrastructure.Models public class JsonWebKey { public string alg { get; set; } = string.Empty; + public string e { get; set; } = string.Empty; + public string[] key_ops { get; set; } = []; + public string kid { get; set; } = string.Empty; + public string kty { get; set; } = string.Empty; + public string n { get; set; } = string.Empty; + public string use { get; set; } = string.Empty; + public string x5t { get; set; } = string.Empty; + public string[] x5c { get; set; } = []; } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs index f81669b..830d3db 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs @@ -3,6 +3,5 @@ public class JsonWebKeySet { public JsonWebKey[] keys { get; set; } = []; - } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/Links.cs b/Source/CDR.Register.API.Infrastructure/Models/Links.cs index beab1de..c4ae64c 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/Links.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/Links.cs @@ -4,7 +4,9 @@ namespace CDR.Register.API.Infrastructure.Models { public class Links { - /// Fully qualified link to this API call + /// + /// Fully qualified link to this API call. + /// public Uri? Self { get; set; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/LinksPaginated.cs b/Source/CDR.Register.API.Infrastructure/Models/LinksPaginated.cs index 2fe6344..aea26ce 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/LinksPaginated.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/LinksPaginated.cs @@ -4,19 +4,19 @@ namespace CDR.Register.API.Infrastructure.Models { public class LinksPaginated { - /// URI to the first page of this set. Mandatory if this response is not the first page + /// URI to the first page of this set. Mandatory if this response is not the first page. public Uri? First { get; set; } - /// URI to the last page of this set. Mandatory if this response is not the last page + /// URI to the last page of this set. Mandatory if this response is not the last page. public Uri? Last { get; set; } - /// URI to the next page of this set. Mandatory if this response is not the last page + /// URI to the next page of this set. Mandatory if this response is not the last page. public Uri? Next { get; set; } - /// URI to the previous page of this set. Mandatory if this response is not the first page + /// URI to the previous page of this set. Mandatory if this response is not the first page. public Uri? Prev { get; set; } - /// Fully qualified link to this API call + /// Fully qualified link to this API call. public Uri? Self { get; set; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/MetaPaginated.cs b/Source/CDR.Register.API.Infrastructure/Models/MetaPaginated.cs index a6b8ab2..a9427a4 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/MetaPaginated.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/MetaPaginated.cs @@ -3,6 +3,7 @@ public class MetaPaginated { public int? TotalRecords { get; set; } + public int? TotalPages { get; set; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs b/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs index baa0df6..fbda1ae 100644 --- a/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs +++ b/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs @@ -1,12 +1,10 @@ using System; using System.Threading.Tasks; using CDR.Register.Domain.Models; -using CDR.Register.Repository.Infrastructure; using CDR.Register.Repository.Interfaces; namespace CDR.Register.API.Infrastructure.Services { - /// /// Service to check the status of a Data Recipient. /// @@ -14,7 +12,7 @@ namespace CDR.Register.API.Infrastructure.Services /// Checks the: /// - software product status = ACTIVE /// - brand status = ACTIVE - /// - participation status = ACTIVE + /// - participation status = ACTIVE. /// public class DataRecipientStatusCheckService : IDataRecipientStatusCheckService { @@ -42,6 +40,7 @@ public async Task ValidateSoftwareProductStatus(Guid software { errorList.Errors.Add(ResponseErrorList.DataRecipientSoftwareProductNotActive()); } + if (!softwareProduct.DataRecipientBrand.IsActive || !softwareProduct.DataRecipientBrand.DataRecipient.IsActive) { errorList.Errors.Add(ResponseErrorList.DataRecipientParticipationNotActive()); diff --git a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs index f10f252..7d19160 100644 --- a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs +++ b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs @@ -19,21 +19,21 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) { var typeList = new List() { typeof(PolicyAuthorizeAttribute) }; var relAtts = AttributeExtensions.GetAttributes(typeList, context.MethodInfo, true); - + var authAtt = (PolicyAuthorizeAttribute?)relAtts.FirstOrDefault(attr => attr.GetType() == typeof(PolicyAuthorizeAttribute)); var openApiObj = new OpenApiObject(); if (authAtt != null) { - //Get the details of the policy - var authPolicy = authAtt.policy.GetPolicy(); + // Get the details of the policy + var authPolicy = authAtt.RegisterAuthorisationPolicy.GetPolicy(); if (authPolicy != null) { AddPolicyRequirements(openApiObj, authPolicy); } - } + } operation.Extensions.Add("x-authorisation-policy", openApiObj); } @@ -44,14 +44,17 @@ private static void AddPolicyRequirements(OpenApiObject openApiObj, Authorisatio { openApiObj["hasHolderOfKeyRequirement"] = new OpenApiBoolean(true); } + if (authPolicy.HasAccessTokenRequirement) { openApiObj["hasAccessTokenRequirement"] = new OpenApiBoolean(true); } + if (authPolicy.HasMtlsRequirement) { openApiObj["hasMtlsRequirement"] = new OpenApiBoolean(true); } + if (!authPolicy.ScopeRequirement.IsNullOrEmpty()) { openApiObj["scopeRequirement"] = new OpenApiString(authPolicy.ScopeRequirement); diff --git a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/CustomDocumentFilter.cs b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/CustomDocumentFilter.cs index 5399790..974ccb3 100644 --- a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/CustomDocumentFilter.cs +++ b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/CustomDocumentFilter.cs @@ -21,8 +21,8 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) foreach (var op in path.Operations.Select(op => op.Value)) { - //api-version included as it is currently used in some places in PT instead of x-v - if (op.Parameters.Any(p => p.Name.Equals("x-v",System.StringComparison.OrdinalIgnoreCase)) || op.Parameters.Any(p => p.Name.Equals("api-version", System.StringComparison.OrdinalIgnoreCase))) + // api-version included as it is currently used in some places in PT instead of x-v + if (op.Parameters.Any(p => p.Name.Equals("x-v", System.StringComparison.OrdinalIgnoreCase)) || op.Parameters.Any(p => p.Name.Equals("api-version", System.StringComparison.OrdinalIgnoreCase))) { op.Extensions.TryGetValue("x-version", out var value); if (value == null && !string.IsNullOrWhiteSpace(swaggerDoc.Info.Version)) diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs b/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs index 0f68f1d..c4d945d 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs @@ -23,13 +23,13 @@ public void AddParameters(IApiVersionParameterDescriptionContext context) context.AddParameter("x-min-v", ApiVersionParameterLocation.Header); } - public string Read(HttpRequest request) + public string? Read(HttpRequest request) { var endpointOption = _options.GetApiEndpointVersionOption(request.Path); if (endpointOption == null) { - //handle any endpoint that hasn't been defined in options + // handle any endpoint that hasn't been defined in options endpointOption = new CdrApiEndpointVersionOptions(string.Empty, false, int.Parse(_options.DefaultVersion)); } else if (!endpointOption.IsVersioned) @@ -64,7 +64,7 @@ public string Read(HttpRequest request) return Domain.Constants.ErrorTitles.InvalidVersion; } - private static string CalculateVersion(int xvInt, string xvMinString, CdrApiEndpointVersionOptions endpointOption) + private static string? CalculateVersion(int xvInt, string? xvMinString, CdrApiEndpointVersionOptions endpointOption) { if (!string.IsNullOrEmpty(xvMinString)) { diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs b/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs index 360e041..59822e6 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs @@ -1,18 +1,20 @@ using System; -using System.Runtime.Serialization; namespace CDR.Register.API.Infrastructure.Versioning { public class InvalidVersionException : Exception { - public string HeaderName { get; set; } = ""; + public string HeaderName { get; set; } = string.Empty; - public InvalidVersionException() : base() { } + public InvalidVersionException() + : base() + { + } - public InvalidVersionException(string headerName) : base() + public InvalidVersionException(string headerName) + : base() { this.HeaderName = headerName; - } - + } } } diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs b/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs index 3f26229..12be6f3 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs @@ -6,14 +6,16 @@ public class MissingRequiredHeaderException : Exception { public string HeaderName { get; set; } - public MissingRequiredHeaderException() : base() + public MissingRequiredHeaderException() + : base() { HeaderName = string.Empty; } - public MissingRequiredHeaderException(string headerName) : base() + public MissingRequiredHeaderException(string headerName) + : base() { this.HeaderName = headerName; - } + } } } diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/UnsupportedVersionException.cs b/Source/CDR.Register.API.Infrastructure/Versioning/UnsupportedVersionException.cs index 79c07fa..1906e7b 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/UnsupportedVersionException.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/UnsupportedVersionException.cs @@ -4,13 +4,19 @@ namespace CDR.Register.API.Infrastructure.Versioning { public class UnsupportedVersionException : Exception { - public UnsupportedVersionException() : base() { } + public UnsupportedVersionException() + : base() + { + } - public UnsupportedVersionException(string message) : base(message) { } + public UnsupportedVersionException(string message) + : base(message) + { + } public UnsupportedVersionException(int minVersion, int maxVersion) : base($"minimum version: {minVersion}, maximum version: {maxVersion}") { - } + } } } diff --git a/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj b/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj index f984908..29ba5c4 100644 --- a/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj +++ b/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj @@ -6,13 +6,23 @@ $(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.Register.API.Logger/RequestResponseLogger.cs b/Source/CDR.Register.API.Logger/RequestResponseLogger.cs index 74be6b9..e9da2b3 100644 --- a/Source/CDR.Register.API.Logger/RequestResponseLogger.cs +++ b/Source/CDR.Register.API.Logger/RequestResponseLogger.cs @@ -9,27 +9,30 @@ 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, new ConfigurationReaderOptions { 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", "") + .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(); } @@ -45,7 +48,6 @@ protected virtual void Dispose(bool disposing) { Serilog.Log.CloseAndFlush(); } - } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs b/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs index 6629c37..9c8e81d 100644 --- a/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs +++ b/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; -using System.Net; using System.Net.Http.Headers; using System.Web; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -15,12 +14,18 @@ namespace CDR.Register.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 string? _currentProcessName; + 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; @@ -39,15 +44,6 @@ public class RequestResponseLoggingMiddleware private string? _softwareId; private string? _fapiInteractionId; private string? _dataHolderBrandId; - private readonly string? _currentProcessName; - - - private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; - readonly RequestDelegate _next; - private readonly ILogger _requestResponseLogger; - private readonly IConfiguration _configuration; - - public RequestResponseLoggingMiddleware(RequestDelegate next, IRequestResponseLogger requestResponseLogger, IConfiguration configuration) { @@ -93,15 +89,14 @@ 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); } } - private async Task ExtractRequestProperties(HttpContext context) { try @@ -132,7 +127,7 @@ private async Task ExtractRequestProperties(HttpContext context) } } - static class ClaimIdentifiers + private static class ClaimIdentifiers { public const string ClientId = "client_id"; public const string Iss = "iss"; @@ -144,19 +139,17 @@ 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 ?? ""; + var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; idToSet = id; } } - - private void ExtractIdFromRequest(HttpRequest request) { try { - //try fetching from the clientid in the body for connect/par + // try fetching from the clientid in the body for connect/par if (!string.IsNullOrEmpty(_requestBody) && _requestBody.Contains("client_assertion=") && string.IsNullOrEmpty(_clientId)) { var nameValueCollection = HttpUtility.ParseQueryString(_requestBody); @@ -167,21 +160,20 @@ private void ExtractIdFromRequest(HttpRequest request) if (assertion != null) { // in this case we set the iss to clientid - _softwareId = String.Empty; + _softwareId = string.Empty; SetIdFromJwt(assertion, ClaimIdentifiers.Iss, ref _softwareId); } } - } - //try fetching x-fapi-interaction-id. After fetching we don't return as we need other important ids. + // try fetching x-fapi-interaction-id. After fetching we don't return as we need other important ids. _fapiInteractionId = string.Empty; if (request.Headers.TryGetValue("x-fapi-interaction-id", out var interactionid)) { _fapiInteractionId = interactionid; } - //try fetching from the JWT in the authorization header + // try fetching from the JWT in the authorization header var authorization = request.Headers[HeaderNames.Authorization]; if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue) && string.IsNullOrEmpty(_softwareId)) { @@ -190,17 +182,15 @@ private void ExtractIdFromRequest(HttpRequest request) if (scheme == JwtBearerDefaults.AuthenticationScheme && parameter != null) { - _softwareId = String.Empty; + _softwareId = string.Empty; SetIdFromJwt(parameter, ClaimIdentifiers.ClientId, ref _softwareId); } } - } catch (Exception ex) { _exceptionMessage = ex.Message; } - } private string ReadStreamInChunks(Stream stream) @@ -217,7 +207,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) @@ -225,13 +216,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; @@ -297,7 +286,7 @@ private string GetHost(HttpRequest request) // 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. } @@ -314,8 +303,8 @@ private string GetSourceContext() case "CDR.Register.Status.API": return "SB-REG-STS"; } + return string.Empty; } - } } diff --git a/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs b/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs index d81f5e6..ea11d2d 100644 --- a/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs +++ b/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs @@ -3,7 +3,6 @@ using CDR.Register.Domain.Entities; using Microsoft.Extensions.Configuration; using System.Collections.Generic; -using System.Linq; using DomainEntities = CDR.Register.Domain.Entities; namespace CDR.Register.Admin.API.Business @@ -12,12 +11,10 @@ public class AdminMappingProfile : Profile { public AdminMappingProfile() { - CreateMap() .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source)) .ForMember(dest => dest.DataRecipientBrands, source => source.MapFrom(source => source.DataRecipientBrands)); - CreateMap() .ForMember(dest => dest.AccreditationLevelId, source => source.MapFrom(source => source.AccreditationLevel)); @@ -27,8 +24,8 @@ public AdminMappingProfile() CreateMap() .ForMember(dest => dest.RedirectUri, source => source.MapFrom(src => src.RedirectUris != null ? string.Join(" ", src.RedirectUris) : string.Empty)) - .ForMember(dest => dest.RedirectUris, opts => opts.Ignore()) //Ignore this as it is a computed property with no setter - .ForMember(dest => dest.Scope, opt => opt.MapFrom(src => src.Scope)); + .ForMember(dest => dest.RedirectUris, opts => opts.Ignore()) // Ignore this as it is a computed property with no setter + .ForMember(dest => dest.Scope, opt => opt.MapFrom(src => src.Scope ?? string.Empty)); CreateMap(); @@ -36,7 +33,6 @@ public AdminMappingProfile() CreateMap(); CreateMap(); CreateMap(); - CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.LegalEntityId)) @@ -69,24 +65,4 @@ public AdminMappingProfile() .ForMember(dest => dest.DataHolder, source => source.MapFrom(source => source)); } } - - public class SoftwareScopeResolver : IMemberValueResolver - { - private readonly IConfiguration config; - - public SoftwareScopeResolver(IConfiguration config) - { - this.config=config; - } - - public string Resolve(Model.SoftwareProduct source, DomainEntities.SoftwareProduct destination, string sourceMember, string destMember, ResolutionContext context) - { - if (source.Scope == null) - { - return config["SoftwareProductDefaultScopes"] ?? ""; - } - - return source.Scope; - } - } } diff --git a/Source/CDR.Register.Admin.API/Business/Model/DataHolderAuthenticationModel.cs b/Source/CDR.Register.Admin.API/Business/Model/DataHolderAuthenticationModel.cs new file mode 100644 index 0000000..4102dbb --- /dev/null +++ b/Source/CDR.Register.Admin.API/Business/Model/DataHolderAuthenticationModel.cs @@ -0,0 +1,9 @@ +namespace CDR.Register.Admin.API.Business.Model +{ + public class DataHolderAuthenticationModel + { + public string RegisterUType { get; set; } = string.Empty; + + public string JwksEndpoint { get; set; } = string.Empty; + } +} diff --git a/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs b/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs index e7b249e..bc16faf 100644 --- a/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs +++ b/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs @@ -1,23 +1,27 @@ using CDR.Register.Admin.API.Business.Validators; -using CDR.Register.API.Infrastructure; using CDR.Register.Domain.Entities; using CDR.Register.Domain.Models; using System; using System.Collections.Generic; -using System.Linq; namespace CDR.Register.Admin.API.Business.Model { public class DataHolderBrandModel { - public Guid DataHolderBrandId { get; set; } + public Guid DataHolderBrandId { get; set; } = Guid.Empty; + public string BrandName { get; set; } = string.Empty; + public string[] Industries { get; set; } = []; + public string LogoUri { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; public DataHolderLegalEntityModel? LegalEntity { get; set; } = null; + public DataHolderEndpointModel? EndpointDetail { get; set; } = null; + public DataHolderAuthenticationModel? AuthDetails { get; set; } = null; public ResponseErrorList Validate(DataHolderBrand existingDataHolderBrand) @@ -38,7 +42,7 @@ public ResponseErrorList Validate(DataHolderBrand existingDataHolderBrand) detail = $"Value '{error.AttemptedValue}' is not allowed for {error.PropertyName}"; } - responseErrorList.Errors.Add(new Error(error.ErrorCode,error.ErrorMessage,detail)); + responseErrorList.Errors.Add(new Error(error.ErrorCode, error.ErrorMessage, detail)); } return responseErrorList; @@ -65,54 +69,27 @@ private List ValidateWithExisting(DataHolderBrand existingDataHolderBrand // Validate all the parent IDs. if (existingDataHolderBrand.DataHolder == null) // This ensures it is a DH Participation { - errorList.Add(new Error(Domain.Constants.ErrorCodes.Cds.InvalidField, Domain.Constants.ErrorTitles.InvalidField, + errorList.Add(new Error( + Domain.Constants.ErrorCodes.Cds.InvalidField, + Domain.Constants.ErrorTitles.InvalidField, $"Brand {this.DataHolderBrandId} is not a Data Holder.")); } else if (existingDataHolderBrand.DataHolder?.LegalEntity.LegalEntityId != this.LegalEntity?.LegalEntityId) { - errorList.Add(new Error(Domain.Constants.ErrorCodes.Cds.InvalidField, Domain.Constants.ErrorTitles.InvalidField, + errorList.Add(new Error( + Domain.Constants.ErrorCodes.Cds.InvalidField, + Domain.Constants.ErrorTitles.InvalidField, $"Brand {this.DataHolderBrandId} is already associated with a different legal entity.")); } else if (!string.Equals(existingDataHolderBrand.DataHolder?.Industry, this.Industries[0], StringComparison.InvariantCultureIgnoreCase)) { - errorList.Add(new Error(Domain.Constants.ErrorCodes.Cds.InvalidField, Domain.Constants.ErrorTitles.InvalidField, - $"Brand {this.DataHolderBrandId} is already associated with the same legal entity in a different industry.")); + errorList.Add(new Error( + Domain.Constants.ErrorCodes.Cds.InvalidField, + Domain.Constants.ErrorTitles.InvalidField, + $"Brand {this.DataHolderBrandId} is already associated with the same legal entity in a different industry.")); } - return errorList; } } - - public class DataHolderEndpointModel - { - public string Version { get; set; } = string.Empty; - public string PublicBaseUri { get; set; } = string.Empty; - public string ResourceBaseUri { get; set; } = string.Empty; - public string InfosecBaseUri { get; set; } = string.Empty; - public string? ExtensionBaseUri { get; set; } - public string WebsiteUri { get; set; } = string.Empty; - } - - public class DataHolderAuthenticationModel - { - public string RegisterUType { get; set; } = string.Empty; - public string JwksEndpoint { get; set; } = string.Empty; - } - - public class DataHolderLegalEntityModel - { - public Guid LegalEntityId { get; set; } - public string LegalEntityName { get; set; } = string.Empty; - public string LogoUri { get; set; } = string.Empty; - public string? RegistrationNumber { get; set; } - public DateTime? RegistrationDate { get; set; } - public string? RegisteredCountry { get; set; } - public string? Abn { get; set; } - public string? Acn { get; set; } - public string? Arbn { get; set; } - public string? AnzsicDivision { get; set; } - public string? OrganisationType { get; set; } - public string Status { get; set; } = string.Empty; - } } diff --git a/Source/CDR.Register.Admin.API/Business/Model/DataHolderEndpointModel.cs b/Source/CDR.Register.Admin.API/Business/Model/DataHolderEndpointModel.cs new file mode 100644 index 0000000..789f38e --- /dev/null +++ b/Source/CDR.Register.Admin.API/Business/Model/DataHolderEndpointModel.cs @@ -0,0 +1,17 @@ +namespace CDR.Register.Admin.API.Business.Model +{ + public class DataHolderEndpointModel + { + public string Version { get; set; } = string.Empty; + + public string PublicBaseUri { get; set; } = string.Empty; + + public string ResourceBaseUri { get; set; } = string.Empty; + + public string InfosecBaseUri { get; set; } = string.Empty; + + public string? ExtensionBaseUri { get; set; } + + public string WebsiteUri { get; set; } = string.Empty; + } +} diff --git a/Source/CDR.Register.Admin.API/Business/Model/DataHolderLegalEntityModel.cs b/Source/CDR.Register.Admin.API/Business/Model/DataHolderLegalEntityModel.cs new file mode 100644 index 0000000..0f39dd8 --- /dev/null +++ b/Source/CDR.Register.Admin.API/Business/Model/DataHolderLegalEntityModel.cs @@ -0,0 +1,31 @@ +using System; + +namespace CDR.Register.Admin.API.Business.Model +{ + public class DataHolderLegalEntityModel + { + public Guid LegalEntityId { get; set; } = Guid.Empty; + + public string LegalEntityName { get; set; } = string.Empty; + + public string LogoUri { get; set; } = string.Empty; + + public string? RegistrationNumber { get; set; } + + public DateTime? RegistrationDate { get; set; } + + public string? RegisteredCountry { get; set; } + + public string? Abn { get; set; } + + public string? Acn { get; set; } + + public string? Arbn { get; set; } + + public string? AnzsicDivision { get; set; } + + public string? OrganisationType { get; set; } + + public string Status { get; set; } = string.Empty; + } +} diff --git a/Source/CDR.Register.Admin.API/Business/Model/LegalEntity.cs b/Source/CDR.Register.Admin.API/Business/Model/LegalEntity.cs index 757f469..9c12b5b 100644 --- a/Source/CDR.Register.Admin.API/Business/Model/LegalEntity.cs +++ b/Source/CDR.Register.Admin.API/Business/Model/LegalEntity.cs @@ -34,6 +34,5 @@ public class LegalEntity public string? OrganisationType { get; set; } = null; public virtual ICollection DataRecipientBrands { get; set; } = []; - } } diff --git a/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs b/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs new file mode 100644 index 0000000..af72346 --- /dev/null +++ b/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Microsoft.Extensions.Configuration; +using DomainEntities = CDR.Register.Domain.Entities; + +namespace CDR.Register.Admin.API.Business +{ + public class SoftwareScopeResolver : IMemberValueResolver + { + private readonly IConfiguration config; + + public SoftwareScopeResolver(IConfiguration config) + { + this.config = config; + } + + public string Resolve(Model.SoftwareProduct source, DomainEntities.SoftwareProduct destination, string sourceMember, string destMember, ResolutionContext context) + { + if (source.Scope == null) + { + return config["SoftwareProductDefaultScopes"] ?? string.Empty; + } + + return source.Scope; + } + } +} diff --git a/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs index 8c669d5..ba50288 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs @@ -11,26 +11,25 @@ public class BrandValidator : AbstractValidator { public BrandValidator() { - //mandatory checks + // mandatory checks RuleFor(x => x.DataRecipientBrandId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.BrandName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - //Field lengths + // Field lengths RuleFor(x => x.BrandName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.BrandName}' is not allowed for BrandName"); RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.LogoUri}' is not allowed for LogoUri"); - - //invalid field - RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.BrandStatusType result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.Status}' is not allowed for Status"); - + + // invalid field + RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.BrandStatusType _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.Status}' is not allowed for Status"); + RuleForEach(x => x.SoftwareProducts).SetValidator(new SoftwareProductValidator()); - //duplicate check + // duplicate check RuleFor(x => x.SoftwareProducts).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); } - private bool HaveUniqueIds(ICollection? softwareProducts) { return softwareProducts?.Select(s => s.SoftwareProductId).Distinct().Count() == softwareProducts?.Count; @@ -47,6 +46,7 @@ private bool HaveUniqueIds(ICollection? softwareProducts) return $"Duplicate softwareProductId '{brand.SoftwareProducts.ElementAt(i).SoftwareProductId}' is not allowed in the same request"; } } + return null; } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs index e1c244b..c8e6ab8 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs @@ -15,7 +15,7 @@ public DataHolderAuthenticationValidator() RuleFor(x => x.JwksEndpoint).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // Enum Validations - RuleFor(x => x.RegisterUType).Must(x => Enum.TryParse(x.Replace("-", string.Empty), true, out RegisterUType result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + RuleFor(x => x.RegisterUType).Must(x => Enum.TryParse(x.Replace("-", string.Empty), true, out RegisterUType _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); // Length Validations RuleFor(x => x.JwksEndpoint).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs index ba11069..8da9192 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs @@ -21,17 +21,26 @@ public DataHolderBrandValidator() RuleFor(x => x.AuthDetails).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // Enum Validations - RuleForEach(x => x.Industries).Must(x => Enum.TryParse(x, true, out Industry result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out DhParticipationStatus result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + RuleForEach(x => x.Industries).Must(x => Enum.TryParse(x, true, out Industry _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out DhParticipationStatus _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); // Length Validations RuleFor(x => x.BrandName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); // Child Validation - RuleFor(x => x.EndpointDetail).SetValidator(new DataHolderEndpointValidator()); - RuleFor(x => x.AuthDetails).SetValidator(new DataHolderAuthenticationValidator()); - RuleFor(x => x.LegalEntity).SetValidator(new DataHolderLegalEntityValidator()); + When(x => x != null, () => + { + RuleFor(x => x.EndpointDetail!).SetValidator(new DataHolderEndpointValidator()); + }); + When(x => x != null, () => + { + RuleFor(x => x.AuthDetails!).SetValidator(new DataHolderAuthenticationValidator()); + }); + When(x => x != null, () => + { + RuleFor(x => x.LegalEntity!).SetValidator(new DataHolderLegalEntityValidator()); + }); } } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs index 24753f0..38116d5 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs @@ -17,8 +17,8 @@ public DataHolderLegalEntityValidator() RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // Enum Validations - RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out DhStatus result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.OrganisationType).Must(x => string.IsNullOrEmpty(x) || Enum.TryParse(x.Replace("_", string.Empty), true, out OrganisationType result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out DhStatus _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + RuleFor(x => x.OrganisationType).Must(x => string.IsNullOrEmpty(x) || Enum.TryParse(x.Replace("_", string.Empty), true, out OrganisationType _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); // Length Validations RuleFor(x => x.LegalEntityName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); diff --git a/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs index a53627b..c4f8bf0 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs @@ -11,28 +11,28 @@ public class LegalEntityValidator : AbstractValidator { public LegalEntityValidator() { - //check all mandatory fields + // check all mandatory fields RuleFor(x => x.LegalEntityId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.LegalEntityName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.AccreditationNumber).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.AccreditationLevel).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.DataRecipientBrands).Must(x => x!= null && x.Count>0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + RuleFor(x => x.DataRecipientBrands).Must(x => x != null && x.Count > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - //enum validations - RuleFor(x => x.AccreditationLevel).Must(x => Enum.TryParse(x, true, out Repository.Entities.AccreditationLevelType result)).WithErrorCode(ErrorCodes.Cds.InvalidField). + // enum validations + RuleFor(x => x.AccreditationLevel).Must(x => Enum.TryParse(x, true, out Repository.Entities.AccreditationLevelType _)).WithErrorCode(ErrorCodes.Cds.InvalidField). WithMessage(ErrorTitles.InvalidField). WithState(le => $"Value '{le.AccreditationLevel}' is not allowed for AccreditationLevel"); - RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.ParticipationStatusType result)). + RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.ParticipationStatusType _)). WithErrorCode(ErrorCodes.Cds.InvalidField). WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Status}' is not allowed for Status"); - RuleFor(x => x.OrganisationType).Must(x => x == null || Enum.TryParse(x?.Replace("_", ""), true, out Repository.Entities.OrganisationTypes result)). + RuleFor(x => x.OrganisationType).Must(x => x == null || Enum.TryParse(x?.Replace("_", string.Empty), true, out Repository.Entities.OrganisationTypes _)). WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField). WithState(le => $"Value '{le.OrganisationType}' is not allowed for OrganisationType"); - //field lengths - RuleFor(x => x.LegalEntityName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState( le => $"Value '{le.LegalEntityName}' is not allowed for LegalEntityName"); + // field lengths + RuleFor(x => x.LegalEntityName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.LegalEntityName}' is not allowed for LegalEntityName"); RuleFor(x => x.AccreditationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.AccreditationNumber}' is not allowed for AccreditationNumber"); RuleFor(x => x.AccreditationLevel).MaximumLength(13).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.AccreditationLevel}' is not allowed for AccreditationLevel"); RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.LogoUri}' is not allowed for LogoUri"); @@ -42,7 +42,7 @@ public LegalEntityValidator() RuleFor(x => x.Acn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Acn}' is not allowed for Acn"); RuleFor(x => x.Arbn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Arbn}' is not allowed for Arbn"); RuleFor(x => x.AnzsicDivision).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.AnzsicDivision}' is not allowed for AnzsicDivision"); - + RuleForEach(x => x.DataRecipientBrands).SetValidator(new BrandValidator()); RuleFor(x => x.DataRecipientBrands).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); @@ -59,12 +59,13 @@ public LegalEntityValidator() return $"Duplicate DataRecipientBrandId '{legalEntity.DataRecipientBrands.ElementAt(i).DataRecipientBrandId}' is not allowed in the same request"; } } + return null; } private bool HaveUniqueIds(ICollection? brands) { return brands?.Select(b => b.DataRecipientBrandId).Distinct().Count() == brands?.Count; - } + } } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs index ed20f36..cd7c4fe 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs @@ -8,12 +8,12 @@ public class SoftwareProductCertificateValidator : AbstractValidator x.CommonName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.Thumbprint).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.CommonName).MaximumLength(2000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.CommonName}' is not allowed for CommonName"); RuleFor(x => x.Thumbprint).MaximumLength(2000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.Thumbprint}' is not allowed for Thumbprint"); - } + } } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs index e40eb24..239c70d 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs @@ -1,7 +1,6 @@ using CDR.Register.Admin.API.Business.Model; using FluentValidation; using System; -using System.Linq; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -17,12 +16,12 @@ public SoftwareProductValidator() RuleFor(x => x.ClientUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.RecipientBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.RevocationUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.RedirectUris).Must(x => x!= null && x.Length>0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + RuleFor(x => x.RedirectUris).Must(x => x != null && x.Length > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.JwksUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.Certificates).Must(x => x!= null && x.Count>0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + RuleFor(x => x.Certificates).Must(x => x != null && x.Count > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - //lengths + // lengths RuleFor(x => x.SoftwareProductName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SoftwareProductName}' is not allowed for SoftwareProductName"); RuleFor(x => x.SoftwareProductDescription).MaximumLength(4000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SoftwareProductDescription}' is not allowed for SoftwareProductDescription"); RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.LogoUri}' is not allowed for LogoUri"); @@ -37,10 +36,10 @@ public SoftwareProductValidator() RuleFor(x => x.Scope).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Scope}' is not allowed for Scope"); RuleFor(x => x.Status).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Status}' is not allowed for Status"); - //enum - RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.SoftwareProductStatusType result)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Status}' is not allowed for Status"); + // enum + RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out Repository.Entities.SoftwareProductStatusType _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Status}' is not allowed for Status"); RuleForEach(x => x.Certificates).SetValidator(new SoftwareProductCertificateValidator()); - } + } } } diff --git a/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj b/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj index e5327f9..960b0fe 100644 --- a/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj +++ b/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj @@ -32,19 +32,28 @@ - - + + + - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Admin.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.Admin.xml b/Source/CDR.Register.Admin.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.Admin.xml index 8ef1134..104b87a 100644 --- a/Source/CDR.Register.Admin.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.Admin.xml +++ b/Source/CDR.Register.Admin.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.Admin.xml @@ -8,13 +8,13 @@ This controller action provides an implementation of a JWKS for a Mock Data Recipient. - JWKS + JWKS. This controller action produces a client assertion for a mock data recipient. - Client assertion string + Client assertion string. This client assertion can then be used in a private key jwt request. @@ -23,7 +23,14 @@ This controller action produces a self signed JWT for the mock register. - Self Signed JWT + Self Signed JWT. + + + + Configure Serilog logging. + + App configuration. + Set to True if the database is ready and the MSSqlServer sink will be configured. diff --git a/Source/CDR.Register.Admin.API/Controllers/AdminController.cs b/Source/CDR.Register.Admin.API/Controllers/AdminController.cs index de21902..6f5a000 100644 --- a/Source/CDR.Register.Admin.API/Controllers/AdminController.cs +++ b/Source/CDR.Register.Admin.API/Controllers/AdminController.cs @@ -15,7 +15,6 @@ using Newtonsoft.Json.Serialization; using System; using System.IO; -using System.Linq; using System.Text.Json; using System.Threading.Tasks; @@ -46,13 +45,15 @@ public async Task LoadData() { using var reader = new StreamReader(Request.Body); string json = await reader.ReadToEndAsync(); - string respMsg = ""; + string respMsg = string.Empty; try { bool updated = await _dbContext.SeedDatabaseFromJson(json, _logger, true); if (updated) + { Response.StatusCode = StatusCodes.Status200OK; + } else { Response.StatusCode = StatusCodes.Status400BadRequest; @@ -78,7 +79,7 @@ public async Task LoadData() [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetData() { - //We need to override the usual default settings so that the FK Ids can use their int values instead of their string values + // We need to override the usual default settings so that the FK Ids can use their int values instead of their string values var apiSpecificSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, @@ -150,7 +151,7 @@ public async Task SaveDataRecipient() return BadRequest(new Error(Domain.Constants.ErrorTitles.InvalidField, Domain.Constants.ErrorCodes.Cds.InvalidField, "Empty LegalEntity received")); } - var legalEntity = System.Text.Json.JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); //This is inconsistent with other serializers + var legalEntity = System.Text.Json.JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); // This is inconsistent with other serializers var errors = legalEntity?.GetValidationErrors(new LegalEntityValidator()); if (errors?.Errors.Count > 0) diff --git a/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs b/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs index e155ade..6f9c80c 100644 --- a/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs +++ b/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs @@ -15,9 +15,8 @@ namespace CDR.Register.Admin.API.Controllers [ApiController] [Route("[controller]")] [ApiVersionNeutral] - public class LoopbackController : Controller + public class LoopbackController : ControllerBase { - private readonly IConfiguration _config; public LoopbackController(IConfiguration config) @@ -28,7 +27,7 @@ public LoopbackController(IConfiguration config) /// /// This controller action provides an implementation of a JWKS for a Mock Data Recipient. /// - /// JWKS + /// JWKS. [HttpGet] [Route("MockDataRecipientJwks")] [ServiceFilter(typeof(LogActionEntryAttribute))] @@ -36,7 +35,7 @@ public IActionResult MockDataRecipientJwks() { var cert = new X509Certificate2("Certificates/client.pem"); var key = cert.GetRSAPublicKey(); - var rsaParams = key.ExportParameters(false); + var rsaParams = key!.ExportParameters(false); var kid = GenerateKid(rsaParams, out var e, out var n); var jwk = new CDR.Register.API.Infrastructure.Models.JsonWebKey() { @@ -48,7 +47,7 @@ public IActionResult MockDataRecipientJwks() key_ops = new string[] { "sign", "verify" } }; - return Ok(new CDR.Register.API.Infrastructure.Models.JsonWebKeySet() + return new OkObjectResult(new CDR.Register.API.Infrastructure.Models.JsonWebKeySet() { keys = new CDR.Register.API.Infrastructure.Models.JsonWebKey[] { jwk } }); @@ -57,7 +56,7 @@ public IActionResult MockDataRecipientJwks() /// /// This controller action produces a client assertion for a mock data recipient. /// - /// Client assertion string + /// Client assertion string. /// /// This client assertion can then be used in a private key jwt request. /// @@ -67,7 +66,7 @@ public IActionResult MockDataRecipientJwks() public IActionResult MockDataRecipientClientAssertion() { var privateKeyRaw = System.IO.File.ReadAllText("Certificates/client.key"); - var privateKey = privateKeyRaw.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r\n", "").Trim(); + var privateKey = privateKeyRaw.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty).Replace("\r\n", string.Empty).Trim(); var privateKeyBytes = Convert.FromBase64String(privateKey); string audience = _config.GetValue("IdentityServerTokenUri") ?? "https://localhost:7001/idp/connect/token"; @@ -79,7 +78,7 @@ public IActionResult MockDataRecipientClientAssertion() if (Request.Query.TryGetValue("aud", out var aud)) { - audience = aud.ToString(); + audience = aud.ToString(); } using (var rsa = RSA.Create()) @@ -116,14 +115,14 @@ public IActionResult MockDataRecipientClientAssertion() /// /// This controller action produces a self signed JWT for the mock register. /// - /// Self Signed JWT + /// Self Signed JWT. [HttpGet] [Route("RegisterSelfSignedJwt")] [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult RegisterSelfSignedJwt( [FromQuery] string aud) { - var cert = new X509Certificate2(_config.GetValue("SigningCertificate:Path") ?? "", _config.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); + var cert = new X509Certificate2(_config.GetValue("SigningCertificate:Path") ?? string.Empty, _config.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); var descriptor = new SecurityTokenDescriptor @@ -153,14 +152,15 @@ private static string GenerateKid(RSAParameters rsaParams, out string e, out str { e = Base64UrlEncoder.Encode(rsaParams.Exponent); n = Base64UrlEncoder.Encode(rsaParams.Modulus); - var dict = new Dictionary() { - {"e", e}, - {"kty", "RSA"}, - {"n", n} + var dict = new Dictionary() + { + { "e", e }, + { "kty", "RSA" }, + { "n", n } }; var hash = SHA256.Create(); var hashBytes = hash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict))); return Base64UrlEncoder.Encode(hashBytes); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Admin.API/Extensions/ServiceCollectionExtensions.cs b/Source/CDR.Register.Admin.API/Extensions/ServiceCollectionExtensions.cs index acc10e8..f341373 100644 --- a/Source/CDR.Register.Admin.API/Extensions/ServiceCollectionExtensions.cs +++ b/Source/CDR.Register.Admin.API/Extensions/ServiceCollectionExtensions.cs @@ -39,7 +39,7 @@ public static IServiceCollection AddRegisterAdminAuth(this IServiceCollection se services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { - // Sets the authority to Azure AD. + // Sets the authority to Azure AD. options.Authority = issuer; options.TokenValidationParameters = new TokenValidationParameters() { @@ -51,6 +51,7 @@ public static IServiceCollection AddRegisterAdminAuth(this IServiceCollection se // Validates that the intended audience for the access token is the API app. ValidateAudience = true, + // the application id of the API App Registration. ValidAudience = clientId, diff --git a/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs b/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs index 3ed4ea7..3d65691 100644 --- a/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs +++ b/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs @@ -13,34 +13,34 @@ namespace CDR.Register.Admin.API.Filters public class ApiAuthorizeAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) - { - var configuration = (IConfiguration)context.HttpContext.RequestServices + { + var configuration = (IConfiguration?)context.HttpContext.RequestServices .GetService(typeof(IConfiguration)); - + // Ignore if the Authentication is disabled. - var issuer = configuration.GetValue(Constants.Authorization.Issuer); + var issuer = configuration?.GetValue(Constants.Authorization.Issuer); if (string.IsNullOrEmpty(issuer)) { return; } - var user = context.HttpContext.User; - if (user.Identity == null ||!user.Identity.IsAuthenticated) + var user = context.HttpContext.User; + if (user.Identity == null || !user.Identity.IsAuthenticated) { InvalidToken(context); } // Scope, Role validation must have Admin API write access - var scopeAttributeName = configuration.GetValue(Constants.Authorization.ScopeAttributeName); - var scopeValue = configuration.GetValue(Constants.Authorization.ScopeValue); - var allowedScope = user.FindAll(scopeAttributeName) + var scopeAttributeName = configuration?.GetValue(Constants.Authorization.ScopeAttributeName); + var scopeValue = configuration?.GetValue(Constants.Authorization.ScopeValue); + var allowedScope = !string.IsNullOrEmpty(scopeAttributeName) && user.FindAll(scopeAttributeName) .Any(x => string.Equals(x.Value, scopeValue, StringComparison.OrdinalIgnoreCase)); if (!allowedScope) { InvalidToken(context); } - static void InvalidToken (AuthorizationFilterContext context) + static void InvalidToken(AuthorizationFilterContext context) { 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.Register.Admin.API/Program.cs b/Source/CDR.Register.Admin.API/Program.cs index 1182b10..f746670 100644 --- a/Source/CDR.Register.Admin.API/Program.cs +++ b/Source/CDR.Register.Admin.API/Program.cs @@ -1,8 +1,9 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; +using Serilog.Settings.Configuration; using System; using System.IO; @@ -18,16 +19,7 @@ public static int Main(string[] args) .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", true) .AddEnvironmentVariables() .Build(); - - 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); try { @@ -46,6 +38,31 @@ public static int Main(string[] args) } } + /// + /// 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() diff --git a/Source/CDR.Register.Admin.API/Startup.cs b/Source/CDR.Register.Admin.API/Startup.cs index de4e81b..68b4722 100644 --- a/Source/CDR.Register.Admin.API/Startup.cs +++ b/Source/CDR.Register.Admin.API/Startup.cs @@ -1,10 +1,15 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Threading.Tasks; using CDR.Register.Admin.API.Business.Validators; using CDR.Register.Admin.API.Extensions; using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Models; using CDR.Register.API.Infrastructure.Versioning; -using CDR.Register.Repository.Infrastructure; using CDR.Register.Domain.Extensions; +using CDR.Register.Repository.Infrastructure; using FluentValidation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; @@ -18,13 +23,8 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Serilog; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using static CDR.Register.API.Infrastructure.Constants; using Constants = CDR.Register.Admin.API.Common.Constants; -using System; -using System.Net.Mime; namespace CDR.Register.Admin.API { @@ -56,7 +56,7 @@ public void ConfigureServices(IServiceCollection services) services.AddApiVersioning(options => { - options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); //uses default options atm + options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); // uses default options atm options.ErrorResponses = new ApiVersionErrorResponse(); options.ReportApiVersions = true; }); @@ -68,7 +68,7 @@ public void ConfigureServices(IServiceCollection services) services.AddCdrSwaggerGen(opt => { opt.SwaggerTitle = "Consumer Data Right (CDR) Participant Tooling - Mock Register - Admin API"; - opt.IncludeAuthentication = !string.IsNullOrEmpty(issuer); //authentication is included for CTS when the issuer is not empty + opt.IncludeAuthentication = !string.IsNullOrEmpty(issuer); // authentication is included for CTS when the issuer is not empty }); } @@ -85,7 +85,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< ResponseWriter = CustomResponseWriter }); - if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -124,6 +123,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< logger.LogError("ServiceScope cannot be created"); throw new InvalidOperationException("Service scope could not be created."); } + // Run EF database migrations. if (RunMigrations()) { @@ -134,6 +134,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< logger.LogError("Mirgation failed. Unable to get {Name}", nameof(RegisterDatabaseContext)); throw new InvalidOperationException($"Unable to get {nameof(RegisterDatabaseContext)}"); } + context?.Database.Migrate(); healthCheckMigrationMessage = "Migration completed"; @@ -147,6 +148,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< Task.Run(() => context.SeedDatabaseFromJsonFile(seedDataFilePath, logger, seedDataOverwrite)).Wait(); healthCheckSeedDataMessage = "Seeding of data completed"; } + + // Re-configure logger with the DB now. + Program.ConfigureSerilog(Configuration, true); } // If we get here migration (if required) and seeding (if required) has completed diff --git a/Source/CDR.Register.Admin.API/appsettings.Development.json b/Source/CDR.Register.Admin.API/appsettings.Development.json index e44d96f..ee5e62b 100644 --- a/Source/CDR.Register.Admin.API/appsettings.Development.json +++ b/Source/CDR.Register.Admin.API/appsettings.Development.json @@ -14,7 +14,7 @@ "TokenScopeAttribute": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" }, "Serilog": { - "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.MSSqlServer" ], + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "MinimumLevel": "Debug", "WriteTo": [ { @@ -29,63 +29,6 @@ "path": "C:\\CDR\\Logs\\cdr-mr-admin-api.log", "outputTemplate": "{Timestamp:dd/MM/yyyy HH:mm:ss.fff zzz} {Level} [{SourceContext}] {Message}{NewLine}{Exception}" } - }, - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "Register_Logging_DB", - "sinkOptionsSection": { - "tableName": "LogEvents-Admin-API", - "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.Register.Admin.API/appsettings.Release.json b/Source/CDR.Register.Admin.API/appsettings.Release.json index bf3ac07..333591a 100644 --- a/Source/CDR.Register.Admin.API/appsettings.Release.json +++ b/Source/CDR.Register.Admin.API/appsettings.Release.json @@ -14,7 +14,7 @@ "TokenScopeAttribute": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" }, "Serilog": { - "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.MSSqlServer" ], + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File"], "MinimumLevel": "Debug", "WriteTo": [ { @@ -23,63 +23,6 @@ { "Name": "File", "Args": { "path": "/tmp/cdr-mr-admin-api.log" } - }, - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "Register_Logging_DB", - "sinkOptionsSection": { - "tableName": "LogEvents-Admin-API", - "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.Register.Admin.API/appsettings.json b/Source/CDR.Register.Admin.API/appsettings.json index 02136c9..0eb4f75 100644 --- a/Source/CDR.Register.Admin.API/appsettings.json +++ b/Source/CDR.Register.Admin.API/appsettings.json @@ -27,5 +27,67 @@ "SigningCertificate": { "Path": "Certificates/ssa.pfx", "Password": "#M0ckRegister#" + }, + "SerilogMSSqlServerWriteTo": { + "Using": [ "Serilog.Sinks.MSSqlServer" ], + "WriteTo": [ + { + "Name": "MSSqlServer", + "Args": { + "connectionString": "Register_Logging_DB", + "sinkOptionsSection": { + "tableName": "LogEvents-Admin-API", + "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.Register.Discovery.API/Business/IDiscoveryService.cs b/Source/CDR.Register.Discovery.API/Business/IDiscoveryService.cs index 9ccccc9..877b070 100644 --- a/Source/CDR.Register.Discovery.API/Business/IDiscoveryService.cs +++ b/Source/CDR.Register.Discovery.API/Business/IDiscoveryService.cs @@ -8,6 +8,7 @@ namespace CDR.Register.Discovery.API.Business public interface IDiscoveryService { Task GetDataHolderBrandsAsync(Industry industry, DateTime? updatedSince, int page, int pageSize); + Task GetDataRecipientsAsync(Industry industry); } } diff --git a/Source/CDR.Register.Discovery.API/Business/Models/AuthDetailModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/AuthDetailModel.cs index 6ab4634..8504251 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/AuthDetailModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/AuthDetailModel.cs @@ -3,6 +3,7 @@ public class AuthDetailModel { public string RegisterUType { get; set; } + public string JwksEndpoint { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/DataHolderLegalEntityModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/DataHolderLegalEntityModel.cs index 0cd08bf..82a6036 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/DataHolderLegalEntityModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/DataHolderLegalEntityModel.cs @@ -3,17 +3,27 @@ public class DataHolderLegalEntityModel { public string LegalEntityId { get; set; } + public string LegalEntityName { get; set; } + public string LogoUri { get; set; } + public string RegistrationNumber { get; set; } + public string RegistrationDate { get; set; } + public string RegisteredCountry { get; set; } + public string Abn { get; set; } + public string Acn { get; set; } + public string Arbn { get; set; } + public string AnzsicDivision { get; set; } + public string OrganisationType { get; set; } + public string Status { get; set; } } - -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/DataRecipientBrandModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/DataRecipientBrandModel.cs index 85f232d..67b9bbe 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/DataRecipientBrandModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/DataRecipientBrandModel.cs @@ -3,9 +3,13 @@ public class DataRecipientBrandModel { public string DataRecipientBrandId { get; set; } + public string BrandName { get; set; } + public string LogoUri { get; set; } + public SoftwareProductModel[] SoftwareProducts { get; set; } + public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/EndPointDetailModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/EndPointDetailModel.cs index 91f4503..6d4a4c5 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/EndPointDetailModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/EndPointDetailModel.cs @@ -3,11 +3,15 @@ public class EndpointDetailModel { 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; } + public string WebsiteUri { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataHolderBrandModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataHolderBrandModel.cs index f09e727..18db58f 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataHolderBrandModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataHolderBrandModel.cs @@ -6,13 +6,21 @@ namespace CDR.Register.Discovery.API.Business.Models public class RegisterDataHolderBrandModel { public string DataHolderBrandId { get; set; } + public string BrandName { get; set; } + public List Industries { get; set; } + public string LogoUri { get; set; } + public DataHolderLegalEntityModel LegalEntity { get; set; } + public string Status { get; set; } + public EndpointDetailModel EndpointDetail { get; set; } + public AuthDetailModel[] AuthDetails { get; set; } + public DateTime LastUpdated { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataRecipientModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataRecipientModel.cs index f26b585..0e06b97 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataRecipientModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/RegisterDataRecipientModel.cs @@ -5,12 +5,19 @@ namespace CDR.Register.Discovery.API.Business.Models public class RegisterDataRecipientModel { public Guid LegalEntityId { get; set; } + public string LegalEntityName { get; set; } + public string AccreditationNumber { get; set; } + public string AccreditationLevel { get; set; } + public string LogoUri { get; set; } + public DataRecipientBrandModel[] DataRecipientBrands { get; set; } + public string Status { get; set; } + public DateTime? LastUpdated { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Models/SoftwareProductModel.cs b/Source/CDR.Register.Discovery.API/Business/Models/SoftwareProductModel.cs index d99a3eb..ee84449 100644 --- a/Source/CDR.Register.Discovery.API/Business/Models/SoftwareProductModel.cs +++ b/Source/CDR.Register.Discovery.API/Business/Models/SoftwareProductModel.cs @@ -3,9 +3,13 @@ public class SoftwareProductModel { public string SoftwareProductId { get; set; } + public string SoftwareProductName { get; set; } + public string SoftwareProductDescription { get; set; } + public string LogoUri { get; set; } + public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataHolderBrandList.cs b/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataHolderBrandList.cs index 5f5cc80..d6ab71f 100644 --- a/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataHolderBrandList.cs +++ b/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataHolderBrandList.cs @@ -7,7 +7,9 @@ namespace CDR.Register.Discovery.API.Business.Responses public class ResponseRegisterDataHolderBrandList { public IEnumerable Data { get; set; } + public LinksPaginated Links { get; set; } = new LinksPaginated(); + public MetaPaginated Meta { get; set; } = new MetaPaginated(); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataRecipientList.cs b/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataRecipientList.cs index 8bfa8cb..b7c3889 100644 --- a/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataRecipientList.cs +++ b/Source/CDR.Register.Discovery.API/Business/Responses/ResponseRegisterDataRecipientList.cs @@ -8,7 +8,9 @@ namespace CDR.Register.Discovery.API.Business.Responses public class ResponseRegisterDataRecipientList { public IEnumerable Data { get; set; } + public Links Links { get; set; } = new Links(); + public Meta Meta { get; set; } = new Meta(); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj b/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj index a145e78..6000221 100644 --- a/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj +++ b/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj @@ -11,20 +11,29 @@ - - - + + + + - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs b/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs index 4ed7d81..309c243 100644 --- a/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs +++ b/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using System; +using System.Globalization; using System.Net; using System.Threading.Tasks; @@ -19,14 +20,14 @@ namespace CDR.Register.Discovery.API.Controllers [Consumes("application/json")] public class DiscoveryController : ControllerBase { - private readonly IDiscoveryService _discoveryService; - private readonly IDataRecipientStatusCheckService _statusCheckService; - private readonly IConfiguration _configuration; - // Route names. private const string ROUTE_GET_DATA_HOLDER_BRANDS_XV2 = "GetDataHolderBrandsXV2"; private const string ROUTE_GET_DATA_RECIPIENTS_XV3 = "GetDataRecipientsXV3"; + private readonly IDiscoveryService _discoveryService; + private readonly IDataRecipientStatusCheckService _statusCheckService; + private readonly IConfiguration _configuration; + public DiscoveryController( IDiscoveryService discoveryService, IConfiguration configuration, @@ -37,7 +38,6 @@ public DiscoveryController( _statusCheckService = statusCheckService; } - [HttpGet("v1/{industry}/data-holders/brands", Name = ROUTE_GET_DATA_HOLDER_BRANDS_XV2)] [PolicyAuthorize(RegisterAuthorisationPolicy.DataHolderBrandsApiMultiIndustry)] [ApiVersion("2")] @@ -51,7 +51,6 @@ public async Task GetDataHolderBrandsXV2( [FromQuery(Name = "page"), CheckPage] string page, [FromQuery(Name = "page-size"), CheckPageSize] string pageSize) { - // CTS conformance ID validations var basePathExpression = _configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); if (!string.IsNullOrEmpty(basePathExpression)) @@ -71,7 +70,7 @@ public async Task GetDataHolderBrandsXV2( } // Set the default values for the incoming parameters - DateTime? updatedSinceDate = string.IsNullOrEmpty(updatedSince) ? (DateTime?)null : DateTime.Parse(updatedSince); + DateTime? updatedSinceDate = string.IsNullOrEmpty(updatedSince) ? (DateTime?)null : DateTime.Parse(updatedSince, CultureInfo.InvariantCulture); int pageNumber = string.IsNullOrEmpty(page) ? 1 : int.Parse(page); int pageSizeNumber = string.IsNullOrEmpty(pageSize) ? 25 : int.Parse(pageSize); var response = await _discoveryService.GetDataHolderBrandsAsync(industry.ToIndustry(), updatedSinceDate, pageNumber, pageSizeNumber); @@ -83,8 +82,15 @@ public async Task GetDataHolderBrandsXV2( } // Set pagination meta data - response.Links = this.GetPaginated(ROUTE_GET_DATA_HOLDER_BRANDS_XV2, _configuration, - updatedSinceDate, pageNumber, response.Meta.TotalPages.Value, pageSizeNumber, "", true); + response.Links = this.GetPaginated( + ROUTE_GET_DATA_HOLDER_BRANDS_XV2, + _configuration, + updatedSinceDate, + pageNumber, + response.Meta.TotalPages.Value, + pageSizeNumber, + string.Empty, + true); return Ok(response); } @@ -93,12 +99,12 @@ public async Task GetDataHolderBrandsXV2( [ReturnXV("3")] [ApiVersion("3")] [ETag] - [CheckIndustry()] + [CheckIndustry] [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetDataRecipientsXV3(string industry) { var response = await _discoveryService.GetDataRecipientsAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, ""); + response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); return Ok(response); } @@ -132,6 +138,7 @@ private async Task CheckSoftwareProduct() { return softwareProductId; } + return null; } @@ -140,4 +147,4 @@ private async Task CheckStatus(Guid softwareProductId) return await _statusCheckService.ValidateSoftwareProductStatus(softwareProductId); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Discovery.API/Program.cs b/Source/CDR.Register.Discovery.API/Program.cs index fc78034..87fb76a 100644 --- a/Source/CDR.Register.Discovery.API/Program.cs +++ b/Source/CDR.Register.Discovery.API/Program.cs @@ -1,9 +1,8 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; using System; using System.IO; @@ -21,7 +20,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Discovery.API/Startup.cs b/Source/CDR.Register.Discovery.API/Startup.cs index d1482c7..1171c56 100644 --- a/Source/CDR.Register.Discovery.API/Startup.cs +++ b/Source/CDR.Register.Discovery.API/Startup.cs @@ -1,10 +1,11 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.API.Infrastructure.Middleware; using CDR.Register.API.Infrastructure.Models; using CDR.Register.API.Infrastructure.Versioning; using CDR.Register.API.Logger; using CDR.Register.Discovery.API.Extensions; +using CDR.Register.Domain.Extensions; using CDR.Register.Repository.Infrastructure; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -14,7 +15,6 @@ using Microsoft.Extensions.Hosting; using Serilog; using static CDR.Register.API.Infrastructure.Constants; -using CDR.Register.Domain.Extensions; namespace CDR.Register.Discovery.API { @@ -42,7 +42,7 @@ public void ConfigureServices(IServiceCollection services) services.AddApiVersioning(options => { - options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); //uses default options atm + options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); // uses default options atm options.ErrorResponses = new ApiVersionErrorResponse(); }); diff --git a/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj b/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj index 996e56e..51f445f 100644 --- a/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj +++ b/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj @@ -5,10 +5,12 @@ 1.4.0 1.4.0 1.4.0 + true - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -17,6 +19,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs b/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs index a50597a..adc0332 100644 --- a/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs +++ b/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using CDR.Register.Domain.Entities; using Xunit; @@ -8,7 +8,7 @@ namespace CDR.Register.Domain.UnitTests public class DataRecipientTests { /// - /// When the data recipient has no brands, the last updated date should be null + /// When the data recipient has no brands, the last updated date should be null. /// public static IEnumerable GetEmptyDataRecipientBrands() => new List { diff --git a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj index 0ff2078..82241ec 100644 --- a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj +++ b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + true @@ -13,7 +14,16 @@ + + + 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.Register.Domain/Constants.cs b/Source/CDR.Register.Domain/Constants.cs index 67c473e..980f485 100644 --- a/Source/CDR.Register.Domain/Constants.cs +++ b/Source/CDR.Register.Domain/Constants.cs @@ -5,7 +5,7 @@ public static class Constants public static class ErrorCodes { /// - /// The error codes in this class area defined by the CDR program (not CDS) + /// The error codes in this class area defined by the CDR program (not CDS). /// public static class Generic { @@ -24,8 +24,9 @@ public static class Generic public const string InvalidSoftwareStatement = "invalid_software_statement"; public const string UnapprovedSoftwareStatement = "unapproved_software_statement"; } + /// - /// The error codes in this class must match the definition in CDS + /// The error codes in this class must match the definition in CDS. /// public static class Cds { diff --git a/Source/CDR.Register.Domain/Entities/Brand.cs b/Source/CDR.Register.Domain/Entities/Brand.cs index 756e041..0602624 100644 --- a/Source/CDR.Register.Domain/Entities/Brand.cs +++ b/Source/CDR.Register.Domain/Entities/Brand.cs @@ -5,11 +5,17 @@ namespace CDR.Register.Domain.Entities public abstract class Brand { private DateTime lastUpdated; + public Guid BrandId { get; set; } + public string BrandName { get; set; } + public string LogoUri { get; set; } + public string BrandStatus { get; set; } + public bool IsActive { get; set; } + public DateTime LastUpdated { get => DateTime.SpecifyKind(lastUpdated, DateTimeKind.Utc); set => lastUpdated = value; } } } diff --git a/Source/CDR.Register.Domain/Entities/DataHolder.cs b/Source/CDR.Register.Domain/Entities/DataHolder.cs index ed0f034..07aee85 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolder.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolder.cs @@ -7,11 +7,17 @@ namespace CDR.Register.Domain.Entities public class DataHolder { public Guid DataHolderId { get; set; } + public string Status { get; set; } + public bool IsActive { get; set; } + public string Industry { get; set; } + public List Industries { get; set; } + public DataHolderLegalEntity LegalEntity { get; set; } + public IList Brands { get; set; } public DateTime? LastUpdated diff --git a/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs b/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs index 1459e39..a463e8a 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs @@ -3,6 +3,7 @@ public class DataHolderAuthentication { public string RegisterUType { get; set; } + public string JwksEndpoint { get; set; } } diff --git a/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs b/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs index e64945d..9f6f6ff 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs @@ -5,8 +5,9 @@ namespace CDR.Register.Domain.Entities public class DataHolderBrand : Brand { public DataHolder DataHolder { get; set; } + public IList DataHolderAuthentications { get; set; } + public DataHolderBrandServiceEndpoint DataHolderBrandServiceEndpoint { get; set; } } - } \ No newline at end of file diff --git a/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs b/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs index 9042647..20c5cf6 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs @@ -3,10 +3,15 @@ public class DataHolderBrandServiceEndpoint { 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; } } } \ No newline at end of file diff --git a/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs b/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs index 6c21b2f..1bb8a9c 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs @@ -7,16 +7,27 @@ public class DataHolderLegalEntity private DateTime? _registrationDate; public Guid LegalEntityId { get; set; } + public string LegalEntityName { get; set; } + public string LogoUri { get; set; } + public string RegistrationNumber { get; set; } + public DateTime? RegistrationDate { get => _registrationDate?.Date; set => _registrationDate = value; } + public string RegisteredCountry { get; set; } + public string Abn { get; set; } + public string Acn { get; set; } + public string Arbn { get; set; } + public string AnzsicDivision { get; set; } + public string OrganisationType { get; set; } + public string Status { get; set; } } diff --git a/Source/CDR.Register.Domain/Entities/DataHolderStatus.cs b/Source/CDR.Register.Domain/Entities/DataHolderStatus.cs index 230d7f9..20e1b08 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderStatus.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderStatus.cs @@ -5,6 +5,7 @@ namespace CDR.Register.Domain.Entities public class DataHolderStatus { public Guid LegalEntityId { get; set; } + public string Status { get; set; } } } diff --git a/Source/CDR.Register.Domain/Entities/DataRecipient.cs b/Source/CDR.Register.Domain/Entities/DataRecipient.cs index 9755bdc..c3a53bb 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipient.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipient.cs @@ -7,17 +7,23 @@ namespace CDR.Register.Domain.Entities public class DataRecipient { public Guid DataRecipientId { get; set; } + public string Status { get; set; } + public bool IsActive { get; set; } + public string Industry => "banking"; + public DataRecipientLegalEntity LegalEntity { get; set; } + public IList DataRecipientBrands { get; set; } + public DateTime? LastUpdated { get { return this.DataRecipientBrands != null && this.DataRecipientBrands.Any() - ? DateTime.SpecifyKind(this.DataRecipientBrands.OrderByDescending(brand => brand.LastUpdated).First().LastUpdated, DateTimeKind.Utc) + ? DateTime.SpecifyKind(this.DataRecipientBrands.OrderByDescending(brand => brand.LastUpdated).First().LastUpdated, DateTimeKind.Utc) : null; } } diff --git a/Source/CDR.Register.Domain/Entities/DataRecipientBrand.cs b/Source/CDR.Register.Domain/Entities/DataRecipientBrand.cs index 45ef6e6..8e451ba 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipientBrand.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipientBrand.cs @@ -5,6 +5,7 @@ namespace CDR.Register.Domain.Entities public class DataRecipientBrand : Brand { public DataRecipient DataRecipient { get; set; } + public ICollection SoftwareProducts { get; set; } } } diff --git a/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs b/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs index ee754bd..80ff115 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs @@ -5,21 +5,33 @@ namespace CDR.Register.Domain.Entities public class DataRecipientLegalEntity { public Guid LegalEntityId { get; set; } + public string LegalEntityName { get; set; } + public string LogoUri { get; set; } + public string OrganisationType { get; set; } + public string Status { get; set; } + public string AccreditationNumber { get; set; } + public string AccreditationLevelId { get; set; } private DateTime? _registrationDate; public string RegistrationNumber { get; set; } + public DateTime? RegistrationDate { get => _registrationDate?.Date; set => _registrationDate = value; } + public string RegisteredCountry { get; set; } + public string Abn { get; set; } + public string Acn { get; set; } + public string Arbn { get; set; } + public string AnzsicDivision { get; set; } } } \ No newline at end of file diff --git a/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs b/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs index 32a12d0..225b891 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs @@ -5,12 +5,14 @@ namespace CDR.Register.Domain.Entities public class DataRecipientStatus { public Guid DataRecipientId { get; set; } + public string Status { get; set; } } public class DataRecipientStatusV2 { public Guid LegalEntityId { get; set; } + public string Status { get; set; } } } \ No newline at end of file diff --git a/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs b/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs index 1ae67c2..3fa266f 100644 --- a/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs +++ b/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs @@ -6,22 +6,39 @@ namespace CDR.Register.Domain.Entities public class SoftwareProduct { public Guid SoftwareProductId { get; set; } + public string SoftwareProductName { get; set; } + public string SoftwareProductDescription { get; set; } + public string LogoUri { get; set; } + public string SectorIdentifierUri { get; set; } + public string ClientUri { get; set; } + public string TosUri { get; set; } + public string PolicyUri { get; set; } + public string RecipientBaseUri { get; set; } + public string RevocationUri { get; set; } + public string RedirectUri { get; set; } + public IEnumerable RedirectUris => RedirectUri?.Split(" "); + public string JwksUri { get; set; } + public string Scope { get; set; } + public string Status { get; set; } + public bool IsActive { get; set; } + public ICollection Certificates { get; set; } + public DataRecipientBrand DataRecipientBrand { get; set; } } } \ No newline at end of file diff --git a/Source/CDR.Register.Domain/Entities/SoftwareProductInfosec.cs b/Source/CDR.Register.Domain/Entities/SoftwareProductInfosec.cs index ae97699..2138d75 100644 --- a/Source/CDR.Register.Domain/Entities/SoftwareProductInfosec.cs +++ b/Source/CDR.Register.Domain/Entities/SoftwareProductInfosec.cs @@ -5,8 +5,11 @@ namespace CDR.Register.Domain.Entities public class SoftwareProductInfosec { public string Id { get; set; } + public string Name { get; set; } + public string JwksUri { get; set; } + public IEnumerable X509Certificates { get; set; } } } diff --git a/Source/CDR.Register.Domain/Entities/SoftwareProductStatus.cs b/Source/CDR.Register.Domain/Entities/SoftwareProductStatus.cs index 2a54272..cae0ee0 100644 --- a/Source/CDR.Register.Domain/Entities/SoftwareProductStatus.cs +++ b/Source/CDR.Register.Domain/Entities/SoftwareProductStatus.cs @@ -5,6 +5,7 @@ namespace CDR.Register.Domain.Entities public class SoftwareProductStatus { public Guid SoftwareProductId { get; set; } + public string Status { get; set; } } } diff --git a/Source/CDR.Register.Domain/Entities/SoftwareStatementAssertion.cs b/Source/CDR.Register.Domain/Entities/SoftwareStatementAssertion.cs index ed81ced..ecddcab 100644 --- a/Source/CDR.Register.Domain/Entities/SoftwareStatementAssertion.cs +++ b/Source/CDR.Register.Domain/Entities/SoftwareStatementAssertion.cs @@ -3,7 +3,9 @@ public class SoftwareStatementAssertion { public DataRecipientBrand DataRecipientBrand { get; set; } + public SoftwareProduct SoftwareProduct { get; set; } + public DataRecipientLegalEntity LegalEntity { get; set; } } } diff --git a/Source/CDR.Register.Domain/Extensions/MvcBuilderExtensions.cs b/Source/CDR.Register.Domain/Extensions/MvcBuilderExtensions.cs index a33ec1c..2f3be57 100644 --- a/Source/CDR.Register.Domain/Extensions/MvcBuilderExtensions.cs +++ b/Source/CDR.Register.Domain/Extensions/MvcBuilderExtensions.cs @@ -18,8 +18,7 @@ public static IMvcBuilder AddCdrNewtonsoftJson(this IMvcBuilder mvcBuilder) options.SerializerSettings.NullValueHandling = defaultSettings.NullValueHandling; options.SerializerSettings.Formatting = defaultSettings.Formatting; options.SerializerSettings.Converters = defaultSettings.Converters; - } - ); + }); } } } diff --git a/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs b/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs index c8c3ff7..217c553 100644 --- a/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs +++ b/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs @@ -7,7 +7,8 @@ namespace CDR.Register.Domain.Models { public class CdrJsonSerializerSettings : JsonSerializerSettings { - public CdrJsonSerializerSettings() : base() + public CdrJsonSerializerSettings() + : base() { ContractResolver = new CamelCasePropertyNamesContractResolver(); DefaultValueHandling = DefaultValueHandling.Include; diff --git a/Source/CDR.Register.Domain/Models/Error.cs b/Source/CDR.Register.Domain/Models/Error.cs index 67daed7..edf9bbf 100644 --- a/Source/CDR.Register.Domain/Models/Error.cs +++ b/Source/CDR.Register.Domain/Models/Error.cs @@ -25,25 +25,25 @@ public Error(string code, string title, string detail, string metaUrn) } /// - /// Error code + /// Error code. /// [Required] public string Code { get; set; } /// - /// Error title + /// Error title. /// [Required] public string Title { get; set; } /// - /// Error detail + /// Error detail. /// [Required] public string Detail { get; set; } /// - /// Optional additional data for specific error types + /// Optional additional data for specific error types. /// public MetaError Meta { get; set; } } diff --git a/Source/CDR.Register.Domain/Models/ResponseErrorList.cs b/Source/CDR.Register.Domain/Models/ResponseErrorList.cs index 1d33bbb..e9d45b8 100644 --- a/Source/CDR.Register.Domain/Models/ResponseErrorList.cs +++ b/Source/CDR.Register.Domain/Models/ResponseErrorList.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; namespace CDR.Register.Domain.Models { @@ -31,7 +30,7 @@ public ResponseErrorList(string errorCode, string errorTitle, string errorDetail } /// - /// Add unexpected error to the response error list + /// Add unexpected error to the response error list. /// public ResponseErrorList AddUnexpectedError(string message) { @@ -46,7 +45,7 @@ public ResponseErrorList AddUnexpectedError() } /// - /// Add invalid industry error to the response error list + /// Add invalid industry error to the response error list. /// public ResponseErrorList AddInvalidIndustry() { @@ -115,22 +114,22 @@ public static Error InvalidDateTime() return new Error(Constants.ErrorCodes.Cds.InvalidDateTime, Constants.ErrorTitles.InvalidDateTime, "{0} should be valid DateTimeString"); } - public static Error InvalidPageSize() //Should be looked at compared to CDS + public static Error InvalidPageSize() // Should be looked at compared to CDS { return new Error(Constants.ErrorCodes.Cds.InvalidPageSize, Constants.ErrorTitles.InvalidPageSize, "Page size not a positive Integer"); } - public static Error PageSizeTooLarge() //Should be looked at compared to CDS + public static Error PageSizeTooLarge() // Should be looked at compared to CDS { return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page size too large"); } - public static Error InvalidPage() //Should be looked at compared to CDS + public static Error InvalidPage() // Should be looked at compared to CDS { return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page not a positive integer"); } - public static Error PageOutOfRange() //Should be looked at compared to CDS + public static Error PageOutOfRange() // Should be looked at compared to CDS { return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page is out of range"); } diff --git a/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs b/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs index 1b94df2..ca446f7 100644 --- a/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs +++ b/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs @@ -3,7 +3,9 @@ public class BusinessRuleError { public string Code { get; set; } + public string Title { get; set; } + public string Detail { get; set; } public BusinessRuleError(string errorCode, string errorTitle, string errorDetail) diff --git a/Source/CDR.Register.Domain/ValueObjects/Page.cs b/Source/CDR.Register.Domain/ValueObjects/Page.cs index d3123f4..f6c3398 100644 --- a/Source/CDR.Register.Domain/ValueObjects/Page.cs +++ b/Source/CDR.Register.Domain/ValueObjects/Page.cs @@ -3,14 +3,17 @@ namespace CDR.Register.Domain.ValueObjects { - public class Page where T : IEnumerable + public class Page + where T : IEnumerable { public T Data { get; set; } public int CurrentPage { get; set; } + public int PageSize { get; set; } public int TotalRecords { get; set; } + public int TotalPages { get diff --git a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj index c31a050..4dde46a 100644 --- a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj +++ b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj @@ -23,18 +23,27 @@ - - - + + + + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Infosec/Constants.cs b/Source/CDR.Register.Infosec/Constants.cs index 0aa8c29..944ea7c 100644 --- a/Source/CDR.Register.Infosec/Constants.cs +++ b/Source/CDR.Register.Infosec/Constants.cs @@ -1,7 +1,7 @@ namespace CDR.Register.Infosec { public static class Constants - { + { public static class Scopes { public const string RegisterRead = "cdr-register:read"; diff --git a/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml b/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml index da8428a..6ea86c8 100644 --- a/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml +++ b/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml @@ -6,10 +6,10 @@ - + Validate Client Assertion. - client_id (form param) when provided and must match client assertion issuer and subject - clientAssertion + client_id (form param) when provided and must match client assertion issuer and subject. + client Assertion. diff --git a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs index f29abdd..1d82a77 100644 --- a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs +++ b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs @@ -1,4 +1,4 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using CDR.Register.Infosec.Models; using IdentityModel; using Microsoft.AspNetCore.Mvc; @@ -48,7 +48,7 @@ public DiscoveryDocument Get() [Route("openid-configuration/jwks")] public API.Infrastructure.Models.JsonWebKeySet? GetJwks() { - var cert = new X509Certificate2(_configuration.GetValue("SigningCertificate:Path") ?? "", _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); + var cert = new X509Certificate2(_configuration.GetValue("SigningCertificate:Path") ?? string.Empty, _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); var cert64 = Convert.ToBase64String(cert.RawData); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); var thumbprint = Base64Url.Encode(cert.GetCertHash()); @@ -75,11 +75,13 @@ public DiscoveryDocument Get() x5c = [cert64], alg = "PS256" } + ] }; return jwks; } + return null; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Infosec/Controllers/TokenController.cs b/Source/CDR.Register.Infosec/Controllers/TokenController.cs index 5917e3a..c190397 100644 --- a/Source/CDR.Register.Infosec/Controllers/TokenController.cs +++ b/Source/CDR.Register.Infosec/Controllers/TokenController.cs @@ -1,4 +1,4 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using CDR.Register.Domain.Entities; using CDR.Register.Infosec.Interfaces; using CDR.Register.Infosec.Models; @@ -58,7 +58,7 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques return (false, "invalid_request", "Content-Type is not application/x-www-form-urlencoded", null); } - // Basic validation. + // Basic validation. var basicValidationResult = ValidateBasicParameters(clientAssertion); if (!basicValidationResult.isValid) { @@ -66,7 +66,7 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques } // Grant type needs to be client_credentials. - if (clientAssertion.grant_type!= null && !clientAssertion.grant_type.Equals("client_credentials", StringComparison.OrdinalIgnoreCase)) + if (clientAssertion.grant_type != null && !clientAssertion.grant_type.Equals("client_credentials", StringComparison.OrdinalIgnoreCase)) { return (false, "unsupported_grant_type", "grant_type must be client_credentials", null); } @@ -84,7 +84,7 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques return scopeValidationResult; } - // Code changes for client id optional + // Code changes for client id optional // The issuer of the client assertion is the client_id of the calling data recipient. // Need to extract the client_id (iss) from client assertion to load the client details. var tokenValidationResult = ValidateClientAssertionToken(clientAssertion.client_assertion); @@ -94,7 +94,7 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques } // Validate the client assertion. - var clientAssertionResult = await _tokenService.ValidateClientAssertion(clientAssertion.client_id ?? "", clientAssertion.client_assertion ?? ""); + var clientAssertionResult = await _tokenService.ValidateClientAssertion(clientAssertion.client_id ?? string.Empty, clientAssertion.client_assertion ?? string.Empty); if (!clientAssertionResult.isValid) { @@ -165,7 +165,7 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques { var handler = new JwtSecurityTokenHandler(); - if (clientAssertion == null || !handler.CanReadToken(clientAssertion)) + if (clientAssertion == null || !handler.CanReadToken(clientAssertion)) { return (false, ErrorCodes.Generic.InvalidClient, "Invalid client_assertion - token validation error", null); } @@ -179,8 +179,6 @@ public async Task GetAccessToken([FromForm] ClientAssertionReques return (true, null, null, null); } - - private bool IsValidCertificate( SoftwareProductInfosec client) { @@ -196,6 +194,7 @@ private bool IsValidCertificate( // Find a matching cert for the software product client. var certs = client.X509Certificates.ToList(); + // Check if there is a matching cert with the provided common name (validating the thumbprint is not required) var matchingCert = certs.Find(c => c.CommonName.GetCommonName().Equals(httpHeaderCommonName, StringComparison.OrdinalIgnoreCase)); @@ -205,4 +204,4 @@ private bool IsValidCertificate( return matchingCert != null; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Infosec/Models/DiscoveryDocument.cs b/Source/CDR.Register.Infosec/Models/DiscoveryDocument.cs index e84fd07..8a56aa9 100644 --- a/Source/CDR.Register.Infosec/Models/DiscoveryDocument.cs +++ b/Source/CDR.Register.Infosec/Models/DiscoveryDocument.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; namespace CDR.Register.Infosec.Models -{ +{ public class DiscoveryDocument { [JsonProperty("issuer")] @@ -42,6 +42,5 @@ public class DiscoveryDocument [JsonProperty("token_endpoint_auth_signing_alg_values_supported")] public string[]? TokenEndpointAuthSigningAlgValuesSupported { get; set; } - } } diff --git a/Source/CDR.Register.Infosec/Models/ResponseBase.cs b/Source/CDR.Register.Infosec/Models/IResponseBase.cs similarity index 100% rename from Source/CDR.Register.Infosec/Models/ResponseBase.cs rename to Source/CDR.Register.Infosec/Models/IResponseBase.cs diff --git a/Source/CDR.Register.Infosec/Program.cs b/Source/CDR.Register.Infosec/Program.cs index 51c3e6f..b9a4904 100644 --- a/Source/CDR.Register.Infosec/Program.cs +++ b/Source/CDR.Register.Infosec/Program.cs @@ -1,11 +1,5 @@ -using System; -using System.IO; -using CDR.Register.API.Infrastructure; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; +using CDR.Register.API.Infrastructure; using Serilog; -using Serilog.Events; namespace CDR.Register.Infosec { @@ -21,7 +15,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Infosec/Services/ClientService.cs b/Source/CDR.Register.Infosec/Services/ClientService.cs index 2d9d7e6..81a1e71 100644 --- a/Source/CDR.Register.Infosec/Services/ClientService.cs +++ b/Source/CDR.Register.Infosec/Services/ClientService.cs @@ -19,6 +19,7 @@ public ClientService(IRegisterInfosecRepository infosecRepository) { return null; } + if (!Guid.TryParse(clientId, out Guid softwareProductId)) { return null; diff --git a/Source/CDR.Register.Infosec/Services/ITokenService.cs b/Source/CDR.Register.Infosec/Services/ITokenService.cs index 3c438e7..3875519 100644 --- a/Source/CDR.Register.Infosec/Services/ITokenService.cs +++ b/Source/CDR.Register.Infosec/Services/ITokenService.cs @@ -1,11 +1,10 @@ -using System.Threading.Tasks; -using CDR.Register.Domain.Entities; +using CDR.Register.Domain.Entities; using Microsoft.IdentityModel.Tokens; namespace CDR.Register.Infosec.Interfaces { public interface ITokenService - { + { Task<(bool isValid, string? message, SoftwareProductInfosec? client)> ValidateClientAssertion(string client_id, string clientAssertion); Task CreateAccessToken( @@ -17,6 +16,5 @@ Task CreateAccessToken( Task> GetClientKeys(SoftwareProductInfosec client); Task> GetClientJwks(SoftwareProductInfosec client); - } } diff --git a/Source/CDR.Register.Infosec/Services/TokenService.cs b/Source/CDR.Register.Infosec/Services/TokenService.cs index ab30a6f..5e3d9b0 100644 --- a/Source/CDR.Register.Infosec/Services/TokenService.cs +++ b/Source/CDR.Register.Infosec/Services/TokenService.cs @@ -33,10 +33,10 @@ public TokenService( } /// - /// + /// Validate Client Assertion. /// - /// client_id (form param) when provided and must match client assertion issuer and subject - /// clientAssertion + /// client_id (form param) when provided and must match client assertion issuer and subject. + /// client Assertion. /// public async Task<(bool isValid, string? message, SoftwareProductInfosec? client)> ValidateClientAssertion(string? client_id, string clientAssertion) { @@ -46,7 +46,7 @@ public TokenService( { var handler = new JwtSecurityTokenHandler(); - // Validate the client assertion token. + // Validate the client assertion token. var invalidToken = handler.ReadJwtToken(clientAssertion); var clientId = invalidToken.Issuer; if (string.IsNullOrEmpty(clientId)) @@ -57,8 +57,7 @@ public TokenService( // client_id (form param) when provided and must match client assertion issuer and subject if (!string.IsNullOrEmpty(client_id) && (!client_id.Equals(invalidToken.Issuer, StringComparison.OrdinalIgnoreCase) || - !client_id.Equals(invalidToken.Subject, StringComparison.OrdinalIgnoreCase) - )) + !client_id.Equals(invalidToken.Subject, StringComparison.OrdinalIgnoreCase))) { return (false, "Invalid client_assertion - 'sub' and 'iss' must be set to the client_id", null); } @@ -122,15 +121,14 @@ private async Task BuildTokenValidationParameters( return new TokenValidationParameters { ValidateIssuer = false, - IssuerSigningKeys = (await GetClientKeys(client)), + IssuerSigningKeys = await GetClientKeys(client), ValidateIssuerSigningKey = true, ValidAudiences = validAudiences, ValidateAudience = true, AudienceValidator = (IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) => { - - bool isValid = audiences.Any(audience => validationParameters.ValidAudiences.Contains(audience, StringComparer.OrdinalIgnoreCase)); + bool isValid = audiences.Any(audience => validationParameters.ValidAudiences.Contains(audience, StringComparer.OrdinalIgnoreCase)); if (!isValid) { @@ -170,12 +168,12 @@ public async Task CreateAccessToken( string scope, string cnf) { - var cert = await Task.Run(() => new X509Certificate2(_configuration.GetValue("SigningCertificate:Path") ?? "", _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable)); + var cert = await Task.Run(() => new X509Certificate2(_configuration.GetValue("SigningCertificate:Path") ?? string.Empty, _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable)); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); var issuer = _configuration.GetInfosecBaseUrl(_httpContextAccessor.HttpContext); - List claims = [new Claim("client_id", client.Id), - new Claim("jti", Guid.NewGuid().ToString()), + List claims = [new Claim("client_id", client.Id), + new Claim("jti", Guid.NewGuid().ToString()), new Claim("scope", scope)]; claims.Add(new Claim( @@ -228,7 +226,7 @@ public async Task> GetClientJwks(SoftwareProductInfosec client handler.SetServerCertificateValidation(_configuration); var httpClient = new HttpClient(handler); - var passUserAgent = _configuration.GetValue("PassUserAgent"); //allows CTS to attach a header for request filtering + var passUserAgent = _configuration.GetValue("PassUserAgent"); // allows CTS to attach a header for request filtering if (passUserAgent) { httpClient.DefaultRequestHeaders.Add("User-Agent", "mock-register"); diff --git a/Source/CDR.Register.Infosec/Startup.cs b/Source/CDR.Register.Infosec/Startup.cs index c47017e..f12ba5a 100644 --- a/Source/CDR.Register.Infosec/Startup.cs +++ b/Source/CDR.Register.Infosec/Startup.cs @@ -58,7 +58,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDataProtection() .SetApplicationName("reg-infosec") .PersistKeysToStackExchangeRedis( - StackExchange.Redis.ConnectionMultiplexer.Connect(Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache) ?? ""), + StackExchange.Redis.ConnectionMultiplexer.Connect(Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache) ?? string.Empty), "register-cache-dp-keys"); } else diff --git a/Source/CDR.Register.IntegrationTests/API/Discovery/US27560_GetDataRecipients_MultiIndustry_Tests.cs b/Source/CDR.Register.IntegrationTests/API/Discovery/US27560_GetDataRecipients_MultiIndustry_Tests.cs index 811a94b..a229f63 100644 --- a/Source/CDR.Register.IntegrationTests/API/Discovery/US27560_GetDataRecipients_MultiIndustry_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/API/Discovery/US27560_GetDataRecipients_MultiIndustry_Tests.cs @@ -22,7 +22,10 @@ namespace CDR.Register.IntegrationTests.API.Discovery /// public class US27560_GetDataRecipients_MultiIndustry_Tests : BaseTest { - public US27560_GetDataRecipients_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US27560_GetDataRecipients_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } // Get expected data recipients private static string GetExpectedDataRecipients(string url) @@ -70,8 +73,10 @@ private static string GetExpectedDataRecipients(string url) lastUpdated = participation.Brands.OrderByDescending(brand => brand.LastUpdated).First().LastUpdated.ToString("yyyy-MM-ddTHH:mm:ssZ") }) .ToList(), + // DF: these are new properties that need to be included in the Get Data Recipients payload. - links = new { + links = new + { self = url }, meta = new object() @@ -92,18 +97,18 @@ private static string GetExpectedDataRecipients(string url) [InlineData(3, "banking")] [InlineData(3, "energy")] [InlineData(3, "telco")] - public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int XV, string industry) + public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int xv, string industry) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/{industry}/data-recipients"; var expectedDataRecipients = GetExpectedDataRecipients(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV.ToString() + XV = xv.ToString() }.SendAsync(); // Assert @@ -116,7 +121,7 @@ public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int XV, Assert_HasContentType_ApplicationJson(response.Content); // // Assert - Check XV - Assert_HasHeader(XV.ToString(), response.Headers, "x-v"); + Assert_HasHeader(xv.ToString(), response.Headers, "x-v"); // Assert - Check json await Assert_HasContent_Json(expectedDataRecipients, response.Content); @@ -129,18 +134,18 @@ public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int XV, [InlineData(3, "banking")] [InlineData(3, "energy")] [InlineData(3, "telco")] - public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int XV, string industry) + public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int xv, string industry) { - // Arrange + // Arrange var url = $"{GenerateDynamicCtsUrl(DISCOVERY_DOWNSTREAM_BASE_URL)}/cdr-register/v1/{industry}/data-recipients"; var expectedDataRecipients = GetExpectedDataRecipients(ReplacePublicHostName(url, DISCOVERY_DOWNSTREAM_BASE_URL)); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV.ToString() + XV = xv.ToString() }.SendAsync(); // Assert @@ -153,7 +158,7 @@ public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_DataRecipients Assert_HasContentType_ApplicationJson(response.Content); // // Assert - Check XV - Assert_HasHeader(XV.ToString(), response.Headers, "x-v"); + Assert_HasHeader(xv.ToString(), response.Headers, "x-v"); // Assert - Check json await Assert_HasContent_Json(expectedDataRecipients, response.Content); @@ -165,12 +170,12 @@ public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_DataRecipients [InlineData("foo")] // AC06 public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(string? ifNoneMatch) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients"; var expectedDataRecipients = GetExpectedDataRecipients(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, @@ -202,16 +207,16 @@ public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(str public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModified_ETag() { // Arrange - Get SoftwareProductsStatus and save the ETag - var expectedETag = (await new Infrastructure.API + var expectedETag = (await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients", XV = "3", - IfNoneMatch = null, // ie If-None-Match is not set + IfNoneMatch = null, // ie If-None-Match is not set }.SendAsync()).Headers.GetValues("ETag").First().Trim('"'); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients", @@ -231,29 +236,28 @@ public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModi } [Theory] - [InlineData("3", "4", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-min-v is ignored when > x-v - [InlineData("3", "2", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is supported and higher than x-min-v - [InlineData("3", "3", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is supported equal to x-min-v - [InlineData("4", "3", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is NOT supported and x-min-v is supported - [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is supported but x-min-v is invalid (not a positive integer) - [InlineData("99", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) - [InlineData("4", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //invalid. x-v is not supported and x-min-v invalid - [InlineData("4", "4", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. Both x-v and x-min-v exceed supported version of 3 - [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("2", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("foo", "3", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is invalid with valid x-min-v - [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (negative integer) is invalid with missing x-min-v - [InlineData("4", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is higher than supported version of 3 - [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is an empty string - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is missing + [InlineData("3", "4", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-min-v is ignored when > x-v + [InlineData("3", "2", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is supported and higher than x-min-v + [InlineData("3", "3", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is supported equal to x-min-v + [InlineData("4", "3", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is NOT supported and x-min-v is supported + [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is supported but x-min-v is invalid (not a positive integer) + [InlineData("99", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) + [InlineData("4", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // invalid. x-v is not supported and x-min-v invalid + [InlineData("4", "4", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. Both x-v and x-min-v exceed supported version of 3 + [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("2", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("foo", "3", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is invalid with valid x-min-v + [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (negative integer) is invalid with missing x-min-v + [InlineData("4", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is higher than supported version of 3 + [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is an empty string + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is missing public async Task ACX01_VersionHeaderValidation(string? xv, string? minXv, string expectedXv, HttpStatusCode expectedHttpStatusCode, bool isExpectedToBeSupported, string expecetdError) { - // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients", @@ -280,7 +284,6 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? minXv, strin // Assert - Check error response await Assert_HasContent_Json(expecetdError, response.Content); } - } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Discovery/US27562_GetDataHolderBrands_MultiIndustry_Tests.cs b/Source/CDR.Register.IntegrationTests/API/Discovery/US27562_GetDataHolderBrands_MultiIndustry_Tests.cs index f787278..0fee2aa 100644 --- a/Source/CDR.Register.IntegrationTests/API/Discovery/US27562_GetDataHolderBrands_MultiIndustry_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/API/Discovery/US27562_GetDataHolderBrands_MultiIndustry_Tests.cs @@ -1,6 +1,4 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.IntegrationTests.Infrastructure; -using CDR.Register.IntegrationTests.Models; +using CDR.Register.IntegrationTests.Infrastructure; using CDR.Register.Repository.Entities; using CDR.Register.Repository.Infrastructure; using FluentAssertions; @@ -8,8 +6,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; using System; using System.Linq; using System.Net; @@ -27,9 +23,14 @@ namespace CDR.Register.IntegrationTests.API.Discovery /// public class US27562_GetDataHolderBrands_MultiIndustry_Tests : BaseTest { - public US27562_GetDataHolderBrands_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US27562_GetDataHolderBrands_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + // Participation/Brand/SoftwareProduct Ids - private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup + private const string BRANDID = "20C0864B-CEEF-4DE0-8944-EB0962F825EB"; private const string SOFTWAREPRODUCTID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; @@ -78,8 +79,7 @@ static string Link(string baseUrl, DateTime? updatedSince, int? page = null, int .Include(brand => brand.Participation.Industry) .Where(brand => brand.Participation.ParticipationTypeId == ParticipationTypes.Dh && - (industry == null || (industry != null && brand.Participation.Industry.IndustryTypeCode == industry)) - ) + (industry == null || (industry != null && brand.Participation.Industry.IndustryTypeCode == industry))) .Where(brand => brand.Participation.StatusId == ParticipationStatusType.Active) .Where(brand => brand.BrandStatusId == BrandStatusType.Active) .Where(brand => updatedSince == null || brand.LastUpdated > updatedSince); @@ -91,6 +91,7 @@ static string Link(string baseUrl, DateTime? updatedSince, int? page = null, int { throw new Exception($"Page {page} out of range. Min Page is {MINPAGE}"); } + var maxPage = ((totalRecords - 1) / pageSize) + 1; if (page > maxPage) { @@ -191,6 +192,7 @@ static string GetUrl(string baseUrl, DateTime? updatedSince, int? queryPage, int { query.Add("page", queryPage.Value); } + if (queryPageSize != null) { query.Add("page-size", queryPageSize.Value); @@ -213,7 +215,7 @@ static string GetUrl(string baseUrl, DateTime? updatedSince, int? queryPage, int CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -247,7 +249,7 @@ static string GetUrl(string baseUrl, DateTime? updatedSince, int? queryPage, int } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] [InlineData("energy")] [InlineData("telco")] @@ -264,7 +266,6 @@ public async Task AC01_Get_WithNoQueryString_ShouldRespondWith_200OK_First25Reco [InlineData("telco")] public async Task AC01_CTS_URL_Get_WithNoQueryString_ShouldRespondWith_200OK_First25RecordsAsync(string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { - // Arrange string conformanceId = Guid.NewGuid().ToString(); string tokenEndpoint = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; @@ -285,7 +286,7 @@ public async Task AC01_CTS_URL_Get_WithNoQueryString_ShouldRespondWith_200OK_Fir CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME }.GetAsync(addCertificateToRequest: false); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -321,7 +322,7 @@ public async Task AC01_CTS_URL_Get_WithNoQueryString_ShouldRespondWith_200OK_Fir } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] public async Task AC02_Get_WithPageSize5_ShouldRespondWith_200OK_Page1Of5Records(string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { @@ -329,7 +330,7 @@ public async Task AC02_Get_WithPageSize5_ShouldRespondWith_200OK_Page1Of5Records } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] public async Task AC03_Get_WithPageSize5_AndPage3_ShouldRespondWith_200OK_Page3Of5Records(string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { @@ -337,7 +338,7 @@ public async Task AC03_Get_WithPageSize5_AndPage3_ShouldRespondWith_200OK_Page3O } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] public async Task AC04_Get_WithPageSize5_AndPage6_ShouldRespondWith_200OK_Page6Of5Records(string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { @@ -345,7 +346,7 @@ public async Task AC04_Get_WithPageSize5_AndPage6_ShouldRespondWith_200OK_Page6O } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] public async Task AC05_Get_WithUpdatedSince01042021_ShouldRespondWith_200OK_2Records(string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { @@ -353,7 +354,7 @@ public async Task AC05_Get_WithUpdatedSince01042021_ShouldRespondWith_200OK_2Rec } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] [InlineData("energy")] [InlineData("telco")] @@ -383,7 +384,7 @@ public async Task AC07_Get_WithUpdatedSinceInvalidDate_ShouldRespondWith_400BadR CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -405,7 +406,7 @@ public async Task AC07_Get_WithUpdatedSinceInvalidDate_ShouldRespondWith_400BadR // Assert - Check error response if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) { - // Assert - Check content type + // Assert - Check content type Assert_HasContentType_ApplicationJson(response.Content); // Assert - Check error response @@ -426,7 +427,7 @@ public async Task AC07_Get_WithUpdatedSinceInvalidDate_ShouldRespondWith_400BadR private static async Task Test_AC09_AC10(string? accessToken, string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.Unauthorized) { - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -478,7 +479,7 @@ public async Task AC11_Get_WithExpiredAccessToken_ShouldRespondWith_401Unauthori // Expired at "Tuesday, May 18, 2021 11:33:45 PM GMT+10:00" var accessToken = "eyJhbGciOiJQUzI1NiIsImtpZCI6IkFBMjRGMTg1RUUzRjY3NTA0ODA4RkM0RTI2QjEzNUI5OUU2M0JEQTkiLCJ0eXAiOiJhdCtqd3QiLCJ4NXQiOiJxaVR4aGU0X1oxQklDUHhPSnJFMXVaNWp2YWsifQ.eyJuYmYiOjE2MjEzNDQ1MjUsImV4cCI6MTYyMTM0NDgyNSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzAwMC9pZHAiLCJhdWQiOiJjZHItcmVnaXN0ZXIiLCJjbGllbnRfaWQiOiI2ZjdhMWI4ZS04Nzk5LTQ4YTgtOTAxMS1lMzkyMDM5MWY3MTMiLCJqdGkiOiJDODRBNTM5MTA2QjI4NUJBODI2RjZGMDQ3MjU4RjBBNCIsImlhdCI6MTYyMTM0NDUyNSwic2NvcGUiOlsiY2RyLXJlZ2lzdGVyOmJhbms6cmVhZCJdLCJjbmYiOnsieDV0I1MyNTYiOiI1OEQ3NkY3QTYxQ0Q3MjZEQTFDNTRGNjg5OEU4RTY5RUE0Qzg4MDYwIn19.RTU-zrqkb-WXcJzCz62SJ4h19lj8MDyGcvLOmg0qx05WFbAsY4mEP3gsoqM1LJfq4ncw7RqSvbkCNQQ-NOnyoBHF8MGe7mzdUh3YrD0_lTg20Dkx1-l044svtP_CKTI3rXT3bZaYWce0Tb1s3mrJzfN3ja23o93FGR-wbIwHp2347b0DxjznpKBw5meLhAjS7OCx6_uMm1la6IziSQgqMd2WaA-od7w8J5br-Nn-QZZi7X1KGiPEKFDFNk8KrUdPc4NCH6t7f-Sbc34KNNEWfAOJkWdDrmsBaifSlWvSlS4nUnurGHYkmimA2JUuv3ZTqzCcLRamEER1ZoTcIs_PDw"; - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -500,7 +501,7 @@ public async Task AC11_Get_WithExpiredAccessToken_ShouldRespondWith_401Unauthori } [Theory] - [InlineData(null, HttpStatusCode.NotFound)] + [InlineData(null, HttpStatusCode.NotFound)] [InlineData("banking")] [InlineData("energy")] [InlineData("telco")] @@ -513,7 +514,7 @@ public async Task AC12_Get_WithDifferentHolderOfKey_ShouldRespondWith_401Unautho CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = ADDITIONAL_CERTIFICATE_FILENAME, // ie different holder of key CertificatePassword = ADDITIONAL_CERTIFICATE_PASSWORD, @@ -543,7 +544,7 @@ private static async Task Test_AC13_AC14_AC15_AC16(string queryString, HttpStatu CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -565,10 +566,10 @@ private static async Task Test_AC13_AC14_AC15_AC16(string queryString, HttpStatu // Assert - Check error response if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) { - // Assert - Check content type + // Assert - Check content type Assert_HasContentType_ApplicationJson(response.Content); - // Assert - Check error response + // Assert - Check error response await Assert_HasContent_Json(expectedContent, response.Content); } } @@ -597,7 +598,10 @@ private static async Task Test_AC13_AC14_AC15_AC16(string queryString, HttpStatu [InlineData("foo", HttpStatusCode.BadRequest, "telco")] public async Task AC13_Get_WithInvalidPageSize_ShouldRespondWith_400BadRequest_PageSizeMustBePositiveInteger(string pageSize, HttpStatusCode expectedStatusCode, string? industry) { - await Test_AC13_AC14_AC15_AC16($"page-size={pageSize}", expectedStatusCode, @" + await Test_AC13_AC14_AC15_AC16( + $"page-size={pageSize}", + expectedStatusCode, + @" { ""errors"": [ { @@ -633,7 +637,10 @@ await Test_AC13_AC14_AC15_AC16($"page-size={pageSize}", expectedStatusCode, @" [InlineData("foo", HttpStatusCode.BadRequest, "telco")] public async Task AC14_Get_WithInvalidPage_ShouldRespondWith_400BadRequest_PageMustBePositiveInteger(string page, HttpStatusCode expectedStatusCode, string? industry) { - await Test_AC13_AC14_AC15_AC16($"page={page}", expectedStatusCode, @" + await Test_AC13_AC14_AC15_AC16( + $"page={page}", + expectedStatusCode, + @" { ""errors"": [ { @@ -653,7 +660,10 @@ await Test_AC13_AC14_AC15_AC16($"page={page}", expectedStatusCode, @" [InlineData("3", "telco")] public async Task AC15_Get_WithPageOutOfRange_ShouldRespondWith_400BadRequest_PageExceedsMaxNumberOfPages(string page, string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.BadRequest) { - await Test_AC13_AC14_AC15_AC16($"page={page}", expectedStatusCode, @" + await Test_AC13_AC14_AC15_AC16( + $"page={page}", + expectedStatusCode, + @" { ""errors"": [ { @@ -673,7 +683,10 @@ await Test_AC13_AC14_AC15_AC16($"page={page}", expectedStatusCode, @" [InlineData("1001", "telco")] public async Task AC16_Get_WithPageSizeTooLarge_ShouldRespondWith_400BadRequest_PageSizeTooLarge(string pageSize, string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.BadRequest) { - await Test_AC13_AC14_AC15_AC16($"page-size={pageSize}", expectedStatusCode, @" + await Test_AC13_AC14_AC15_AC16( + $"page-size={pageSize}", + expectedStatusCode, + @" { ""errors"": [ { @@ -686,9 +699,12 @@ await Test_AC13_AC14_AC15_AC16($"page-size={pageSize}", expectedStatusCode, @" industry); } - delegate void BeforeTestAC181920(); - delegate void AfterTestAC181920Request(); - private static async Task Test_AC17_AC18_AC19(HttpStatusCode expectedStatusCode, + private delegate void BeforeTestAC181920(); + + private delegate void AfterTestAC181920Request(); + + private static async Task Test_AC17_AC18_AC19( + HttpStatusCode expectedStatusCode, BeforeTestAC181920? beforeRequest, AfterTestAC181920Request? afterRequest, string? industry) @@ -699,7 +715,7 @@ private static async Task Test_AC17_AC18_AC19(HttpStatusCode expectedStatusCode, CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -724,10 +740,10 @@ private static async Task Test_AC17_AC18_AC19(HttpStatusCode expectedStatusCode, // Assert - Check error response if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) { - // Assert - Check content type + // Assert - Check content type Assert_HasContentType_ApplicationJson(response.Content); - // Assert - Check error response + // Assert - Check error response var expectedContent = @" { ""errors"": [ @@ -749,30 +765,30 @@ private static async Task Test_AC17_AC18_AC19(HttpStatusCode expectedStatusCode, } [Theory] - [InlineData(1, HttpStatusCode.NotFound, null)] // Active - [InlineData(2, HttpStatusCode.NotFound, null)] // Removed - [InlineData(3, HttpStatusCode.NotFound, null)] // Suspended - [InlineData(4, HttpStatusCode.NotFound, null)] // Revoked + [InlineData(1, HttpStatusCode.NotFound, null)] // Active + [InlineData(2, HttpStatusCode.NotFound, null)] // Removed + [InlineData(3, HttpStatusCode.NotFound, null)] // Suspended + [InlineData(4, HttpStatusCode.NotFound, null)] // Revoked [InlineData(5, HttpStatusCode.NotFound, null)] // Surrendered - [InlineData(6, HttpStatusCode.NotFound, null)] // Inactive - [InlineData(1, HttpStatusCode.OK, "banking")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Removed - [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Suspended - [InlineData(4, HttpStatusCode.Forbidden, "banking")] // Revoked + [InlineData(6, HttpStatusCode.NotFound, null)] // Inactive + [InlineData(1, HttpStatusCode.OK, "banking")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Removed + [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Suspended + [InlineData(4, HttpStatusCode.Forbidden, "banking")] // Revoked [InlineData(5, HttpStatusCode.Forbidden, "banking")] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden, "banking")] // Inactive - [InlineData(1, HttpStatusCode.OK, "energy")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Removed - [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Suspended - [InlineData(4, HttpStatusCode.Forbidden, "energy")] // Revoked + [InlineData(6, HttpStatusCode.Forbidden, "banking")] // Inactive + [InlineData(1, HttpStatusCode.OK, "energy")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Removed + [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Suspended + [InlineData(4, HttpStatusCode.Forbidden, "energy")] // Revoked [InlineData(5, HttpStatusCode.Forbidden, "energy")] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden, "energy")] // Inactive - [InlineData(1, HttpStatusCode.OK, "telco")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Removed - [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Suspended - [InlineData(4, HttpStatusCode.Forbidden, "telco")] // Revoked + [InlineData(6, HttpStatusCode.Forbidden, "energy")] // Inactive + [InlineData(1, HttpStatusCode.OK, "telco")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Removed + [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Suspended + [InlineData(4, HttpStatusCode.Forbidden, "telco")] // Revoked [InlineData(5, HttpStatusCode.Forbidden, "telco")] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden, "telco")] // Inactive + [InlineData(6, HttpStatusCode.Forbidden, "telco")] // Inactive public async Task ACX17_Get_WithDataRecipientNotActive_ShouldRespondWith_403Forbidden( int participationStatusId, HttpStatusCode expectedStatusCode, @@ -788,18 +804,18 @@ await Test_AC17_AC18_AC19( } [Theory] - [InlineData(1, HttpStatusCode.NotFound, null)] // Active - [InlineData(2, HttpStatusCode.NotFound, null)] // Inactive - [InlineData(3, HttpStatusCode.NotFound, null)] // Removed - [InlineData(1, HttpStatusCode.OK, "banking")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Removed - [InlineData(1, HttpStatusCode.OK, "energy")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Removed - [InlineData(1, HttpStatusCode.OK, "telco")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Removed + [InlineData(1, HttpStatusCode.NotFound, null)] // Active + [InlineData(2, HttpStatusCode.NotFound, null)] // Inactive + [InlineData(3, HttpStatusCode.NotFound, null)] // Removed + [InlineData(1, HttpStatusCode.OK, "banking")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Removed + [InlineData(1, HttpStatusCode.OK, "energy")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Removed + [InlineData(1, HttpStatusCode.OK, "telco")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Removed public async Task ACX18_Get_WithDataRecipientBrandNotActive_ShouldRespondWith_403Forbidden( int brandStatusId, HttpStatusCode expectedStatusCode, @@ -811,23 +827,22 @@ await Test_AC17_AC18_AC19( expectedStatusCode, beforeRequest: () => SetBrandStatusId(BRANDID, brandStatusId), afterRequest: () => SetBrandStatusId(BRANDID, saveBrandStatusId), - industry: industry - ); + industry: industry); } [Theory] - [InlineData(1, HttpStatusCode.NotFound, null)] // Active - [InlineData(2, HttpStatusCode.NotFound, null)] // Inactive - [InlineData(3, HttpStatusCode.NotFound, null)] // Removed - [InlineData(1, HttpStatusCode.OK, "banking")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Removed - [InlineData(1, HttpStatusCode.OK, "energy")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Removed - [InlineData(1, HttpStatusCode.OK, "telco")] // Active - [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Inactive - [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Removed + [InlineData(1, HttpStatusCode.NotFound, null)] // Active + [InlineData(2, HttpStatusCode.NotFound, null)] // Inactive + [InlineData(3, HttpStatusCode.NotFound, null)] // Removed + [InlineData(1, HttpStatusCode.OK, "banking")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "banking")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "banking")] // Removed + [InlineData(1, HttpStatusCode.OK, "energy")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "energy")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "energy")] // Removed + [InlineData(1, HttpStatusCode.OK, "telco")] // Active + [InlineData(2, HttpStatusCode.Forbidden, "telco")] // Inactive + [InlineData(3, HttpStatusCode.Forbidden, "telco")] // Removed public async Task ACX19_Get_WithDataRecipientSoftwareProductNotActive_ShouldRespondWith_403Forbidden( int softwareProductStatusId, HttpStatusCode expectedStatusCode, @@ -839,8 +854,7 @@ await Test_AC17_AC18_AC19( expectedStatusCode, beforeRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, softwareProductStatusId), afterRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, saveSoftwareProductStatusId), - industry: industry - ); + industry: industry); } [Theory] @@ -854,7 +868,7 @@ public async Task ACX20_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotMod }.GetAsync(); // Arrange - Get brands and save the ETag - var expectedETag = (await new Infrastructure.API + var expectedETag = (await new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -862,11 +876,11 @@ public async Task ACX20_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotMod URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", AccessToken = accessToken, XV = "2", - IfNoneMatch = null, // ie If-None-Match is not set + IfNoneMatch = null, // ie If-None-Match is not set }.SendAsync()).Headers.GetValues("ETag").First().Trim('"'); // Act - Use Etag - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -899,7 +913,7 @@ public async Task ACX01_Get_WithInvalidIndustry_ShouldRespondWith_400BadRequest( CertificatePassword = CERTIFICATE_PASSWORD }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -921,10 +935,10 @@ public async Task ACX01_Get_WithInvalidIndustry_ShouldRespondWith_400BadRequest( // Assert - Check error response if (response.StatusCode != HttpStatusCode.OK) { - // Assert - Check content type + // Assert - Check content type Assert_HasContentType_ApplicationJson(response.Content); - // Assert - Check error response + // Assert - Check error response var expectedContent = @" { ""errors"": [ @@ -955,7 +969,7 @@ public async Task ACX02_Get_WithScope_ShouldRespondWith_200OK(string? industry, Scope = scope }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -977,36 +991,36 @@ public async Task ACX02_Get_WithScope_ShouldRespondWith_200OK(string? industry, } [Theory] - [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-min-v is ignored when > x-v - [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported and higher than x-min-v - [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported equal to x-min-v - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z - [InlineData("2", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is supported but x-min-v (not a positive integer) - [InlineData("99", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v (not a positive integer) - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Unsupported. x-v is not supported and x-min-v invalid - [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. Both x-v and x-min-v exceed supported version of 2 - [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is invalid with valid x-min-v - [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (negative integer) is invalid with missing x-min-v - [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is higher than supported version of 2 - [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is an empty string - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is missing - //Also check industry specific calls - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "banking")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "energy")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "telco")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "banking")] //Unsupported. x-v is not supported and x-min-v invalid - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "energy")] //Unsupported. x-v is not supported and x-min-v invalid - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "telco")] //Unsupported. x-v is not supported and x-min-v invalid - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "banking")] //Invalid. x-v header is missing - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "energy")] //Invalid. x-v header is missing - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "telco")] //Invalid. x-v header is missing + [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-min-v is ignored when > x-v + [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported and higher than x-min-v + [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported equal to x-min-v + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z + [InlineData("2", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is supported but x-min-v (not a positive integer) + [InlineData("99", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v (not a positive integer) + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Unsupported. x-v is not supported and x-min-v invalid + [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. Both x-v and x-min-v exceed supported version of 2 + [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is invalid with valid x-min-v + [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (negative integer) is invalid with missing x-min-v + [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is higher than supported version of 2 + [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is an empty string + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is missing + + // Also check industry specific calls + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "banking")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "energy")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "", "telco")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "banking")] // Unsupported. x-v is not supported and x-min-v invalid + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "energy")] // Unsupported. x-v is not supported and x-min-v invalid + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR, "telco")] // Unsupported. x-v is not supported and x-min-v invalid + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "banking")] // Invalid. x-v header is missing + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "energy")] // Invalid. x-v header is missing + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR, "telco")] // Invalid. x-v header is missing public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string expectedXv, HttpStatusCode expectedHttpStatusCode, bool isExpectedToBeSupported, string expecetdError, string industry = "all") { - // Arrange var accessToken = await new Infrastructure.AccessToken { @@ -1014,7 +1028,7 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -1047,7 +1061,6 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string // Assert - Check error response await Assert_HasContent_Json(expecetdError, response.Content); } - } } } diff --git a/Source/CDR.Register.IntegrationTests/API/SSA/US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests.cs b/Source/CDR.Register.IntegrationTests/API/SSA/US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests.cs index 0c17663..4e61bc1 100644 --- a/Source/CDR.Register.IntegrationTests/API/SSA/US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/API/SSA/US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests.cs @@ -1,5 +1,4 @@ -using CDR.Register.Domain.Entities; -using CDR.Register.IntegrationTests.Extensions; +using CDR.Register.IntegrationTests.Extensions; using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; @@ -14,7 +13,6 @@ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; -using static System.Net.WebRequestMethods; #nullable enable @@ -22,25 +20,66 @@ namespace CDR.Register.IntegrationTests.API.SSA { /// /// Integration tests for GetSoftwareStatementAssertion. - /// + /// public class US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests : BaseTest { - public US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } // Participation/Brand/SoftwareProduct Ids - private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup. + private const string BRANDID = "20C0864B-CEEF-4DE0-8944-EB0962F825EB"; private const string SOFTWAREPRODUCTID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; - enum AccessTokenType + private enum AccessTokenType { ValidAccessToken, // Get and send valid access token InvalidAccessToken, // Send an invalid access token ExpiredAccessToken, // Send expired access token NoAccessToken, // Don't send any access token - }; - delegate void BeforeSSARequest(); - delegate void AfterSSARequest(); + } + + private delegate void BeforeSSARequest(); + + private delegate void AfterSSARequest(); + + private static async Task GetAccessToken( + AccessTokenType accessTokenType, + string? getAccessTokenCertificateFilename, + string? getAccessTokenCertificatePassword, + string certificateFilename, + string certificatePassword) + { + // Access token + switch (accessTokenType) + { + case AccessTokenType.ValidAccessToken: + // Get the access token with the valid certificate. + return await new Infrastructure.AccessToken + { + CertificateFilename = getAccessTokenCertificateFilename ?? certificateFilename, + CertificatePassword = getAccessTokenCertificatePassword ?? certificatePassword + }.GetAsync(); + + case AccessTokenType.InvalidAccessToken: + return "foo"; + + case AccessTokenType.ExpiredAccessToken: + // Represents an expired access token. + // "exp": 1621344825 + // Expired at "Tuesday, May 18, 2021 11:33:45 PM GMT+10:00" + return "eyJhbGciOiJQUzI1NiIsImtpZCI6IkFBMjRGMTg1RUUzRjY3NTA0ODA4RkM0RTI2QjEzNUI5OUU2M0JEQTkiLCJ0eXAiOiJhdCtqd3QiLCJ4NXQiOiJxaVR4aGU0X1oxQklDUHhPSnJFMXVaNWp2YWsifQ.eyJuYmYiOjE2MjEzNDQ1MjUsImV4cCI6MTYyMTM0NDgyNSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzAwMC9pZHAiLCJhdWQiOiJjZHItcmVnaXN0ZXIiLCJjbGllbnRfaWQiOiI2ZjdhMWI4ZS04Nzk5LTQ4YTgtOTAxMS1lMzkyMDM5MWY3MTMiLCJqdGkiOiJDODRBNTM5MTA2QjI4NUJBODI2RjZGMDQ3MjU4RjBBNCIsImlhdCI6MTYyMTM0NDUyNSwic2NvcGUiOlsiY2RyLXJlZ2lzdGVyOmJhbms6cmVhZCJdLCJjbmYiOnsieDV0I1MyNTYiOiI1OEQ3NkY3QTYxQ0Q3MjZEQTFDNTRGNjg5OEU4RTY5RUE0Qzg4MDYwIn19.RTU-zrqkb-WXcJzCz62SJ4h19lj8MDyGcvLOmg0qx05WFbAsY4mEP3gsoqM1LJfq4ncw7RqSvbkCNQQ-NOnyoBHF8MGe7mzdUh3YrD0_lTg20Dkx1-l044svtP_CKTI3rXT3bZaYWce0Tb1s3mrJzfN3ja23o93FGR-wbIwHp2347b0DxjznpKBw5meLhAjS7OCx6_uMm1la6IziSQgqMd2WaA-od7w8J5br-Nn-QZZi7X1KGiPEKFDFNk8KrUdPc4NCH6t7f-Sbc34KNNEWfAOJkWdDrmsBaifSlWvSlS4nUnurGHYkmimA2JUuv3ZTqzCcLRamEER1ZoTcIs_PDw"; + + case AccessTokenType.NoAccessToken: + return null; + + default: + throw new NotSupportedException(); + } + } private static async Task Test_GetSSA( string certificateFilename, @@ -59,47 +98,22 @@ private static async Task Test_GetSSA( string brandId = BRANDID, string softwareProductId = SOFTWAREPRODUCTID) { - async Task GetAccessToken() - { - // Access token - switch (accessTokenType) - { - case AccessTokenType.ValidAccessToken: - // Get the access token with the valid certificate. - return await new Infrastructure.AccessToken - { - CertificateFilename = getAccessTokenCertificateFilename ?? certificateFilename, - CertificatePassword = getAccessTokenCertificatePassword ?? certificatePassword - }.GetAsync(); - - case AccessTokenType.InvalidAccessToken: - return "foo"; - - case AccessTokenType.ExpiredAccessToken: - // Represents an expired access token. - // "exp": 1621344825 - // Expired at "Tuesday, May 18, 2021 11:33:45 PM GMT+10:00" - return "eyJhbGciOiJQUzI1NiIsImtpZCI6IkFBMjRGMTg1RUUzRjY3NTA0ODA4RkM0RTI2QjEzNUI5OUU2M0JEQTkiLCJ0eXAiOiJhdCtqd3QiLCJ4NXQiOiJxaVR4aGU0X1oxQklDUHhPSnJFMXVaNWp2YWsifQ.eyJuYmYiOjE2MjEzNDQ1MjUsImV4cCI6MTYyMTM0NDgyNSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzAwMC9pZHAiLCJhdWQiOiJjZHItcmVnaXN0ZXIiLCJjbGllbnRfaWQiOiI2ZjdhMWI4ZS04Nzk5LTQ4YTgtOTAxMS1lMzkyMDM5MWY3MTMiLCJqdGkiOiJDODRBNTM5MTA2QjI4NUJBODI2RjZGMDQ3MjU4RjBBNCIsImlhdCI6MTYyMTM0NDUyNSwic2NvcGUiOlsiY2RyLXJlZ2lzdGVyOmJhbms6cmVhZCJdLCJjbmYiOnsieDV0I1MyNTYiOiI1OEQ3NkY3QTYxQ0Q3MjZEQTFDNTRGNjg5OEU4RTY5RUE0Qzg4MDYwIn19.RTU-zrqkb-WXcJzCz62SJ4h19lj8MDyGcvLOmg0qx05WFbAsY4mEP3gsoqM1LJfq4ncw7RqSvbkCNQQ-NOnyoBHF8MGe7mzdUh3YrD0_lTg20Dkx1-l044svtP_CKTI3rXT3bZaYWce0Tb1s3mrJzfN3ja23o93FGR-wbIwHp2347b0DxjznpKBw5meLhAjS7OCx6_uMm1la6IziSQgqMd2WaA-od7w8J5br-Nn-QZZi7X1KGiPEKFDFNk8KrUdPc4NCH6t7f-Sbc34KNNEWfAOJkWdDrmsBaifSlWvSlS4nUnurGHYkmimA2JUuv3ZTqzCcLRamEER1ZoTcIs_PDw"; - - case AccessTokenType.NoAccessToken: - return null; - - default: - throw new NotSupportedException(); - } - } - // Arrange - string URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-recipients/brands/{brandId}/software-products/{softwareProductId}/ssa"; + string url = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-recipients/brands/{brandId}/software-products/{softwareProductId}/ssa"; - var accessToken = await GetAccessToken(); + var accessToken = await GetAccessToken( + accessTokenType, + getAccessTokenCertificateFilename, + getAccessTokenCertificatePassword, + certificateFilename, + certificatePassword); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = certificateFilename, CertificatePassword = certificatePassword, HttpMethod = HttpMethod.Get, - URL = URL, + URL = url, AccessToken = accessToken, XV = x_v, XMinV = x_min_v @@ -138,14 +152,14 @@ private static async Task Test_GetSSA( [Theory] [InlineData(3)] - public async Task AC01_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int XV) + public async Task AC01_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int xv) { // Arrange - Get SoftwareProduct using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - var softwareProduct = dbContext.SoftwareProducts.AsNoTracking() + var softwareProduct = await dbContext.SoftwareProducts.AsNoTracking() .Include(sp => sp.Brand) .Where(sp => sp.SoftwareProductId == new Guid(SOFTWAREPRODUCTID)) - .Single(); + .SingleAsync(); // Arrange - Get access token var accessToken = await new Infrastructure.AccessToken @@ -155,18 +169,17 @@ public async Task AC01_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int XV) }.GetAsync(); // Act - Send request to SSA API - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa", - XV = XV.ToString(), + XV = xv.ToString(), AccessToken = accessToken }.SendAsync(); - await AssertSsa(response, softwareProduct, XV); - + await AssertSsa(response, softwareProduct, xv); } [Theory] @@ -178,10 +191,10 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different { // Arrange - Get SoftwareProduct using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - var softwareProduct = dbContext.SoftwareProducts.AsNoTracking() + var softwareProduct = await dbContext.SoftwareProducts.AsNoTracking() .Include(sp => sp.Brand) .Where(sp => sp.SoftwareProductId == new Guid(SOFTWAREPRODUCTID)) - .Single(); + .SingleAsync(); // Arrange - Get access token var accessToken = await new Infrastructure.AccessToken @@ -191,7 +204,7 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different }.GetAsync(); // Act - Send request to SSA API - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -202,10 +215,9 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different }.SendAsync(); await AssertSsa(response, softwareProduct, 3); - } - const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" + private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" { ""errors"": [ { @@ -218,11 +230,11 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different [Theory] [InlineData(1, HttpStatusCode.OK)] // Active - [InlineData(2, HttpStatusCode.Forbidden)] // Removed - [InlineData(3, HttpStatusCode.Forbidden)] // Suspended - [InlineData(4, HttpStatusCode.Forbidden)] // Revoked + [InlineData(2, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Suspended + [InlineData(4, HttpStatusCode.Forbidden)] // Revoked [InlineData(5, HttpStatusCode.Forbidden)] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden)] // Inactive + [InlineData(6, HttpStatusCode.Forbidden)] // Inactive public async Task AC03_GetSSA_WithParticipationStatusNotActive_ShouldRespondWith_403Forbidden( int participationStatusId, HttpStatusCode expectedStatusCode) @@ -235,14 +247,13 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetParticipationStatusId(PARTICIPATIONID, participationStatusId), afterRequest: () => SetParticipationStatusId(PARTICIPATIONID, saveParticipationStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive - [InlineData(3, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC04_GetSSA_WithBrandNotActive_ShouldRespondWith_403Forbidden( int brandStatusId, HttpStatusCode expectedStatusCode) @@ -255,14 +266,13 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetBrandStatusId(BRANDID, brandStatusId), afterRequest: () => SetBrandStatusId(BRANDID, saveBrandStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive - [InlineData(3, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC05_GetSSA_WithSoftwareProductNotActive_ShouldRespondWith_403Forbidden( int softwareProductStatusId, HttpStatusCode expectedStatusCode) @@ -275,8 +285,7 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, softwareProductStatusId), afterRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, saveSoftwareProductStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] @@ -323,19 +332,18 @@ await Test_GetSSA( ADDITIONAL_CERTIFICATE_PASSWORD, HttpStatusCode.Unauthorized, getAccessTokenCertificateFilename: CERTIFICATE_FILENAME, - getAccessTokenCertificatePassword: CERTIFICATE_PASSWORD - ); + getAccessTokenCertificatePassword: CERTIFICATE_PASSWORD); } [Theory] [InlineData("3", "foo")] - public async Task AC11_GetSSA_InvalidSoftwareProductId_ShouldRespondWith_404NotFound(string XV, string softwareProductId) + public async Task AC11_GetSSA_InvalidSoftwareProductId_ShouldRespondWith_404NotFound(string xv, string softwareProductId) { await Test_GetSSA( CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD, HttpStatusCode.NotFound, - x_v: XV, + x_v: xv, softwareProductId: softwareProductId, expectedContent: $@" {{ @@ -368,8 +376,7 @@ await Test_GetSSA( CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD, HttpStatusCode.NotFound, - brandId: Guid.NewGuid().ToString() - ); + brandId: Guid.NewGuid().ToString()); } private static async Task GetSsaJwks() @@ -380,32 +387,31 @@ private static async Task GetSsaJwks() var jwksClient = new HttpClient(clientHandler); var jwksResponse = await jwksClient.GetAsync($"{TLS_BaseURL}/cdr-register/v1/jwks"); return new JsonWebKeySet(await jwksResponse.Content.ReadAsStringAsync()); - } + } [Theory] - [InlineData("3", "4", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-min-v is ignored when > x-v - [InlineData("3", "2", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is supported and higher than x-min-v - [InlineData("3", "3", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is supported equal to x-min-v - [InlineData("4", "3", "3", HttpStatusCode.OK, true, "")] //Valid. Should return v3 - x-v is NOT supported and x-min-v is supported - [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is supported but x-min-v is invalid (not a positive integer) - [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) - [InlineData("4", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v invalid - [InlineData("4", "4", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. Both x-v and x-min-v exceed supported version of 3 - [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("2", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("foo", "3", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is invalid with valid x-min-v - [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (negative integer) is invalid with missing x-min-v - [InlineData("4", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is higher than supported version of 3 - [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is an empty string - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is missing + [InlineData("3", "4", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-min-v is ignored when > x-v + [InlineData("3", "2", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is supported and higher than x-min-v + [InlineData("3", "3", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is supported equal to x-min-v + [InlineData("4", "3", "3", HttpStatusCode.OK, true, "")] // Valid. Should return v3 - x-v is NOT supported and x-min-v is supported + [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is supported but x-min-v is invalid (not a positive integer) + [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) + [InlineData("4", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v invalid + [InlineData("4", "4", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. Both x-v and x-min-v exceed supported version of 3 + [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("2", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("foo", "3", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is invalid with valid x-min-v + [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (negative integer) is invalid with missing x-min-v + [InlineData("4", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is higher than supported version of 3 + [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is an empty string + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is missing public async Task ACX01_VersionHeaderValidation(string? xv, string? xminv, string expectedXv, HttpStatusCode expectedHttpStatusCode, bool isExpectedToBeSupported, string expecetdError) { - // Arrange - string URL = $"{MTLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa"; + string url = $"{MTLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa"; var accessToken = await new Infrastructure.AccessToken { @@ -413,18 +419,17 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? xminv, strin CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, HttpMethod = HttpMethod.Get, - URL = URL, + URL = url, AccessToken = accessToken, XV = xv, XMinV = xminv }; - // Act var response = await api.SendAsync(); @@ -443,27 +448,25 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? xminv, strin { // Assert - Check error response await Assert_HasContent_Json(expecetdError, response.Content); - } } - } + [Trait("Category", "CTSONLY")] [Theory] [InlineData(3)] - public async Task AC01_CTS_URL_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int XV) + public async Task AC01_CTS_URL_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int xv) { string conformanceId = Guid.NewGuid().ToString(); - // conformanceId = "5186c407-0114-480a-86a3-7ef072d221bc"; string tokenEndpoint = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; string ssasEndpoint = $"{GenerateDynamicCtsUrl(SSA_DOWNSTREAM_BASE_URL, conformanceId)}/cdr-register/v1/all/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa"; // Arrange - Get SoftwareProduct using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - Repository.Entities.SoftwareProduct softwareProduct = dbContext.SoftwareProducts.AsNoTracking() + Repository.Entities.SoftwareProduct softwareProduct = await dbContext.SoftwareProducts.AsNoTracking() .Include(sp => sp.Brand) .Where(sp => sp.SoftwareProductId == new Guid(SOFTWAREPRODUCTID)) - .Single(); + .SingleAsync(); // Arrange - Get access token var accessToken = await new Infrastructure.AccessToken @@ -478,20 +481,20 @@ public async Task AC01_CTS_URL_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(in }.GetAsync(addCertificateToRequest: false); // Act - Send request to SSA API - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = ssasEndpoint, - XV = XV.ToString(), + XV = xv.ToString(), AccessToken = accessToken, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME }.SendAsync(); - await AssertSsa(response, softwareProduct, XV); + await AssertSsa(response, softwareProduct, xv); } - private static async Task AssertSsa(HttpResponseMessage response, Repository.Entities.SoftwareProduct softwareProduct, int XV) + private static async Task AssertSsa(HttpResponseMessage response, Repository.Entities.SoftwareProduct softwareProduct, int xv) { // Assert using (new AssertionScope()) @@ -505,44 +508,44 @@ private static async Task AssertSsa(HttpResponseMessage response, Repository.Ent var ssaJwks = await GetSsaJwks(); // Assert - Check XV - Assert_HasHeader(XV.ToString(), response.Headers, "x-v"); + Assert_HasHeader(xv.ToString(), response.Headers, "x-v"); // Assert - SSA - var SSA = new JwtSecurityTokenHandler().ReadJwtToken(await response.Content.ReadAsStringAsync()); + var ssa = new JwtSecurityTokenHandler().ReadJwtToken(await response.Content.ReadAsStringAsync()); // Assert - SSA Header - SSA.Header.Alg.Should().Be("PS256"); - SSA.Header.Kid.Should().Be(ssaJwks.Keys.First().Kid); - SSA.Header.Typ.Should().Be("JWT"); + ssa.Header.Alg.Should().Be("PS256"); + ssa.Header.Kid.Should().Be(ssaJwks.Keys[0].Kid); + ssa.Header.Typ.Should().Be("JWT"); // Assert - SSA Claims - SSA.AssertClaim("iss", "cdr-register"); + ssa.AssertClaim("iss", "cdr-register"); - SSA.AssertClaim("iat", null); - SSA.AssertClaim("exp", null); - long iat = Convert.ToInt64(SSA.Claim("iat").Value); - long exp = Convert.ToInt64(SSA.Claim("exp").Value); + ssa.AssertClaim("iat", null); + ssa.AssertClaim("exp", null); + long iat = Convert.ToInt64(ssa.Claim("iat").Value); + long exp = Convert.ToInt64(ssa.Claim("exp").Value); exp.Should().Be(iat + 600); // Check expiry is 10 minutes (ie 600 seconds) - SSA.AssertClaim("jti", null); - SSA.AssertClaim("org_id", softwareProduct.Brand.BrandId.ToString()); - SSA.AssertClaim("org_name", softwareProduct.Brand.BrandName); - SSA.AssertClaim("client_name", softwareProduct.SoftwareProductName); - SSA.AssertClaim("client_description", softwareProduct.SoftwareProductDescription); - SSA.AssertClaim("client_uri", softwareProduct.ClientUri); - SSA.AssertClaimIsArray("redirect_uris", softwareProduct.RedirectUris.Split(" ")); - SSA.AssertClaim("logo_uri", softwareProduct.LogoUri); - SSA.AssertClaim("tos_uri", softwareProduct.TosUri, true); - SSA.AssertClaim("policy_uri", softwareProduct.PolicyUri, true); - SSA.AssertClaim("jwks_uri", softwareProduct.JwksUri); - SSA.AssertClaim("revocation_uri", softwareProduct.RevocationUri); - SSA.AssertClaim("software_id", softwareProduct.SoftwareProductId.ToString()); - SSA.AssertClaim("software_roles", "data-recipient-software-product"); - SSA.AssertClaim("scope", softwareProduct.Scope); - - if (XV >= 2) + ssa.AssertClaim("jti", null); + ssa.AssertClaim("org_id", softwareProduct.Brand.BrandId.ToString()); + ssa.AssertClaim("org_name", softwareProduct.Brand.BrandName); + ssa.AssertClaim("client_name", softwareProduct.SoftwareProductName); + ssa.AssertClaim("client_description", softwareProduct.SoftwareProductDescription); + ssa.AssertClaim("client_uri", softwareProduct.ClientUri); + ssa.AssertClaimIsArray("redirect_uris", softwareProduct.RedirectUris.Split(" ")); + ssa.AssertClaim("logo_uri", softwareProduct.LogoUri); + ssa.AssertClaim("tos_uri", softwareProduct.TosUri, true); + ssa.AssertClaim("policy_uri", softwareProduct.PolicyUri, true); + ssa.AssertClaim("jwks_uri", softwareProduct.JwksUri); + ssa.AssertClaim("revocation_uri", softwareProduct.RevocationUri); + ssa.AssertClaim("software_id", softwareProduct.SoftwareProductId.ToString()); + ssa.AssertClaim("software_roles", "data-recipient-software-product"); + ssa.AssertClaim("scope", softwareProduct.Scope); + + if (xv >= 2) { - SSA.AssertClaim("recipient_base_uri", softwareProduct.RecipientBaseUri); + ssa.AssertClaim("recipient_base_uri", softwareProduct.RecipientBaseUri); } // Assert - Validate SSA Signature @@ -553,7 +556,7 @@ private static async Task AssertSsa(HttpResponseMessage response, Repository.Ent RequireSignedTokens = true, ValidateIssuerSigningKey = true, - IssuerSigningKey = ssaJwks.Keys.First(), + IssuerSigningKey = ssaJwks.Keys[0], ValidateIssuer = true, ValidIssuer = "cdr-register", @@ -562,7 +565,7 @@ private static async Task AssertSsa(HttpResponseMessage response, Repository.Ent }; // Validate token (throws exception if token fails to validate) - new JwtSecurityTokenHandler().ValidateToken(SSA.RawData, validationParameters, out var validatedToken); + new JwtSecurityTokenHandler().ValidateToken(ssa.RawData, validationParameters, out _); } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Status/US27556_GetDataRecipientStatus_MultiIndustry_Tests.cs b/Source/CDR.Register.IntegrationTests/API/Status/US27556_GetDataRecipientStatus_MultiIndustry_Tests.cs index 170cf5b..87ad299 100644 --- a/Source/CDR.Register.IntegrationTests/API/Status/US27556_GetDataRecipientStatus_MultiIndustry_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/API/Status/US27556_GetDataRecipientStatus_MultiIndustry_Tests.cs @@ -18,11 +18,15 @@ namespace CDR.Register.IntegrationTests.API.Status { /// - /// Integration tests for GetDataRecipientStatus V2 endpoints + /// Integration tests for GetDataRecipientStatus V2 endpoints. /// public class US27556_GetDataRecipientStatus_MultiIndustry_Tests : BaseTest { - public US27556_GetDataRecipientStatus_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US27556_GetDataRecipientStatus_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + private static string GetExpectedDataRecipientsStatus(string url) { using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); @@ -51,18 +55,18 @@ private static string GetExpectedDataRecipientsStatus(string url) [Theory] [InlineData("2", "2")] - public async Task AC01_AC02_Get_ShouldRespondWith_200OK_DataRecipientsStatus(string? XV, string expectedXV) + public async Task AC01_AC02_Get_ShouldRespondWith_200OK_DataRecipientsStatus(string? xv, string expectedXV) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/status"; var expectedDataRecipientStatus = GetExpectedDataRecipientsStatus(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV + XV = xv }.SendAsync(); // Assert @@ -85,15 +89,15 @@ public async Task AC01_AC02_Get_ShouldRespondWith_200OK_DataRecipientsStatus(str [Trait("Category", "CTSONLY")] [Theory] [InlineData("2", "2")] - public async Task AC01_AC02_CTS_URL_Get_ShouldRespondWith_200OK_DataRecipientsStatus(string? XV, string expectedXV) + public async Task AC01_AC02_CTS_URL_Get_ShouldRespondWith_200OK_DataRecipientsStatus(string? xv, string expectedXV) { - // Arrange + // Arrange var url = $"{GenerateDynamicCtsUrl(STATUS_DOWNSTREAM_BASE_URL)}/cdr-register/v1/all/data-recipients/status"; - string publicHostName = Configuration["PublicHostName"] ?? ""; - string expectedUrl = ReplacePublicHostName(url, STATUS_DOWNSTREAM_BASE_URL); + string publicHostName = Configuration["PublicHostName"] ?? string.Empty; + string expectedUrl; - if (String.IsNullOrEmpty(publicHostName)) + if (string.IsNullOrEmpty(publicHostName)) { expectedUrl = url; } @@ -101,14 +105,15 @@ public async Task AC01_AC02_CTS_URL_Get_ShouldRespondWith_200OK_DataRecipientsSt { expectedUrl = url.Replace(STATUS_DOWNSTREAM_BASE_URL, publicHostName); } + var expectedDataRecipientStatus = GetExpectedDataRecipientsStatus(expectedUrl); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV + XV = xv }.SendAsync(); // Assert @@ -133,12 +138,12 @@ public async Task AC01_AC02_CTS_URL_Get_ShouldRespondWith_200OK_DataRecipientsSt [InlineData("foo")] // AC06 public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(string? ifNoneMatch) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/status"; var expectedDataRecipientsStatus = GetExpectedDataRecipientsStatus(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, @@ -170,16 +175,16 @@ public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(str public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModified_ETag() { // Arrange - Get SoftwareProductsStatus and save the ETag - var expectedETag = (await new Infrastructure.API + var expectedETag = (await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/status", XV = "2", - IfNoneMatch = null, // ie If-None-Match is not set + IfNoneMatch = null, // ie If-None-Match is not set }.SendAsync()).Headers.GetValues("ETag").First().Trim('"'); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/status", @@ -199,28 +204,27 @@ public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModi } [Theory] - [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-min-v is ignored when > x-v - [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported and higher than x-min-v - [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported equal to x-min-v - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z - [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is supported but x-min-v is invalid (not a positive integer) - [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v invalid - [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. Both x-v and x-min-v exceed supported version of 2 - [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is invalid with valid x-min-v - [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (negative integer) is invalid with missing x-min-v - [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is higher than supported version of 2 - [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is an empty string - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is missing + [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-min-v is ignored when > x-v + [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported and higher than x-min-v + [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported equal to x-min-v + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z + [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is supported but x-min-v is invalid (not a positive integer) + [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v invalid + [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. Both x-v and x-min-v exceed supported version of 2 + [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is invalid with valid x-min-v + [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (negative integer) is invalid with missing x-min-v + [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is higher than supported version of 2 + [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is an empty string + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is missing public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string expectedXv, HttpStatusCode expectedHttpStatusCode, bool isExpectedToBeSupported, string expecetdError) { - // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients/status", @@ -247,7 +251,6 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string // Assert - Check error response await Assert_HasContent_Json(expecetdError, response.Content); } - } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Status/US27558_GetSoftwareProductStatus_MultiIndustry_Tests.cs b/Source/CDR.Register.IntegrationTests/API/Status/US27558_GetSoftwareProductStatus_MultiIndustry_Tests.cs index 00257d0..d6d51ff 100644 --- a/Source/CDR.Register.IntegrationTests/API/Status/US27558_GetSoftwareProductStatus_MultiIndustry_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/API/Status/US27558_GetSoftwareProductStatus_MultiIndustry_Tests.cs @@ -16,11 +16,15 @@ namespace CDR.Register.IntegrationTests.API.Status { /// - /// Integration tests for GetSoftwareProductStatus v2 endpoints - /// + /// Integration tests for GetSoftwareProductStatus v2 endpoints. + /// public class US27558_GetSoftwareProductStatus_MultiIndustry_Tests : BaseTest { - public US27558_GetSoftwareProductStatus_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US27558_GetSoftwareProductStatus_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + private static string GetExpectedSoftwareProductStatus(string url) { using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); @@ -36,7 +40,8 @@ private static string GetExpectedSoftwareProductStatus(string url) }) .OrderBy(sp => sp.softwareProductId.ToString()) .ToList(), - links = new { + links = new + { self = url }, meta = new object() @@ -47,18 +52,18 @@ private static string GetExpectedSoftwareProductStatus(string url) [Theory] [InlineData("2", "2")] - public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(string? XV, string expectedXV) + public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(string? xv, string expectedXV) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/software-products/status"; var expectedSoftwareProductsStatus = GetExpectedSoftwareProductStatus(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV + XV = xv }.SendAsync(); // Assert @@ -81,18 +86,18 @@ public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(strin [Trait("Category", "CTSONLY")] [Theory] [InlineData("2", "2")] - public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(string? XV, string expectedXV) + public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(string? xv, string expectedXV) { - // Arrange + // Arrange var url = $"{GenerateDynamicCtsUrl(STATUS_DOWNSTREAM_BASE_URL)}/cdr-register/v1/all/data-recipients/brands/software-products/status"; var expectedSoftwareProductsStatus = GetExpectedSoftwareProductStatus(ReplacePublicHostName(url, STATUS_DOWNSTREAM_BASE_URL)); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, - XV = XV + XV = xv }.SendAsync(); // Assert @@ -117,12 +122,12 @@ public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_SoftwareProduc [InlineData("foo")] // AC06 public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(string? ifNoneMatch) { - // Arrange + // Arrange var url = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/software-products/status"; var expectedSoftwareProductsStatus = GetExpectedSoftwareProductStatus(url); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = url, @@ -154,16 +159,16 @@ public async Task AC04_AC06_Get_WithIfNoneMatch_ShouldRespondWith_200OK_ETag(str public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModified_ETag() { // Arrange - Get SoftwareProductsStatus and save the ETag - var expectedETag = (await new Infrastructure.API + var expectedETag = (await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/software-products/status", XV = "2", - IfNoneMatch = null, // ie If-None-Match is not set + IfNoneMatch = null, // ie If-None-Match is not set }.SendAsync()).Headers.GetValues("ETag").First().Trim('"'); // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/software-products/status", @@ -183,28 +188,27 @@ public async Task AC05_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotModi } [Theory] - [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-min-v is ignored when > x-v - [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported and higher than x-min-v - [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is supported equal to x-min-v - [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] //Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z - [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is supported but x-min-v is invalid (not a positive integer) - [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) - [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is not supported and x-min-v invalid - [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. Both x-v and x-min-v exceed supported version of 2 - [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is an obsolete version - [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (not a positive integer) is invalid with missing x-min-v - [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v is invalid with valid x-min-v - [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] //Invalid. x-v (negative integer) is invalid with missing x-min-v - [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] //Unsupported. x-v is higher than supported version of 2 - [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is an empty string - [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] //Invalid. x-v header is missing + [InlineData("2", "3", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-min-v is ignored when > x-v + [InlineData("2", "1", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported and higher than x-min-v + [InlineData("2", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is supported equal to x-min-v + [InlineData("3", "2", "2", HttpStatusCode.OK, true, "")] // Valid. Should return v2 - x-v is NOT supported and x-min-v is supported Z + [InlineData("3", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is supported but x-min-v is invalid (not a positive integer) + [InlineData("4", "foo", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v is invalid (not a positive integer) + [InlineData("3", "0", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is not supported and x-min-v invalid + [InlineData("3", "3", "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. Both x-v and x-min-v exceed supported version of 2 + [InlineData("1", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is an obsolete version + [InlineData("foo", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("0", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (not a positive integer) is invalid with missing x-min-v + [InlineData("foo", "2", "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v is invalid with valid x-min-v + [InlineData("-1", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_INVALID_VERSION_ERROR)] // Invalid. x-v (negative integer) is invalid with missing x-min-v + [InlineData("3", null, "N/A", HttpStatusCode.NotAcceptable, false, EXPECTED_UNSUPPORTED_ERROR)] // Unsupported. x-v is higher than supported version of 2 + [InlineData("", null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is an empty string + [InlineData(null, null, "N/A", HttpStatusCode.BadRequest, false, EXPECTED_MISSING_X_V_ERROR)] // Invalid. x-v header is missing public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string expectedXv, HttpStatusCode expectedHttpStatusCode, bool isExpectedToBeSupported, string expecetdError) { - // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients/brands/software-products/status", @@ -231,7 +235,6 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string // Assert - Check error response await Assert_HasContent_Json(expecetdError, response.Content); } - } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs b/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs index 5cee650..3915cef 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs @@ -1,11 +1,11 @@ using CDR.Register.IntegrationTests.Models; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; namespace CDR.Register.IntegrationTests.API.Update { - //TODO: Work out why this class exists and errors are defined in multiple places in IntTests project public class ExpectedErrors { public enum ErrorType @@ -17,10 +17,15 @@ public enum ErrorType UnsupportedVersion, Unauthorized } - private List _expectedErrors; + private const string CDS_ERROR_PREFIX = "urn:au-cds:error:cds-all:"; + private readonly List _expectedErrors; - public List errors { get => _expectedErrors.ToList(); } + /// + /// This is used in serialised JSON to compare against the actual JSON. Do not remove. + /// + [JsonProperty(PropertyName = "errors")] + public List Errors { get => _expectedErrors.ToList(); } public ExpectedErrors() { @@ -84,7 +89,5 @@ public void AddExpectedError(ErrorType errorType, string detail) throw new NotSupportedException($"{nameof(ErrorType)}={errorType}"); } } - } - } diff --git a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs index 3116608..b915ec7 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs @@ -23,7 +23,10 @@ namespace CDR.Register.IntegrationTests.API.Update { public class US50480_UpdateDataHolders : BaseTest { - public US50480_UpdateDataHolders(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US50480_UpdateDataHolders(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } private const string UPDATE_DATA_HOLDER_CURRENT_API_VERSION = "1"; @@ -51,7 +54,6 @@ public async Task AC02_Add_New_DataHolder_With_Only_Mandatory_Fields_Http_200(In // Assert participation created correctly VerifyParticipationRecord(dataHolderMetadata.LegalEntity.LegalEntityId, "DH", industry.ToString(), dataHolderMetadata.Status); - } [Theory] @@ -69,7 +71,6 @@ public async Task AC02_Add_New_DataHolder_With_All_Fields_Fields_Http_200(Indust VerifySuccessfulDataHolderUpdate(dataHolderMetadata, response, industry); VerifyParticipationRecord(dataHolderMetadata.LegalEntity.LegalEntityId, "DH", industry.ToString(), dataHolderMetadata.Status); - } [Fact] @@ -97,17 +98,16 @@ public async Task AC03_Modify_Existing_DataHolder_200() VerifySuccessfulDataHolderUpdate(dataHolderMetadata, modifiedResponse, Industry.Banking); VerifyParticipationRecord(dataHolderMetadata.LegalEntity.LegalEntityId, "DH", Industry.Banking.ToString(), dataHolderMetadata.Status); - } [Fact] public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_200() { - // Generate valid payload. DataHolderMetadata originalDataHolderMetadata = CreateValidDataholderMetadata(Industry.Banking, true); + // Send to Register - HttpResponseMessage response = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata)); + await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata)); // Send Original request to Register HttpResponseMessage originalResponse = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata)); @@ -118,10 +118,10 @@ public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_200() // Generate valid payload. DataHolderMetadata newDataHolderMetadata = CreateValidDataholderMetadata(Industry.Banking, true); - //Set Legal Entity Id to Original Legal Entity Id. Register should attach new brand to Legal Entity + // Set Legal Entity Id to Original Legal Entity Id. Register should attach new brand to Legal Entity newDataHolderMetadata.LegalEntity.LegalEntityId = originalDataHolderMetadata.LegalEntity.LegalEntityId; - //Also modify other fields + // Also modify other fields newDataHolderMetadata.BrandName = "Test Automation Modified Brand Name"; newDataHolderMetadata.EndpointDetail.PublicBaseUri = $"{TEST_DATA_BASE_URI}/ModifiedPublicBaseUri"; newDataHolderMetadata.AuthDetails.JwksEndpoint = $"{TEST_DATA_BASE_URI}/ModifiedJwks"; @@ -132,19 +132,18 @@ public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_200() // Assert http response and database updates. VerifySuccessfulDataHolderUpdate(newDataHolderMetadata, modifiedResponse, Industry.Banking); - //Also Check Original is not affected + // Also Check Original is not affected VerifySuccessfulDataHolderUpdate(originalDataHolderMetadata, originalResponse, Industry.Banking); VerifyParticipationRecord(newDataHolderMetadata.LegalEntity.LegalEntityId, "DH", Industry.Banking.ToString(), newDataHolderMetadata.Status); - } [Fact] public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_With_Different_Industry_200() { - // Generate valid Banking payload. DataHolderMetadata originalDataHolderMetadata = CreateValidDataholderMetadata(Industry.Banking, true); + // Send to Register _ = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata)); @@ -157,10 +156,10 @@ public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_With_Different_Ind // Generate valid Energy payload. DataHolderMetadata newDataHolderMetadata = CreateValidDataholderMetadata(Industry.Energy, true); - //Set Legal Entity Id to Original Legal Entity Id. Register should attach new brand to Legal Entity, and create a new participation record. + // Set Legal Entity Id to Original Legal Entity Id. Register should attach new brand to Legal Entity, and create a new participation record. newDataHolderMetadata.LegalEntity.LegalEntityId = originalDataHolderMetadata.LegalEntity.LegalEntityId; - //Also modify other fields + // Also modify other fields newDataHolderMetadata.BrandName = "Test Automation Energy Brand Name"; newDataHolderMetadata.EndpointDetail.PublicBaseUri = $"{TEST_DATA_BASE_URI}/EnergyPublicBaseUri"; newDataHolderMetadata.AuthDetails.JwksEndpoint = $"{TEST_DATA_BASE_URI}/EnergyJwks"; @@ -176,7 +175,6 @@ public async Task AC03_Add_New_Brand_To_Existing_Legal_Entity_With_Different_Ind // Verify Energy(subsequent) Participant VerifyParticipationRecord(newDataHolderMetadata.LegalEntity.LegalEntityId, "DH", Industry.Energy.ToString(), newDataHolderMetadata.Status); - } [Theory] @@ -190,7 +188,7 @@ public async Task AC04_Missing_Version_In_Header_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new (); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingHeader, "An API version x-v header is required, but was not specified."); // Assert Response @@ -207,14 +205,13 @@ public async Task AC05_Invalid_Version_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new (); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.InvalidVersion, "Version is not a positive Integer."); // Assert Response await VerifyBadRequest(expectedErrors, response); } - [Theory] [InlineData("20")] public async Task AC10_Unsupported_Version_Http_400(string xv) @@ -224,7 +221,7 @@ public async Task AC10_Unsupported_Version_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataHolderRequest(GetJsonFromModel(originalDataHolderMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new (); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.UnsupportedVersion, "Requested version is lower than the minimum version or greater than maximum version."); // Assert Response @@ -288,7 +285,8 @@ public async Task AC06_Missing_Mandatory_Fields_Http_400(string testId, string e [InlineData("Max Length EndPoint - ExtensionBaseUri", "endpointDetail.extensionBaseUri", 1000)] [InlineData("Max Length EndPoint - WebsiteUri", "endpointDetail.websiteUri", 1000)] [InlineData("Max Length Legal Entity - Name", "legalEntity.legalEntityName", 200)] - //we don't check accredatation number for DHs as they don't have it. + + // we don't check accredatation number for DHs as they don't have it. [InlineData("Max Length Legal Entity - logoUri", "legalEntity.logoUri", 1000)] [InlineData("Max Length Legal Entity - Registration Number", "legalEntity.registrationNumber", 100)] [InlineData("Max Length Legal Entity - Registered Country", "legalEntity.registeredCountry", 100)] @@ -312,14 +310,13 @@ public async Task ACXX_Maximum_Field_Length_Exceeded(string testId, string eleme await VerifyInvalidAndValidFieldResponse(response, dataHolderMetadata, ConvertJsonPathToPascalCase(elementUnderTest), maxLengthValue, true); // Create dataHolder using maximum field lenght plus one - string maxLengthPlusOneValue = (maxLength+1).GenerateRandomString(); + string maxLengthPlusOneValue = (maxLength + 1).GenerateRandomString(); dataHolderMetadata = ReplaceModelValueBasedOnJsonPath(dataHolderMetadata, elementUnderTest, maxLengthPlusOneValue); Log.Information($"-ve:\n{GetJsonFromModel(dataHolderMetadata)}"); // Send and verify negative scenario HttpResponseMessage responseNegative = await PostUpdateDataHolderRequest(GetJsonFromModel(dataHolderMetadata)); await VerifyInvalidAndValidFieldResponse(responseNegative, dataHolderMetadata, ConvertJsonPathToPascalCase(elementUnderTest), maxLengthPlusOneValue, false); - } [Theory] @@ -343,22 +340,18 @@ public async Task AC07_Valid_And_Invalid_Industry(string industry, bool isValid switch (industry.ToUpper()) { - case "ENERGY": industryEnum = Industry.Energy; break; - case "BANKING": default: industryEnum = Industry.Banking; break; - } dataHolderMetadata.Industries = dataHolderMetadata.Industries.ConvertAll(x => x.ToUpper()); await VerifyInvalidAndValidFieldResponse(httpResponseMessage, dataHolderMetadata, "Industries[0]", industry, isValid, industryEnum); - } [Theory] @@ -381,7 +374,6 @@ public async Task AC07_Valid_And_Invalid_Data_Holder_Status(string status, bool dataHolderMetadata.Status = status.ToUpper(); await VerifyInvalidAndValidFieldResponse(httpResponseMessage, dataHolderMetadata, "Status", status, isValid); - } [Theory] @@ -407,7 +399,6 @@ public async Task AC07_Valid_And_Invalid_Data_Holder_Legal_Entity_Organisation_T dataHolderMetadata.LegalEntity.OrganisationType = orgType.ToUpper(); await VerifyInvalidAndValidFieldResponse(httpResponseMessage, dataHolderMetadata, "LegalEntity.OrganisationType", orgType, isValid); - } [Theory] @@ -430,7 +421,6 @@ public async Task AC07_Valid_And_Invalid_Data_Holder_Legal_Entity_Status(string dataHolderMetadata.LegalEntity.Status = status.ToUpper(); await VerifyInvalidAndValidFieldResponse(httpResponseMessage, dataHolderMetadata, "LegalEntity.Status", status, isValid); - } [Theory] @@ -451,7 +441,6 @@ public async Task AC07_Valid_And_Invalid_Data_Holder_Auth_Details_RegisterUType( Log.Information($"modified:\n{GetJsonFromModel(dataHolderMetadata)}"); await VerifyInvalidAndValidFieldResponse(httpResponseMessage, dataHolderMetadata, "AuthDetails.RegisterUType", registerUType, isValid); - } [Fact] @@ -493,7 +482,6 @@ public async Task AC09_Brand_Already_Associated_With_Same_Legal_Entity_And_Diffe HttpResponseMessage modifiedResponse = await PostUpdateDataHolderRequest(GetJsonFromModel(dataHolderMetadata)); await VerifyInvalidPayloadResponse(modifiedResponse, $"Brand {dataHolderMetadata.DataHolderBrandId} is already associated with the same legal entity in a different industry."); - } [Trait("Category", "CTSONLY")] @@ -577,13 +565,10 @@ private static async Task PostUpdateDataHolderRequest(strin Log.Information($"Response from admin/metadata/data-holders API: {httpResponse.StatusCode} \n{httpResponse.Content.ReadAsStringAsync().Result}"); return httpResponse; - } private static async Task VerifyInvalidAndValidFieldResponse(HttpResponseMessage response, DataHolderMetadata dataHolderMetadata, string field, string value, bool isValid, Industry industry = Industry.Banking) { - // var response = await SendToStub(GetJsonFromModel(dataHolderMetadata), isValid ? "PASS" : "InvalidField"); - if (isValid) { string actualDataHolder = GetActualDataHolderFromDatabase(dataHolderMetadata.LegalEntity.LegalEntityId, dataHolderMetadata.DataHolderBrandId, industry); @@ -601,7 +586,6 @@ private static async Task VerifyInvalidAndValidFieldResponse(HttpResponseMessage private static async Task VerifyInvalidPayloadResponse(HttpResponseMessage response, string expectedErrorMessage) { - ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.InvalidField, expectedErrorMessage); @@ -612,7 +596,7 @@ private static void VerifySuccessfulDataHolderUpdate(DataHolderMetadata expected { using (new AssertionScope()) { - //Check status code + // Check status code httpResponseFromRegister.StatusCode.Should().Be(HttpStatusCode.OK); string actualDataHolder = GetActualDataHolderFromDatabase(expectedDataHolderMetadata.LegalEntity.LegalEntityId, expectedDataHolderMetadata.DataHolderBrandId, industry); @@ -625,7 +609,6 @@ private static void VerifySuccessfulDataHolderUpdate(DataHolderMetadata expected private static DataHolderMetadata CreateValidDataholderMetadata(Industry industry, bool includeOptionalFields = false) { - DataHolderMetadata dataHolderMetadata = new DataHolderMetadata(); dataHolderMetadata.DataHolderBrandId = Guid.NewGuid().ToString(); dataHolderMetadata.BrandName = "Test Automation Brand Name"; @@ -667,15 +650,13 @@ private static DataHolderMetadata CreateValidDataholderMetadata(Industry industr dataHolderMetadata.LegalEntity.AnzsicDivision = "Test Automation Anzsic Division"; dataHolderMetadata.LegalEntity.OrganisationType = "SOLE_TRADER"; dataHolderMetadata.EndpointDetail.ExtensionBaseUri = $"{TEST_DATA_BASE_URI}/extensionBaseUri"; - }; + } return dataHolderMetadata; - } private static string GetActualDataHolderFromDatabase(string legalEntityId, string brandId, Industry industry) { - var legalEntityIdGuid = Guid.Parse(legalEntityId); try @@ -725,7 +706,6 @@ private static string GetActualDataHolderFromDatabase(string legalEntityId, stri infosecBaseUri = brand.Endpoint.InfosecBaseUri, extensionBaseUri = brand.Endpoint.ExtensionBaseUri, websiteUri = brand.Endpoint.WebsiteUri - }, authDetails = new { diff --git a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs index 1781b34..aec0c69 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs @@ -1,5 +1,4 @@ -using CDR.Register.API.Infrastructure.Models; -using CDR.Register.IntegrationTests.Extensions; +using CDR.Register.IntegrationTests.Extensions; using CDR.Register.IntegrationTests.Models; using CDR.Register.Repository.Infrastructure; using FluentAssertions; @@ -24,7 +23,10 @@ namespace CDR.Register.IntegrationTests.API.Update { public class US50480_UpdateDataRecipients : BaseTest { - public US50480_UpdateDataRecipients(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US50480_UpdateDataRecipients(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } private const string UPDATE_DATA_RECIPIENT_CURRENT_API_VERSION = "1"; private const string DEFAULT_SCOPES = "openid profile bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:payees:read bank:regular_payments:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.usage:read energy:electricity.der:read energy:accounts.basic:read energy:accounts.basic:read energy:accounts.detail:read " + @@ -59,12 +61,12 @@ public async Task AC03_UpdateExistingLegalEntity_Http_200() DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); // Send to Register - var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); + await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - //Using the same Legal Entity, update some fields. + // Using the same Legal Entity, update some fields. dataRecipientMetadata.LegalEntityName = "Updated Legal Entity Name"; - dataRecipientMetadata.DataRecipientBrands.First().BrandName = "Updated Brand Name"; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductName = "Updated Software Product Name"; + dataRecipientMetadata.DataRecipientBrands[0].BrandName = "Updated Brand Name"; + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductName = "Updated Software Product Name"; // Send Updated Data Recipient to Register var updateResponse = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); @@ -110,20 +112,19 @@ public async Task AC05_Add_Brand_To_Existing_Legal_Entiry_Http_200() // Send to Register _ = await PostUpdateDataRecipientRequest(GetJsonFromModel(originalDataRecipient)); - - //Using the same Legal Entity, Add + // Using the same Legal Entity, Add DataRecipientMetadata newDataRecipientMetadata = GetCopyOfDataRecipient(originalDataRecipient); - newDataRecipientMetadata.DataRecipientBrands.First().DataRecipientBrandId = Guid.NewGuid().ToString(); - newDataRecipientMetadata.DataRecipientBrands.First().BrandName = "Brand Name 2"; - newDataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductId = Guid.NewGuid().ToString(); - newDataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductName = "Software Product Name 2"; + newDataRecipientMetadata.DataRecipientBrands[0].DataRecipientBrandId = Guid.NewGuid().ToString(); + newDataRecipientMetadata.DataRecipientBrands[0].BrandName = "Brand Name 2"; + newDataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductId = Guid.NewGuid().ToString(); + newDataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductName = "Software Product Name 2"; // Send Updated Data Recipient to Register var updateResponse = await PostUpdateDataRecipientRequest(GetJsonFromModel(newDataRecipientMetadata)); // Assert http response and database updates. DataRecipientMetadata expectedDataRecipientMetadata = GetCopyOfDataRecipient(originalDataRecipient); - expectedDataRecipientMetadata.DataRecipientBrands.Add(newDataRecipientMetadata.DataRecipientBrands.First()); + expectedDataRecipientMetadata.DataRecipientBrands.Add(newDataRecipientMetadata.DataRecipientBrands[0]); VerifySuccessfulDataRecipientUpdate(expectedDataRecipientMetadata, updateResponse); // Assert Participation Record created correctly @@ -136,8 +137,8 @@ public async Task AC08_Add_Multiple_Duplicate_Certificates() // Generate valid payload without a Software Product DataRecipientMetadata dataRecipient = GenerateValidDataRecipient(false); - //Add Duplicate Cert - dataRecipient.DataRecipientBrands.First().SoftwareProducts.First().Certificates.Add(new DataRecipientMetadata.Certificate() + // Add Duplicate Cert + dataRecipient.DataRecipientBrands[0].SoftwareProducts[0].Certificates.Add(new DataRecipientMetadata.Certificate() { CommonName = "Test Automation Certificate Common Name", Thumbprint = TEST_DATA_THUMBPRINT @@ -147,7 +148,7 @@ public async Task AC08_Add_Multiple_Duplicate_Certificates() var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipient)); // Remove one of the certificates from expected results - dataRecipient.DataRecipientBrands.First().SoftwareProducts.First().Certificates.Remove(dataRecipient.DataRecipientBrands.First().SoftwareProducts.First().Certificates.First()); + dataRecipient.DataRecipientBrands[0].SoftwareProducts[0].Certificates.Remove(dataRecipient.DataRecipientBrands[0].SoftwareProducts[0].Certificates[0]); // Assert http response and database updates. VerifySuccessfulDataRecipientUpdate(dataRecipient, response); @@ -156,7 +157,6 @@ public async Task AC08_Add_Multiple_Duplicate_Certificates() VerifyParticipationRecord(dataRecipient.LegalEntityId, DATA_RECIPIENT_PARTICIPATION_TYPE, null, dataRecipient.Status); } - [Fact] public async Task ACXX_Add_New_Legal_Entity_With_Multiple_Software_Products_Http_200() { @@ -164,7 +164,7 @@ public async Task ACXX_Add_New_Legal_Entity_With_Multiple_Software_Products_Http DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); // Add second Software Product - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(new DataRecipientMetadata.SoftwareProduct() + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(new DataRecipientMetadata.SoftwareProduct() { SoftwareProductId = Guid.NewGuid().ToString(), SoftwareProductName = "Test Automation Data Recipient Brand Name 001 - Software Product 002", @@ -191,7 +191,7 @@ public async Task ACXX_Add_New_Legal_Entity_With_Multiple_Software_Products_Http var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); // Assert http response and database updates. - dataRecipientMetadata.DataRecipientBrands.ToList().OrderBy(b => b.DataRecipientBrandId); + dataRecipientMetadata.DataRecipientBrands.OrderBy(b => b.DataRecipientBrandId); // Assert http response and database updates. VerifySuccessfulDataRecipientUpdate(dataRecipientMetadata, response); @@ -206,7 +206,7 @@ public async Task AC10_Scope_Not_Specified_Http_200() // Generate valid payload DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); - //Remove Scope(s) + // Remove Scope(s) dataRecipientMetadata.DataRecipientBrands.ForEach(dataRecipientBrand => { dataRecipientBrand.SoftwareProducts.ForEach(softwareProduct => @@ -218,7 +218,7 @@ public async Task AC10_Scope_Not_Specified_Http_200() // Send to Register var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - //Update expected Data Reciepent to use default scopes. + // Update expected Data Reciepent to use default scopes. dataRecipientMetadata.DataRecipientBrands.ForEach(dataRecipientBrand => { dataRecipientBrand.SoftwareProducts.ForEach(softwareProduct => @@ -234,7 +234,6 @@ public async Task AC10_Scope_Not_Specified_Http_200() // Assert Participation Record created correctly VerifyParticipationRecord(dataRecipientMetadata.LegalEntityId, DATA_RECIPIENT_PARTICIPATION_TYPE, null, dataRecipientMetadata.Status); - } [Fact] @@ -251,7 +250,6 @@ public async Task AC11_Add_New_Legal_Entity_With_Only_Mandatory_Fields_Http_200( // Assert Participation Record created correctly VerifyParticipationRecord(dataRecipientMetadata.LegalEntityId, DATA_RECIPIENT_PARTICIPATION_TYPE, null, dataRecipientMetadata.Status); - } [Fact] @@ -260,7 +258,7 @@ public async Task AC11_Add_New_Legal_Entity_With_Only_Mandatory_Fields_And_Nil_S // Generate valid payload with only mandatory/minimun fields. DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Clear(); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Clear(); // Send to Register var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); @@ -283,7 +281,7 @@ public async Task AC12_Missing_Version_In_Header_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingHeader, "An API version x-v header is required, but was not specified."); // Assert Response @@ -301,14 +299,13 @@ public async Task AC13_Invalid_Version_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.InvalidVersion, "Version is not a positive Integer."); // Assert Response await VerifyBadRequest(expectedErrors, response); } - [Theory] [InlineData("20")] public async Task AC20_Unsupported_Version_Http_400(string xv) @@ -319,7 +316,7 @@ public async Task AC20_Unsupported_Version_Http_400(string xv) // Send to Register with blank x-v header var response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata), xv: xv); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.UnsupportedVersion, "Requested version is lower than the minimum version or greater than maximum version."); // Assert Response @@ -363,7 +360,7 @@ public async Task AC14_Missing_Mandatory_Fields_Http_400(string testId, string e string expectedMissingField = ConvertJsonPathToPascalCase(elementToRemove); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingField, $"{expectedMissingField}"); @@ -378,25 +375,23 @@ public async Task AC14_Missing_Mandatory_Fields_Http_400(string testId, string e // Assert Response await VerifyBadRequest(expectedErrors, response); - } [Fact] public async Task AC14_Missing_Multiple_Mandatory_Fields_Http_400() { - DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(); Log.Information($"Original Payload:\n{GetJsonFromModel(dataRecipientMetadata)}"); // Remove mandatory fields - dataRecipientMetadata.AccreditationNumber = ""; + dataRecipientMetadata.AccreditationNumber = string.Empty; dataRecipientMetadata.LegalEntityName = null; - dataRecipientMetadata.DataRecipientBrands.First().LogoUri = ""; - dataRecipientMetadata.DataRecipientBrands.First().BrandName = null; + dataRecipientMetadata.DataRecipientBrands[0].LogoUri = string.Empty; + dataRecipientMetadata.DataRecipientBrands[0].BrandName = null; Log.Information($"Modified Payload (elements removed):\n{GetJsonFromModel(dataRecipientMetadata)}"); - ExpectedErrors expectedErrors = new(); + ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingField, "LegalEntityName"); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingField, "AccreditationNumber"); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.MissingField, "DataRecipientBrands[0].BrandName"); @@ -407,7 +402,6 @@ public async Task AC14_Missing_Multiple_Mandatory_Fields_Http_400() // Assert Response await VerifyBadRequest(expectedErrors, response); - } [Theory] @@ -429,7 +423,6 @@ public async Task AC15_Valid_And_Invalid_Data_Recipient_Acreditation_Level(strin dataRecipientMetadata.AccreditationLevel = dataRecipientMetadata.AccreditationLevel.ToUpper(); await VerifyInvalidAndValidFieldResponse(response, dataRecipientMetadata, "AccreditationLevel", accreditationLevel, isValid); - } [Theory] @@ -455,7 +448,6 @@ public async Task AC15_Valid_And_Invalid_Data_Recipient_Organisation_Type(string dataRecipientMetadata.OrganisationType = dataRecipientMetadata.OrganisationType.ToUpper(); await VerifyInvalidAndValidFieldResponse(response, dataRecipientMetadata, "OrganisationType", orgType, isValid); - } [Theory] @@ -480,7 +472,6 @@ public async Task AC15_Valid_And_Invalid_Data_Recipient_Status(string status, bo dataRecipientMetadata.Status = dataRecipientMetadata.Status.ToUpper(); await VerifyInvalidAndValidFieldResponse(response, dataRecipientMetadata, "Status", status, isValid); - } [Theory] @@ -495,15 +486,14 @@ public async Task AC15_Valid_And_Invalid_Data_Recipient_Brand_Status(string stat Log.Information($"Original Payload:\n{GetJsonFromModel(dataRecipientMetadata)}"); // Modify DR Status - dataRecipientMetadata.DataRecipientBrands.First().Status = status; + dataRecipientMetadata.DataRecipientBrands[0].Status = status; Log.Information($"Modified Payload (elements removed):\n{GetJsonFromModel(dataRecipientMetadata)}"); HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - dataRecipientMetadata.DataRecipientBrands.First().Status = dataRecipientMetadata.DataRecipientBrands.First().Status.ToUpper(); + dataRecipientMetadata.DataRecipientBrands[0].Status = dataRecipientMetadata.DataRecipientBrands[0].Status.ToUpper(); await VerifyInvalidAndValidFieldResponse(response, dataRecipientMetadata, "Status", status, isValid); - } [Theory] @@ -518,15 +508,14 @@ public async Task AC15_Valid_And_Invalid_Data_Recipient_Software_Product_Status( Log.Information($"Original Payload:\n{GetJsonFromModel(dataRecipientMetadata)}"); // Modify DR - SP Status - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().Status = status; + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].Status = status; Log.Information($"Modified Payload (elements removed):\n{GetJsonFromModel(dataRecipientMetadata)}"); HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().Status = dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().Status.ToUpper(); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].Status = dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].Status.ToUpper(); await VerifyInvalidAndValidFieldResponse(response, dataRecipientMetadata, "Status", status, isValid); - } [Fact] @@ -542,12 +531,11 @@ public async Task AC16_Brand_Already_Associated_With_Different_Legal_Entity() Log.Information($"Modified from Database:\n{GetJsonFromModel(originalDataRecipientMetadata)}"); - string dataRecipientBrandId = originalDataRecipientMetadata.DataRecipientBrands.First().DataRecipientBrandId; + string dataRecipientBrandId = originalDataRecipientMetadata.DataRecipientBrands[0].DataRecipientBrandId; HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(originalDataRecipientMetadata)); - await VerifyInvalidPayloadResponse(response, originalDataRecipientMetadata, $"dataRecipientBrandId '{dataRecipientBrandId}' is already associated with a different legal entity."); - + await VerifyInvalidPayloadResponse(response, $"dataRecipientBrandId '{dataRecipientBrandId}' is already associated with a different legal entity."); } [Fact] @@ -559,18 +547,17 @@ public async Task AC17_Software_Product_Already_Associated_With_Dataholder_Brand Log.Information($"original from Database:\n{GetJsonFromModel(originalDataRecipientMetadata)}"); // Get First Software Product Id. - string softwareProductId = originalDataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductId; + string softwareProductId = originalDataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductId; // Generate new Data Recipient payload and replace softwareProductId with the already associated softwareProductId (already linked to orignal Dataholder Brand). DataRecipientMetadata generatedDataRecipientMetadata = GenerateValidDataRecipient(); - generatedDataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductId = softwareProductId; + generatedDataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductId = softwareProductId; Log.Information($"Payload to send:\n{GetJsonFromModel(generatedDataRecipientMetadata)}"); HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(generatedDataRecipientMetadata)); - await VerifyInvalidPayloadResponse(response, generatedDataRecipientMetadata, $"Value '{softwareProductId}' in SoftwareProductId is already associated with a different brand."); - + await VerifyInvalidPayloadResponse(response, $"Value '{softwareProductId}' in SoftwareProductId is already associated with a different brand."); } [Fact] @@ -579,7 +566,7 @@ public async Task AC18_Duplicate_Data_Recipient_Brands_Http_400() // Generate valid payload with only mandatory/minimun fields. DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); - string brandId = dataRecipientMetadata.DataRecipientBrands.First().DataRecipientBrandId; + string brandId = dataRecipientMetadata.DataRecipientBrands[0].DataRecipientBrandId; // Add Data Recipient Brand with duplicate id dataRecipientMetadata.DataRecipientBrands.Add(new DataRecipientMetadata.DataRecipientBrand() @@ -594,7 +581,7 @@ public async Task AC18_Duplicate_Data_Recipient_Brands_Http_400() HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - await VerifyInvalidPayloadResponse(response, dataRecipientMetadata, $"Duplicate DataRecipientBrandId '{brandId}' is not allowed in the same request"); + await VerifyInvalidPayloadResponse(response, $"Duplicate DataRecipientBrandId '{brandId}' is not allowed in the same request"); } [Fact] @@ -604,19 +591,18 @@ public async Task AC19_Duplicate_Software_Products_Http_400() DataRecipientMetadata dataRecipientMetadata = GenerateValidDataRecipient(false); Log.Information($"Original Payload:\n{GetJsonFromModel(dataRecipientMetadata)}"); - string initialSoftwareProductId = dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SoftwareProductId; + string initialSoftwareProductId = dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SoftwareProductId; // Add Software Product with duplicate id - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(GenerateNewSofwareProduct()); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(GenerateNewSofwareProduct()); // Set both Software Products to the same id - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.ForEach(sp => sp.SoftwareProductId = initialSoftwareProductId); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.ForEach(sp => sp.SoftwareProductId = initialSoftwareProductId); Log.Information($"Payload to send:\n{GetJsonFromModel(dataRecipientMetadata)}"); HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - await VerifyInvalidPayloadResponse(response, dataRecipientMetadata, $"Duplicate softwareProductId '{initialSoftwareProductId}' is not allowed in the same request"); - + await VerifyInvalidPayloadResponse(response, $"Duplicate softwareProductId '{initialSoftwareProductId}' is not allowed in the same request"); } [Fact] @@ -634,15 +620,14 @@ public async Task AC19_Multiple_Duplicate_Software_Products_Http_400() DataRecipientMetadata.SoftwareProduct softwareProduct4 = GenerateNewSofwareProduct(); softwareProduct3.SoftwareProductId = softwareProduct4.SoftwareProductId; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(softwareProduct1); - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(softwareProduct2); - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(softwareProduct3); - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.Add(softwareProduct4); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(softwareProduct1); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(softwareProduct2); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(softwareProduct3); + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts.Add(softwareProduct4); HttpResponseMessage response = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); - await VerifyInvalidPayloadResponse(response, dataRecipientMetadata, $"Duplicate softwareProductId '{softwareProduct1.SoftwareProductId}' is not allowed in the same request"); - + await VerifyInvalidPayloadResponse(response, $"Duplicate softwareProductId '{softwareProduct1.SoftwareProductId}' is not allowed in the same request"); } [Theory] @@ -691,10 +676,8 @@ public async Task ACXX_Maximum_Field_Length_Exceeded(string testId, string eleme // Send and verify negative scenario HttpResponseMessage responseNegative = await PostUpdateDataRecipientRequest(GetJsonFromModel(dataRecipientMetadata)); await VerifyInvalidAndValidFieldResponse(responseNegative, dataRecipientMetadata, ConvertJsonPathToPascalCase(elementUnderTest.GetLastFieldFromJsonPath()), maxLengthPlusOneValue, false); - } - private static string RemoveEmptyJsonArrays(string json) { JObject jObject = JsonConvert.DeserializeObject(json); @@ -710,7 +693,6 @@ private static DataRecipientMetadata GetCopyOfDataRecipient(DataRecipientMetadat private static async Task VerifyInvalidAndValidFieldResponse(HttpResponseMessage response, DataRecipientMetadata dataRecipientMetadata, string field, string value, bool isValid) { - if (isValid) { VerifySuccessfulDataRecipientUpdate(dataRecipientMetadata, response); @@ -724,21 +706,19 @@ private static async Task VerifyInvalidAndValidFieldResponse(HttpResponseMessage } } - private static async Task VerifyInvalidPayloadResponse(HttpResponseMessage response, DataRecipientMetadata dataRecipientMetadata, string expectedErrorMessage) + private static async Task VerifyInvalidPayloadResponse(HttpResponseMessage response, string expectedErrorMessage) { - ExpectedErrors expectedErrors = new ExpectedErrors(); expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.InvalidField, expectedErrorMessage); await VerifyBadRequest(expectedErrors, response); - } private static void VerifySuccessfulDataRecipientUpdate(DataRecipientMetadata expectedDataRecipientMetadata, HttpResponseMessage httpResponseFromRegister) { using (new AssertionScope()) { - //Check status code + // Check status code httpResponseFromRegister.StatusCode.Should().Be(HttpStatusCode.OK); // For each data recipient, order software products and set default scope to default if missing @@ -753,7 +733,6 @@ private static void VerifySuccessfulDataRecipientUpdate(DataRecipientMetadata ex sp.Scope ??= DEFAULT_SCOPES; } } - } expectedDataRecipientMetadata.DataRecipientBrands = expectedDataRecipientMetadata.DataRecipientBrands.OrderBy(b => b.DataRecipientBrandId).ToList(); @@ -771,11 +750,9 @@ private static void VerifySuccessfulDataRecipientUpdate(DataRecipientMetadata ex private static async Task PostUpdateDataRecipientRequest(string payload, string xv = UPDATE_DATA_RECIPIENT_CURRENT_API_VERSION) { - var clientHandler = new HttpClientHandler(); clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; var client = new HttpClient(clientHandler); - //var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:3500/admin/metadata/data-recipients"); var request = new HttpRequestMessage(HttpMethod.Post, $"{ADMIN_BaseURL}/admin/metadata/data-recipients"); request.Content = new StringContent($"{payload}", Encoding.UTF8, "application/json"); @@ -792,12 +769,10 @@ private static async Task PostUpdateDataRecipientRequest(st Log.Information($"Response from admin/metadata/data-recipients API: {httpResponse.StatusCode} \n{httpResponse.Content.ReadAsStringAsync().Result}"); return httpResponse; - } private static DataRecipientMetadata GenerateValidDataRecipient(bool includeOptionalFields = false) { - DataRecipientMetadata dataRecipientMetadata = new DataRecipientMetadata { LegalEntityId = Guid.NewGuid().ToString(), @@ -812,7 +787,7 @@ private static DataRecipientMetadata GenerateValidDataRecipient(bool includeOpti { DataRecipientBrandId = Guid.NewGuid().ToString(), BrandName = "Test Automation Data Recipient Brand Name 001", - LogoUri= $"{TEST_DATA_BASE_URI}/DRBlogo.png", + LogoUri = $"{TEST_DATA_BASE_URI}/DRBlogo.png", Status = "ACTIVE", SoftwareProducts = new List { @@ -832,19 +807,18 @@ private static DataRecipientMetadata GenerateValidDataRecipient(bool includeOpti dataRecipientMetadata.Arbn = "987654321"; dataRecipientMetadata.AnzsicDivision = "Test Automation Anzsic Division"; dataRecipientMetadata.OrganisationType = "COMPANY"; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().SectorIdentifierUri = $"{TEST_DATA_BASE_URI}/SectorIdentifierUri"; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().TosUri = $"{TEST_DATA_BASE_URI}/TosUri"; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().PolicyUri = $"{TEST_DATA_BASE_URI}/PolicyUri"; - dataRecipientMetadata.DataRecipientBrands.First().SoftwareProducts.First().Scope = "openid profile cdr-register:read"; - + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].SectorIdentifierUri = $"{TEST_DATA_BASE_URI}/SectorIdentifierUri"; + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].TosUri = $"{TEST_DATA_BASE_URI}/TosUri"; + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].PolicyUri = $"{TEST_DATA_BASE_URI}/PolicyUri"; + dataRecipientMetadata.DataRecipientBrands[0].SoftwareProducts[0].Scope = "openid profile cdr-register:read"; } return dataRecipientMetadata; - } + private static DataRecipientMetadata.SoftwareProduct GenerateNewSofwareProduct() { - DataRecipientMetadata.SoftwareProduct softwareProduct = new() + DataRecipientMetadata.SoftwareProduct softwareProduct = new DataRecipientMetadata.SoftwareProduct() { SoftwareProductId = Guid.NewGuid().ToString(), SoftwareProductName = "Test Automation Data Recipient Brand Name 001 - Software Product 001", @@ -871,7 +845,6 @@ private static DataRecipientMetadata.SoftwareProduct GenerateNewSofwareProduct() private static string GetActualDataRecipientFromDatabase(string legalEntityId) { - var legalEntityIdGuid = Guid.Parse(legalEntityId); try @@ -902,7 +875,7 @@ private static string GetActualDataRecipientFromDatabase(string legalEntityId) registeredCountry = participation.LegalEntity.RegisteredCountry, abn = participation.LegalEntity.Abn, acn = participation.LegalEntity.Acn, - arbn = participation.LegalEntity.Arbn == "" ? null : participation.LegalEntity.Arbn, + arbn = participation.LegalEntity.Arbn == string.Empty ? null : participation.LegalEntity.Arbn, anzsicDivision = participation.LegalEntity.AnzsicDivision, organisationType = participation.LegalEntity.OrganisationType.OrganisationTypeCode.ToUpper(), status = participation.Status.ParticipationStatusCode, @@ -935,10 +908,8 @@ private static string GetActualDataRecipientFromDatabase(string legalEntityId) }) }), }), - }); - string jsonRepresentation = JsonConvert.SerializeObject(expectedDataRecipients.First()); return RemoveEmptyJsonArrays(jsonRepresentation); diff --git a/Source/CDR.Register.IntegrationTests/BaseTest.cs b/Source/CDR.Register.IntegrationTests/BaseTest.cs index cfc6175..8bf4e23 100644 --- a/Source/CDR.Register.IntegrationTests/BaseTest.cs +++ b/Source/CDR.Register.IntegrationTests/BaseTest.cs @@ -31,30 +31,9 @@ namespace CDR.Register.IntegrationTests { - class DisplayTestMethodNameAttribute : BeforeAfterTestAttribute + public abstract class BaseTest : BaseTest0, IClassFixture { - static int count = 0; - - public override void Before(MethodInfo methodUnderTest) - { - Log.Information($"********** Test #{++count} - {methodUnderTest.DeclaringType?.Name}.{methodUnderTest.Name} **********"); - Console.WriteLine($"Test #{++count} - {methodUnderTest.DeclaringType?.Name}.{methodUnderTest.Name}"); - } - - } - - // Put all tests in same collection because we need them to run sequentially since some tests are mutating DB. - [Collection("IntegrationTests")] - [TestCaseOrderer("CDR.Register.IntegrationTests.XUnit.Orderers.AlphabeticalOrderer", "CDR.Register.IntegrationTests")] - [DisplayTestMethodName] - abstract public class BaseTest0 - { - } - - abstract public class BaseTest : BaseTest0, IClassFixture - { - - public BaseTest(ITestOutputHelper output, TestFixture testFixture) + protected BaseTest(ITestOutputHelper output, TestFixture testFixture) { Log.Logger = new LoggerConfiguration() .WriteTo.Console(theme: AnsiConsoleTheme.Code) @@ -67,18 +46,28 @@ public BaseTest(ITestOutputHelper output, TestFixture testFixture) .CreateLogger(); JsonConvert.DefaultSettings = () => new CdrJsonSerializerSettings(); - TestFixture=testFixture; + TestFixture = testFixture; } + private const string REGISTER_RW = "DefaultConnection"; + + public static string CONNECTIONSTRING_REGISTER_RW + { + get + { + var connectionString = ConnectionStringCheck.Check(Configuration.GetConnectionString(REGISTER_RW)); + if (connectionString == null) + { + throw new Exception($"Configuration setting for '{REGISTER_RW}' not found"); + } - const string REGISTER_RW = "DefaultConnection"; + return connectionString!; + } + } - static public string CONNECTIONSTRING_REGISTER_RW => - ConnectionStringCheck.Check(Configuration.GetConnectionString(REGISTER_RW) - ?? throw new Exception($"Configuration setting for '{REGISTER_RW}' not found")); + private static IConfigurationRoot? configuration; - static private IConfigurationRoot? configuration; - static public IConfigurationRoot Configuration + public static IConfigurationRoot Configuration { get { @@ -107,34 +96,48 @@ static public IConfigurationRoot Configuration // This seed data is copied from ..\CDR.Register.Admin.API\Data\ (see CDR.Register.IntegrationTests.csproj) public static readonly string SEEDDATA_FILENAME = $"seed-data.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; - // URLs - static public string TLS_BaseURL => Configuration["TLS_BaseURL"] - ?? throw new Exception($"{nameof(TLS_BaseURL)} - configuration setting not found"); - static public string MTLS_BaseURL => Configuration["MTLS_BaseURL"] - ?? throw new Exception($"{nameof(MTLS_BaseURL)} - configuration setting not found"); - static public string ADMIN_BaseURL => Configuration["Admin_BaseURL"] - ?? throw new Exception($"{nameof(ADMIN_BaseURL)} - configuration setting not found"); - static public readonly string ADMIN_URL = ADMIN_BaseURL + "/admin/metadata"; - static public readonly string IDENTITYSERVER_URL = MTLS_BaseURL + "/idp/connect/token"; - // START CTS Settings - static public string AZURE_AD_TOKEN_ENDPOINT_URL => Configuration["CtsSettings:AzureAd:TokenEndpointUrl"] ?? throw new Exception($"{nameof(AZURE_AD_TOKEN_ENDPOINT_URL)} - configuration setting not found"); - static public string AZURE_AD_CLIENT_ID => Configuration["CtsSettings:AzureAd:ClientId"] ?? throw new Exception($"{nameof(AZURE_AD_CLIENT_ID)} - configuration setting not found"); - static public string AZURE_AD_CLIENT_SECRET => Configuration["CtsSettings:AzureAd:ClientSecret"] ?? throw new Exception($"{nameof(AZURE_AD_CLIENT_SECRET)} - configuration setting not found"); - static public string AZURE_AD_UNAUTHORISED_CLIENT_ID => Configuration["CtsSettings:AzureAd:UnauthorisedClientId"] ?? throw new Exception($"{nameof(AZURE_AD_UNAUTHORISED_CLIENT_ID)} - configuration setting not found"); - static public string AZURE_AD_UNAUTHORISED_CLIENT_SECRET => Configuration["CtsSettings:AzureAd:UnauthorisedClientSecret"] ?? throw new Exception($"{nameof(AZURE_AD_UNAUTHORISED_CLIENT_SECRET)} - configuration setting not found"); - static public string AZURE_AD_SCOPE => Configuration["CtsSettings:AzureAd:Scope"] ?? throw new Exception($"{nameof(AZURE_AD_SCOPE)} - configuration setting not found"); - static public string AZURE_AD_GRANT_TYPE => Configuration["CtsSettings:AzureAd:GrantType"] ?? throw new Exception($"{nameof(AZURE_AD_GRANT_TYPE)} - configuration setting not found"); public const string EXPIRED_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyJ9.eyJhdWQiOiI3YzVmZmE2Yy1jN2ZhLTRlNDktODMyZi1lZWQ0MzBmODE1MjUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYWZiYmE3ZDAtNjc2Zi00MGI3LTkwYmQtOGY0NjM4MDM0YjgyL3YyLjAiLCJpYXQiOjE2ODM3ODIyMTgsIm5iZiI6MTY4Mzc4MjIxOCwiZXhwIjoxNjgzNzg2MTE4LCJhaW8iOiJFMlpnWUdnS2VoSXp6ODFpMWFYazA2NG5jcU15ZU90dUNhOUs4NTB6dGNiYjhvR1g5UVVBIiwiYXpwIjoiMzA5MjJhZWUtMDc5OS00NzkxLWFkNDUtMjI2NTUwZjg2OGJlIiwiYXpwYWNyIjoiMSIsIm9pZCI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInJoIjoiMC5BVUVBMEtlN3IyOW50MENRdlk5R09BTkxnbXo2WDN6NngwbE9neV91MURENEZTVkJBQUEuIiwicm9sZXMiOlsiQXBpLkFkbWluLlBhcnRpY2lwYW50TWV0YURhdGEuV3JpdGUiLCJBcGkuQWRtaW4uUGFydGljaXBhbnRNZXRhRGF0YS5SZWFkIl0sInN1YiI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInRpZCI6ImFmYmJhN2QwLTY3NmYtNDBiNy05MGJkLThmNDYzODAzNGI4MiIsInV0aSI6IlB6SEF3aTlXY2t5dXRkbGRSSkVTQUEiLCJ2ZXIiOiIyLjAifQ.VjMh6-FRMLWAIkYloADH--fTgUVfNgN2XYx3yeJUew1JiCRpiABj4JYkieBxkQ4vrWfj79F3O1ggf2SEOy49nym037CdA3TfW83kpw7MOVHH38-VG-LR_sobAMsS40N4dwNrvsfRQxjQha8gcnskPvtYAWYOII2vfMFxrxAeChwsDGd6A-b5-vo26GyQjebZLcfhMAgu79HFKgIrQRg9MYQ5ZI2wISi2T_d43RYyluXHBtVyCRIfEmUy3aTyJBo6ZHW5omhbUgDp9otwUmwFkv4xrmdrz5ADgqaMelEVllyJrUD9de_wvAh9V5q5Bu6bJhufQoKWXgO-dKIx6baJOA"; - static public string SSA_DOWNSTREAM_BASE_URL => Configuration["SSA_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(SSA_DOWNSTREAM_BASE_URL)} - configuration setting not found"); - static public string DISCOVERY_DOWNSTREAM_BASE_URL => Configuration["Discovery_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(DISCOVERY_DOWNSTREAM_BASE_URL)} - configuration setting not found"); - static public string STATUS_DOWNSTREAM_BASE_URL => Configuration["Status_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(STATUS_DOWNSTREAM_BASE_URL)} - configuration setting not found"); - static public string IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL => Configuration["IdentityProvider_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)} - configuration setting not found"); + public static string AZURE_AD_TOKEN_ENDPOINT_URL => Configuration["CtsSettings:AzureAd:TokenEndpointUrl"] ?? throw new Exception($"{nameof(AZURE_AD_TOKEN_ENDPOINT_URL)} - configuration setting not found"); + + public static string AZURE_AD_CLIENT_ID => Configuration["CtsSettings:AzureAd:ClientId"] ?? throw new Exception($"{nameof(AZURE_AD_CLIENT_ID)} - configuration setting not found"); + + public static string AZURE_AD_CLIENT_SECRET => Configuration["CtsSettings:AzureAd:ClientSecret"] ?? throw new Exception($"{nameof(AZURE_AD_CLIENT_SECRET)} - configuration setting not found"); + + public static string AZURE_AD_UNAUTHORISED_CLIENT_ID => Configuration["CtsSettings:AzureAd:UnauthorisedClientId"] ?? throw new Exception($"{nameof(AZURE_AD_UNAUTHORISED_CLIENT_ID)} - configuration setting not found"); + + public static string AZURE_AD_UNAUTHORISED_CLIENT_SECRET => Configuration["CtsSettings:AzureAd:UnauthorisedClientSecret"] ?? throw new Exception($"{nameof(AZURE_AD_UNAUTHORISED_CLIENT_SECRET)} - configuration setting not found"); + + public static string AZURE_AD_SCOPE => Configuration["CtsSettings:AzureAd:Scope"] ?? throw new Exception($"{nameof(AZURE_AD_SCOPE)} - configuration setting not found"); + + public static string AZURE_AD_GRANT_TYPE => Configuration["CtsSettings:AzureAd:GrantType"] ?? throw new Exception($"{nameof(AZURE_AD_GRANT_TYPE)} - configuration setting not found"); + + public static string SSA_DOWNSTREAM_BASE_URL => Configuration["SSA_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(SSA_DOWNSTREAM_BASE_URL)} - configuration setting not found"); + + public static string DISCOVERY_DOWNSTREAM_BASE_URL => Configuration["Discovery_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(DISCOVERY_DOWNSTREAM_BASE_URL)} - configuration setting not found"); + + public static string STATUS_DOWNSTREAM_BASE_URL => Configuration["Status_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(STATUS_DOWNSTREAM_BASE_URL)} - configuration setting not found"); + + public static string IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL => Configuration["IdentityProvider_Downstream_BaseUrl"] ?? throw new Exception($"{nameof(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)} - configuration setting not found"); public TestFixture TestFixture { get; } - //END CTS Settings + // END CTS Settings + + // URLs + public static string TLS_BaseURL => Configuration["TLS_BaseURL"] + ?? throw new Exception($"{nameof(TLS_BaseURL)} - configuration setting not found"); + + public static string MTLS_BaseURL => Configuration["MTLS_BaseURL"] + ?? throw new Exception($"{nameof(MTLS_BaseURL)} - configuration setting not found"); + + public static string ADMIN_BaseURL => Configuration["Admin_BaseURL"] + ?? throw new Exception($"{nameof(ADMIN_BaseURL)} - configuration setting not found"); + + public static readonly string ADMIN_URL = ADMIN_BaseURL + "/admin/metadata"; + + public static readonly string IDENTITYSERVER_URL = MTLS_BaseURL + "/idp/connect/token"; protected const string EXPECTED_UNSUPPORTED_ERROR = @" { @@ -170,10 +173,10 @@ static public IConfigurationRoot Configuration }"; /// - /// Assert response content and expectedJson are equivalent + /// Assert response content and expectedJson are equivalent. /// - /// The expected json - /// The response content + /// The expected json. + /// The response content. public static async Task Assert_HasContent_Json(string expectedJson, HttpContent content) { var actualJson = await content.ReadAsStringAsync(); @@ -182,14 +185,15 @@ public static async Task Assert_HasContent_Json(string expectedJson, HttpContent } /// - /// Assert actual json is equivalent to expected json + /// Assert actual json is equivalent to expected json. /// - /// The expected json - /// The actual json + /// The expected json. + /// The actual json. public static void Assert_Json(string expectedJson, string actualJson) { Log.Information($"Verifying Actual Json:\n{actualJson}\n against expected:\n{expectedJson}"); - //Use Fluentassertions.Json to compare. Whitespace and order is ignored. + + // Use Fluentassertions.Json to compare. Whitespace and order is ignored. JToken expected = JToken.Parse(expectedJson); JToken actual = JToken.Parse(actualJson); actual.Should().BeEquivalentTo(expected); @@ -197,17 +201,16 @@ public static void Assert_Json(string expectedJson, string actualJson) /// /// Assert headers has a single header with the expected value. - /// If expectedValue is null then just check for the existence of the header (and not it's value) + /// If expectedValue is null then just check for the existence of the header (and not it's value). /// - /// The expected header value - /// The headers to check - /// Name of header to check + /// The expected header value. + /// The headers to check. + /// Name of header to check. public static void Assert_HasHeader(string? expectedValue, HttpHeaders headers, string name) { headers.Should().NotBeNull(); if (headers != null) { - // headers.Contains(name).Should().BeTrue($"Header '{name}' is missing"); headers.Contains(name).Should().BeTrue($"should contain {name} header"); if (headers.Contains(name)) { @@ -224,9 +227,8 @@ public static void Assert_HasHeader(string? expectedValue, HttpHeaders headers, } /// - /// Assert header has content type of ApplicationJson + /// Assert header has content type of ApplicationJson. /// - /// public static void Assert_HasContentType_ApplicationJson(HttpContent content) { content.Should().NotBeNull(); @@ -236,7 +238,7 @@ public static void Assert_HasContentType_ApplicationJson(HttpContent content) } /// - /// Assert claim exists + /// Assert claim exists. /// public static void AssertClaim(IEnumerable claims, string claimType, string claimValue) { @@ -253,7 +255,7 @@ protected static string GetJsonFromModel(T model) } /// - /// Get status of SoftwareProduct + /// Get status of SoftwareProduct. /// public static int GetSoftwareProductStatusId(string softwareProductId) { @@ -267,7 +269,7 @@ public static int GetSoftwareProductStatusId(string softwareProductId) } /// - /// Set status of SoftwareProduct + /// Set status of SoftwareProduct. /// public static void SetSoftwareProductStatusId(string softwareProductId, int statusId) { @@ -287,11 +289,11 @@ public static void SetSoftwareProductStatusId(string softwareProductId, int stat if (selectCommand.ExecuteScalarInt32() != 1) { throw new Exception("Status not updated"); - }; + } } /// - /// Get status of Brand + /// Get status of Brand. /// public static int GetBrandStatusId(string brandId) { @@ -305,7 +307,7 @@ public static int GetBrandStatusId(string brandId) } /// - /// Set status of Brand + /// Set status of Brand. /// public static void SetBrandStatusId(string brandId, int statusId) { @@ -325,11 +327,11 @@ public static void SetBrandStatusId(string brandId, int statusId) if (selectCommand.ExecuteScalarInt32() != 1) { throw new Exception("Status not updated"); - }; + } } /// - /// Get participationid for brand + /// Get participationid for brand. /// public static string GetParticipationId(string brandId) { @@ -343,7 +345,7 @@ public static string GetParticipationId(string brandId) } /// - /// Get status of Participation + /// Get status of Participation. /// public static int GetParticipationStatusId(string participationId) { @@ -357,7 +359,7 @@ public static int GetParticipationStatusId(string participationId) } /// - /// Get status of Participation + /// Get status of Participation. /// public static void SetParticipationStatusId(string participationId, int statusId) { @@ -377,8 +379,9 @@ public static void SetParticipationStatusId(string participationId, int statusId if (selectCommand.ExecuteScalarInt32() != 1) { throw new Exception("Status not updated"); - }; + } } + protected static void VerifyParticipationRecord(string legalEntiryId, string expectedParticipationType, string expectedIndustryType, string expectedStatus) { using SqlConnection registerConnection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); @@ -459,12 +462,10 @@ protected static void VerifyBrandLastUpdatedDateRecord(string legalEntiryId) Log.Information($"Verifying LastUpdate Brand with Id: '{b.BrandId}' is within 120 seconds from '{DateTime.UtcNow}'"); b.LastUpdated.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(120), because: $"Brand with Id: '{b.BrandId}' was just updated/added."); } - } protected static ICollection GetActualBrandsFromDatabase(string legalEntityId) { - var legalEntityIdGuid = Guid.Parse(legalEntityId); try @@ -492,7 +493,7 @@ protected static async Task VerifyBadRequest(ExpectedErrors expectedErrors, Http using (new AssertionScope()) { - //Check status code + // Check status code httpResponseFromRegister.StatusCode.Should().Be(HttpStatusCode.BadRequest); string actualErrors = await httpResponseFromRegister.Content.ReadAsStringAsync(); @@ -507,7 +508,7 @@ protected static async Task VerifyNotAcceptableRequest(ExpectedErrors expectedEr using (new AssertionScope()) { - //Check status code + // Check status code httpResponseFromRegister.StatusCode.Should().Be(HttpStatusCode.NotAcceptable); string actualErrors = await httpResponseFromRegister.Content.ReadAsStringAsync(); @@ -523,13 +524,12 @@ protected static async Task VerifyUnauthorisedRequest(HttpResponseMessage httpRe using (new AssertionScope()) { - //Check status code + // Check status code httpResponseFromRegister.StatusCode.Should().Be(HttpStatusCode.Unauthorized); string actualErrors = await httpResponseFromRegister.Content.ReadAsStringAsync(); Assert_Json(expectedErrorJson, actualErrors); - } } @@ -549,7 +549,7 @@ protected static string ConvertJsonPathToPascalCase(string jsonPath) { JToken mainJoken = JObject.FromObject(model ?? throw new ArgumentNullException(nameof(model))); - //If an array element, remove element. + // If an array element, remove element. if (path.Last() == ']') { JToken token = mainJoken.SelectToken(path, true) ?? throw new Exception("mainJoken cannot be null."); @@ -562,20 +562,19 @@ protected static string ConvertJsonPathToPascalCase(string jsonPath) } return mainJoken.ToObject(); - } protected static T? ReplaceModelValueBasedOnJsonPath(T model, string path, string newValue) { JToken mainJoken = JObject.FromObject(model ?? throw new ArgumentNullException(nameof(model))); - JToken tokenToUpdate = mainJoken.SelectToken(path) ?? throw new Exception($"tokenToUpdate is null. Ensure json path '{path}' exists."); ; + JToken tokenToUpdate = mainJoken.SelectToken(path) ?? throw new Exception($"tokenToUpdate is null. Ensure json path '{path}' exists."); tokenToUpdate.Replace(newValue); return mainJoken.ToObject(); - } + public static string GenerateDynamicCtsUrl(string baseUrl, string? conformanceId = null) { if (conformanceId == null) @@ -590,9 +589,9 @@ public static string GenerateDynamicCtsUrl(string baseUrl, string? conformanceId protected static string ReplaceSecureHostName(string url, string hostNamedToReplace) { - string secureHostname = Configuration["SecureHostName"] ?? ""; + string secureHostname = Configuration["SecureHostName"] ?? string.Empty; - if (String.IsNullOrEmpty(secureHostname)) + if (string.IsNullOrEmpty(secureHostname)) { return url; } @@ -604,9 +603,9 @@ protected static string ReplaceSecureHostName(string url, string hostNamedToRepl protected static string ReplacePublicHostName(string url, string hostNamedToReplace) { - string publicHostname = Configuration["PublicHostName"] ?? ""; + string publicHostname = Configuration["PublicHostName"] ?? string.Empty; - if (String.IsNullOrEmpty(publicHostname)) + if (string.IsNullOrEmpty(publicHostname)) { return url; } @@ -615,6 +614,25 @@ protected static string ReplacePublicHostName(string url, string hostNamedToRepl return url.Replace(hostNamedToReplace, publicHostname); } } + } + [AttributeUsage(AttributeTargets.Class)] + internal class DisplayTestMethodNameAttribute : BeforeAfterTestAttribute + { + private static int count = 0; + + public override void Before(MethodInfo methodUnderTest) + { + Log.Information($"********** Test #{++count} - {methodUnderTest.DeclaringType?.Name}.{methodUnderTest.Name} **********"); + Console.WriteLine($"Test #{++count} - {methodUnderTest.DeclaringType?.Name}.{methodUnderTest.Name}"); + } + } + + // Put all tests in same collection because we need them to run sequentially since some tests are mutating DB. + [Collection("IntegrationTests")] + [TestCaseOrderer("CDR.Register.IntegrationTests.XUnit.Orderers.AlphabeticalOrderer", "CDR.Register.IntegrationTests")] + [DisplayTestMethodName] + public abstract class BaseTest0 + { } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj index facad0a..66df957 100644 --- a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj +++ b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj @@ -5,6 +5,7 @@ $(Version) $(Version) $(Version) + True @@ -32,19 +33,20 @@ - + - - - - + + + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -58,6 +60,14 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.IntegrationTests/ConnectionStringCheck.cs b/Source/CDR.Register.IntegrationTests/ConnectionStringCheck.cs index ceff2dc..87d55a0 100644 --- a/Source/CDR.Register.IntegrationTests/ConnectionStringCheck.cs +++ b/Source/CDR.Register.IntegrationTests/ConnectionStringCheck.cs @@ -1,24 +1,19 @@ -#nullable enable +#nullable enable using System; -using Xunit; -using FluentAssertions.Execution; -using FluentAssertions; namespace CDR.Register.IntegrationTests { - static public class ConnectionStringCheck + public static class ConnectionStringCheck { internal const string PRODUCTION_SERVER = "prod.database.windows.net"; // public code so only use substring for matching - // TODO - MJS - Whitelist would be better since if production database ever changes server this blacklist will fail unless someone remembers to update - static readonly string[] Blacklist = new string[] { - PRODUCTION_SERVER - }; + // MJS - Whitelist would be better since if production database ever changes server this blacklist will fail unless someone remembers to update + private static readonly string[] Blacklist = new string[] { PRODUCTION_SERVER }; - static public string? Check(string? connectionString) + public static string? Check(string? connectionString) { - if (!String.IsNullOrEmpty(connectionString)) + if (!string.IsNullOrEmpty(connectionString)) { // Reject if blacklisted string found in connectionString foreach (string blacklisted in Blacklist) @@ -33,53 +28,4 @@ static public class ConnectionStringCheck return connectionString; } } - - public class ConnectionStringCheckUnitTests - { - const string PRODUCTION_SERVER_FOO = "foo" + ConnectionStringCheck.PRODUCTION_SERVER + "foo"; // blacklist is checking for substrings, so surround with "foo" to ensure we are testing this - - [Theory] - [InlineData(PRODUCTION_SERVER_FOO)] - [InlineData(PRODUCTION_SERVER_FOO, true)] - public void WhenOnBlackList_ShouldThrowException(string connectionString, bool? uppercase = false) - { - if (uppercase == true) - { - connectionString = connectionString.ToUpper(); - } - - using (new AssertionScope()) - { - // Act/Assert - Action act = () => ConnectionStringCheck.Check(connectionString); - using (new AssertionScope()) - { - act.Should().Throw(); - } - } - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("foo")] - [InlineData("sql-cdrsandbox-dev.database.windows.net")] - [InlineData("sql-cdrsandbox-test.database.windows.net")] - [InlineData("localhost")] - [InlineData("mssql")] - public void WhenNotOnBlackList_ShouldNotThrowException(string? connectionString) - { - using (new AssertionScope()) - { - // Act/Assert - string? returnedConnectionString = null; - Action act = () => returnedConnectionString = ConnectionStringCheck.Check(connectionString); - using (new AssertionScope()) - { - act.Should().NotThrow(); - returnedConnectionString?.Should().Be(connectionString); - } - } - } - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.IntegrationTests/ConnectionStringCheckUnitTests.cs b/Source/CDR.Register.IntegrationTests/ConnectionStringCheckUnitTests.cs new file mode 100644 index 0000000..97cb5bf --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/ConnectionStringCheckUnitTests.cs @@ -0,0 +1,58 @@ +#nullable enable + +using System; +using FluentAssertions; +using FluentAssertions.Execution; +using Xunit; + +namespace CDR.Register.IntegrationTests +{ + public class ConnectionStringCheckUnitTests + { + private const string PRODUCTION_SERVER_FOO = "foo" + ConnectionStringCheck.PRODUCTION_SERVER + "foo"; // blacklist is checking for substrings, so surround with "foo" to ensure we are testing this + + [Theory] + [InlineData(PRODUCTION_SERVER_FOO)] + [InlineData(PRODUCTION_SERVER_FOO, true)] + public void WhenOnBlackList_ShouldThrowException(string connectionString, bool? uppercase = false) + { + if (uppercase == true) + { + connectionString = connectionString.ToUpper(); + } + + using (new AssertionScope()) + { + // Act/Assert + Action act = () => ConnectionStringCheck.Check(connectionString); + using (new AssertionScope()) + { + act.Should().Throw(); + } + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("foo")] + [InlineData("sql-cdrsandbox-dev.database.windows.net")] + [InlineData("sql-cdrsandbox-test.database.windows.net")] + [InlineData("localhost")] + [InlineData("mssql")] + public void WhenNotOnBlackList_ShouldNotThrowException(string? connectionString) + { + using (new AssertionScope()) + { + // Act/Assert + string? returnedConnectionString = null; + Action act = () => returnedConnectionString = ConnectionStringCheck.Check(connectionString); + using (new AssertionScope()) + { + act.Should().NotThrow(); + returnedConnectionString?.Should().Be(connectionString); + } + } + } + } +} diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs index 6a6281a..7bb84b4 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs @@ -1,18 +1,18 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; using System.Linq; namespace CDR.Register.IntegrationTests.Extensions { - static public class JTokenExtensions + public static class JTokenExtensions { /// - /// Remove child JToken from JToken + /// Remove child JToken from JToken. /// - /// JToken from which to remove child JToken - /// Key of child to remove - static public void Remove(this JToken jToken, string key) + /// JToken from which to remove child JToken. + /// Key of child to remove. + public static void Remove(this JToken jToken, string key) { var t = jToken[key]; @@ -32,18 +32,18 @@ static public void Remove(this JToken jToken, string key) } /// - /// Should a JToken be removed? + /// Should a JToken be removed?. /// public delegate bool ShouldRemove(JToken jToken); /// - /// Recursively remove child JTokens from a JToken + /// Recursively remove child JTokens from a JToken. /// - /// Root JToken to start processing from - /// Should a token be removed? + /// Root JToken to start processing from. + /// Should a token be removed?. public static void RemoveJTokens(this JToken rootJToken, ShouldRemove shouldRemove) { - List jTokensToRemove = new(); + List jTokensToRemove = []; void DoJToken(JToken jToken) { @@ -55,6 +55,7 @@ void DoJToken(JToken jToken) { jTokensToRemove.Add(childJToken); } + // Not marked for removal, so process children if applicable else if (childJToken.HasValues) { @@ -75,7 +76,7 @@ void DoJToken(JToken jToken) } /// - /// Recursively remove null JTokens from a root JToken + /// Recursively remove null JTokens from a root JToken. /// public static void RemoveNulls(this JToken rootJToken) { @@ -83,7 +84,7 @@ public static void RemoveNulls(this JToken rootJToken) } /// - /// Recursively remove empty array JTokens from a root JToken + /// Recursively remove empty array JTokens from a root JToken. /// public static void RemoveEmptyArrays(this JToken rootJToken) { @@ -101,6 +102,7 @@ public static void RemovePath(this JToken jToken, string jsonPath) } public delegate JToken Replace(JToken token); + public static void ReplacePath(this JToken jToken, string jsonPath, Replace replace) { var tokens = jToken.SelectTokens(jsonPath); @@ -112,7 +114,7 @@ public static void ReplacePath(this JToken jToken, string jsonPath, Replace repl } /// - /// Sort tokens under jsonPath by key + /// Sort tokens under jsonPath by key. /// public static void Sort(this JToken rootToken, string jsonPath, string key) { @@ -134,7 +136,7 @@ public static void Sort(this JToken rootToken, string jsonPath, string key) } /// - /// Sort array by key + /// Sort array by key. /// public static void SortArray(this JToken token, string key) { @@ -158,18 +160,19 @@ public static void SortArray(this JToken token, string key) } /// - /// Sort path arrays by key + /// Sort path arrays by key. /// - /// For arrays matching this path - /// Key to sort array by + /// Token to sort. + /// For arrays matching this path. + /// Key to sort array by. public static void SortArray(this JToken token, string path, string key) { var tokens = token.SelectTokens(path); - foreach (var _token in tokens) + foreach (var tokenToSort in tokens) { - _token.SortArray(key); + tokenToSort.SortArray(key); } } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs index fe28f4b..4fbf405 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using Newtonsoft.Json.Linq; namespace CDR.Register.IntegrationTests.Extensions @@ -6,8 +6,8 @@ namespace CDR.Register.IntegrationTests.Extensions public static class JsonExtensions { /// - /// Strip comments from json string. - /// The json will be reserialized so it's formatting may change (ie whitespace/indentation etc) + /// Strip comments from json string. + /// The json will be reserialized so it's formatting may change (ie whitespace/indentation etc). /// public static string JsonStripComments(this string json) { @@ -23,7 +23,7 @@ public static string JsonStripComments(this string json) } /// - /// Compare json. + /// Compare json. /// Json is converted to JTokens prior to comparision, thus formatting is ignore. /// Returns true if json is equivalent, otherwise false. /// diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs index 5183085..3a8a61d 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs @@ -1,4 +1,4 @@ -using System.IdentityModel.Tokens.Jwt; +using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using FluentAssertions; @@ -7,22 +7,23 @@ namespace CDR.Register.IntegrationTests.Extensions { - static public class JwtSecurityTokenExtensions + public static class JwtSecurityTokenExtensions { /// /// Get claim for claimType. Throws exception if no claim or multiple claims (ie must be a single claim for claimType). /// /// - static public Claim Claim(this JwtSecurityToken jwt, string claimType) - => jwt.Claims.Where(claim => claim.Type == claimType).Single(); + public static Claim Claim(this JwtSecurityToken jwt, string claimType) + => jwt.Claims.Single(claim => claim.Type == claimType); /// /// Assert JWT contains a claim with given value. /// - /// The claim type to assert - /// The claim value to assert. If null then claim value can be anything (it is not checked) - /// If true then the claim itself is optional and doesn't need to exist in the claims - static public void AssertClaim(this JwtSecurityToken jwt, string claimType, string? claimValue, bool optional = false) + /// The JWT token to assert. + /// The claim type to assert. + /// The claim value to assert. If null then claim value can be anything (it is not checked). + /// If true then the claim itself is optional and doesn't need to exist in the claims. + public static void AssertClaim(this JwtSecurityToken jwt, string claimType, string? claimValue, bool optional = false) { var claims = jwt.Claims.Where(claim => claim.Type == claimType); @@ -43,7 +44,7 @@ static public void AssertClaim(this JwtSecurityToken jwt, string claimType, stri } } - static public void AssertClaimIsArray(this JwtSecurityToken jwt, string claimType, string[]? claimValues) + public static void AssertClaimIsArray(this JwtSecurityToken jwt, string claimType, string[]? claimValues) { var claims = jwt.Claims.Where(claim => claim.Type == claimType); diff --git a/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs index e1c18f2..1775f06 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs @@ -1,14 +1,14 @@ -using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient; using System; namespace CDR.Register.IntegrationTests.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.Register.IntegrationTests/Extensions/StringExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/StringExtensions.cs index 643abbe..d92a07d 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/StringExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/StringExtensions.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CDR.Register.IntegrationTests.Extensions { @@ -14,7 +11,6 @@ public static string GenerateRandomString(this int length) Random random = new Random(); return new string(Enumerable.Repeat(allowableChars, length) .Select(s => s[random.Next(s.Length)]).ToArray()); - } public static string GetLastFieldFromJsonPath(this string path) diff --git a/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs b/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs index e5b2801..2b6a1aa 100644 --- a/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IdentityModel.Tokens.Jwt; using System.Net; using System.Net.Http; @@ -18,20 +18,24 @@ namespace CDR.Register.IntegrationTests.Gateway { /// /// Integration tests for mTLS Gateway. - /// + /// public class US12841_Gateway_MTLS_Tests : BaseTest { - public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + // Client certificates - const string INVALID_CERTIFICATE_FILENAME = "Certificates/client-invalid.pfx"; + private const string INVALID_CERTIFICATE_FILENAME = "Certificates/client-invalid.pfx"; // Server certificate - const string SERVER_CERTIFICATE_FILENAME = "Certificates/server.pfx"; - const string SERVER_CERTIFICATE_PASSWORD = "#M0ckDataHolder#"; + private const string SERVER_CERTIFICATE_FILENAME = "Certificates/server.pfx"; + private const string SERVER_CERTIFICATE_PASSWORD = "#M0ckDataHolder#"; // Client assertion - private static readonly string AUDIENCE = IDENTITYSERVER_URL; private const string SCOPE = "cdr-register:read"; + private static readonly string AUDIENCE = IDENTITYSERVER_URL; // Token request private const string GRANT_TYPE = "client_credentials"; @@ -40,27 +44,28 @@ public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture te private const string ISSUER = CLIENT_ID; // Participation/Brand/SoftwareProduct Ids - private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup + private const string BRANDID = "20C0864B-CEEF-4DE0-8944-EB0962F825EB"; private const string SOFTWAREPRODUCTID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; /// - /// Get HttpClient with client certificate + /// Get HttpClient with client certificate. /// private static HttpClient GetClient(string certificateFilename, string certificatePassword) { - var _clientHandler = new HttpClientHandler(); - _clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; // Attach certificate var clientCertificate = new X509Certificate2(certificateFilename, certificatePassword, X509KeyStorageFlags.Exportable); - _clientHandler.ClientCertificates.Add(clientCertificate); + clientHandler.ClientCertificates.Add(clientCertificate); - return new HttpClient(_clientHandler); + return new HttpClient(clientHandler); } /// - /// Get HttpRequestMessage for access token request + /// Get HttpRequestMessage for access token request. /// private static HttpRequestMessage GetAccessTokenRequest(string certificateFilename, string certificatePassword) { @@ -115,8 +120,8 @@ static string BuildContent(string scope, string grant_type, string client_id, st [Fact] public async Task AC01_PostAccessTokenRequest_WithClientCert_ShouldRespondWith_200OK_AccessToken() { - // Expected JWT claims - string JWT_CLAIM_ISS = $"{TLS_BaseURL}/idp"; + // Expected JWT claims + string jwtClaimIss = $"{TLS_BaseURL}/idp"; const string JWT_CLAIM_AUD = "cdr-register"; const string JWT_CLAIM_CLIENT_ID = CLIENT_ID; const string JWT_CLAIM_SCOPE = SCOPE; @@ -126,7 +131,7 @@ public async Task AC01_PostAccessTokenRequest_WithClientCert_ShouldRespondWith_2 const int ACCESSTOKEN_EXPIRESIN = 300; const string ACCESSTOKEN_TOKENTYPE = JwtBearerDefaults.AuthenticationScheme; - // Arrange + // Arrange var client = GetClient(CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD); var accessTokenRequest = GetAccessTokenRequest(CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD); @@ -156,7 +161,7 @@ public async Task AC01_PostAccessTokenRequest_WithClientCert_ShouldRespondWith_2 // Assert - Check the JWT access_token var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken.access_token); - AssertClaim(jwt.Claims, "iss", JWT_CLAIM_ISS); + AssertClaim(jwt.Claims, "iss", jwtClaimIss); AssertClaim(jwt.Claims, "aud", JWT_CLAIM_AUD); AssertClaim(jwt.Claims, "client_id", JWT_CLAIM_CLIENT_ID); AssertClaim(jwt.Claims, "scope", JWT_CLAIM_SCOPE); @@ -216,8 +221,10 @@ public async Task AC04_PostAccessTokenRequest_WithServerCert_ShouldThrow_HttpReq } } - delegate void BeforeDataHolderBrandsRequest(); - delegate void AfterDataHolderBrandsRequest(); + private delegate void BeforeDataHolderBrandsRequest(); + + private delegate void AfterDataHolderBrandsRequest(); + private static async Task Test_GetDataHolderBrands( string certificateFilename, string certificatePassword, @@ -228,14 +235,14 @@ private static async Task Test_GetDataHolderBrands( string expectedContent = null) { // DataHolderBrands - string URL = $"{MTLS_BaseURL}/cdr-register/v1/banking/data-holders/brands"; + string url = $"{MTLS_BaseURL}/cdr-register/v1/banking/data-holders/brands"; const string XV = "2"; // Arrange var client = GetClient(certificateFilename, certificatePassword); - var request = new HttpRequestMessage(HttpMethod.Get, URL); + var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("x-v", XV); // Supply access token with request? @@ -304,7 +311,7 @@ public async Task AC09_GetDataHolderBrands_WithNoAccessToken_AndClientCert_Shoul await Test_GetDataHolderBrands(CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD, HttpStatusCode.Unauthorized, false); } - const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" + private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" { ""errors"": [ { @@ -337,7 +344,7 @@ await Test_GetDataHolderBrands( [Theory] [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive - [InlineData(3, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC11_GetDataHolderBrands_WithAccessToken_AndBrandStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( int brandStatusId, HttpStatusCode expectedStatusCode) @@ -350,17 +357,16 @@ await Test_GetDataHolderBrands( expectedStatusCode, beforeRequest: () => SetBrandStatusId(BRANDID, brandStatusId), afterRequest: () => SetBrandStatusId(BRANDID, saveBrandStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] [InlineData(1, HttpStatusCode.OK)] // Active - [InlineData(2, HttpStatusCode.Forbidden)] // Removed - [InlineData(3, HttpStatusCode.Forbidden)] // Suspended - [InlineData(4, HttpStatusCode.Forbidden)] // Revoked + [InlineData(2, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Suspended + [InlineData(4, HttpStatusCode.Forbidden)] // Revoked [InlineData(5, HttpStatusCode.Forbidden)] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden)] // Inactive + [InlineData(6, HttpStatusCode.Forbidden)] // Inactive public async Task AC12_GetDataHolderBrands_WithAccessToken_AndParticipationStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( int participationStatusId, HttpStatusCode expectedStatusCode) @@ -373,12 +379,13 @@ await Test_GetDataHolderBrands( expectedStatusCode, beforeRequest: () => SetParticipationStatusId(PARTICIPATIONID, participationStatusId), afterRequest: () => SetParticipationStatusId(PARTICIPATIONID, saveParticipationStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } - delegate void BeforeSSARequest(); - delegate void AfterSSARequest(); + private delegate void BeforeSSARequest(); + + private delegate void AfterSSARequest(); + private static async Task Test_GetSSA( string certificateFilename, string certificatePassword, @@ -388,13 +395,13 @@ private static async Task Test_GetSSA( AfterSSARequest afterRequest = null, string expectedContent = null) { - string URL = $"{MTLS_BaseURL}/cdr-register/v1/banking/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa"; + string url = $"{MTLS_BaseURL}/cdr-register/v1/banking/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa"; const string XV = "3"; // Arrange var client = GetClient(certificateFilename, certificatePassword); - var request = new HttpRequestMessage(HttpMethod.Get, URL); + var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("x-v", XV); // Supply access token with request? @@ -467,7 +474,7 @@ public async Task AC17_GetSSA_WithNoAccessToken_AndClientCert_ShouldRespondWith_ [Theory] [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive - [InlineData(3, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC18_GetSSA_WithAccessToken_AndSoftwareProductStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( int softwareProductStatusId, HttpStatusCode expectedStatusCode) @@ -480,14 +487,13 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, softwareProductStatusId), afterRequest: () => SetSoftwareProductStatusId(SOFTWAREPRODUCTID, saveSoftwareProductStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive - [InlineData(3, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC19_GetSSA_WithAccessToken_AndBrandStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( int brandStatusId, HttpStatusCode expectedStatusCode) @@ -500,17 +506,16 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetBrandStatusId(BRANDID, brandStatusId), afterRequest: () => SetBrandStatusId(BRANDID, saveBrandStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } [Theory] [InlineData(1, HttpStatusCode.OK)] // Active - [InlineData(2, HttpStatusCode.Forbidden)] // Removed - [InlineData(3, HttpStatusCode.Forbidden)] // Suspended - [InlineData(4, HttpStatusCode.Forbidden)] // Revoked + [InlineData(2, HttpStatusCode.Forbidden)] // Removed + [InlineData(3, HttpStatusCode.Forbidden)] // Suspended + [InlineData(4, HttpStatusCode.Forbidden)] // Revoked [InlineData(5, HttpStatusCode.Forbidden)] // Surrendered - [InlineData(6, HttpStatusCode.Forbidden)] // Inactive + [InlineData(6, HttpStatusCode.Forbidden)] // Inactive public async Task AC20_GetSSA_WithAccessToken_AndParticipationStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( int participationStatusId, HttpStatusCode expectedStatusCode) @@ -523,8 +528,7 @@ await Test_GetSSA( expectedStatusCode, beforeRequest: () => SetParticipationStatusId(PARTICIPATIONID, participationStatusId), afterRequest: () => SetParticipationStatusId(PARTICIPATIONID, saveParticipationStatusId), - expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE - ); + expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } } } diff --git a/Source/CDR.Register.IntegrationTests/IdentityServer/US12665_IdentityServer_OIDC_Tests.cs b/Source/CDR.Register.IntegrationTests/IdentityServer/US12665_IdentityServer_OIDC_Tests.cs index 867a7fa..d35564e 100644 --- a/Source/CDR.Register.IntegrationTests/IdentityServer/US12665_IdentityServer_OIDC_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/IdentityServer/US12665_IdentityServer_OIDC_Tests.cs @@ -1,6 +1,5 @@ -using System.Net; +using System.Net; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; @@ -11,16 +10,19 @@ namespace CDR.Register.IntegrationTests.IdentityServer { /// /// Integration tests for Identity Server OIDC. - /// + /// public class US12665_IdentityServer_OIDC_Tests : BaseTest { - public US12665_IdentityServer_OIDC_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US12665_IdentityServer_OIDC_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + [Fact] public async Task AC01_Get_ShouldRespondWith_200OK_OIDC() { - // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, @@ -30,7 +32,6 @@ public async Task AC01_Get_ShouldRespondWith_200OK_OIDC() // Assert await AssertOidcConfiguration(response, TLS_BaseURL, MTLS_BaseURL); - } [Trait("Category", "CTSONLY")] @@ -39,8 +40,9 @@ public async Task AC02_Get_ShouldRespondWith_200OK_OIDC() { // Arrange string ctsBaseUrl = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}"; + // Act - var response = await new Infrastructure.API + var response = await new Infrastructure.Api { HttpMethod = HttpMethod.Get, URL = $"{ctsBaseUrl}/idp/.well-known/openid-configuration", @@ -48,7 +50,6 @@ public async Task AC02_Get_ShouldRespondWith_200OK_OIDC() // Assert await AssertOidcConfiguration(response, ReplacePublicHostName(ctsBaseUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), ReplaceSecureHostName(ctsBaseUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)); - } private static async Task AssertOidcConfiguration(HttpResponseMessage response, string publicBaseUrl, string secureBaseUrl) diff --git a/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs b/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs index a0bf65d..6ce50d0 100644 --- a/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; @@ -13,7 +13,6 @@ using FluentAssertions; using FluentAssertions.Execution; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; using Serilog; using Xunit; @@ -26,10 +25,14 @@ namespace CDR.Register.IntegrationTests.IdentityServer { /// /// Integration tests for Identity Server Token Tests. - /// + /// public class US14045_IdentityServer_Token_Tests : BaseTest { - public US14045_IdentityServer_Token_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US14045_IdentityServer_Token_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + protected const string CLIENTASSERTION_ISSUER = "86ecb655-9eba-409c-9be3-59e7adf7080d"; protected static string CLIENTASSERTION_AUDIENCE => MTLS_BaseURL + "/idp/connect/token"; @@ -41,24 +44,24 @@ public US14045_IdentityServer_Token_Tests(ITestOutputHelper outputHelper, TestFi private static HttpClient GetClient() { - var _clientHandler = new HttpClientHandler(); - _clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - return new HttpClient(_clientHandler); + return new HttpClient(clientHandler); } private static HttpClient GetClientWithCertificate( string certificateFilename = CERTIFICATE_FILENAME, string certificatePassword = CERTIFICATE_PASSWORD) { - var _clientHandler = new HttpClientHandler(); - _clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; // Attach certificate var clientCertificate = new X509Certificate2(certificateFilename, certificatePassword, X509KeyStorageFlags.Exportable); - _clientHandler.ClientCertificates.Add(clientCertificate); + clientHandler.ClientCertificates.Add(clientCertificate); - return new HttpClient(_clientHandler); + return new HttpClient(clientHandler); } private static string GetClientAssertion( @@ -81,7 +84,7 @@ private static string GetClientAssertion( } var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, jtiClaim); - return tokenizer.Generate(clientAssertionIssuer, clientAssetionAudience, subClaim, signingAlgorithm: signingAlgorithm); + return tokenizer.Generate(clientAssertionIssuer, clientAssetionAudience, subClaim, signingAlgorithm: signingAlgorithm!); } private static HttpRequestMessage GetAccessTokenRequest( @@ -89,7 +92,7 @@ private static HttpRequestMessage GetAccessTokenRequest( string? client_id, string? client_assertion_type, string? client_assertion, - string content_type_header = "application/x-www-form-urlencoded", + string? content_type_header = "application/x-www-form-urlencoded", string scope = CLIENTASSERTION_SCOPE, string? tokenEndpointUrl = null) { @@ -132,7 +135,7 @@ static string BuildContent(string? grant_type, string? client_id, string? client Content = new StringContent( BuildContent(grant_type, client_id, client_assertion_type, client_assertion, scope), Encoding.UTF8, - content_type_header) + content_type_header!) }; if (string.IsNullOrEmpty(content_type_header)) @@ -159,7 +162,7 @@ static async Task GetValidatedToken(AccessToken accessToken) var jwksResponse = await jwksClient.GetAsync($"{TLS_BaseURL}/idp/.well-known/openid-configuration/jwks"); var jwks = new JsonWebKeySet(await jwksResponse.Content.ReadAsStringAsync()); - // Setup validation paramters + // Setup validation paramters var validationParameters = new TokenValidationParameters() { ValidateLifetime = true, @@ -167,7 +170,7 @@ static async Task GetValidatedToken(AccessToken accessToken) RequireSignedTokens = true, ValidateIssuerSigningKey = true, - IssuerSigningKey = jwks.Keys.First(), + IssuerSigningKey = jwks.Keys[0], ValidateIssuer = true, ValidIssuer = $"{TLS_BaseURL}/idp", @@ -193,7 +196,7 @@ static async Task GetValidatedToken(AccessToken accessToken) CLIENTASSERTION_CLIENT_ASSERTION_TYPE, clientAssertion); - // Act + // Act var response = await client.SendAsync(request); Log.Information("Response from {IdentityServerUrl}: {StatusCode} \n{Content}", IDENTITYSERVER_URL, response.StatusCode, await response.Content.ReadAsStringAsync()); @@ -219,10 +222,10 @@ static async Task GetValidatedToken(AccessToken accessToken) // Assert - Check scope accessToken.scope.Should().Contain("cdr-register:read"); - // Assert - Validate access token + // Assert - Validate access token SecurityToken? validatedToken = null; Func getValidatedTokenFunc = async () => validatedToken = await GetValidatedToken(accessToken); - await getValidatedTokenFunc.Should().NotThrowAsync(); // If token is valid then no exceptions should be thrown + await getValidatedTokenFunc.Should().NotThrowAsync(); // If token is valid then no exceptions should be thrown validatedToken?.Should().NotBeNull(); // Assert - Check the cnf claim @@ -265,16 +268,16 @@ public async Task AC04_TokenRequest_InvaldiContentType_ShouldRespondWith_400BadR if (expectedError != null) { - // Assert - Check error response + // Assert - Check error response await Assert_HasContent_Json(expectedError, response.Content); } } } [Theory] - [InlineData(null)] // omit granttype - [InlineData("")] // blank granttype - [InlineData("foo")] // non-blank + [InlineData(null)] // omit granttype + [InlineData("")] // blank granttype + [InlineData("foo")] // non-blank public async Task AC05_TokenRequest_InvalidGrantType_ShouldRespondWith_400BadRequest_ErrorResponse(string? grantType) { // Arrange @@ -298,7 +301,7 @@ public async Task AC05_TokenRequest_InvalidGrantType_ShouldRespondWith_400BadReq response.StatusCode.Should().Be(HttpStatusCode.BadRequest); // Assert - Check error response - var expectedContent = String.IsNullOrEmpty(grantType) ? + var expectedContent = string.IsNullOrEmpty(grantType) ? @"{""error"":""invalid_client"",""error_description"":""grant_type not provided""}" : @"{""error"":""unsupported_grant_type"",""error_description"":""grant_type must be client_credentials""}"; await Assert_HasContent_Json(expectedContent, response.Content); @@ -306,8 +309,8 @@ public async Task AC05_TokenRequest_InvalidGrantType_ShouldRespondWith_400BadReq } [Theory] - [InlineData(null)] // omit client assertion - [InlineData("")] // blank + [InlineData(null)] // omit client assertion + [InlineData("")] // blank [InlineData("foo")] // invalid public async Task AC06_TokenRequest_InvalidClientAssertion_ShouldRespondWith_400BadRequest_ErrorResponse(string? clientAssertion) { @@ -330,7 +333,7 @@ public async Task AC06_TokenRequest_InvalidClientAssertion_ShouldRespondWith_400 response.StatusCode.Should().Be(HttpStatusCode.BadRequest); // Assert - Check error reponse - var expectedContent = String.IsNullOrEmpty(clientAssertion) ? + var expectedContent = string.IsNullOrEmpty(clientAssertion) ? @"{""error"":""invalid_client"",""error_description"":""client_assertion not provided""}" : @"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - token validation error""}"; await Assert_HasContent_Json(expectedContent, response.Content); @@ -339,7 +342,7 @@ public async Task AC06_TokenRequest_InvalidClientAssertion_ShouldRespondWith_400 [Theory] [InlineData(null)] // omit client assertion type - [InlineData("")] // blank + [InlineData("")] // blank [InlineData("foo")] // invalid public async Task AC07_AC08_TokenRequest_InvalidClientAssertionType_ShouldRespondWith_400BadRequest_ErrorResponse(string? clientAssertionType) { @@ -364,7 +367,7 @@ public async Task AC07_AC08_TokenRequest_InvalidClientAssertionType_ShouldRespon response.StatusCode.Should().Be(HttpStatusCode.BadRequest); // Assert - Check error reponse - var expectedContent = String.IsNullOrEmpty(clientAssertionType) ? + var expectedContent = string.IsNullOrEmpty(clientAssertionType) ? @"{""error"":""invalid_client"",""error_description"":""client_assertion_type not provided""}" : @"{""error"":""invalid_client"",""error_description"":""client_assertion_type must be urn:ietf:params:oauth:client-assertion-type:jwt-bearer""}"; await Assert_HasContent_Json(expectedContent, response.Content); @@ -372,7 +375,7 @@ public async Task AC07_AC08_TokenRequest_InvalidClientAssertionType_ShouldRespon } [Theory] - [InlineData("")] // blank + [InlineData("")] // blank [InlineData("cdr-register:bank:read")] // invalid old scope [InlineData("foo")] // invalid public async Task AC09_TokenRequest_InvaldScope_ShouldRespondWith_400BadRequest_ErrorResponse(string scope) @@ -413,7 +416,7 @@ public async Task AC11_TokenRequest_MissingJTI_ShouldRespondWith_400BadRequest_E // Arrange var client = GetClientWithCertificate(); - var clientAssertion = GetClientAssertion(jtiClaim: ""); + var clientAssertion = GetClientAssertion(jtiClaim: string.Empty); var request = GetAccessTokenRequest( CLIENTASSERTION_GRANT_TYPE, @@ -546,7 +549,7 @@ public async Task AC15_TokenRequest_DuplicateJTI_ShouldRespondWith_400BadRequest // Assert - Check status code response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - // Assert - Check error response + // Assert - Check error response var expectedContent = @"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - 'jti' in the client assertion token must be unique""}"; await Assert_HasContent_Json(expectedContent, response.Content); @@ -554,7 +557,7 @@ public async Task AC15_TokenRequest_DuplicateJTI_ShouldRespondWith_400BadRequest } [Theory] - [InlineData(ADDITIONAL_CERTIFICATE_FILENAME, ADDITIONAL_CERTIFICATE_PASSWORD)] // invalid certificate + [InlineData(ADDITIONAL_CERTIFICATE_FILENAME, ADDITIONAL_CERTIFICATE_PASSWORD)] // invalid certificate public async Task AC16_TokenRequest_ValidWithInvalidClientCertificate_ShouldRespondWith_400BadRequest_ErrorResponse(string certificateFilename, string certificatePassword) { // Arrange @@ -584,10 +587,10 @@ public async Task AC16_TokenRequest_ValidWithInvalidClientCertificate_ShouldResp } // Use MemberData due to dynamic aud strings - [Theory, MemberData(nameof(ValidAudienceScenarios))] + [Theory] + [MemberData(nameof(ValidAudienceScenarios))] public async Task AC00_TokenRequest_ValidAudience(string aud, string scenarioDescription) { - Log.Information("Running positive test case for valid audience of {aud} - {scenarioDescription}", aud, scenarioDescription); // Arrange @@ -614,7 +617,6 @@ public async Task AC00_TokenRequest_ValidAudience(string aud, string scenarioDes string responseString = await response.Content.ReadAsStringAsync(); responseString.Should().Contain("access_token", because: aud + " - " + scenarioDescription); } - } public static IEnumerable ValidAudienceScenarios @@ -627,10 +629,10 @@ public static IEnumerable ValidAudienceScenarios } // Use MemberData due to dynamic aud strings - [Theory, MemberData(nameof(InvalidAudienceScenarios))] + [Theory] + [MemberData(nameof(InvalidAudienceScenarios))] public async Task AC00_TokenRequest_InvalidAudience(string aud, string scenarioDescription) { - Log.Information("Running negative test case for invalid audience of {aud} - {scenarioDescription}", aud, scenarioDescription); // Arrange @@ -638,7 +640,6 @@ public async Task AC00_TokenRequest_InvalidAudience(string aud, string scenarioD var clientAssertion = GetClientAssertion(clientAssetionAudience: aud); - var request = GetAccessTokenRequest( CLIENTASSERTION_GRANT_TYPE, CLIENTASSERTION_CLIENT_ID, @@ -654,10 +655,9 @@ public async Task AC00_TokenRequest_InvalidAudience(string aud, string scenarioD // Assert - Check status code response.StatusCode.Should().Be(HttpStatusCode.BadRequest, because: aud + " - " + scenarioDescription); - // Assert - Check error response + // Assert - Check error response await Assert_HasContent_Json(@"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - token validation error""}", response.Content); } - } public static IEnumerable InvalidAudienceScenarios @@ -666,7 +666,7 @@ public static IEnumerable InvalidAudienceScenarios { yield return new string[] { $"foo", "'foo' is not a valid token endpoint or OIDC issuer" }; yield return new string[] { $"{MTLS_BaseURL}/idp/connect/token/x", "Audience is only partial Token Endpoint match (only start matches)" }; - yield return new string[] { $"foo{MTLS_BaseURL}/idp/connect/token", "Audience is only partial Token Endpoint match (only end matches)" }; + yield return new string[] { $"foo{MTLS_BaseURL}/idp/connect/token", "Audience is only partial Token Endpoint match (only end matches)" }; yield return new string[] { $"{TLS_BaseURL}/idp/foo", "Audience is only partial OICD issuer match (only start matches)" }; yield return new string[] { $"foo{TLS_BaseURL}/idp", "Audience is only partial OICD issuer match (only end matches)" }; yield return new string[] { $"{TLS_BaseURL}/idp/connect/token", "Audience is only partial OICD issuer match - Ends with '/connect/token'" }; @@ -676,10 +676,10 @@ public static IEnumerable InvalidAudienceScenarios // Use MemberData due to dynamic aud strings [Trait("Category", "CTSONLY")] - [Theory, MemberData(nameof(ValidCtsUrlAudienceScenarios))] + [Theory] + [MemberData(nameof(ValidCtsUrlAudienceScenarios))] public static async Task AC00_TokenRequest_ValidAudience_DynamicBasePath(string aud, string scenarioDescription, string tokenEndpointUrl) { - Log.Information("Running positive test case for valid audience of {aud} - {scenarioDescription}", aud, scenarioDescription); // Arrange - Get access token @@ -706,10 +706,9 @@ public static async Task AC00_TokenRequest_ValidAudience_DynamicBasePath(string string responseString = await response.Content.ReadAsStringAsync(); responseString.Should().Contain("access_token", because: aud + " - " + scenarioDescription); } - } - public static IEnumerable ValidCtsUrlAudienceScenarios + public static IEnumerable ValidCtsUrlAudienceScenarios { get { @@ -723,10 +722,10 @@ public static IEnumerable ValidCtsUrlAudienceScenarios // Use MemberData due to dynamic aud strings [Trait("Category", "CTSONLY")] - [Theory, MemberData(nameof(InvalidCtsUrlAudienceScenarios))] + [Theory] + [MemberData(nameof(InvalidCtsUrlAudienceScenarios))] public async Task AC00_TokenRequest_InvalidAudience_DynamicBasePath(string aud, string scenarioDescription, string tokenEndpointUrl) { - Log.Information("Running negative test case for audience of {aud} - {scenarioDescription}", aud, scenarioDescription); // Arrange - Get access token @@ -749,10 +748,9 @@ public async Task AC00_TokenRequest_InvalidAudience_DynamicBasePath(string aud, // Assert - Check status code response.StatusCode.Should().Be(HttpStatusCode.BadRequest, because: aud + " - " + scenarioDescription); - // Assert - Check error response + // Assert - Check error response await Assert_HasContent_Json(@"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - token validation error""}", response.Content); } - } public static IEnumerable InvalidCtsUrlAudienceScenarios diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs index fbf87ab..e7b5fa5 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs @@ -1,29 +1,30 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.JwtBearer; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -#nullable enable +#nullable enable namespace CDR.Register.IntegrationTests.Infrastructure { /// - /// Call API + /// Call API. /// - public class API + public class Api { private const string CERTIFICATE_THUMBPRINT_HEADER_NAME = "X-SSLClientCertThumbprint"; private const string CERTIFICATE_CN_HEADER_NAME = "X-SSLClientCertCN"; + /// - /// Filename of certificate to use. + /// Filename of certificate to use. /// If null then no certificate will be attached to the request. /// public string? CertificateFilename { get; set; } /// - /// Password for certificate. + /// Password for certificate. /// If null then no certificate password will be set. /// public string? CertificatePassword { get; set; } @@ -64,18 +65,22 @@ public class API public string? IfNoneMatch { get; set; } public string? CertificateThumbprint { get; set; } = null; + public string? CertificateCn { get; set; } = null; /// /// Send a request to the API. /// - /// The API response + /// The API response. public async Task SendAsync() { // Build request HttpRequestMessage BuildRequest() { - if (HttpMethod == null) { throw new Exception($"{nameof(API)}.{nameof(SendAsync)}.{nameof(BuildRequest)} - {nameof(HttpMethod)} not set"); } + if (HttpMethod == null) + { + throw new Exception($"{nameof(Api)}.{nameof(SendAsync)}.{nameof(BuildRequest)} - {nameof(HttpMethod)} not set"); + } var request = new HttpRequestMessage(HttpMethod, URL); @@ -127,8 +132,7 @@ HttpClient GetClient() clientHandler.ClientCertificates.Add(new X509Certificate2( CertificateFilename, CertificatePassword, - X509KeyStorageFlags.Exportable - )); + X509KeyStorageFlags.Exportable)); return new HttpClient(clientHandler); } diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs index 17b0ad6..818847f 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -13,8 +13,6 @@ namespace CDR.Register.IntegrationTests.Infrastructure { public class AccessToken { - private static readonly string IDENTITYSERVER_URL = BaseTest.IDENTITYSERVER_URL; - private static readonly string AUDIENCE = IDENTITYSERVER_URL; private const string SCOPE = "cdr-register:read"; private const string GRANT_TYPE = "client_credentials"; private const string CLIENT_ID = "86ecb655-9eba-409c-9be3-59e7adf7080d"; @@ -22,35 +20,46 @@ public class AccessToken private const string ISSUER = CLIENT_ID; private const string CERTIFICATE_THUMBPRINT_HEADER_NAME = "X-SSLClientCertThumbprint"; private const string CERTIFICATE_CN_HEADER_NAME = "X-SSLClientCertCN"; + private static readonly string IDENTITYSERVER_URL = BaseTest.IDENTITYSERVER_URL; + private static readonly string AUDIENCE = IDENTITYSERVER_URL; /// - /// Filename of certificate to use. + /// Filename of certificate to use. /// If null then no certificate will be attached to the request. /// public string? CertificateFilename { get; set; } /// - /// Password for certificate. + /// Password for certificate. /// If null then no certificate password will be set. /// public string? CertificatePassword { get; set; } public string? Issuer { get; set; } = ISSUER; + public string Audience { get; set; } = AUDIENCE; + public string Scope { get; set; } = SCOPE; + public string GrantType { get; set; } = GRANT_TYPE; + public string? ClientId { get; set; } = CLIENT_ID; + public string ClientAssertionType { get; set; } = CLIENT_ASSERTION_TYPE; + public string TokenEndPoint { get; set; } = BaseTest.IDENTITYSERVER_URL; + public string? CertificateThumbprint { get; set; } = null; + public string? CertificateCn { get; set; } = null; /// - /// Get HttpRequestMessage for access token request + /// Get HttpRequestMessage for access token request. /// private HttpRequestMessage CreateAccessTokenRequest( - string? certificateFilename, string? certificatePassword, - string issuer, string audience, + string certificateFilename, string certificatePassword, + string? issuer, + string audience, string scope, string grant_type, string client_id, string client_assertion_type, string? certificateThumprint, string? certificateCn) { static string BuildContent(string scope, string grant_type, string client_id, string client_assertion_type, string client_assertion) @@ -86,7 +95,7 @@ static string BuildContent(string scope, string grant_type, string client_id, st } var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, Guid.NewGuid().ToString()); - var client_assertion = tokenizer.Generate(issuer, audience, issuer); + var client_assertion = tokenizer.Generate(issuer, audience, issuer ?? string.Empty); var request = new HttpRequestMessage(HttpMethod.Post, TokenEndPoint) { @@ -108,11 +117,10 @@ static string BuildContent(string scope, string grant_type, string client_id, st } /// - /// Get an access token from Identity Server + /// Get an access token from Identity Server. /// public async Task GetAsync(bool addCertificateToRequest = true) { - HttpResponseMessage response = await SendAccessTokenRequest(addCertificateToRequest); if (response.StatusCode != HttpStatusCode.OK) @@ -129,25 +137,25 @@ static string BuildContent(string scope, string grant_type, string client_id, st public async Task SendAccessTokenRequest(bool addCertificateToRequest = true) { - // Create ClientHandler - var _clientHandler = new HttpClientHandler(); - _clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + // Create ClientHandler + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; // Attach client certificate to handler if (CertificateFilename != null && addCertificateToRequest) { var clientCertificate = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.Exportable); - _clientHandler.ClientCertificates.Add(clientCertificate); + clientHandler.ClientCertificates.Add(clientCertificate); } // Create HttpClient - using var client = new HttpClient(_clientHandler); + using var client = new HttpClient(clientHandler); // Create an access token request var request = CreateAccessTokenRequest( - CertificateFilename, CertificatePassword, + CertificateFilename!, CertificatePassword!, Issuer, Audience, - Scope, GrantType, ClientId, ClientAssertionType, CertificateThumbprint, CertificateCn); + Scope, GrantType, ClientId!, ClientAssertionType, CertificateThumbprint, CertificateCn); // Request the access token return await client.SendAsync(request); diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs index 6dbab19..57616e8 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs @@ -3,21 +3,23 @@ namespace CDR.Register.IntegrationTests.Infrastructure { /// - /// Key/Value pair builder + /// Key/Value pair builder. /// public class KeyValuePairBuilder { private readonly string _delimiter; private readonly StringBuilder _sb = new StringBuilder(); + /// - /// Get key/value pairs as string + /// Get key/value pairs as string. /// public string Value => _sb.ToString(); private int _count = 0; + /// - /// Number of key/value pairs + /// Number of key/value pairs. /// public int Count => _count; @@ -27,10 +29,10 @@ public KeyValuePairBuilder(string delimiter = "&") } /// - /// Append key/value pair + /// Append key/value pair. /// - /// Key to add - /// Value to add + /// Key to add. + /// Value to add. public void Add(string key, string value) { if (_sb.Length > 0) @@ -44,20 +46,20 @@ public void Add(string key, string value) } /// - /// Add key/value pair + /// Add key/value pair. /// - /// Key to add - /// Value to add + /// Key to add. + /// Value to add. public void Add(string key, int value) { Add(key, value.ToString()); } /// - /// Add key/value pair + /// Add key/value pair. /// - /// Key to add - /// Value to add + /// Key to add. + /// Value to add. public void Add(string key, long value) { Add(key, value.ToString()); diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs index 312cd8f..6ace4b9 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs @@ -8,6 +8,8 @@ using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; +#nullable enable + namespace CDR.Register.IntegrationTests.Infrastructure { /// @@ -15,18 +17,21 @@ namespace CDR.Register.IntegrationTests.Infrastructure /// public class PrivateKeyJwt { - public string PrivateKeyBase64 { get; private set; } - private readonly string JtiClaim; + + private readonly string jtiClaim; /// - /// Default constructor. + /// Initializes a new instance of the class. /// public PrivateKeyJwt() { + PrivateKeyBase64 = string.Empty; + jtiClaim = string.Empty; } /// + /// Initializes a new instance of the class. /// Provide the primary key to the constructor. /// /// Base64 encoded private key. @@ -39,9 +44,16 @@ public PrivateKeyJwt(string privateKey) { PrivateKeyBase64 = FormatKey(privateKey); } + else + { + PrivateKeyBase64 = string.Empty; + } + + jtiClaim = string.Empty; } /// + /// Initializes a new instance of the class. /// Provide the Pkcs8 private key from X509 certificate. /// /// The path to the certificate. @@ -51,15 +63,21 @@ public PrivateKeyJwt(string certFilePath, string pwd, string jtiClaim) { var cert = new X509Certificate2(certFilePath, pwd, X509KeyStorageFlags.Exportable); var rsa = cert.GetRSAPrivateKey(); - var pvtKeyBytes = rsa.ExportPkcs8PrivateKey(); + if (rsa == null) + { + PrivateKeyBase64 = string.Empty; + jtiClaim = string.Empty; + } + + var pvtKeyBytes = rsa!.ExportPkcs8PrivateKey(); this.PrivateKeyBase64 = Convert.ToBase64String(pvtKeyBytes); - JtiClaim = jtiClaim; + this.jtiClaim = jtiClaim; } /// /// Load the private key from a file. /// - /// Path to the private key file + /// Path to the private key file. public void LoadPrivateKeyFromFile(string filePath) { if (string.IsNullOrEmpty(filePath)) @@ -70,7 +88,6 @@ public void LoadPrivateKeyFromFile(string filePath) if (!File.Exists(filePath)) { throw new FileNotFoundException("filePath cannot be found"); - } var privateKey = File.ReadAllText(filePath); @@ -80,11 +97,11 @@ public void LoadPrivateKeyFromFile(string filePath) /// /// Generate the private_key_jwt using the provided private key. /// - /// The issuer of the JWT, usually set to the softwareProductId - /// The audience of the JWT, usually set to the target token endpoint - /// The subject of the JWT, usually set to the softwareProductId - /// A base64 encoded JWT - public string Generate(string issuer, string audience, string subject, string signingAlgorithm = SecurityAlgorithms.RsaSsaPssSha256) + /// The issuer of the JWT, usually set to the softwareProductId. + /// The audience of the JWT, usually set to the target token endpoint. + /// The subject of the JWT, usually set to the softwareProductId. + /// A base64 encoded JWT. + public string Generate(string? issuer, string audience, string subject, string signingAlgorithm = SecurityAlgorithms.RsaSsaPssSha256) { if (string.IsNullOrEmpty(PrivateKeyBase64)) { @@ -122,16 +139,15 @@ public string Generate(string issuer, string audience, string subject, string si Audience = audience, Expires = DateTime.UtcNow.AddMinutes(10), Subject = new ClaimsIdentity(new List { new Claim("sub", subject) }), - //SigningCredentials = new SigningCredentials(privateSecurityKey, SecurityAlgorithms.RsaSsaPssSha256), SigningCredentials = new SigningCredentials(privateSecurityKey, signingAlgorithm), NotBefore = null, IssuedAt = null, Claims = new Dictionary() }; - if (!string.IsNullOrEmpty(JtiClaim)) + if (!string.IsNullOrEmpty(jtiClaim)) { - descriptor.Claims.Add("jti", JtiClaim); + descriptor.Claims.Add("jti", jtiClaim); } var tokenHandler = new JsonWebTokenHandler(); @@ -140,24 +156,25 @@ public string Generate(string issuer, string audience, string subject, string si } /// - /// Format the private key by removing the markers and newline characters, + /// Format the private key by removing the markers and newline characters. /// - /// Raw private key - /// Formatted private key + /// Raw private key. + /// Formatted private key. private static string FormatKey(string privateKey) { - return privateKey.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r\n", "").Trim(); + return privateKey.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty).Replace("\r\n", string.Empty).Trim(); } private static string GenerateKeyId(RSAParameters rsaParams, out string e, out string n) { e = Base64UrlEncoder.Encode(rsaParams.Exponent); n = Base64UrlEncoder.Encode(rsaParams.Modulus); - var dict = new Dictionary() { - {"e", e}, - {"kty", "RSA"}, - {"n", n} - }; + var dict = new Dictionary() + { + { "e", e }, + { "kty", "RSA" }, + { "n", n } + }; var hash = SHA256.Create(); var hashBytes = hash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict))); return Base64UrlEncoder.Encode(hashBytes); @@ -166,8 +183,8 @@ private static string GenerateKeyId(RSAParameters rsaParams, out string e, out s /// /// Generate the KeyId (kid) used in the header of a JWT or in a JWK. /// - /// RSA - /// The generated kid value + /// RSA. + /// The generated kid value. private static string GenerateKeyId(RSA rsa) { var rsaParameters = rsa.ExportParameters(false); diff --git a/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs b/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs index 6738140..a62ac87 100644 --- a/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -15,12 +15,16 @@ namespace CDR.Register.IntegrationTests.Miscellaneous { /// /// Integration tests for Register Admin API. - /// + /// public class US12677_RegisterEnrolment_Tests : BaseTest { - public US12677_RegisterEnrolment_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US12677_RegisterEnrolment_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + /// - /// Get the repository as json + /// Get the repository as json. /// private static async Task GetJson() { @@ -33,7 +37,7 @@ private static async Task GetJson() } /// - /// Load (replace) the repository from json + /// Load (replace) the repository from json. /// private static async Task PostJson(string json) { @@ -54,12 +58,12 @@ private static async Task PostJson(string json) [Fact] public async Task AC01_Get_ShouldRespondWith_200OK_RepositoryAsJson() { - // Arrange + // Arrange await TestFixture.Seeddata(); // TestFixture.InitializeAsync() seeds data but also then patches data in the database. Since we are just testing if import works need to import again (but without patching data). var json = await File.ReadAllTextAsync(SEEDDATA_FILENAME); var jToken = JToken.Parse(json); - // Act + // Act var response = await GetJson(); var responseJson = await response.Content.ReadAsStringAsync(); var jTokenResponse = JToken.Parse(responseJson); @@ -133,7 +137,10 @@ static void ClearField(JToken jToken, string fieldName) foreach (var item in jToken.SelectTokens($"..{fieldName}")) { var prop = item as JValue; - if (prop != null) prop.Value = null; + if (prop != null) + { + prop.Value = null; + } } } @@ -146,7 +153,7 @@ static void ClearField(JToken jToken, string fieldName) ClearField(jToken, "legalEntityStatusId"); } - static JToken Cleanup(JToken jToken) + private static JToken Cleanup(JToken jToken) { // Sort jToken["legalEntities"].SortArray("legalEntityId"); @@ -160,7 +167,8 @@ static JToken Cleanup(JToken jToken) jToken.RemoveEmptyArrays(); // Fix mismatch in version, convert to string - jToken.ReplacePath("$..legalEntities..participations..brands..endpoint..version", + jToken.ReplacePath( + "$..legalEntities..participations..brands..endpoint..version", t => $"{t}"); // Issues with Guids and property names having different cases... not ideal, but just convert to upper case @@ -205,11 +213,13 @@ public async Task AC07_AC08_Get_ThenPostUpdatedData_ShouldRespondWith_200OK_Retu static void UpdateNames(JToken jToken) { if (jToken["legalEntities"] != null) + { foreach (var legalEntity in jToken["legalEntities"]) { legalEntity["legalEntityName"] += " updated"; if (legalEntity["participations"] != null) + { foreach (var participation in legalEntity["participations"]) { foreach (var brand in participation["brands"]) @@ -217,13 +227,17 @@ static void UpdateNames(JToken jToken) brand["brandName"] += " updated"; if (brand["softwareProducts"] != null) + { foreach (var softwareProduct in brand["softwareProducts"]) { softwareProduct["softwareProductName"] += " updated"; } + } } } + } } + } } // Arrange diff --git a/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs b/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs index 33be5b9..55b293d 100644 --- a/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs @@ -17,7 +17,10 @@ namespace CDR.Register.IntegrationTests.Miscellaneous { public class US50483_DynamicUrl_Tests : BaseTest { - public US50483_DynamicUrl_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } + public US50483_DynamicUrl_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } private const string DEFAULT_SOFTWAREPRODUCT_ID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; @@ -45,9 +48,8 @@ public async Task AC01_Get_Data_Holder_Brands_Invalid_Certificate_Header_For_Acc CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME }.GetAsync(addCertificateToRequest: false); - var api = new Infrastructure.API + var api = new Infrastructure.Api { - HttpMethod = HttpMethod.Get, URL = getDataholderBrandsUrl, AccessToken = accessToken, @@ -63,7 +65,6 @@ public async Task AC01_Get_Data_Holder_Brands_Invalid_Certificate_Header_For_Acc // Assert await VerifyInvalidTokenRepsonse(response); - } [Trait("Category", "CTSONLY")] @@ -72,7 +73,6 @@ public async Task AC01_Get_Data_Holder_Brands_Invalid_Certificate_Header_For_Acc [InlineData("Missing Certificate Thumbprint", "", DEFAULT_CERTIFICATE_COMMON_NAME)] public async Task AC02_Get_Access_Token_Invalid_Certificate_Header_For_Access_Token(string testDescription, string certificateThumbPrint, string certificateCommonName) { - Log.Information("Executing test for {TestDescription}.\nCertificate ThumbPrint: {CertificateThumbPrint}.\nCertificateCommonName: {CertificateCommonName}", testDescription, certificateThumbPrint, certificateCommonName); // Arrange @@ -96,11 +96,9 @@ public async Task AC02_Get_Access_Token_Invalid_Certificate_Header_For_Access_To // Assert using (new AssertionScope()) { - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); await Assert_HasContent_Json(@"{""error"":""invalid_client"",""error_description"":""Client certificate validation failed""}", response.Content); - } } @@ -108,9 +106,8 @@ public async Task AC02_Get_Access_Token_Invalid_Certificate_Header_For_Access_To [Fact] public async Task AC03_Get_Access_Token_Invalid_Url_Regular_Expression_For_Access_Token() { - string tokenEndpoint = $"{IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL}/foo/idp/connect/token"; - + // Arrange - Get access token var accessToken = new AccessToken { @@ -127,13 +124,12 @@ public async Task AC03_Get_Access_Token_Invalid_Url_Regular_Expression_For_Acces // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); - } [Trait("Category", "CTSONLY")] - [Fact] + [Fact] public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() - { + { // Arrange string conformanceId = Guid.NewGuid().ToString(); string mismatachedConformanceId = Guid.NewGuid().ToString(); @@ -152,9 +148,8 @@ public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME }.GetAsync(addCertificateToRequest: false); - var api = new Infrastructure.API + var api = new Infrastructure.Api { - HttpMethod = HttpMethod.Get, URL = getDataholderBrandsUrl, AccessToken = accessToken, @@ -170,14 +165,12 @@ public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() // Assert await VerifyInvalidTokenRepsonse(response); - } private static async Task VerifyInvalidTokenRepsonse(HttpResponseMessage response) { using (new AssertionScope()) { - response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); ExpectedErrors expectedErrors = new ExpectedErrors(); @@ -186,20 +179,17 @@ private static async Task VerifyInvalidTokenRepsonse(HttpResponseMessage respons string actualErrors = await response.Content.ReadAsStringAsync(); Assert_Json(GetJsonFromModel(expectedErrors), actualErrors); - } } [Trait("Category", "CTSONLY")] [Theory] - [InlineData("Both Header and Database using only Common Name", DEFAULT_CERTIFICATE_THUMBPRINT, "Test Common Name", "Test Common Name")] - [InlineData("Both Header and Database using Full Distinguished Name", DEFAULT_CERTIFICATE_THUMBPRINT, "CN=TestCommonName1,O=Test Org,OU=Test Org Unit,C=AU", "CN=TestCommonName1,O=Test Org,OU=Test Org Unit,C=AU")] - [InlineData("Header using only Common Name and Database using Full Distinguished Name", DEFAULT_CERTIFICATE_THUMBPRINT, "Test Common Name 2", "CN=Test Common Name 2,O=Test Org,OU=Test Org Unit,C=AU")] - [InlineData("Header using Full Distinguished Name and Database using only Common Name", DEFAULT_CERTIFICATE_THUMBPRINT, "CN=Test Common Name 3,O=Test Org,OU=Test Org Unit,C=AU", "Test Common Name 3")] - + [InlineData("Both Header and Database using only Common Name", DEFAULT_CERTIFICATE_THUMBPRINT, "Test Common Name", "Test Common Name")] + [InlineData("Both Header and Database using Full Distinguished Name", DEFAULT_CERTIFICATE_THUMBPRINT, "CN=TestCommonName1,O=Test Org,OU=Test Org Unit,C=AU", "CN=TestCommonName1,O=Test Org,OU=Test Org Unit,C=AU")] + [InlineData("Header using only Common Name and Database using Full Distinguished Name", DEFAULT_CERTIFICATE_THUMBPRINT, "Test Common Name 2", "CN=Test Common Name 2,O=Test Org,OU=Test Org Unit,C=AU")] + [InlineData("Header using Full Distinguished Name and Database using only Common Name", DEFAULT_CERTIFICATE_THUMBPRINT, "CN=Test Common Name 3,O=Test Org,OU=Test Org Unit,C=AU", "Test Common Name 3")] public async Task AC05_Get_Access_Token_With_Valid_Certificate_Header(string testDescription, string certificateThumbPrint, string certificateCommonName, string dbCommonName) { - Log.Information("Executing test for {TestDescription}.\nCertificate ThumbPrint: {CertThumbPrint}.\nCertificateCommonName: {CertCommonName}.\nDatabase Common Name: {DbCommonName}.", testDescription, certificateThumbPrint, certificateCommonName, dbCommonName); // Arrange @@ -229,9 +219,8 @@ public async Task AC05_Get_Access_Token_With_Valid_Certificate_Header(string tes // Send request to get Data Holders to ensure Access token works. string validAccessToken = await accessToken.GetAsync(); - var api = new Infrastructure.API + var api = new Infrastructure.Api { - HttpMethod = HttpMethod.Get, URL = getDataholderBrandsUrl, AccessToken = validAccessToken, @@ -246,7 +235,6 @@ public async Task AC05_Get_Access_Token_With_Valid_Certificate_Header(string tes Log.Information("Response from {GetDataholderBrandsUrl} Endpoint: {StatusCode} \n{Content}", getDataholderBrandsUrl, accessTokenResponse.StatusCode, await accessTokenResponse.Content.ReadAsStringAsync()); getDataHolderResponse.StatusCode.Should().Be(HttpStatusCode.OK, because: $"Get Data Holder should work when{testDescription}"); - } private static void SetCertificateCommonName(string softwareProductId, string commonName) @@ -267,10 +255,7 @@ private static void SetCertificateCommonName(string softwareProductId, string co if (selectCommand.ExecuteScalarInt32() == 0) { throw new Exception($"Common name was not updated for Software Product: {softwareProductId}"); - }; + } } - - - } } diff --git a/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs b/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs index 2e2efaf..8756cd1 100644 --- a/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs +++ b/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs @@ -1,15 +1,16 @@ -using System.Collections.Generic; - -namespace CDR.Register.IntegrationTests.Models +namespace CDR.Register.IntegrationTests.Models { /// - /// Access token + /// Access token. /// public class AccessToken { public string access_token { get; set; } + public int expires_in { get; set; } + public string token_type { get; set; } + public string scope { get; set; } } } diff --git a/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs b/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs index d888f0a..284ec98 100644 --- a/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs +++ b/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs @@ -5,8 +5,6 @@ namespace CDR.Register.IntegrationTests.Models { public class DataHolderMetadata { - - [JsonProperty("dataHolderBrandId")] public string DataHolderBrandId { get; set; } @@ -98,7 +96,6 @@ public class LegalEntityChild [JsonProperty("status")] public string Status { get; set; } - } - + } } } diff --git a/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs b/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs index 1731c0f..3206879 100644 --- a/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs +++ b/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs @@ -124,7 +124,5 @@ public class SoftwareProduct [JsonProperty("certificates")] public List Certificates { get; set; } } - - } } diff --git a/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs b/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs index 02e9cf8..cb4aecd 100644 --- a/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs +++ b/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs @@ -1,11 +1,9 @@ using Newtonsoft.Json; -using System.Collections.Generic; namespace CDR.Register.IntegrationTests.Models { public class ExpectedApiErrors { - [JsonProperty("code")] public string Code { get; set; } @@ -17,11 +15,10 @@ public class ExpectedApiErrors [JsonProperty("meta")] public MetaData Meta { get; set; } - + public class MetaData { + // This is to get a empty JSON object } - } - } diff --git a/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs b/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs index 1731c56..4566a09 100644 --- a/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs +++ b/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs @@ -14,7 +14,7 @@ public ExpectedDataRecipients() [JsonProperty("data")] public List Data { get; set; } } - + public class ExpectedDataRecipients_V2 { public ExpectedDataRecipients_V2() @@ -30,7 +30,7 @@ public class DataRecipient { public DataRecipient() { - DataRecipientBrands = new List(); + DataRecipientBrands = new List(); } [JsonProperty("accreditationNumber")] @@ -62,7 +62,7 @@ public class DataRecipient_V2 { public DataRecipient_V2() { - DataRecipientBrands = new List(); + DataRecipientBrands = new List(); } [JsonProperty("accreditationNumber")] @@ -89,12 +89,12 @@ public DataRecipient_V2() [JsonProperty("lastUpdated")] public DateTime LastUpdated { get; set; } } - + public class DataRecipientBrand { public DataRecipientBrand() { - SoftwareProducts = new List(); + SoftwareProducts = new List(); } [JsonProperty("dataRecipientBrandId")] @@ -112,7 +112,7 @@ public DataRecipientBrand() [JsonProperty("status")] public string Status { get; set; } } - + public class SoftwareProduct { [JsonProperty("softwareProductId")] @@ -142,4 +142,4 @@ public class SoftwareProduct [JsonProperty("status")] public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs index 8d65c39..e3b7666 100644 --- a/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs @@ -1,167 +1,22 @@ // #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Dapper; +using Dapper.Contrib.Extensions; using FluentAssertions; using FluentAssertions.Execution; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; -using System; -using System.Threading.Tasks; using Xunit; -using Dapper; -using Microsoft.Data.SqlClient; -using System.Collections.Generic; -using Dapper.Contrib.Extensions; -using System.IO; #nullable enable namespace CDR.Register.IntegrationTests.TemporalTables { - [Table("LegalEntity")] - class LegalEntity - { - [ExplicitKey] - public Guid LegalEntityId { get; set; } = Guid.NewGuid(); - public string? LegalEntityName { get; set; } = "foo"; - public string? LogoUri { get; set; } = "foo"; - } - - [Table("Participation")] - class Participation - { - [ExplicitKey] - public Guid ParticipationId { get; set; } = Guid.NewGuid(); - public Guid? LegalEntityId { get; set; } - public int? ParticipationTypeId { get; set; } = 1; - public int? IndustryId { get; set; } = 1; - public int? StatusId { get; set; } = 1; - } - - [Table("Brand")] - class Brand - { - [ExplicitKey] - public Guid BrandId { get; set; } = Guid.NewGuid(); - public string? BrandName { get; set; } = "foo"; - public string? LogoUri { get; set; } = "foo"; - public int? BrandStatusId { get; set; } = 1; - public Guid? ParticipationId { get; set; } - public DateTime? LastUpdated { get; set; } = DateTime.Now; - } - - [Table("AuthDetail")] - class AuthDetail - { - [ExplicitKey] - public Guid BrandId { get; set; } - public int? RegisterUTypeId { get; set; } = 1; - public string? JwksEndpoint { get; set; } = "foo"; - } - - [Table("Endpoint")] - class Endpoint - { - [ExplicitKey] - public Guid BrandId { get; set; } - public string? Version { get; set; } = "foo"; - public string? PublicBaseUri { get; set; } = "foo"; - public string? ResourceBaseUri { get; set; } = "foo"; - public string? InfosecBaseUri { get; set; } = "foo"; - public string? ExtensionBaseUri { get; set; } = "foo"; - public string? WebsiteUri { get; set; } = "foo"; - } - - [Table("SoftwareProduct")] - class SoftwareProduct - { - [ExplicitKey] - public Guid SoftwareProductId { get; set; } = Guid.NewGuid(); - public string? SoftwareProductName { get; set; } = "foo"; - public string? SoftwareProductDescription { get; set; } = "foo"; - public string? LogoUri { get; set; } = "foo"; - public string? SectorIdentifierUri { get; set; } = "foo"; - public string? ClientUri { get; set; } = "foo"; - public string? TosUri { get; set; } = "foo"; - public string? PolicyUri { get; set; } = "foo"; - public string? RecipientBaseUri { get; set; } = "foo"; - public string? RevocationUri { get; set; } = "foo"; - public string? RedirectUris { get; set; } = "foo"; - public string? JwksUri { get; set; } = "foo"; - public string? Scope { get; set; } = "foo"; - public int? StatusId { get; set; } = 1; - public Guid? BrandId { get; set; } - } - - [Table("SoftwareProductCertificate")] - class SoftwareProductCertificate - { - [ExplicitKey] - public Guid SoftwareProductCertificateId { get; set; } = Guid.NewGuid(); - public Guid SoftwareProductId { get; set; } - public string? CommonName { get; set; } = "foo"; - public string? Thumbprint { get; set; } = "foo"; - } - - [Table("ParticipationStatus")] - class ParticipationStatus - { - [ExplicitKey] - public int? ParticipationStatusId { get; set; } - public string? ParticipationStatusCode { get; set; } - } - - static class DatabaseSeeder - { - static private async Task Purge(SqlConnection connection) - { - static async Task PurgeTable(SqlConnection connection, string tableName, bool temporal = true) - { - // Delete data - await connection.ExecuteAsync($"delete {tableName}"); - - // Now cleanup temporal data too - if (temporal) - { - await connection.ExecuteAsync($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = OFF)"); - await connection.ExecuteAsync($"DELETE FROM [{tableName}History]"); - await connection.ExecuteAsync($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[{tableName}History], DATA_CONSISTENCY_CHECK = OFF))"); - } - } - - await PurgeTable(connection, "SoftwareProductCertificate"); - await PurgeTable(connection, "SoftwareProduct"); - await PurgeTable(connection, "Endpoint"); - await PurgeTable(connection, "AuthDetail"); - await PurgeTable(connection, "Brand"); - await PurgeTable(connection, "Participation"); - await PurgeTable(connection, "LegalEntity"); - - // purge lookup tables - // await PurgeTable(connection, "AccreditationLevel", false); - // await PurgeTable(connection, "BrandStatus", false); - // await PurgeTable(connection, "IndustryType", false); - // await PurgeTable(connection, "LegalEntityStatus", false); - // await PurgeTable(connection, "OrganisationType", false); - // await PurgeTable(connection, "ParticipationStatus", false); - // await PurgeTable(connection, "ParticipationType", false); - // await PurgeTable(connection, "RegisterUType", false); - // await PurgeTable(connection, "SoftwareProductStatus", false); - } - - static public async Task Execute() - { - using var connection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); - connection.Open(); - - await Purge(connection); - await TestFixture.Seeddata(); - - await connection.ExecuteAsync("delete ParticipationStatus where ParticipationStatusId = -999"); - await connection.InsertAsync(new ParticipationStatus { ParticipationStatusId = -999, ParticipationStatusCode = "foo" }); - } - } - - public class US29068_TemporalTables : BaseTest0 + public class US29068_TemporalTableTests : BaseTest0 { private static async Task GetTableJson(SqlConnection connection, string tableName, DateTime? pointInTimeUTC = null) { @@ -176,16 +31,16 @@ private static async Task GetTableJson(SqlConnection connection, string string sql = @$" select * from {tableName} - {(pointInTimeUTC == null ? "" : $"for system_time as of '{pointInTimeUTC:yyyy-MM-dd HH:mm:ss.fffffff}'")} + {(pointInTimeUTC == null ? string.Empty : $"for system_time as of '{pointInTimeUTC:yyyy-MM-dd HH:mm:ss.fffffff}'")} order by {orderby}"; data = await connection.QueryAsync(sql); - // return JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); return JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings { }); } - delegate Task Mutate(SqlConnection connection); + private delegate Task Mutate(SqlConnection connection); + private async Task Test(string tableName, Mutate mutate) { await Test(new string[] { tableName }, mutate); @@ -197,7 +52,7 @@ private static async Task Test(string[] tableNames, Mutate mutate) await DatabaseSeeder.Execute(); using var registerConnection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); - registerConnection.Open(); + await registerConnection.OpenAsync(); var pointInTimeUTC = DateTime.UtcNow; @@ -213,6 +68,7 @@ private static async Task Test(string[] tableNames, Mutate mutate) { await mutate(registerConnection); } + var modifiedData = new Dictionary(); foreach (string tableName in tableNames) { @@ -251,12 +107,12 @@ private static async Task Test(string[] tableNames, Mutate mutate) } } + // [InlineData("AuthDetail")] // One-to-one for brand, ie can't insert another row for existing brand + // [InlineData("Endpoint")] // One-to-one for brand, ie can't insert another row for existing brand [Theory] [InlineData("LegalEntity")] [InlineData("Participation")] [InlineData("Brand")] - // [InlineData("AuthDetail")] // One-to-one for brand, ie can't insert another row for existing brand - // [InlineData("Endpoint")] // One-to-one for brand, ie can't insert another row for existing brand [InlineData("SoftwareProduct")] [InlineData("SoftwareProductCertificate")] public async Task ACX01_AfterInsertingRecord_PointInTimeQueryShouldNotReturnInsertedRecord(string tableName) @@ -268,27 +124,27 @@ await Test(tableName, async (connection) => "LegalEntity" => connection.InsertAsync(new LegalEntity()), "Participation" => connection.InsertAsync(new Participation() { - LegalEntityId = connection.ExecuteScalar("select top 1 legalentityid from legalentity") + LegalEntityId = await connection.ExecuteScalarAsync("select top 1 legalentityid from legalentity") }), "Brand" => connection.InsertAsync(new Brand() { - ParticipationId = connection.ExecuteScalar("select top 1 participationid from participation") + ParticipationId = await connection.ExecuteScalarAsync("select top 1 participationid from participation") }), "AuthDetail" => connection.InsertAsync(new AuthDetail() { - BrandId = connection.ExecuteScalar("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") }), "Endpoint" => connection.InsertAsync(new Endpoint() { - BrandId = connection.ExecuteScalar("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") }), "SoftwareProduct" => connection.InsertAsync(new SoftwareProduct() { - BrandId = connection.ExecuteScalar("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") }), "SoftwareProductCertificate" => connection.InsertAsync(new SoftwareProductCertificate() { - SoftwareProductId = connection.ExecuteScalar("select top 1 softwareProductid from softwareProduct") + SoftwareProductId = await connection.ExecuteScalarAsync("select top 1 softwareProductid from softwareProduct") }), _ => throw new NotSupportedException() }; @@ -296,67 +152,6 @@ await Test(tableName, async (connection) => }); } -// FIXME - MJS - These tests fail randomly in pipeline, sometimes they work, other times not. -/* - [Theory] - [InlineData("LegalEntity")] - [InlineData("Participation")] - [InlineData("Brand")] - [InlineData("AuthDetail")] - [InlineData("Endpoint")] - [InlineData("SoftwareProduct")] - [InlineData("SoftwareProductCertificate")] - public async Task ACX02_AfterUpdatingRecord_PointInTimeQueryShouldNotReturnUpdatedRecord(string tableName) - { - await Test(tableName, async (connection) => - { - var update = tableName switch - { - "LegalEntity" => connection.ExecuteAsync("update LegalEntity set LegalEntityName = @LegalEntityName where LegalEntityId = @LegalEntityId", - new - { - LegalEntityId = await connection.ExecuteScalarAsync("select top 1 LegalEntityId from LegalEntity"), - LegalEntityName = Guid.NewGuid().ToString() - }), - "Participation" => connection.ExecuteAsync("update Participation set StatusId = -999 where ParticipationId = @ParticipationId", - new { ParticipationId = await connection.ExecuteScalarAsync("select top 1 ParticipationId from Participation") }), - "Brand" => connection.ExecuteAsync("update Brand set BrandName = @BrandName where BrandId = @BrandId", - new - { - BrandId = await connection.ExecuteScalarAsync("select top 1 BrandId from Brand"), - BrandName = Guid.NewGuid().ToString() - }), - "AuthDetail" => connection.ExecuteAsync("update AuthDetail set JwksEndpoint = @JwksEndpoint where BrandId = @BrandId", - new - { - BrandId = await connection.ExecuteScalarAsync("select top 1 BrandId from Brand"), - JwksEndpoint = Guid.NewGuid().ToString() - }), - "Endpoint" => connection.ExecuteAsync("update Endpoint set PublicBaseUri = @PublicBaseUri where BrandId = @BrandId", - new - { - BrandId = await connection.ExecuteScalarAsync("select top 1 BrandId from Brand"), - PublicBaseUri = Guid.NewGuid().ToString() - }), - "SoftwareProduct" => connection.ExecuteAsync("update SoftwareProduct set SoftwareProductName = @SoftwareProductName where SoftwareProductId = @SoftwareProductId", - new - { - SoftwareProductId = await connection.ExecuteScalarAsync("select top 1 SoftwareProductId from SoftwareProduct"), - SoftwareProductName = Guid.NewGuid().ToString() - }), - "SoftwareProductCertificate" => connection.ExecuteAsync("update SoftwareProductCertificate set CommonName = @CommonName where SoftwareProductCertificateId = @SoftwareProductCertificateId", - new - { - SoftwareProductCertificateId = await connection.ExecuteScalarAsync("select top 1 SoftwareProductCertificateId from SoftwareProductCertificate"), - CommonName = Guid.NewGuid().ToString() - }), - _ => throw new NotSupportedException() - }; - await update; - }); - } -*/ - [Fact] public async Task ACX03_AfterDeletingRecords_PointInTimeQueryShouldReturnDeletedRecords() { @@ -374,4 +169,174 @@ await Test( }); } } + + [Table("LegalEntity")] + internal class LegalEntity + { + [ExplicitKey] + public Guid LegalEntityId { get; set; } = Guid.NewGuid(); + + public string? LegalEntityName { get; set; } = "foo"; + + public string? LogoUri { get; set; } = "foo"; + } + + [Table("Participation")] + internal class Participation + { + [ExplicitKey] + public Guid ParticipationId { get; set; } = Guid.NewGuid(); + + public Guid? LegalEntityId { get; set; } + + public int? ParticipationTypeId { get; set; } = 1; + + public int? IndustryId { get; set; } = 1; + + public int? StatusId { get; set; } = 1; + } + + [Table("Brand")] + internal class Brand + { + [ExplicitKey] + public Guid BrandId { get; set; } = Guid.NewGuid(); + + public string? BrandName { get; set; } = "foo"; + + public string? LogoUri { get; set; } = "foo"; + + public int? BrandStatusId { get; set; } = 1; + + public Guid? ParticipationId { get; set; } + + public DateTime? LastUpdated { get; set; } = DateTime.Now; + } + + [Table("AuthDetail")] + internal class AuthDetail + { + [ExplicitKey] + public Guid BrandId { get; set; } + + public int? RegisterUTypeId { get; set; } = 1; + + public string? JwksEndpoint { get; set; } = "foo"; + } + + [Table("Endpoint")] + internal class Endpoint + { + [ExplicitKey] + public Guid BrandId { get; set; } + + public string? Version { get; set; } = "foo"; + + public string? PublicBaseUri { get; set; } = "foo"; + + public string? ResourceBaseUri { get; set; } = "foo"; + + public string? InfosecBaseUri { get; set; } = "foo"; + + public string? ExtensionBaseUri { get; set; } = "foo"; + + public string? WebsiteUri { get; set; } = "foo"; + } + + [Table("SoftwareProduct")] + internal class SoftwareProduct + { + [ExplicitKey] + public Guid SoftwareProductId { get; set; } = Guid.NewGuid(); + + public string? SoftwareProductName { get; set; } = "foo"; + + public string? SoftwareProductDescription { get; set; } = "foo"; + + public string? LogoUri { get; set; } = "foo"; + + public string? SectorIdentifierUri { get; set; } = "foo"; + + public string? ClientUri { get; set; } = "foo"; + + public string? TosUri { get; set; } = "foo"; + + public string? PolicyUri { get; set; } = "foo"; + + public string? RecipientBaseUri { get; set; } = "foo"; + + public string? RevocationUri { get; set; } = "foo"; + + public string? RedirectUris { get; set; } = "foo"; + + public string? JwksUri { get; set; } = "foo"; + + public string? Scope { get; set; } = "foo"; + + public int? StatusId { get; set; } = 1; + + public Guid? BrandId { get; set; } + } + + [Table("SoftwareProductCertificate")] + internal class SoftwareProductCertificate + { + [ExplicitKey] + public Guid SoftwareProductCertificateId { get; set; } = Guid.NewGuid(); + + public Guid SoftwareProductId { get; set; } + + public string? CommonName { get; set; } = "foo"; + + public string? Thumbprint { get; set; } = "foo"; + } + + [Table("ParticipationStatus")] + internal class ParticipationStatus + { + [ExplicitKey] + public int? ParticipationStatusId { get; set; } + + public string? ParticipationStatusCode { get; set; } + } + + internal static class DatabaseSeeder + { + private static async Task Purge(SqlConnection connection) + { + static async Task PurgeTable(SqlConnection connection, string tableName, bool temporal = true) + { + // Delete data + await connection.ExecuteAsync($"delete {tableName}"); + + // Now cleanup temporal data too + if (temporal) + { + await connection.ExecuteAsync($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = OFF)"); + await connection.ExecuteAsync($"DELETE FROM [{tableName}History]"); + await connection.ExecuteAsync($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[{tableName}History], DATA_CONSISTENCY_CHECK = OFF))"); + } + } + + await PurgeTable(connection, "SoftwareProductCertificate"); + await PurgeTable(connection, "SoftwareProduct"); + await PurgeTable(connection, "Endpoint"); + await PurgeTable(connection, "AuthDetail"); + await PurgeTable(connection, "Brand"); + await PurgeTable(connection, "Participation"); + await PurgeTable(connection, "LegalEntity"); + } + + public static async Task Execute() + { + using var connection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); + await connection.OpenAsync(); + + await Purge(connection); + await TestFixture.Seeddata(); + + await connection.ExecuteAsync("delete ParticipationStatus where ParticipationStatusId = -999"); + await connection.InsertAsync(new ParticipationStatus { ParticipationStatusId = -999, ParticipationStatusCode = "foo" }); + } + } } diff --git a/Source/CDR.Register.IntegrationTests/XUnit/AlphabeticalOrderer.cs b/Source/CDR.Register.IntegrationTests/XUnit/AlphabeticalOrderer.cs index 94fb520..345f8be 100644 --- a/Source/CDR.Register.IntegrationTests/XUnit/AlphabeticalOrderer.cs +++ b/Source/CDR.Register.IntegrationTests/XUnit/AlphabeticalOrderer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Xunit.Abstractions; using Xunit.Sdk; @@ -7,7 +7,7 @@ namespace CDR.Register.IntegrationTests.XUnit.Orderers { public class AlphabeticalOrderer : ITestCaseOrderer { - public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase => - testCases.OrderBy(testCase => testCase.TestMethod.Method.Name); + public IEnumerable OrderTestCases(IEnumerable testCases) + where TTestCase : ITestCase => testCases.OrderBy(testCase => testCase.TestMethod.Method.Name); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj index a43a3d0..de7bde9 100644 --- a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj +++ b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj @@ -4,6 +4,7 @@ $(Version) $(Version) $(Version) + true @@ -13,7 +14,7 @@ - + all @@ -24,8 +25,16 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs b/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs index d592cfe..92f6335 100644 --- a/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs +++ b/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; namespace CDR.Register.Repository.Entities { @@ -12,14 +7,15 @@ public class AccreditationLevel [Key] public AccreditationLevelType AccreditationLevelId { get; set; } - [MaxLength(100), Required] + [MaxLength(100)] + [Required] public string AccreditationLevelCode { get; set; } } public enum AccreditationLevelType { - //Sponsored by Default - Sponsored = 0, + // Sponsored by Default + Sponsored = 0, Unrestricted = 1 } } diff --git a/Source/CDR.Register.Repository/Entities/AuthDetail.cs b/Source/CDR.Register.Repository/Entities/AuthDetail.cs index 83b720e..1f76a02 100644 --- a/Source/CDR.Register.Repository/Entities/AuthDetail.cs +++ b/Source/CDR.Register.Repository/Entities/AuthDetail.cs @@ -6,10 +6,15 @@ namespace CDR.Register.Repository.Entities public class AuthDetail { public Guid BrandId { get; set; } + public Brand Brand { get; set; } + public RegisterUTypes RegisterUTypeId { get; set; } + public RegisterUType RegisterUType { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] public string JwksEndpoint { get; set; } } } diff --git a/Source/CDR.Register.Repository/Entities/Brand.cs b/Source/CDR.Register.Repository/Entities/Brand.cs index c714c0e..6e46d8f 100644 --- a/Source/CDR.Register.Repository/Entities/Brand.cs +++ b/Source/CDR.Register.Repository/Entities/Brand.cs @@ -13,18 +13,30 @@ public Brand() [Key] public Guid BrandId { get; set; } - [MaxLength(200), Required] + + [MaxLength(200)] + [Required] public string BrandName { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] + public string LogoUri { get; set; } + public BrandStatusType BrandStatusId { get; set; } + public BrandStatus BrandStatus { get; set; } + public Guid ParticipationId { get; set; } + public Participation Participation { get; set; } + public DateTime LastUpdated { get; set; } public virtual ICollection SoftwareProducts { get; set; } + public virtual ICollection AuthDetails { get; set; } + public virtual Endpoint Endpoint { get; set; } } } diff --git a/Source/CDR.Register.Repository/Entities/BrandStatus.cs b/Source/CDR.Register.Repository/Entities/BrandStatus.cs index 399b835..661e319 100644 --- a/Source/CDR.Register.Repository/Entities/BrandStatus.cs +++ b/Source/CDR.Register.Repository/Entities/BrandStatus.cs @@ -1,21 +1,22 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace CDR.Register.Repository.Entities { - public class BrandStatus - { - [Key] - public BrandStatusType BrandStatusId { get; set; } - [MaxLength(25), Required] - public string BrandStatusCode { get; set; } - } + public class BrandStatus + { + [Key] + public BrandStatusType BrandStatusId { get; set; } - public enum BrandStatusType - { - Unknown = 0, - Active = 1, - Inactive = 2, - Removed = 3 - } -} \ No newline at end of file + [MaxLength(25)] + [Required] + public string BrandStatusCode { get; set; } + } + + public enum BrandStatusType + { + Unknown = 0, + Active = 1, + Inactive = 2, + Removed = 3 + } +} diff --git a/Source/CDR.Register.Repository/Entities/Endpoint.cs b/Source/CDR.Register.Repository/Entities/Endpoint.cs index 7821da1..8cb0838 100644 --- a/Source/CDR.Register.Repository/Entities/Endpoint.cs +++ b/Source/CDR.Register.Repository/Entities/Endpoint.cs @@ -9,19 +9,30 @@ public class Endpoint [Key] [ForeignKey("Brand")] public Guid BrandId { get; set; } - [MaxLength(25), Required] + + [MaxLength(25)] + [Required] public string Version { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] public string PublicBaseUri { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] public string ResourceBaseUri { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] public string InfosecBaseUri { get; set; } + [MaxLength(1000)] public string ExtensionBaseUri { get; set; } - [MaxLength(1000), Required] + + [MaxLength(1000)] + [Required] public string WebsiteUri { get; set; } - public virtual Brand Brand { get; set; } + public virtual Brand Brand { get; set; } } } diff --git a/Source/CDR.Register.Repository/Entities/Enumerations.cs b/Source/CDR.Register.Repository/Entities/Enumerations.cs index b5afb0a..5d50c16 100644 --- a/Source/CDR.Register.Repository/Entities/Enumerations.cs +++ b/Source/CDR.Register.Repository/Entities/Enumerations.cs @@ -21,7 +21,7 @@ public enum OrganisationType public enum AccreditationLevel { - //Sponsored by Default + // Sponsored by Default Sponsored = 0, Unrestricted = 1 } @@ -60,7 +60,7 @@ public enum ParticipationType public enum RegisterUType { - Unknown = 0, + Unknown = 0, SignedJwt = 1 } diff --git a/Source/CDR.Register.Repository/Entities/IndustryType.cs b/Source/CDR.Register.Repository/Entities/IndustryType.cs index 1248df3..ac5533e 100644 --- a/Source/CDR.Register.Repository/Entities/IndustryType.cs +++ b/Source/CDR.Register.Repository/Entities/IndustryType.cs @@ -8,7 +8,8 @@ public class IndustryType [Key] public Industry IndustryTypeId { get; set; } - [MaxLength(25), Required] + [MaxLength(25)] + [Required] public string IndustryTypeCode { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Entities/LegalEntity.cs b/Source/CDR.Register.Repository/Entities/LegalEntity.cs index 6292ce0..3ccb294 100644 --- a/Source/CDR.Register.Repository/Entities/LegalEntity.cs +++ b/Source/CDR.Register.Repository/Entities/LegalEntity.cs @@ -14,10 +14,12 @@ public LegalEntity() [Key] public Guid LegalEntityId { get; set; } - [MaxLength(200), Required] + [MaxLength(200)] + [Required] public string LegalEntityName { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string LogoUri { get; set; } [MaxLength(100)] @@ -41,15 +43,16 @@ public LegalEntity() public string AnzsicDivision { get; set; } public OrganisationTypes? OrganisationTypeId { get; set; } + public OrganisationType OrganisationType { get; set; } [MaxLength(100)] public string AccreditationNumber { get; set; } public AccreditationLevelType? AccreditationLevelId { get; set; } + public AccreditationLevel AccreditationLevel { get; set; } public virtual ICollection Participations { get; set; } - } } diff --git a/Source/CDR.Register.Repository/Entities/OrganisationType.cs b/Source/CDR.Register.Repository/Entities/OrganisationType.cs index 0433d45..75b3d5a 100644 --- a/Source/CDR.Register.Repository/Entities/OrganisationType.cs +++ b/Source/CDR.Register.Repository/Entities/OrganisationType.cs @@ -7,7 +7,8 @@ public class OrganisationType [Key] public OrganisationTypes OrganisationTypeId { get; set; } - [MaxLength(100), Required] + [MaxLength(100)] + [Required] public string OrganisationTypeCode { get; set; } } @@ -21,4 +22,4 @@ public enum OrganisationTypes GovernmentEntity = 5, Other = 6 } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Entities/Participation.cs b/Source/CDR.Register.Repository/Entities/Participation.cs index b18e7d6..b2dd679 100644 --- a/Source/CDR.Register.Repository/Entities/Participation.cs +++ b/Source/CDR.Register.Repository/Entities/Participation.cs @@ -16,15 +16,19 @@ public Participation() public Guid ParticipationId { get; set; } public Guid LegalEntityId { get; set; } + public LegalEntity LegalEntity { get; set; } public ParticipationTypes ParticipationTypeId { get; set; } + public ParticipationType ParticipationType { get; set; } public Industry? IndustryId { get; set; } + public IndustryType Industry { get; set; } public ParticipationStatusType StatusId { get; set; } + public ParticipationStatus Status { get; set; } public virtual ICollection Brands { get; set; } diff --git a/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs b/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs index 56559a8..96d7e2d 100644 --- a/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs +++ b/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs @@ -7,14 +7,15 @@ public class ParticipationStatus [Key] public ParticipationStatusType ParticipationStatusId { get; set; } - [MaxLength(25), Required] + [MaxLength(25)] + [Required] public string ParticipationStatusCode { get; set; } /// /// Applicable participation types. If null or Unknown, it's available for all participation types. /// - public ParticipationTypes? ParticipationTypeId { get; set; } - } + public ParticipationTypes? ParticipationTypeId { get; set; } + } public enum ParticipationStatusType { @@ -26,4 +27,4 @@ public enum ParticipationStatusType Surrendered = 5, Inactive = 6 } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Entities/ParticipationType.cs b/Source/CDR.Register.Repository/Entities/ParticipationType.cs index 2393e85..23866e6 100644 --- a/Source/CDR.Register.Repository/Entities/ParticipationType.cs +++ b/Source/CDR.Register.Repository/Entities/ParticipationType.cs @@ -7,7 +7,8 @@ public class ParticipationType [Key] public ParticipationTypes ParticipationTypeId { get; set; } - [MaxLength(2), Required] + [MaxLength(2)] + [Required] public string ParticipationTypeCode { get; set; } } @@ -17,4 +18,4 @@ public enum ParticipationTypes Dh = 1, Dr = 2 } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Entities/RegisterUType.cs b/Source/CDR.Register.Repository/Entities/RegisterUType.cs index c7e1239..1980d12 100644 --- a/Source/CDR.Register.Repository/Entities/RegisterUType.cs +++ b/Source/CDR.Register.Repository/Entities/RegisterUType.cs @@ -6,7 +6,9 @@ public class RegisterUType { [Key] public RegisterUTypes RegisterUTypeId { get; set; } - [MaxLength(25), Required] + + [MaxLength(25)] + [Required] public string RegisterUTypeCode { get; set; } } diff --git a/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs b/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs index 27edab3..4746314 100644 --- a/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs +++ b/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs @@ -16,19 +16,22 @@ public SoftwareProduct() [Key] public Guid SoftwareProductId { get; set; } - [MaxLength(200), Required] + [MaxLength(200)] + [Required] public string SoftwareProductName { get; set; } [MaxLength(4000)] public string SoftwareProductDescription { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string LogoUri { get; set; } [MaxLength(2048)] public string SectorIdentifierUri { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string ClientUri { get; set; } [MaxLength(1000)] @@ -40,22 +43,28 @@ public SoftwareProduct() [MaxLength(1000)] public string RecipientBaseUri { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string RevocationUri { get; set; } - [MaxLength(2000), Required] + [MaxLength(2000)] + [Required] public string RedirectUris { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string JwksUri { get; set; } - [MaxLength(1000), Required] + [MaxLength(1000)] + [Required] public string Scope { get; set; } public SoftwareProductStatusType StatusId { get; set; } + public SoftwareProductStatus Status { get; set; } public Guid BrandId { get; set; } + public Brand Brand { get; set; } public ICollection Certificates { get; set; } diff --git a/Source/CDR.Register.Repository/Entities/SoftwareProductCertificate.cs b/Source/CDR.Register.Repository/Entities/SoftwareProductCertificate.cs index 0a1b1f4..9e01798 100644 --- a/Source/CDR.Register.Repository/Entities/SoftwareProductCertificate.cs +++ b/Source/CDR.Register.Repository/Entities/SoftwareProductCertificate.cs @@ -12,10 +12,12 @@ public class SoftwareProductCertificate public SoftwareProduct SoftwareProduct { get; set; } - [MaxLength(2000), Required] + [MaxLength(2000)] + [Required] public string CommonName { get; set; } - [MaxLength(2000), Required] + [MaxLength(2000)] + [Required] public string Thumbprint { get; set; } } } diff --git a/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs b/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs index f220fb2..5cd67a9 100644 --- a/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs +++ b/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs @@ -6,7 +6,9 @@ public class SoftwareProductStatus { [Key] public SoftwareProductStatusType SoftwareProductStatusId { get; set; } - [MaxLength(25), Required] + + [MaxLength(25)] + [Required] public string SoftwareProductStatusCode { get; set; } } @@ -17,4 +19,4 @@ public enum SoftwareProductStatusType Inactive = 2, Removed = 3 } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/IRegisterAdminRepository.cs b/Source/CDR.Register.Repository/IRegisterAdminRepository.cs index 9115f04..806b9a2 100644 --- a/Source/CDR.Register.Repository/IRegisterAdminRepository.cs +++ b/Source/CDR.Register.Repository/IRegisterAdminRepository.cs @@ -10,6 +10,7 @@ public interface IRegisterAdminRepository Task GetDataHolderBrandAsync(Guid brandId); public Task AddOrUpdateDataRecipient(DataRecipient dataRecipient); + Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand dataHolderBrand); } } diff --git a/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs b/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs index 0bf1297..971734f 100644 --- a/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs +++ b/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs @@ -1,6 +1,5 @@ using CDR.Register.Domain.Entities; using CDR.Register.Domain.ValueObjects; -using CDR.Register.Repository.Infrastructure; using System; using System.Threading.Tasks; @@ -9,7 +8,9 @@ namespace CDR.Register.Repository.Interfaces public interface IRegisterDiscoveryRepository { Task> GetDataHolderBrandsAsync(Infrastructure.Industry industry, DateTime? updatedSince, int page, int pageSize); + Task GetDataRecipientsAsync(Infrastructure.Industry industry); + Task GetSoftwareProductIdAsync(Guid softwareProductId); } } diff --git a/Source/CDR.Register.Repository/IRegisterStatusRepository .cs b/Source/CDR.Register.Repository/IRegisterStatusRepository.cs similarity index 91% rename from Source/CDR.Register.Repository/IRegisterStatusRepository .cs rename to Source/CDR.Register.Repository/IRegisterStatusRepository.cs index 1fe6065..77e1377 100644 --- a/Source/CDR.Register.Repository/IRegisterStatusRepository .cs +++ b/Source/CDR.Register.Repository/IRegisterStatusRepository.cs @@ -1,5 +1,4 @@ using CDR.Register.Domain.Entities; -using CDR.Register.Repository.Infrastructure; using System.Threading.Tasks; namespace CDR.Register.Repository.Interfaces @@ -7,7 +6,9 @@ namespace CDR.Register.Repository.Interfaces public interface IRegisterStatusRepository { Task GetDataRecipientStatusesAsync(Infrastructure.Industry industry); + Task GetSoftwareProductStatusesAsync(Infrastructure.Industry industry); + Task GetDataHolderStatusesAsync(Infrastructure.Industry industry); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/IRepositoryMapper.cs b/Source/CDR.Register.Repository/IRepositoryMapper.cs index f17bf96..35dd89d 100644 --- a/Source/CDR.Register.Repository/IRepositoryMapper.cs +++ b/Source/CDR.Register.Repository/IRepositoryMapper.cs @@ -5,16 +5,19 @@ namespace CDR.Register.Repository public interface IRepositoryMapper { DataRecipientLegalEntity Map(Entities.LegalEntity legalEntity); + Entities.LegalEntity Map(DataRecipientLegalEntity legalEntity); DataRecipientBrand Map(Entities.Brand brand); + Entities.Brand Map(DataRecipientBrand brand); SoftwareProductInfosec Map(Entities.SoftwareProduct softwareProduct); - Entities.SoftwareProduct Map(SoftwareProduct softwareProduct); - SoftwareProduct MapSoftwareProduct(Entities.SoftwareProduct softwareProduct); + Entities.SoftwareProduct Map(SoftwareProduct softwareProduct); Entities.SoftwareProductCertificate Map(SoftwareProductCertificateInfosec softwareProductCertificate); + + SoftwareProduct MapSoftwareProduct(Entities.SoftwareProduct softwareProduct); } } diff --git a/Source/CDR.Register.Repository/Infrastructure/ScopeConstants.cs b/Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs similarity index 100% rename from Source/CDR.Register.Repository/Infrastructure/ScopeConstants.cs rename to Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs diff --git a/Source/CDR.Register.Repository/Infrastructure/Extensions.cs b/Source/CDR.Register.Repository/Infrastructure/Extensions.cs index e4db886..3b70765 100644 --- a/Source/CDR.Register.Repository/Infrastructure/Extensions.cs +++ b/Source/CDR.Register.Repository/Infrastructure/Extensions.cs @@ -1,15 +1,15 @@ using System; -using System.Linq; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; +using CDR.Register.Domain.ValueObjects; using CDR.Register.Repository.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using DomainEntities = CDR.Register.Domain.Entities; -using System.Collections.Generic; -using CDR.Register.Domain.ValueObjects; namespace CDR.Register.Repository.Infrastructure { @@ -57,9 +57,9 @@ public static void SeedDatabase(this ModelBuilder modelBuilder) } /// - /// This is the initial database seed. If there are records in the database, this will not re-seed the database + /// This is the initial database seed. If there are records in the database, this will not re-seed the database. /// - public async static Task SeedDatabaseFromJsonFile( + public static async Task SeedDatabaseFromJsonFile( this RegisterDatabaseContext registerDatabaseContext, string jsonFileFullPath, ILogger logger, @@ -76,9 +76,9 @@ public async static Task SeedDatabaseFromJsonFile( } /// - /// This is the initial database seed. If there are records in the database, this will not re-seed the database + /// This is the initial database seed. If there are records in the database, this will not re-seed the database. /// - public async static Task SeedDatabaseFromJson( + public static async Task SeedDatabaseFromJson( this RegisterDatabaseContext registerDatabaseContext, string json, ILogger logger, @@ -106,7 +106,7 @@ public async static Task SeedDatabaseFromJson( /// /// Retrieves all participant metadata from the database, serialises to JSON and return as a string. /// - public async static Task GetJsonFromDatabase( + public static async Task GetJsonFromDatabase( this RegisterDatabaseContext registerDatabaseContext) { var allData = await registerDatabaseContext.LegalEntities.AsNoTracking().OrderBy(l => l.LegalEntityName) @@ -135,9 +135,9 @@ public async static Task GetJsonFromDatabase( /// /// Re-Seed the database from the input JSON data. All existing data in the database will be removed prior to creating the new data set. /// - public async static Task ReSeedDatabaseFromJson(this RegisterDatabaseContext registerDatabaseContext, string json, ILogger logger) + public static async Task ReSeedDatabaseFromJson(this RegisterDatabaseContext registerDatabaseContext, string json, ILogger logger) { - using (var transaction = registerDatabaseContext.Database.BeginTransaction()) + using (var transaction = await registerDatabaseContext.Database.BeginTransactionAsync()) { try { @@ -146,11 +146,10 @@ public async static Task ReSeedDatabaseFromJson(this RegisterDatabaseConte // Remove all existing data in the system var existingLegalEntities = await registerDatabaseContext.LegalEntities.AsNoTracking().ToListAsync(); registerDatabaseContext.RemoveRange(existingLegalEntities); - registerDatabaseContext.SaveChanges(); + await registerDatabaseContext.SaveChangesAsync(); logger.LogInformation("Existing data removed from the repository."); - logger.LogInformation("Adding JSON data to repository..."); // Re-create all participants from the incoming JSON. @@ -159,10 +158,10 @@ public async static Task ReSeedDatabaseFromJson(this RegisterDatabaseConte { var newLegalEntities = allData["legalEntities"].ToObject(); registerDatabaseContext.LegalEntities.AddRange(newLegalEntities); - registerDatabaseContext.SaveChanges(); + await registerDatabaseContext.SaveChangesAsync(); // Finally commit the transaction - transaction.Commit(); + await transaction.CommitAsync(); logger.LogInformation("JSON data added to the repository."); return true; @@ -172,15 +171,19 @@ public async static Task ReSeedDatabaseFromJson(this RegisterDatabaseConte { // Log any errors. logger.LogError(ex, "Error while seeding the database."); - throw; + throw new InvalidOperationException("Error while seeding the database."); } + return false; } } - public static async Task AddOrUpdateDataRecipientLegalEntity(this DomainEntities.DataRecipient dataRecipient, - LegalEntity legalEntity, RegisterDatabaseContext registerDatabaseContext, - IRepositoryMapper repositoryMapper, ILogger logger) + public static async Task AddOrUpdateDataRecipientLegalEntity( + this DomainEntities.DataRecipient dataRecipient, + LegalEntity legalEntity, + RegisterDatabaseContext registerDatabaseContext, + IRepositoryMapper repositoryMapper, + ILogger logger) { BusinessRuleError error = null; @@ -190,29 +193,29 @@ public static async Task AddOrUpdateDataRecipientLegalEntity( var drBrandToSave = repositoryMapper.Map(dataRecipientBrand); drBrandToSave.LastUpdated = DateTime.UtcNow; - //create new brand as it is a new one - if (null == dbBrand) + // create new brand as it is a new one + if (dbBrand == null) { logger.LogInformation("New Brand of id:{BrandId} name:{BrandName} getting added to the repository.", dataRecipientBrand.BrandId, dataRecipientBrand.BrandName); await registerDatabaseContext.Brands.AddAsync(drBrandToSave); } - //handle participation + // handle participation if ((error = await drBrandToSave.AddOrUpdateDataRecipientParticipation(legalEntity, dataRecipient, registerDatabaseContext, logger)) != null) { logger.LogError("Update participation encountered error of {Error}", @error); return error; } - //update the brand as it is an existing one. + // update the brand as it is an existing one. if (dbBrand != null) { logger.LogInformation("Updating Brand of id:{BrandId} name:{BrandName}", dataRecipientBrand.BrandId, dataRecipientBrand.BrandName); registerDatabaseContext.Brands.Update(drBrandToSave); } - //handle software products + // handle software products if ((error = await drBrandToSave.UpsertRecipientBrandSoftwareProducts(registerDatabaseContext, repositoryMapper, dataRecipientBrand.SoftwareProducts, logger)) != null) { logger.LogError("Update SoftwareProduct encountered error of {Error}", @error); @@ -223,10 +226,13 @@ public static async Task AddOrUpdateDataRecipientLegalEntity( return error; } - public static async Task UpsertRecipientBrandSoftwareProducts(this Brand brand, RegisterDatabaseContext registerDbContext, - IRepositoryMapper repositoryMapper, ICollection softwareProducts, ILogger logger) + public static async Task UpsertRecipientBrandSoftwareProducts( + this Brand brand, + RegisterDatabaseContext registerDbContext, + IRepositoryMapper repositoryMapper, + ICollection softwareProducts, + ILogger logger) { - brand.SoftwareProducts ??= new List(); foreach (var s in softwareProducts) @@ -237,10 +243,12 @@ public static async Task UpsertRecipientBrandSoftwareProducts if (existingSoftwareProduct != null) { - //check if we getting assigned to new brand. + // check if we getting assigned to new brand. if (existingSoftwareProduct.BrandId != brand.BrandId) { - return new BusinessRuleError("urn:au-cds:error:cds-all:Field/Invalid", "Invalid Field", + return new BusinessRuleError( + "urn:au-cds:error:cds-all:Field/Invalid", + "Invalid Field", $"Value '{existingSoftwareProduct.SoftwareProductId}' in SoftwareProductId is already associated with a different brand."); } @@ -248,7 +256,7 @@ public static async Task UpsertRecipientBrandSoftwareProducts registerDbContext.SoftwareProducts.Update(softwareProduct); } - if (null == existingSoftwareProduct) + if (existingSoftwareProduct == null) { softwareProduct.BrandId = brand.BrandId; logger.LogInformation("Adding new SoftwareProduct of id:{SoftwareProductId} for name:{SoftwareProdcutName}", softwareProduct.SoftwareProductId, softwareProduct.SoftwareProductName); @@ -261,17 +269,21 @@ public static async Task UpsertRecipientBrandSoftwareProducts return null; } - public static async Task UpsertSoftwareProductCertificates(this SoftwareProduct softwareProduct, RegisterDatabaseContext registerDbContext, - IRepositoryMapper repositoryMapper, ICollection certificates, ILogger logger) + public static async Task UpsertSoftwareProductCertificates( + this SoftwareProduct softwareProduct, + RegisterDatabaseContext registerDbContext, + IRepositoryMapper repositoryMapper, + ICollection certificates, + ILogger logger) { softwareProduct.Certificates ??= []; foreach (var c in certificates) { - var existingCertificate = await registerDbContext.SoftwareProductCertificates.SingleOrDefaultAsync(cert => - cert.Thumbprint == c.Thumbprint && - cert.SoftwareProductId == softwareProduct.SoftwareProductId && - cert.CommonName == c.CommonName); + var existingCertificate = await registerDbContext.SoftwareProductCertificates.SingleOrDefaultAsync(cert => + cert.Thumbprint == c.Thumbprint && + cert.SoftwareProductId == softwareProduct.SoftwareProductId && + cert.CommonName == c.CommonName); if (existingCertificate != null) { @@ -280,29 +292,33 @@ public static async Task UpsertSoftwareProductCertificates(this SoftwareProduct registerDbContext.SoftwareProductCertificates.Update(existingCertificate); } - if (null == existingCertificate) + if (existingCertificate == null) { var certificate = repositoryMapper.Map(c); certificate.SoftwareProductId = softwareProduct.SoftwareProductId; await registerDbContext.SoftwareProductCertificates.AddAsync(certificate); await registerDbContext.SaveChangesAsync(); logger.LogInformation("Adding new SoftwareProductCertificate of id:{SoftwareProductCertificateId} for SoftwareProductId:{SoftwareProductId}", certificate.SoftwareProductCertificateId, certificate.SoftwareProductId); - } + } } } - public static async Task AddOrUpdateDataRecipientParticipation(this Brand brand, LegalEntity legalEntity, - DomainEntities.DataRecipient dataRecipient, RegisterDatabaseContext registerDatabaseContext, ILogger logger) + public static async Task AddOrUpdateDataRecipientParticipation( + this Brand brand, + LegalEntity legalEntity, + DomainEntities.DataRecipient dataRecipient, + RegisterDatabaseContext registerDatabaseContext, + ILogger logger) { var existingParticipant = await registerDatabaseContext.Participations. Include(x => x.LegalEntity). - SingleOrDefaultAsync(p => (p.LegalEntityId == legalEntity.LegalEntityId || p.Brands.Any(b => b.BrandId == brand.BrandId) && p.ParticipationTypeId == ParticipationTypes.Dr)); + SingleOrDefaultAsync(p => (p.LegalEntityId == legalEntity.LegalEntityId || (p.Brands.Any(b => b.BrandId == brand.BrandId) && p.ParticipationTypeId == ParticipationTypes.Dr))); var participationStatus = (ParticipationStatusType)Enum.Parse(typeof(Entities.ParticipationStatusType), dataRecipient.LegalEntity.Status, true); if (existingParticipant != null) { - //check if there is a change in the participation between brand and legal entity + // check if there is a change in the participation between brand and legal entity if (existingParticipant.LegalEntityId != legalEntity.LegalEntityId) { return new BusinessRuleError("urn:au-cds:error:cds-all:Field/Invalid", "Invalid Field", $"dataRecipientBrandId '{brand.BrandId}' is already associated with a different legal entity."); @@ -312,7 +328,7 @@ public static async Task AddOrUpdateDataRecipientParticipatio existingParticipant.StatusId = participationStatus; } - //create the Participation if required. + // create the Participation if required. if (existingParticipant == null) { var participant = new Participation @@ -325,15 +341,15 @@ public static async Task AddOrUpdateDataRecipientParticipatio legalEntity.Participations ??= new List(); legalEntity.Participations.Add(participant); - //assigning new participation to the brand. + // assigning new participation to the brand. brand.ParticipationId = participant.ParticipationId; await registerDatabaseContext.Participations.AddAsync(participant); await registerDatabaseContext.SaveChangesAsync(); logger.LogInformation("Adding new Participation of id:{ParticipationId} getting added to the repository.", participant.ParticipationId); - } + } return null; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs b/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs index 068d6da..d17390b 100644 --- a/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs +++ b/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs @@ -23,10 +23,9 @@ public MappingProfile() .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Participations.FirstOrDefault().Status.ParticipationStatusCode.ToUpper())); CreateMap() - .ForMember(dest => dest.OrganisationTypeId, source => source.MapFrom(source => source.OrganisationType == null ? null : Enum.Parse(typeof(Entities.OrganisationTypes), source.OrganisationType.Replace("_", ""), true))) + .ForMember(dest => dest.OrganisationTypeId, source => source.MapFrom(source => source.OrganisationType == null ? null : Enum.Parse(typeof(Entities.OrganisationTypes), source.OrganisationType.Replace("_", string.Empty), true))) .ForMember(dest => dest.OrganisationType, opt => opt.Ignore()); - CreateMap() .ForMember(dest => dest.DataHolderId, source => source.MapFrom(source => source.ParticipationId)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status.ParticipationStatusCode)) @@ -77,12 +76,11 @@ public MappingProfile() .ForMember(dest => dest.BrandStatus, opts => opts.Ignore()) .ForMember(dest => dest.SoftwareProducts, opts => opts.Ignore()); - CreateMap() .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status.SoftwareProductStatusCode)) .ForMember(dest => dest.IsActive, source => source.MapFrom(source => source.Status.SoftwareProductStatusId == SoftwareProductStatusType.Active)) .ForMember(dest => dest.RedirectUri, source => source.MapFrom(src => src.RedirectUris)) - .ForMember(dest => dest.RedirectUris, opts => opts.Ignore()) //Ignore this as it is a computed property with no setter + .ForMember(dest => dest.RedirectUris, opts => opts.Ignore()) // Ignore this as it is a computed property with no setter .ForMember(dest => dest.DataRecipientBrand, source => source.MapFrom(s => s.Brand)); CreateMap() @@ -102,7 +100,7 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.RegisterUType, source => source.MapFrom(source => source.RegisterUType.RegisterUTypeCode)); CreateMap() - .ForMember(dest => dest.RegisterUTypeId, source => source.MapFrom(source => + .ForMember(dest => dest.RegisterUTypeId, source => source.MapFrom(source => Enum.Parse(typeof(RegisterUTypes), source.RegisterUType.Replace("-", string.Empty), true))) .ForMember(dest => dest.JwksEndpoint, source => source.MapFrom(source => source.JwksEndpoint)) .ForMember(dest => dest.RegisterUType, opt => opt.Ignore()) @@ -113,4 +111,4 @@ public MappingProfile() .ReverseMap(); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs index 9cbe792..bb0e3d8 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs @@ -4,62 +4,74 @@ namespace CDR.Register.Repository.Infrastructure { + public class RegisterDatabaseContext : DbContext + { + public RegisterDatabaseContext() + { + } - public class RegisterDatabaseContext : DbContext - { - public RegisterDatabaseContext() - { - - } - - public RegisterDatabaseContext(DbContextOptions options) : base(options) - { - } - - public DbSet LegalEntities { get; set; } - public DbSet Participations { get; set; } - public DbSet Brands { get; set; } - public DbSet BrandStatuses { get; set; } - public DbSet Endpoints { get; set; } - public DbSet AuthDetails { get; set; } - public DbSet SoftwareProducts { get; set; } - public DbSet IndustryTypes { get; set; } - public DbSet OrganisationTypes { get; set; } - public DbSet ParticicipationStatuses { get; set; } - public DbSet ParticipationTypes { get; set; } - public DbSet SoftwareProductStatuses { get; set; } - public DbSet RegisterUTypes { get; set; } - public DbSet SoftwareProductCertificates { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - foreach (var clrType in modelBuilder.Model.GetEntityTypes().Select(e => e.ClrType)) - { - // Use the entity name instead of the Context.DbSet name - // refs https://docs.microsoft.com/en-us/ef/core/modeling/entity-types?tabs=fluent-api#table-name - modelBuilder.Entity(clrType).ToTable(clrType.Name); - } - - // Add composite primary keys - modelBuilder.Entity() - .ToTable("AuthDetail", t => t.IsTemporal()) - .HasKey(c => new { c.BrandId, c.RegisterUTypeId }); - - // Configure 1-to-1 relationship. - modelBuilder.Entity() - .ToTable("Brand", t => t.IsTemporal()) - .HasOne(b => b.Endpoint) - .WithOne(e => e.Brand); - - // Other, Temporal table configurations - modelBuilder.Entity().ToTable("Endpoint", t => t.IsTemporal()); - modelBuilder.Entity().ToTable("LegalEntity", t => t.IsTemporal()); - modelBuilder.Entity().ToTable("Participation", t => t.IsTemporal()); - modelBuilder.Entity().ToTable("SoftwareProduct", t => t.IsTemporal()); - modelBuilder.Entity().ToTable("SoftwareProductCertificate", t => t.IsTemporal()); - - // Seed the database with reference data and initial data - modelBuilder.SeedDatabase(); - } - } + public RegisterDatabaseContext(DbContextOptions options) + : base(options) + { + } + + public DbSet LegalEntities { get; set; } + + public DbSet Participations { get; set; } + + public DbSet Brands { get; set; } + + public DbSet BrandStatuses { get; set; } + + public DbSet Endpoints { get; set; } + + public DbSet AuthDetails { get; set; } + + public DbSet SoftwareProducts { get; set; } + + public DbSet IndustryTypes { get; set; } + + public DbSet OrganisationTypes { get; set; } + + public DbSet ParticicipationStatuses { get; set; } + + public DbSet ParticipationTypes { get; set; } + + public DbSet SoftwareProductStatuses { get; set; } + + public DbSet RegisterUTypes { get; set; } + + public DbSet SoftwareProductCertificates { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + foreach (var clrType in modelBuilder.Model.GetEntityTypes().Select(e => e.ClrType)) + { + // Use the entity name instead of the Context.DbSet name + // refs https://docs.microsoft.com/en-us/ef/core/modeling/entity-types?tabs=fluent-api#table-name + modelBuilder.Entity(clrType).ToTable(clrType.Name); + } + + // Add composite primary keys + modelBuilder.Entity() + .ToTable("AuthDetail", t => t.IsTemporal()) + .HasKey(c => new { c.BrandId, c.RegisterUTypeId }); + + // Configure 1-to-1 relationship. + modelBuilder.Entity() + .ToTable("Brand", t => t.IsTemporal()) + .HasOne(b => b.Endpoint) + .WithOne(e => e.Brand); + + // Other, Temporal table configurations + modelBuilder.Entity().ToTable("Endpoint", t => t.IsTemporal()); + modelBuilder.Entity().ToTable("LegalEntity", t => t.IsTemporal()); + modelBuilder.Entity().ToTable("Participation", t => t.IsTemporal()); + modelBuilder.Entity().ToTable("SoftwareProduct", t => t.IsTemporal()); + modelBuilder.Entity().ToTable("SoftwareProductCertificate", t => t.IsTemporal()); + + // Seed the database with reference data and initial data + modelBuilder.SeedDatabase(); + } + } } diff --git a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs index ec9f829..584a97f 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContextDesignTimeFactory.cs @@ -1,12 +1,11 @@ -using System; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; namespace CDR.Register.Repository.Infrastructure { /// /// This is the DB context initialisation for the tooling such as migrations. - /// When the tooling runs the migration, it looks first for a class that implements IDesignTimeDbContextFactory and if found, + /// When the tooling runs the migration, it looks first for a class that implements IDesignTimeDbContextFactory and if found, /// it will use that for configuring the context. Runtime behavior is not affected by any configuration set in the factory class. /// public class RegisterDatabaseContextDesignTimeFactory : IDesignTimeDbContextFactory @@ -25,4 +24,4 @@ public RegisterDatabaseContext CreateDbContext(string[] args) return new RegisterDatabaseContext(options); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs b/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs index 20921fc..a0d68a8 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs @@ -36,11 +36,6 @@ public Entities.LegalEntity Map(Domain.Entities.DataRecipientLegalEntity legalEn return _mapper.Map(legalEntity); } - public Domain.Entities.SoftwareProduct MapSoftwareProduct(Entities.SoftwareProduct softwareProduct) - { - return _mapper.Map(softwareProduct); - } - public Entities.Brand Map(DataRecipientBrand brand) { return _mapper.Map(brand); @@ -55,5 +50,10 @@ public Entities.SoftwareProductCertificate Map(SoftwareProductCertificateInfosec { return _mapper.Map(softwareProductCertificate); } + + public Domain.Entities.SoftwareProduct MapSoftwareProduct(Entities.SoftwareProduct softwareProduct) + { + return _mapper.Map(softwareProduct); + } } } diff --git a/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs b/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs index 8d2c635..47aedc7 100644 --- a/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs +++ b/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs @@ -66,7 +66,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_LegalEntityStatus", x => x.LegalEntityStatusId); }); - + migrationBuilder.InsertData( table: "LegalEntityStatus", columns: new[] { "LegalEntityStatusId", "LegalEntityStatusCode" }, @@ -85,11 +85,10 @@ protected override void Up(MigrationBuilder migrationBuilder) { 1, "Unrestricted" } }); - //Updating exsiting data + // Updating exsiting data migrationBuilder.Sql(@"Update IndustryType Set IndustryTypeCode='Banking' where IndustryTypeId=1; Update IndustryType Set IndustryTypeCode='Energy' where IndustryTypeId=2;"); - migrationBuilder.CreateIndex( name: "IX_LegalEntity_AccreditationLevelId", table: "LegalEntity", @@ -152,7 +151,7 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropIndex( name: "IX_LegalEntity_LegalEntityStatusId", table: "LegalEntity"); - + migrationBuilder.DropColumn( name: "AccreditationLevelId", table: "LegalEntity"); diff --git a/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs b/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs index 69b3b9a..090b60b 100644 --- a/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs +++ b/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs @@ -9,17 +9,17 @@ public partial class V5 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { - // Add the additional Participation to LE - var participationTypeIds = new int[] { (int)ParticipationTypes.Dh, (int)ParticipationTypes.Dr }; - foreach (var participationTypeId in participationTypeIds) - { - migrationBuilder.Sql($"INSERT INTO [dbo].[Participation] (ParticipationId, LegalEntityId, ParticipationTypeId, IndustryId, StatusId)\r\nSELECT NEWID(), le.LegalEntityId, {participationTypeId}, 1, 1 FROM [dbo].[LegalEntity] le\r\nLEFT OUTER JOIN [dbo].[Participation] p on p.LegalEntityId = le.LegalEntityId AND p.ParticipationTypeId = {participationTypeId}\r\nWHERE p.ParticipationId IS NULL"); - } - } + // Add the additional Participation to LE + var participationTypeIds = new int[] { (int)ParticipationTypes.Dh, (int)ParticipationTypes.Dr }; + foreach (var participationTypeId in participationTypeIds) + { + migrationBuilder.Sql($"INSERT INTO [dbo].[Participation] (ParticipationId, LegalEntityId, ParticipationTypeId, IndustryId, StatusId)\r\nSELECT NEWID(), le.LegalEntityId, {participationTypeId}, 1, 1 FROM [dbo].[LegalEntity] le\r\nLEFT OUTER JOIN [dbo].[Participation] p on p.LegalEntityId = le.LegalEntityId AND p.ParticipationTypeId = {participationTypeId}\r\nWHERE p.ParticipationId IS NULL"); + } + } protected override void Down(MigrationBuilder migrationBuilder) { - + // Not implemented } } } diff --git a/Source/CDR.Register.Repository/Migrations/20230718020902_V7.cs b/Source/CDR.Register.Repository/Migrations/20230718020902_V7.cs index 9afea2c..9950f8c 100644 --- a/Source/CDR.Register.Repository/Migrations/20230718020902_V7.cs +++ b/Source/CDR.Register.Repository/Migrations/20230718020902_V7.cs @@ -7,9 +7,9 @@ namespace CDR.Register.Repository.Migrations public partial class V7 : Migration { protected override void Up(MigrationBuilder migrationBuilder) - { - // Autognerate ADR###### as the format for AccreditationNumbers eg ADR000099 for null - // Accreditation Number Fixing all the exisitng data. + { + // Autognerate ADR###### as the format for AccreditationNumbers eg ADR000099 for null. + // Accreditation Number Fixing all the exisitng data. var sqlQuery = @"DECLARE @Counter INT; SET @Counter = 0 @@ -27,7 +27,7 @@ FROM Participation P protected override void Down(MigrationBuilder migrationBuilder) { - + // Not required. } } } diff --git a/Source/CDR.Register.Repository/RegisterAdminRepository.cs b/Source/CDR.Register.Repository/RegisterAdminRepository.cs index 7b8d908..06f5473 100644 --- a/Source/CDR.Register.Repository/RegisterAdminRepository.cs +++ b/Source/CDR.Register.Repository/RegisterAdminRepository.cs @@ -21,9 +21,9 @@ public class RegisterAdminRepository : IRegisterAdminRepository public RegisterAdminRepository(RegisterDatabaseContext registerDatabaseContext, IMapper mapper, IRepositoryMapper repositoryMapper, ILogger logger) { _registerDatabaseContext = registerDatabaseContext; - _mapper=mapper; + _mapper = mapper; _reporsitoryMapper = repositoryMapper; - _logger=logger; + _logger = logger; } private async Task<(LegalEntity, Participation)> SaveDataHolderLegalEntity(DataHolder dataHolder) @@ -114,7 +114,7 @@ public async Task AddOrUpdateDataRecipient(DataRecipient data { var existingDataRecipient = await GetDataRecipientIdAsync(dataRecipient.LegalEntity.LegalEntityId); - using var transaction = _registerDatabaseContext.Database.BeginTransaction(); + using var transaction = await _registerDatabaseContext.Database.BeginTransactionAsync(); LegalEntity legalEntity = _reporsitoryMapper.Map(dataRecipient.LegalEntity); @@ -136,8 +136,8 @@ public async Task AddOrUpdateDataRecipient(DataRecipient data { await _registerDatabaseContext.SaveChangesAsync(); await transaction.CommitAsync(); - } - + } + return error; } @@ -148,15 +148,15 @@ public async Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand return false; } - (_ , var savedParticipation) = await SaveDataHolderLegalEntity(dataHolderBrand.DataHolder); + (_, var savedParticipation) = await SaveDataHolderLegalEntity(dataHolderBrand.DataHolder); // Save DH Brand var dhBrandToSave = _mapper.Map(dataHolderBrand); - var existingBrand = _registerDatabaseContext.Brands + var existingBrand = await _registerDatabaseContext.Brands .Include(b => b.Participation) .Include(b => b.AuthDetails) .Include(b => b.Endpoint) - .Where(brand => brand.BrandId == dataHolderBrand.BrandId).FirstOrDefault(); + .Where(brand => brand.BrandId == dataHolderBrand.BrandId).FirstOrDefaultAsync(); if (existingBrand == null) { dhBrandToSave.LastUpdated = DateTime.UtcNow; diff --git a/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs b/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs index 7ba9ed0..e25d6ee 100644 --- a/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs +++ b/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs @@ -89,9 +89,9 @@ public async Task GetDataRecipientsAsync(Infrastructure.Industr return _mapper.Map(allParticipants); } - /// + /// /// The industry parameter is passed but currently not used. - /// + /// protected async Task> ProcessGetDataRecipients(Infrastructure.Industry industry) { return await this._registerDatabaseContext.Participations.AsNoTracking() @@ -125,4 +125,4 @@ protected async Task> ProcessGetDataRecipients(Infrastructur return _mapper.Map(softwareProduct); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/RegisterStatusRepository.cs b/Source/CDR.Register.Repository/RegisterStatusRepository.cs index 3c066e6..99a61b2 100644 --- a/Source/CDR.Register.Repository/RegisterStatusRepository.cs +++ b/Source/CDR.Register.Repository/RegisterStatusRepository.cs @@ -56,8 +56,7 @@ public async Task GetDataRecipientStatusesAsync(Infrastru { SoftwareProductId = sp.SoftwareProductId, Status = sp.Status.SoftwareProductStatusCode - } - ) + }) .OrderBy(sp => sp.SoftwareProductId) .ToArray(); } diff --git a/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs b/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs index 9756253..6a7c7f0 100644 --- a/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs +++ b/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs @@ -32,7 +32,6 @@ public async Task GetSoftwareStatementAssertionAsync LegalEntity = _mapper.Map(x.Brand.Participation.LegalEntity) }) .FirstOrDefaultAsync(); - } } } diff --git a/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj b/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj index a7fe017..0594b09 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj +++ b/Source/CDR.Register.SSA.API.UnitTests/CDR.Register.SSA.API.UnitTests.csproj @@ -5,6 +5,7 @@ $(Version) $(Version) $(Version) + True @@ -39,9 +40,10 @@ - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -50,6 +52,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs b/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs index e9b1a2f..9c15801 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Net.Http; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using CDR.Register.API.Infrastructure; @@ -13,20 +12,22 @@ using Serilog; using Xunit; +#nullable enable + namespace CDR.Register.SSA.API.UnitTests { public class HttpClientHandlerExtensionsTests { private readonly IConfiguration _configuration; - private readonly HttpClientHandler _handler = null!; + private readonly HttpClientHandler _handler; public HttpClientHandlerExtensionsTests() { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddInMemoryCollection(new Dictionary() + .AddInMemoryCollection(new Dictionary() { - { "EnableServerCertificateValidation", "True"}, + { "EnableServerCertificateValidation", "True" }, }) .Build(); _configuration = configuration; @@ -42,7 +43,10 @@ public HttpClientHandlerExtensionsTests() public async Task ServerCertificates_ValidationEnabled_ShouldValidateSslConnection( string certName, string certPassword, bool expected, string reason) { - await using (var mockEndpoint = new MockEndpoint("https://localhost:9990", + Log.Information($"Scenario: {reason}"); + + await using (var mockEndpoint = new MockEndpoint( + "https://localhost:9990", Path.Combine(Directory.GetCurrentDirectory(), "Certificates", certName), certPassword)) { @@ -57,6 +61,7 @@ public async Task ServerCertificates_ValidationEnabled_ShouldValidateSslConnecti { await Assert.ThrowsAsync(async () => await client.GetAsync("https://localhost:9990")); } + await mockEndpoint.Stop(); } } @@ -71,8 +76,11 @@ public MockEndpoint(string url, string certificatePath, string certificatePasswo } public string Url { get; init; } + private int UrlPort => new Uri(Url).Port; + public string CertificatePath { get; init; } + public string CertificatePassword { get; init; } private IWebHost? _host; @@ -84,10 +92,11 @@ public void Start() _host = new WebHostBuilder() .UseKestrel(opts => { - opts.ListenAnyIP(UrlPort, + opts.ListenAnyIP( + UrlPort, opts => opts.UseHttps(new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.Exportable))); }) - .UseStartup(_ => new MockEndpointStartup()) + .UseStartup(typeof(MockEndpointStartup)) .Build(); _host.RunAsync(); @@ -103,7 +112,8 @@ public async Task Stop() } } - bool _disposed; + private bool _disposed; + public async ValueTask DisposeAsync() { Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(DisposeAsync), nameof(MockEndpoint)); @@ -117,8 +127,12 @@ public async ValueTask DisposeAsync() GC.SuppressFinalize(this); } - class MockEndpointStartup + public class MockEndpointStartup { + protected MockEndpointStartup() + { + } + public static void Configure(IApplicationBuilder app) { app.UseHttpsRedirection(); diff --git a/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs b/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs index 7ecabe4..e7fb764 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs @@ -13,10 +13,11 @@ public void GenerateJwks_ValidCertificate_ShouldGenerateJwks() { // Arrange. var path = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ssa.pfx"); - var inMemorySettings = new Dictionary { - {"SigningCertificate:Path", path}, - {"SigningCertificate:Password", "#M0ckRegister#"}, - }; + var inMemorySettings = new Dictionary + { + { "SigningCertificate:Path", path }, + { "SigningCertificate:Password", "#M0ckRegister#" }, + }; IConfiguration configuration = new ConfigurationBuilder() .AddInMemoryCollection(inMemorySettings) .Build(); @@ -38,6 +39,5 @@ public void GenerateJwks_ValidCertificate_ShouldGenerateJwks() Assert.Equal("AQAB", jwk.e); Assert.Equal(2, jwk.key_ops.Length); } - } } diff --git a/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs b/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs index 819b94a..7b0d1b6 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.IO; @@ -14,7 +14,6 @@ namespace CDR.Register.SSA.API.UnitTests { - public class TokenizerServiceTests { private readonly ServiceProvider _serviceProvider; @@ -40,7 +39,7 @@ public TokenizerServiceTests() [Fact] public async Task GenerateJwtTokenAsync_Success() { - //Arrange + // Arrange var tokenizerService = _serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel @@ -67,18 +66,18 @@ public async Task GenerateJwtTokenAsync_Success() software_roles = "data-recipient-software-product", }; - //Generate the SSA JWT token + // Generate the SSA JWT token var ssaToken = await tokenizerService.GenerateJwtTokenAsync(ssa); var tokenHandler = new JwtSecurityTokenHandler(); - //Create the certificate which has only public key + // Create the certificate which has only public key var cert = new X509Certificate2(_configuration["SigningCertificatePublic:Path"]); - //Get credentials from certificate + // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); - //Set token validation parameters + // Set token validation parameters var validationParameters = new TokenValidationParameters() { IssuerSigningKey = certificateSecurityKey, @@ -90,10 +89,11 @@ public async Task GenerateJwtTokenAsync_Success() }; SecurityToken validatedToken; - //Act + + // Act var principal = tokenHandler.ValidateToken(ssaToken, validationParameters, out validatedToken); - //Assert + // Assert Assert.NotNull(validatedToken); Assert.Equal("cdr-register", validatedToken.Issuer); Assert.Equal(17, principal.Claims.Count()); @@ -102,7 +102,7 @@ public async Task GenerateJwtTokenAsync_Success() [Fact] public async Task GenerateJwtTokenAsync_InvalidToken_Failure() { - //Arrange + // Arrange var tokenizerService = _serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel @@ -129,21 +129,21 @@ public async Task GenerateJwtTokenAsync_InvalidToken_Failure() software_roles = "data-recipient-software-product", }; - //Generate the SSA JWT token + // Generate the SSA JWT token var ssaToken = await tokenizerService.GenerateJwtTokenAsync(ssa); - //Create invalid token + // Create invalid token ssaToken = ssaToken.Replace('a', 'b'); var tokenHandler = new JwtSecurityTokenHandler(); - - //Create the certificate which has only public key + + // Create the certificate which has only public key var cert = new X509Certificate2(_configuration["SigningCertificatePublic:Path"]); - //Get credentials from certificate + // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); - //Set token validation parameters + // Set token validation parameters var validationParameters = new TokenValidationParameters() { IssuerSigningKey = certificateSecurityKey, @@ -157,22 +157,23 @@ public async Task GenerateJwtTokenAsync_InvalidToken_Failure() try { SecurityToken validatedToken; - //Act - var principal = tokenHandler.ValidateToken(ssaToken, validationParameters, out validatedToken); + + // Act + tokenHandler.ValidateToken(ssaToken, validationParameters, out validatedToken); } catch (Exception ex) { - var errorMessage = ex.Message.Replace("\n", ""); - //Assert + var errorMessage = ex.Message.Replace("\n", string.Empty); + + // Assert Assert.StartsWith("IDX10511: Signature validation failed.", errorMessage); } - } [Fact] public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() { - //Arrange + // Arrange var tokenizerService = _serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel @@ -199,18 +200,18 @@ public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() software_roles = "data-recipient-software-product", }; - //Generate the SSA JWT token + // Generate the SSA JWT token var ssaToken = await tokenizerService.GenerateJwtTokenAsync(ssa); var tokenHandler = new JwtSecurityTokenHandler(); - //Create the certificate which has only public key + // Create the certificate which has only public key var cert = new X509Certificate2(_configuration["InvalidSigningCertificatePublic:Path"]); - //Get credentials from certificate + // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); - //Set token validation parameters + // Set token validation parameters var validationParameters = new TokenValidationParameters() { IssuerSigningKey = certificateSecurityKey, @@ -224,16 +225,17 @@ public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() try { SecurityToken validatedToken; - //Act - var principal = tokenHandler.ValidateToken(ssaToken, validationParameters, out validatedToken); + + // Act + tokenHandler.ValidateToken(ssaToken, validationParameters, out validatedToken); } catch (Exception ex) { - var errorMessage = ex.Message.Replace("\n", ""); - //Assert + var errorMessage = ex.Message.Replace("\n", string.Empty); + + // Assert Assert.StartsWith("IDX10503: Signature validation failed.", errorMessage); } - } [Fact] @@ -268,14 +270,15 @@ public async Task GenerateJwtTokenAsync_ValidateJwks_Success() // Generate the SSA JWT token var ssaToken = await tokenizerService.GenerateJwtTokenAsync(ssa); var tokenHandler = new JwtSecurityTokenHandler(); - var parsedJwt = tokenHandler.ReadToken(ssaToken) as JwtSecurityToken; + tokenHandler.ReadToken(ssaToken); // Get the certificate service based on config settings. var path = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ssa.pfx"); - var inMemorySettings = new Dictionary { - {"SigningCertificate:Path", path}, - {"SigningCertificate:Password", "#M0ckRegister#"}, - }; + var inMemorySettings = new Dictionary + { + { "SigningCertificate:Path", path }, + { "SigningCertificate:Password", "#M0ckRegister#" }, + }; IConfiguration configuration = new ConfigurationBuilder() .AddInMemoryCollection(inMemorySettings) .Build(); diff --git a/Source/CDR.Register.SSA.API/Business/CertificateService.cs b/Source/CDR.Register.SSA.API/Business/CertificateService.cs index c84fa2f..81d04b0 100644 --- a/Source/CDR.Register.SSA.API/Business/CertificateService.cs +++ b/Source/CDR.Register.SSA.API/Business/CertificateService.cs @@ -21,19 +21,19 @@ public class CertificateService : ICertificateService public CertificateService(IConfiguration config) { - //Create the certificate + // Create the certificate var cert = new X509Certificate2(config["SigningCertificate:Path"], config["SigningCertificate:Password"], X509KeyStorageFlags.Exportable); - //Get credentials from certificate + // Get credentials from certificate SecurityKey = new X509SecurityKey(cert); SigningCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); - //Get certificate kid + // Get certificate kid this.Kid = SigningCredentials.Kid; SigningCredentials.CryptoProviderFactory = new CryptoProviderFactory(); - //Get signature provider + // Get signature provider this.SignatureProvider = SigningCredentials.CryptoProviderFactory.CreateForSigning(SecurityKey, "PS256"); this.JsonWebKeySet = GenerateJwks(); diff --git a/Source/CDR.Register.SSA.API/Business/ICertificateService.cs b/Source/CDR.Register.SSA.API/Business/ICertificateService.cs index ffa85fb..fee353d 100644 --- a/Source/CDR.Register.SSA.API/Business/ICertificateService.cs +++ b/Source/CDR.Register.SSA.API/Business/ICertificateService.cs @@ -5,7 +5,9 @@ namespace CDR.Register.SSA.API.Business public interface ICertificateService { string Kid { get; } + SignatureProvider SignatureProvider { get; } + Register.API.Infrastructure.Models.JsonWebKeySet JsonWebKeySet { get; } } } diff --git a/Source/CDR.Register.SSA.API/Business/ISSAService.cs b/Source/CDR.Register.SSA.API/Business/ISSAService.cs index 6af4105..3151abf 100644 --- a/Source/CDR.Register.SSA.API/Business/ISSAService.cs +++ b/Source/CDR.Register.SSA.API/Business/ISSAService.cs @@ -4,7 +4,7 @@ namespace CDR.Register.SSA.API.Business { - public interface ISSAService + public interface ISsaService { Task GetSoftwareStatementAssertionAsync(Industry industry, string dataRecipientBrandId, string softwareProductId); diff --git a/Source/CDR.Register.SSA.API/Business/Mapper.cs b/Source/CDR.Register.SSA.API/Business/Mapper.cs index ed670f1..0dfa38b 100644 --- a/Source/CDR.Register.SSA.API/Business/Mapper.cs +++ b/Source/CDR.Register.SSA.API/Business/Mapper.cs @@ -35,9 +35,9 @@ public Mapper(IConfiguration config) .ForMember(d => d.software_id, s => s.MapFrom(source => source.SoftwareProduct.SoftwareProductId)) .ForMember(d => d.tos_uri, s => s.MapFrom(source => source.SoftwareProduct.TosUri)) .ForMember(d => d.iss, s => s.MapFrom(source => _config["SSA:Issuer"])) - .ForMember(d => d.iat, s => s.MapFrom(source => (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)) + .ForMember(d => d.iat, s => s.MapFrom(source => (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds)) .ForMember(d => d.jti, s => s.MapFrom(source => Guid.NewGuid().ToString().Replace("-", string.Empty))) - .ForMember(d => d.exp, s => s.MapFrom(source => (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds + long.Parse(_config["SSA:ExpiryInSeconds"]))) + .ForMember(d => d.exp, s => s.MapFrom(source => (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds + long.Parse(_config["SSA:ExpiryInSeconds"]))) .ForMember(d => d.legal_entity_id, s => s.MapFrom(source => source.LegalEntity.LegalEntityId)) .ForMember(d => d.legal_entity_name, s => s.MapFrom(source => source.LegalEntity.LegalEntityName)) .ForMember(d => d.sector_identifier_uri, s => s.MapFrom(source => source.SoftwareProduct.SectorIdentifierUri)); @@ -52,6 +52,7 @@ public SoftwareStatementAssertionModel MapV3(SoftwareStatementAssertion software { return null; } + return _mapper.Map(softwareStatementAssertion); } } diff --git a/Source/CDR.Register.SSA.API/Business/Models/SSAValidationException.cs b/Source/CDR.Register.SSA.API/Business/Models/SSAValidationException.cs index a3513c7..4b2c2c4 100644 --- a/Source/CDR.Register.SSA.API/Business/Models/SSAValidationException.cs +++ b/Source/CDR.Register.SSA.API/Business/Models/SSAValidationException.cs @@ -1,20 +1,16 @@ using System; -using System.Runtime.Serialization; namespace CDR.Register.SSA.API.Business.Models { - - public class SSAValidationException : Exception + public class SsaValidationException : Exception { - public SSAValidationException() + public SsaValidationException() { - } - public SSAValidationException(string message) : base(message) + public SsaValidationException(string message) + : base(message) { - } } - } diff --git a/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs b/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs index da19eeb..5e19b87 100644 --- a/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs +++ b/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs @@ -6,32 +6,32 @@ namespace CDR.Register.SSA.API.Business.Models public class SoftwareStatementAssertionModel { /// - /// iss (issuer) claim denoting the party attesting to the claims in the software statement + /// iss (issuer) claim denoting the party attesting to the claims in the software statement. /// [Required(AllowEmptyStrings = false)] public string iss { get; set; } /// - /// iat (issued at) claim + /// iat (issued at) claim. /// [Required] public long iat { get; set; } /// /// exp (expiration time) claim - /// MUST NOT be accepted for processing + /// MUST NOT be accepted for processing. /// [Required] public long exp { get; set; } /// - /// jti (JWT ID) claim + /// jti (JWT ID) claim. /// [Required(AllowEmptyStrings = false)] public string jti { get; set; } /// - /// A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR) + /// A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). /// [Required(AllowEmptyStrings = false)] public string org_id { get; set; } @@ -43,82 +43,82 @@ public class SoftwareStatementAssertionModel public string org_name { get; set; } /// - /// Human-readable string name of the software product to be presented to the end-user during authorization + /// Human-readable string name of the software product to be presented to the end-user during authorization. /// [Required(AllowEmptyStrings = false)] public string client_name { get; set; } /// - /// Human-readable string name of the software product description to be presented to the end user during authorization + /// Human-readable string name of the software product description to be presented to the end user during authorization. /// [Required(AllowEmptyStrings = false)] public string client_description { get; set; } /// - /// URL string of a web page providing information about the ADR Software Product + /// URL string of a web page providing information about the ADR Software Product. /// [Required(AllowEmptyStrings = false)] public string client_uri { get; set; } /// - /// Array of redirection URI strings for use in redirect-based flows + /// Array of redirection URI strings for use in redirect-based flows. /// [Required] public IEnumerable redirect_uris { get; set; } /// - /// URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval + /// URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval. /// [Required(AllowEmptyStrings = false)] public string logo_uri { get; set; } /// - /// URL string that points to a humanreadable terms of service document for the Software Product + /// URL string that points to a humanreadable terms of service document for the Software Product. /// public string tos_uri { get; set; } /// - /// URL string that points to a humanreadable policy document for the Software Product + /// URL string that points to a humanreadable policy document for the Software Product. /// public string policy_uri { get; set; } /// - /// URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys + /// URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys. /// [Required(AllowEmptyStrings = false)] public string jwks_uri { get; set; } /// - /// URI string that references the location of the Software Product consent revocation endpoint as per https://consumerdatastandardsaustralia.github.io/standards/#end-points + /// URI string that references the location of the Software Product consent revocation endpoint as per https://consumerdatastandardsaustralia.github.io/standards/#end-points. /// [Required(AllowEmptyStrings = false)] public string revocation_uri { get; set; } /// - /// Base URI for the Consumer Data Standard data recipient endpoints. This should be the base to provide reference to all other Data Recipient Endpoints + /// Base URI for the Consumer Data Standard data recipient endpoints. This should be the base to provide reference to all other Data Recipient Endpoints. /// [Required(AllowEmptyStrings = false)] public string recipient_base_uri { get; set; } /// - /// String representing a unique identifier assigned by the ACCC Register and used by registration endpoints to identify the software product to be dynamically registered + /// String representing a unique identifier assigned by the ACCC Register and used by registration endpoints to identify the software product to be dynamically registered. /// [Required(AllowEmptyStrings = false)] public string software_id { get; set; } /// - /// String containing a role of the software in thwe CDR Regime. Initially the only value used with be "data-recipient-software-product" + /// String containing a role of the software in thwe CDR Regime. Initially the only value used with be "data-recipient-software-product". /// public string software_roles { get; set; } /// - /// String containing a space-separated list of scope values that the client can use when requesting access tokens + /// String containing a space-separated list of scope values that the client can use when requesting access tokens. /// [Required(AllowEmptyStrings = false)] public string scope { get; set; } /// - /// URL string that references a sector uri for the client. If present, the server SHOULD display this image to the end-user during approval + /// URL string that references a sector uri for the client. If present, the server SHOULD display this image to the end-user during approval. /// public string sector_identifier_uri { get; set; } @@ -132,4 +132,4 @@ public class SoftwareStatementAssertionModel /// public string legal_entity_name { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.SSA.API/Business/SSAService.cs b/Source/CDR.Register.SSA.API/Business/SSAService.cs index ff3b194..8dbee87 100644 --- a/Source/CDR.Register.SSA.API/Business/SSAService.cs +++ b/Source/CDR.Register.SSA.API/Business/SSAService.cs @@ -1,28 +1,25 @@ -using CDR.Register.API.Infrastructure; -using CDR.Register.Domain.Entities; +using CDR.Register.Domain.Entities; using CDR.Register.Domain.Repositories; -using CDR.Register.Repository.Infrastructure; using CDR.Register.SSA.API.Business.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog.Context; using System; -using System.Linq; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; namespace CDR.Register.SSA.API.Business { - public class SSAService : ISSAService + public class SsaService : ISsaService { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMapper _mapper; private readonly ISoftwareStatementAssertionRepository _repository; private readonly ITokenizerService _tokenizer; - public SSAService( - ILogger logger, + public SsaService( + ILogger logger, IConfiguration configuration, IMapper mapper, ISoftwareStatementAssertionRepository repository, @@ -52,7 +49,7 @@ public async Task GetSoftwareStatementAssertion using (LogContext.PushProperty("MethodName", "GetSoftwareStatementAssertionAsync")) { - _logger.LogDebug("SSA for dataRecipientBrandId: {dataRecipientBrandId} / softwareProductId: {softwareProductId} \r\n{ssa}", dataRecipientBrandId, softwareProductId, ssa.ToJson()); + _logger.LogDebug("SSA for dataRecipientBrandId: {DataRecipientBrandId} / softwareProductId: {SoftwareProductId} \r\n{Ssa}", dataRecipientBrandId, softwareProductId, ssa.ToJson()); } // Validate the SSA @@ -68,7 +65,7 @@ private void Validate(SoftwareStatementAssertionModel ssa) if (!Validator.TryValidateObject(ssa, validationContext, validationResults, true)) { var errorMessage = $"Validation errors in SSA for dataRecipientBrandId: {ssa.org_id} / softwareProductId: {ssa.software_id} \r\n{validationResults.ToJson()}"; - throw new SSAValidationException(errorMessage); + throw new SsaValidationException(errorMessage); } } @@ -84,4 +81,4 @@ private async Task GetSoftwareStatementAssertionAsyn return await _repository.GetSoftwareStatementAssertionAsync(dataRecipientBrandGuid, softwareProductGuid); } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj b/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj index 45276ea..70a9772 100644 --- a/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj +++ b/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj @@ -11,18 +11,27 @@ - - - + + + + - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.SSA.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.SSA.xml b/Source/CDR.Register.SSA.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.SSA.xml index 611e325..a9384c6 100644 --- a/Source/CDR.Register.SSA.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.SSA.xml +++ b/Source/CDR.Register.SSA.API/ConsumerDataRight.ParticipantTooling.MockRegister.API.SSA.xml @@ -11,28 +11,28 @@ - iss (issuer) claim denoting the party attesting to the claims in the software statement + iss (issuer) claim denoting the party attesting to the claims in the software statement. - iat (issued at) claim + iat (issued at) claim. exp (expiration time) claim - MUST NOT be accepted for processing + MUST NOT be accepted for processing. - jti (JWT ID) claim + jti (JWT ID) claim. - A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR) + A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). @@ -42,72 +42,72 @@ - Human-readable string name of the software product to be presented to the end-user during authorization + Human-readable string name of the software product to be presented to the end-user during authorization. - Human-readable string name of the software product description to be presented to the end user during authorization + Human-readable string name of the software product description to be presented to the end user during authorization. - URL string of a web page providing information about the ADR Software Product + URL string of a web page providing information about the ADR Software Product. - Array of redirection URI strings for use in redirect-based flows + Array of redirection URI strings for use in redirect-based flows. - URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval + URL string that references a logo for the client. If present, the server SHOULD display this image to the end-user during approval. - URL string that points to a humanreadable terms of service document for the Software Product + URL string that points to a humanreadable terms of service document for the Software Product. - URL string that points to a humanreadable policy document for the Software Product + URL string that points to a humanreadable policy document for the Software Product. - URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys + URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys. - URI string that references the location of the Software Product consent revocation endpoint as per https://consumerdatastandardsaustralia.github.io/standards/#end-points + URI string that references the location of the Software Product consent revocation endpoint as per https://consumerdatastandardsaustralia.github.io/standards/#end-points. - Base URI for the Consumer Data Standard data recipient endpoints. This should be the base to provide reference to all other Data Recipient Endpoints + Base URI for the Consumer Data Standard data recipient endpoints. This should be the base to provide reference to all other Data Recipient Endpoints. - String representing a unique identifier assigned by the ACCC Register and used by registration endpoints to identify the software product to be dynamically registered + String representing a unique identifier assigned by the ACCC Register and used by registration endpoints to identify the software product to be dynamically registered. - String containing a role of the software in thwe CDR Regime. Initially the only value used with be "data-recipient-software-product" + String containing a role of the software in thwe CDR Regime. Initially the only value used with be "data-recipient-software-product". - String containing a space-separated list of scope values that the client can use when requesting access tokens + String containing a space-separated list of scope values that the client can use when requesting access tokens. - URL string that references a sector uri for the client. If present, the server SHOULD display this image to the end-user during approval + URL string that references a sector uri for the client. If present, the server SHOULD display this image to the end-user during approval. @@ -133,11 +133,11 @@ The software statement assertion. The software statement assertion token as a string. - + - Performs status check against the softwareProductId parameter + Performs status check against the softwareProductId parameter. - Software Product ID + Software Product ID. An ActionResult if there is an error to return, otherwise null if there are no issues. diff --git a/Source/CDR.Register.SSA.API/Controllers/SSAController.cs b/Source/CDR.Register.SSA.API/Controllers/SSAController.cs index db16ccc..ab3f106 100644 --- a/Source/CDR.Register.SSA.API/Controllers/SSAController.cs +++ b/Source/CDR.Register.SSA.API/Controllers/SSAController.cs @@ -15,15 +15,15 @@ namespace CDR.Register.SSA.API.Controllers { [Route("cdr-register")] [ApiController] - public class SSAController : ControllerBase + public class SsaController : ControllerBase { - private readonly ISSAService _ssaService; + private readonly ISsaService _ssaService; private readonly ICertificateService _certificateService; private readonly IDataRecipientStatusCheckService _statusCheckService; private readonly IConfiguration _configuration; - public SSAController( - ISSAService ssaService, + public SsaController( + ISsaService ssaService, ICertificateService certificateService, IDataRecipientStatusCheckService statusCheckService, IConfiguration configuration) @@ -56,7 +56,9 @@ public async Task GetSoftwareStatementAssertionXV3(string industr var result = await CheckSoftwareProduct(softwareProductId); if (result != null) + { return result; + } var ssa = await _ssaService.GetSoftwareStatementAssertionJWTAsync(industry.ToIndustry(), dataRecipientBrandId, softwareProductId); return string.IsNullOrEmpty(ssa) ? NotFound(new ResponseErrorList(ResponseErrorList.NotFound())) : Ok(ssa); @@ -68,38 +70,46 @@ public async Task GetSoftwareStatementAssertionXV3(string industr [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult GetJwks() { - return Ok(_certificateService.JsonWebKeySet); + return new OkObjectResult(_certificateService.JsonWebKeySet); } private Guid? GetSoftwareProductIdFromAccessToken() { string clientId = User.FindFirst("client_id")?.Value; if (Guid.TryParse(clientId, out Guid softwareProductId)) + { return softwareProductId; + } return null; } /// - /// Performs status check against the softwareProductId parameter + /// Performs status check against the softwareProductId parameter. /// - /// Software Product ID + /// Software Product ID. /// An ActionResult if there is an error to return, otherwise null if there are no issues. private async Task CheckSoftwareProduct(string softwareProductId) { // Get the software product id based on the access token. var softwareProductIdAsGuid = GetSoftwareProductIdFromAccessToken(); if (softwareProductIdAsGuid == null) + { return new BadRequestObjectResult(new ResponseErrorList().AddUnexpectedError()); + } // Ensure that the software product id from the access token matches the software product id in the request. if (!softwareProductIdAsGuid.ToString().Equals(softwareProductId, StringComparison.OrdinalIgnoreCase)) + { return new NotFoundObjectResult(new ResponseErrorList(ResponseErrorList.InvalidSoftwareProduct(softwareProductId))); + } // Check the status of the data recipient making the SSA request. var statusErrors = await CheckStatus(softwareProductIdAsGuid.Value); if (statusErrors.HasErrors()) + { return new RegisterForbidResult(statusErrors); + } return null; } @@ -108,6 +118,5 @@ private async Task CheckStatus(Guid softwareProductId) { return await _statusCheckService.ValidateSoftwareProductStatus(softwareProductId); } - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.SSA.API/Extensions/ServiceCollectionExtensions.cs b/Source/CDR.Register.SSA.API/Extensions/ServiceCollectionExtensions.cs index 4a4b157..fd0203f 100644 --- a/Source/CDR.Register.SSA.API/Extensions/ServiceCollectionExtensions.cs +++ b/Source/CDR.Register.SSA.API/Extensions/ServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddRegisterSSA(this IServiceCollection services, IConfiguration configuration) { services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); diff --git a/Source/CDR.Register.SSA.API/Program.cs b/Source/CDR.Register.SSA.API/Program.cs index de22c2e..21b8aa4 100644 --- a/Source/CDR.Register.SSA.API/Program.cs +++ b/Source/CDR.Register.SSA.API/Program.cs @@ -1,9 +1,8 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; using System; using System.IO; @@ -21,7 +20,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.SSA.API/Startup.cs b/Source/CDR.Register.SSA.API/Startup.cs index 99f0ff6..73d9633 100644 --- a/Source/CDR.Register.SSA.API/Startup.cs +++ b/Source/CDR.Register.SSA.API/Startup.cs @@ -1,9 +1,10 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.API.Infrastructure.Middleware; using CDR.Register.API.Infrastructure.Models; using CDR.Register.API.Infrastructure.Versioning; using CDR.Register.API.Logger; +using CDR.Register.Domain.Extensions; using CDR.Register.Repository.Infrastructure; using CDR.Register.SSA.API.Extensions; using Microsoft.AspNetCore.Builder; @@ -13,7 +14,6 @@ using Microsoft.Extensions.Hosting; using Serilog; using static CDR.Register.API.Infrastructure.Constants; -using CDR.Register.Domain.Extensions; namespace CDR.Register.SSA.API { @@ -37,7 +37,7 @@ public void ConfigureServices(IServiceCollection services) services.AddApiVersioning(options => { - options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); //uses default options atm + options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); // uses default options atm options.ErrorResponses = new ApiVersionErrorResponse(); }); diff --git a/Source/CDR.Register.Status.API/Business/IStatusService.cs b/Source/CDR.Register.Status.API/Business/IStatusService.cs index d3802ad..5e7a0f6 100644 --- a/Source/CDR.Register.Status.API/Business/IStatusService.cs +++ b/Source/CDR.Register.Status.API/Business/IStatusService.cs @@ -7,7 +7,9 @@ namespace CDR.Register.Status.API.Business public interface IStatusService { Task GetDataRecipientStatusesAsync(Industry industry); + Task GetSoftwareProductStatusesAsync(Industry industry); + Task GetDataHolderStatusesAsyncXV1(Industry industry); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Models/BaseModel.cs b/Source/CDR.Register.Status.API/Business/Models/BaseModel.cs deleted file mode 100644 index a2eb640..0000000 --- a/Source/CDR.Register.Status.API/Business/Models/BaseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CDR.Register.Status.API.Business.Models -{ - public class BaseModel - { - } -} diff --git a/Source/CDR.Register.Status.API/Business/Models/RegisterDataHolderStatusModel.cs b/Source/CDR.Register.Status.API/Business/Models/RegisterDataHolderStatusModel.cs index fa70a11..08291a7 100644 --- a/Source/CDR.Register.Status.API/Business/Models/RegisterDataHolderStatusModel.cs +++ b/Source/CDR.Register.Status.API/Business/Models/RegisterDataHolderStatusModel.cs @@ -1,9 +1,9 @@ namespace CDR.Register.Status.API.Business.Models { - public class RegisterDataHolderStatusModel : BaseModel + public class RegisterDataHolderStatusModel { public string LegalEntityId { get; set; } + public string Status { get; set; } } - -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Models/RegisterDataRecipientStatusModel.cs b/Source/CDR.Register.Status.API/Business/Models/RegisterDataRecipientStatusModel.cs index f3373a8..b2bbfd4 100644 --- a/Source/CDR.Register.Status.API/Business/Models/RegisterDataRecipientStatusModel.cs +++ b/Source/CDR.Register.Status.API/Business/Models/RegisterDataRecipientStatusModel.cs @@ -1,8 +1,9 @@ namespace CDR.Register.Status.API.Business.Models { - public class RegisterDataRecipientStatusModel : BaseModel + public class RegisterDataRecipientStatusModel { public string LegalEntityId { get; set; } + public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Models/RegisterSoftwareProductStatusModel.cs b/Source/CDR.Register.Status.API/Business/Models/RegisterSoftwareProductStatusModel.cs index d69cb9c..e198c0f 100644 --- a/Source/CDR.Register.Status.API/Business/Models/RegisterSoftwareProductStatusModel.cs +++ b/Source/CDR.Register.Status.API/Business/Models/RegisterSoftwareProductStatusModel.cs @@ -1,8 +1,9 @@ namespace CDR.Register.Status.API.Business.Models { - public class RegisterSoftwareProductStatusModel : BaseModel + public class RegisterSoftwareProductStatusModel { public string SoftwareProductId { get; set; } + public string Status { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataHolderStatusList.cs b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataHolderStatusList.cs index 7da471f..691ba78 100644 --- a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataHolderStatusList.cs +++ b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataHolderStatusList.cs @@ -8,7 +8,9 @@ namespace CDR.Register.Status.API.Business.Responses public class ResponseRegisterDataHolderStatusList { public IEnumerable Data { get; set; } + public Links Links { get; set; } = new Links(); + public Meta Meta { get; set; } = new Meta(); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataRecipientStatusList.cs b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataRecipientStatusList.cs index c8686e8..5a1116e 100644 --- a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataRecipientStatusList.cs +++ b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterDataRecipientStatusList.cs @@ -8,7 +8,9 @@ namespace CDR.Register.Status.API.Business.Responses public class ResponseRegisterDataRecipientStatusList { public IEnumerable Data { get; set; } + public Links Links { get; set; } = new Links(); + public Meta Meta { get; set; } = new Meta(); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterSoftwareProductStatusList.cs b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterSoftwareProductStatusList.cs index f167b95..6065b1b 100644 --- a/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterSoftwareProductStatusList.cs +++ b/Source/CDR.Register.Status.API/Business/Responses/ResponseRegisterSoftwareProductStatusList.cs @@ -8,7 +8,9 @@ namespace CDR.Register.Status.API.Business.Responses public class ResponseRegisterSoftwareProductStatusList { public IEnumerable Data { get; set; } + public Links Links { get; set; } = new Links(); + public Meta Meta { get; set; } = new Meta(); } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj b/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj index f4d1853..0ca27f0 100644 --- a/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj +++ b/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj @@ -11,20 +11,29 @@ - - - + + + + - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/CDR.Register.Status.API/Controllers/StatusController.cs b/Source/CDR.Register.Status.API/Controllers/StatusController.cs index c281bca..b344c10 100644 --- a/Source/CDR.Register.Status.API/Controllers/StatusController.cs +++ b/Source/CDR.Register.Status.API/Controllers/StatusController.cs @@ -16,10 +16,10 @@ public class StatusController : ControllerBase private readonly IConfiguration _configuration; public StatusController( - IStatusService StatusService, + IStatusService statusService, IConfiguration configuration) { - _statusService = StatusService; + _statusService = statusService; _configuration = configuration; } @@ -33,7 +33,7 @@ public StatusController( public async Task GetDataRecipientsStatusXV2(string industry) { var response = await _statusService.GetDataRecipientStatusesAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, ""); + response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); return Ok(response); } @@ -47,7 +47,7 @@ public async Task GetDataRecipientsStatusXV2(string ind public async Task GetSoftwareProductStatusXV2(string industry) { var response = await _statusService.GetSoftwareProductStatusesAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, ""); + response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); return Ok(response); } @@ -61,9 +61,8 @@ public async Task GetSoftwareProductStatusXV2(string in public async Task GetDataHolderStatusXV1(string industry) { var response = await _statusService.GetDataHolderStatusesAsyncXV1(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, ""); + response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); return Ok(response); } - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Status.API/Extensions/ServiceCollectionExtensions.cs b/Source/CDR.Register.Status.API/Extensions/ServiceCollectionExtensions.cs index d9a3d86..ce7858c 100644 --- a/Source/CDR.Register.Status.API/Extensions/ServiceCollectionExtensions.cs +++ b/Source/CDR.Register.Status.API/Extensions/ServiceCollectionExtensions.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - namespace CDR.Register.Status.API.Extensions { public static class ServiceCollectionExtensions diff --git a/Source/CDR.Register.Status.API/Program.cs b/Source/CDR.Register.Status.API/Program.cs index d6730b8..debefd0 100644 --- a/Source/CDR.Register.Status.API/Program.cs +++ b/Source/CDR.Register.Status.API/Program.cs @@ -1,9 +1,8 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; using System; using System.IO; @@ -21,7 +20,7 @@ public static int Main(string[] args) .Build(); Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProcessId() .Enrich.WithProcessName() diff --git a/Source/CDR.Register.Status.API/Startup.cs b/Source/CDR.Register.Status.API/Startup.cs index 36d7bce..fce54c4 100644 --- a/Source/CDR.Register.Status.API/Startup.cs +++ b/Source/CDR.Register.Status.API/Startup.cs @@ -1,9 +1,10 @@ -using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.API.Infrastructure.Middleware; using CDR.Register.API.Infrastructure.Models; using CDR.Register.API.Infrastructure.Versioning; using CDR.Register.API.Logger; +using CDR.Register.Domain.Extensions; using CDR.Register.Repository.Infrastructure; using CDR.Register.Status.API.Extensions; using Microsoft.AspNetCore.Builder; @@ -14,7 +15,6 @@ using Microsoft.Extensions.Hosting; using Serilog; using static CDR.Register.API.Infrastructure.Constants; -using CDR.Register.Domain.Extensions; namespace CDR.Register.Status.API { @@ -37,7 +37,7 @@ public void ConfigureServices(IServiceCollection services) services.AddApiVersioning(options => { - options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); //uses default options atm + options.ApiVersionReader = new CdrVersionReader(new CdrApiOptions()); // uses default options atm options.ErrorResponses = new ApiVersionErrorResponse(); }); diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index dbd3608..46db1b5 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -1,7 +1,10 @@ net8.0 - 2.0.0 + 2.2.0 + true + true + true diff --git a/Source/Dockerfile b/Source/Dockerfile index ad54edf..0bc99fd 100644 --- a/Source/Dockerfile +++ b/Source/Dockerfile @@ -16,6 +16,7 @@ FROM build AS publish # Copy the build props COPY ./Directory.Build.props /app/Directory.Build.props +COPY ./.editorconfig /app/.editorconfig COPY ./CDR.Register.API.Infrastructure/. /app/CDR.Register.API.Infrastructure COPY ./CDR.Register.Repository/. /app/CDR.Register.Repository diff --git a/Source/Dockerfile.integration-tests b/Source/Dockerfile.integration-tests index c7cec66..d98b4b1 100644 --- a/Source/Dockerfile.integration-tests +++ b/Source/Dockerfile.integration-tests @@ -8,6 +8,7 @@ ENV ASPNETCORE_ENVIRONMENT=Release # Copy the build props COPY ./Directory.Build.props ./Directory.Build.props +COPY ./.editorconfig ./.editorconfig # Copy source COPY ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt ./CDR.Register.API.Gateway.mTLS/Certificates/ca.crt diff --git a/Source/Register.sln b/Source/Register.sln index 0b66cf3..90015dd 100644 --- a/Source/Register.sln +++ b/Source/Register.sln @@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6BA02E31-3715-4B2A-943A-284C395FCB4E}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore + .editorconfig = .editorconfig docker-compose.Ecosystem.yml = docker-compose.Ecosystem.yml docker-compose.IntegrationTests.yml = docker-compose.IntegrationTests.yml docker-compose.UnitTests.yml = docker-compose.UnitTests.yml