From ff8e2d69b5fbbdb350f41855203a19649754b5a0 Mon Sep 17 00:00:00 2001 From: CDR-AidenJ Date: Thu, 3 Apr 2025 15:46:52 +1100 Subject: [PATCH 1/2] Synchronise main and develop commit history (#107) Co-authored-by: CDR-CT <85465764+CDR-CT@users.noreply.github.com> From faf4277ca1de0066b31c02a1cc588e6ef4501bac Mon Sep 17 00:00:00 2001 From: CDR-AidenJ Date: Wed, 25 Jun 2025 16:08:44 +1000 Subject: [PATCH 2/2] v2.2.1 release (#110) Co-authored-by: CDR Open Source --- .azuredevops/pipelines/build-v2.yml | 260 +----- .azuredevops/pipelines/code-scanning.yml | 24 + .azuredevops/pull_request_template.md | 27 +- CHANGELOG.md | 4 + README.md | 4 +- Source/.editorconfig | 113 +-- .../CDR.Register.API.Gateway.TLS.csproj | 2 +- .../CDR.Register.API.Gateway.TLS/Startup.cs | 10 +- .../CDR.Register.API.Gateway.mTLS.csproj | 2 +- .../CDR.Register.API.Gateway.mTLS/Program.cs | 2 +- .../CDR.Register.API.Gateway.mTLS/Startup.cs | 14 +- ....API.Infrastructure.Tests.UnitTests.csproj | 6 +- .../Certificates/CertificateValidatorTests.cs | 10 +- .../Versioning/CdrVersionReaderTests.cs | 6 +- .../ValidateContentTypeFilterAttribute.cs | 49 ++ .../AuthorisationPolicyAttribute.cs | 10 +- .../DataRecipientSoftwareProductIdHandler.cs | 16 +- ...taRecipientSoftwareProductIdRequirement.cs | 6 +- .../Authorization/MTLSHandler.cs | 20 +- .../Authorization/PolicyAuthorizeAttribute.cs | 16 +- .../RegisterAuthorisationPolicy.cs | 2 +- .../Authorization/ScopeHandler.cs | 10 +- .../Authorization/ScopeRequirement.cs | 6 +- .../CDR.Register.API.Infrastructure.csproj | 5 +- .../Certificates/CertificateValidator.cs | 14 +- .../Configuration/ConfigureSwaggerOptions.cs | 8 +- .../ConfigureUnversionedSwaggerOptions.cs | 4 +- .../Extensions.cs | 109 ++- .../CdrSwaggerMiddlewareExtensions.cs | 4 +- .../Extensions/HttpClientHandlerExtensions.cs | 6 +- .../Filters/CheckDateAttribute.cs | 2 +- .../Filters/CheckIndustryAttribute.cs | 12 +- .../Filters/CheckPageSizeAttribute.cs | 6 +- .../Filters/LogActionEntryAttribute.cs | 8 +- .../Filters/ReturnXVAttribute.cs | 4 +- .../Middleware/ApiExceptionHandler.cs | 10 +- .../Middleware/ModelStateErrorMiddleware.cs | 6 +- .../Models/CdrApiEndpointVersionOptions.cs | 54 +- .../Models/CdrApiOptions.cs | 6 +- .../Models/JsonWebKey.cs | 28 +- .../Models/JsonWebKeySet.cs | 7 +- .../Models/Links.cs | 2 +- .../Models/LinksPaginated.cs | 10 +- .../DataRecipientStatusCheckService.cs | 4 +- .../AuthorizationOperationFilter.cs | 8 +- .../SetupApiVersionParamsOperationFilter.cs | 4 +- .../Versioning/ApiVersionErrorResponse.cs | 4 +- .../Versioning/CdrVersionReader.cs | 8 +- .../Versioning/InvalidVersionException.cs | 4 +- .../MissingRequiredHeaderException.cs | 6 +- .../CDR.Register.API.Logger.csproj | 2 +- .../IRequestResponseLogger.cs | 8 +- .../LoggerExtensions.cs | 8 +- .../RequestResponseLogger.cs | 27 +- .../RequestResponseLoggingMiddleware.cs | 166 ++-- .../Business/AdminMappingProfile.cs | 29 +- .../Business/Model/Brand.cs | 4 +- .../Business/Model/DataHolderBrandModel.cs | 12 +- .../Business/SoftwareScopeResolver.cs | 2 +- .../Business/Validators/BrandValidator.cs | 29 +- .../DataHolderAuthenticationValidator.cs | 14 +- .../Validators/DataHolderBrandValidator.cs | 42 +- .../Validators/DataHolderEndpointValidator.cs | 22 +- .../DataHolderLegalEntityValidator.cs | 34 +- .../Validators/LegalEntityValidator.cs | 55 +- .../SoftwareProductCertificateValidator.cs | 8 +- .../Validators/SoftwareProductValidator.cs | 57 +- .../CDR.Register.Admin.API.csproj | 2 +- .../Common/Constants.cs | 2 +- ...ticipantTooling.MockRegister.API.Admin.xml | 2 +- .../Controllers/AdminController.cs | 72 +- .../Controllers/LoopbackController.cs | 98 +-- .../Extensions/ErrorHandlingExtensions.cs | 4 +- .../Filters/ApiAuthorizeAttribute.cs | 6 +- Source/CDR.Register.Admin.API/Program.cs | 6 +- Source/CDR.Register.Admin.API/Startup.cs | 66 +- .../Business/DiscoveryService.cs | 18 +- .../Business/IDiscoveryService.cs | 6 +- .../Business/MappingProfile.cs | 26 +- .../CDR.Register.Discovery.API.csproj | 2 +- .../Controllers/DiscoveryController.cs | 44 +- Source/CDR.Register.Discovery.API/Program.cs | 6 +- Source/CDR.Register.Discovery.API/Startup.cs | 14 +- .../CDR.Register.Domain.UnitTests.csproj | 2 +- .../DataRecipientTests.cs | 7 +- .../CDR.Register.Domain.csproj | 2 +- Source/CDR.Register.Domain/Entities/Brand.cs | 2 +- .../Entities/DataHolder.cs | 18 +- .../Entities/DataHolderAuthentication.cs | 8 +- .../Entities/DataHolderBrand.cs | 2 +- .../DataHolderBrandServiceEndpoint.cs | 2 +- .../Entities/DataHolderLegalEntity.cs | 22 +- .../Entities/DataRecipient.cs | 6 +- .../Entities/DataRecipientLegalEntity.cs | 8 +- .../Entities/DataRecipientStatus.cs | 9 +- .../Entities/DataRecipientStatusV2.cs | 11 + .../Entities/SoftwareProduct.cs | 4 +- .../Enums/DhParticipationStatus.cs | 10 + Source/CDR.Register.Domain/Enums/DhStatus.cs | 9 + Source/CDR.Register.Domain/Enums/Industry.cs | 10 + .../Enums/OrganisationType.cs | 13 + .../Enums/RegisterUType.cs | 8 + .../Models/CdrJsonSerializerSettings.cs | 14 +- Source/CDR.Register.Domain/Models/Error.cs | 26 +- .../CDR.Register.Domain/Models/MetaError.cs | 2 +- .../Models/ResponseErrorList.cs | 144 ++-- .../ValueObjects/BusinessRuleError.cs | 14 +- .../CDR.Register.Domain/ValueObjects/Page.cs | 4 +- .../CDR.Register.Infosec.csproj | 2 +- ...cipantTooling.MockRegister.API.Infosec.xml | 2 +- .../Controllers/DiscoveryController.cs | 32 +- .../Controllers/TokenController.cs | 179 ++-- .../Models/ClientAssertionRequest.cs | 19 +- .../Services/ClientService.cs | 4 +- .../Services/ITokenService.cs | 2 +- .../Services/TokenService.cs | 156 ++-- Source/CDR.Register.Infosec/Startup.cs | 16 +- ...0_GetDataRecipients_MultiIndustry_Tests.cs | 168 ++-- ...GetDataHolderBrands_MultiIndustry_Tests.cs | 816 +++++++++--------- .../API/SSA/AccessTokenType.cs | 15 + ...eStatementAssertion_MultiIndustry_Tests.cs | 340 ++++---- ...DataRecipientStatus_MultiIndustry_Tests.cs | 86 +- ...ftwareProductStatus_MultiIndustry_Tests.cs | 84 +- .../API/Update/ErrorType.cs | 15 + .../API/Update/ExpectedErrors.cs | 44 +- .../API/Update/US50480_UpdateDataHolders.cs | 48 +- .../Update/US50480_UpdateDataRecipients.cs | 52 +- .../CDR.Register.IntegrationTests/BaseTest.cs | 194 ++--- .../BaseTest0.cs | 14 + .../CDR.Register.IntegrationTests.csproj | 2 +- .../DisplayTestMethodNameAttribute.cs | 45 + .../Extensions/JTokenExtensions.cs | 17 +- .../Extensions/JsonExtensions.cs | 4 +- .../Extensions/JwtSecurityTokenExtensions.cs | 2 +- .../Extensions/SqlExtensions.cs | 6 +- .../Fixtures/TestFixture.cs | 32 +- .../Gateway/US12841_Gateway_MTLS_Tests.cs | 409 ++++----- .../US14045_IdentityServer_Token_Tests.cs | 334 +++---- .../Infrastructure/API.cs | 54 +- .../Infrastructure/AccessToken.cs | 116 +-- .../Infrastructure/KeyValuePairBuilder.cs | 34 +- .../Infrastructure/PrivateKeyJwt.cs | 35 +- .../US12677_RegisterEnrolment_Tests.cs | 160 ++-- .../Miscellaneous/US50483_DynamicUrl_Tests.cs | 60 +- .../Models/AccessToken.cs | 16 +- .../Models/DataHolderMetadata.cs | 4 +- .../Models/DataRecipient.cs | 38 + .../Models/DataRecipientBrand.cs | 28 + .../Models/DataRecipientMetadata.cs | 4 +- .../Models/DataRecipient_V2.cs | 38 + .../Models/ExpectedApiErrors.cs | 2 +- .../Models/ExpectedDataRecipients.cs | 134 +-- .../Models/ExpectedDataRecipients_V2.cs | 16 + .../Models/SoftwareProduct.cs | 34 + .../TemporalTables/AuthDetail.cs | 20 + .../TemporalTables/Brand.cs | 26 + .../TemporalTables/DatabaseSeeder.cs | 52 ++ .../TemporalTables/Endpoint.cs | 28 + .../TemporalTables/LegalEntity.cs | 20 + .../TemporalTables/Participation.cs | 24 + .../TemporalTables/ParticipationStatus.cs | 17 + .../TemporalTables/SoftwareProduct.cs | 44 + .../SoftwareProductCertificate.cs | 22 + .../US29068_TemporalTableTests.cs | 316 ++----- .../CDR.Register.Repository.csproj | 2 +- .../Entities/AccreditationLevel.cs | 8 +- .../Entities/AuthDetail.cs | 1 + .../CDR.Register.Repository/Entities/Brand.cs | 1 + .../Entities/BrandStatus.cs | 9 +- .../Entities/Enumerations.cs | 18 +- .../Entities/IndustryType.cs | 4 +- .../Entities/LegalEntity.cs | 1 + .../Entities/OrganisationType.cs | 12 +- .../Entities/Participation.cs | 5 +- .../Entities/ParticipationStatus.cs | 14 +- .../Entities/ParticipationType.cs | 8 +- .../Entities/RegisterUType.cs | 7 +- .../Entities/SoftwareProduct.cs | 1 + .../Entities/SoftwareProductStatus.cs | 9 +- .../Enums/AccreditationLevelType.cs | 9 + .../Enums/BrandStatusType.cs | 10 + .../Enums/OrganisationTypes.cs | 13 + .../Enums/ParticipationStatusType.cs | 13 + .../Enums/ParticipationTypes.cs | 9 + .../Enums/RegisterUTypes.cs | 8 + .../Enums/SoftwareProductStatusType.cs | 10 + .../IRegisterAdminRepository.cs | 6 +- .../IRegisterDiscoveryRepository.cs | 6 +- .../IRegisterStatusRepository.cs | 4 +- .../Infrastructure/CdsRegistrationScopes.cs | 2 +- .../Infrastructure/Extensions.cs | 9 +- .../Infrastructure/Industry.cs | 4 +- .../Infrastructure/MappingProfile.cs | 49 +- .../Infrastructure/RegisterDatabaseContext.cs | 4 +- .../Infrastructure/RepositoryMapper.cs | 18 +- .../Migrations/20211005022250_Init.cs | 38 +- .../Migrations/20211223050501_V2.cs | 8 +- .../Migrations/20220915001454_V4.cs | 2 +- .../Migrations/20221005215852_V5.cs | 2 +- .../RegisterAdminRepository.cs | 141 +-- .../RegisterDiscoveryRepository.cs | 89 +- .../RegisterInfosecRepository.cs | 2 +- .../RegisterStatusRepository.cs | 10 +- .../SoftwareStatementAssertionRepository.cs | 12 +- .../CDR.Register.SSA.API.UnitTests.csproj | 2 +- .../HttpClientHandlerExtensionsTests.cs | 47 +- .../JwksTests.cs | 16 +- .../TokenizerServiceTests.cs | 174 ++-- .../Business/CertificateService.cs | 46 +- .../CDR.Register.SSA.API/Business/Mapper.cs | 50 +- .../Models/SoftwareStatementAssertionModel.cs | 111 ++- .../Business/SSAService.cs | 34 +- .../Business/TokenizerService.cs | 8 +- .../CDR.Register.SSA.API.csproj | 2 +- ...articipantTooling.MockRegister.API.SSA.xml | 88 +- .../Controllers/SSAController.cs | 38 +- Source/CDR.Register.SSA.API/Program.cs | 6 +- Source/CDR.Register.SSA.API/Startup.cs | 12 +- .../Business/IStatusService.cs | 4 +- .../Business/MappingProfile.cs | 12 +- .../Business/StatusService.cs | 20 +- .../CDR.Register.Status.API.csproj | 2 +- .../Controllers/StatusController.cs | 26 +- Source/CDR.Register.Status.API/Program.cs | 6 +- Source/CDR.Register.Status.API/Startup.cs | 14 +- Source/Directory.Build.props | 3 +- Source/docker-compose.Ecosystem.yml | 2 +- Source/docker-compose.IntegrationTests.yml | 2 +- Source/docker-compose.UnitTests.yml | 2 +- Source/docker-compose.yml | 2 +- 230 files changed, 4028 insertions(+), 3972 deletions(-) create mode 100644 .azuredevops/pipelines/code-scanning.yml create mode 100644 Source/CDR.Register.API.Infrastructure/Attributes/ValidateContentTypeFilterAttribute.cs create mode 100644 Source/CDR.Register.Domain/Entities/DataRecipientStatusV2.cs create mode 100644 Source/CDR.Register.Domain/Enums/DhParticipationStatus.cs create mode 100644 Source/CDR.Register.Domain/Enums/DhStatus.cs create mode 100644 Source/CDR.Register.Domain/Enums/Industry.cs create mode 100644 Source/CDR.Register.Domain/Enums/OrganisationType.cs create mode 100644 Source/CDR.Register.Domain/Enums/RegisterUType.cs create mode 100644 Source/CDR.Register.IntegrationTests/API/SSA/AccessTokenType.cs create mode 100644 Source/CDR.Register.IntegrationTests/API/Update/ErrorType.cs create mode 100644 Source/CDR.Register.IntegrationTests/BaseTest0.cs create mode 100644 Source/CDR.Register.IntegrationTests/DisplayTestMethodNameAttribute.cs create mode 100644 Source/CDR.Register.IntegrationTests/Models/DataRecipient.cs create mode 100644 Source/CDR.Register.IntegrationTests/Models/DataRecipientBrand.cs create mode 100644 Source/CDR.Register.IntegrationTests/Models/DataRecipient_V2.cs create mode 100644 Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients_V2.cs create mode 100644 Source/CDR.Register.IntegrationTests/Models/SoftwareProduct.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/AuthDetail.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/Brand.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/DatabaseSeeder.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/Endpoint.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/LegalEntity.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/Participation.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/ParticipationStatus.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProduct.cs create mode 100644 Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProductCertificate.cs create mode 100644 Source/CDR.Register.Repository/Enums/AccreditationLevelType.cs create mode 100644 Source/CDR.Register.Repository/Enums/BrandStatusType.cs create mode 100644 Source/CDR.Register.Repository/Enums/OrganisationTypes.cs create mode 100644 Source/CDR.Register.Repository/Enums/ParticipationStatusType.cs create mode 100644 Source/CDR.Register.Repository/Enums/ParticipationTypes.cs create mode 100644 Source/CDR.Register.Repository/Enums/RegisterUTypes.cs create mode 100644 Source/CDR.Register.Repository/Enums/SoftwareProductStatusType.cs diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml index 851bc65..0f88516 100644 --- a/.azuredevops/pipelines/build-v2.yml +++ b/.azuredevops/pipelines/build-v2.yml @@ -15,36 +15,6 @@ pool: vmImage: ubuntu-latest steps: -- task: PowerShell@2 - displayName: Update Obligation date - inputs: - targetType: 'inline' - script: | - $RegisterObligationDate = "$(ObligationDate)" - if (-not [string]::IsNullOrWhiteSpace( $RegisterObligationDate )) { - Write-Host "##vso[task.setvariable variable=ObligationDateTag]$RegisterObligationDate" - } - else { - Write-Host "##vso[task.setvariable variable=ObligationDateTag]23-5" - } - -# Set release version variables for release tags -- task: PowerShell@2 - name: 'splitBranchName' - displayName: 'Split Branch Name' - inputs: - targetType: 'inline' - script: | - $branchParts = "$(Build.SourceBranchName)" -split '\.' - # Write-Host "Branch Name: $branchParts" - $majorVersion = $branchParts[0] - $minorVersion = $branchParts[1] - #$patchVersion = $branchParts[2] - # Write-Host "Major Name: $majorVersion" - Write-Host "##vso[task.setvariable variable=majorVersion]$majorVersion" - Write-Host "##vso[task.setvariable variable=minorVersion]$minorVersion" - Write-Host "##vso[task.setvariable variable=majorMinorVersion]$majorVersion.$minorVersion" - # Build mock-register - task: Docker@2 displayName: Build mock-register image @@ -93,14 +63,6 @@ steps: docker compose --file $(Build.SourcesDirectory)/Source/docker-compose.UnitTests.yml down displayName: 'Unit Tests - Down' condition: always() - -# Login to ACR -- task: Docker@2 - displayName: Login to ACR - condition: always() - inputs: - command: login - containerRegistry: $(AcrBaseUrl) # Login to Shared ACR - task: Docker@2 @@ -128,7 +90,7 @@ steps: docker run \ -v=$(Build.SourcesDirectory)/Source/_temp/mock-register-integration-tests/testresults/results.trx:/app/results.trx:ro \ -v=$(Build.SourcesDirectory)/Source/_temp/mock-register-integration-tests/testresults/formatted/:/app/out/:rw \ - $(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MR" --outputprefix "MR" -o out/ + $(SharedAcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MR" --outputprefix "MR" -o out/ displayName: 'Run trx-formatter' condition: always() env: @@ -181,7 +143,7 @@ steps: docker run \ -v=$(Build.SourcesDirectory)/Source/_temp/mock-register-integration-tests/testresults/results.trx:/app/results.trx:ro \ -v=$(Build.SourcesDirectory)/Source/_temp/mock-register-integration-tests/testresults/formatted/:/app/out/:rw \ - $(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MR-CTS" --outputprefix "MR-CTS" -o out/ + $(SharedAcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MR-CTS" --outputprefix "MR-CTS" -o out/ displayName: 'Run trx-formatter' condition: always() @@ -269,203 +231,37 @@ steps: condition: always() artifact: Database Migration Scripts -- task: Docker@2 - displayName: 'Re-Tag Mock Register container image with :branch-name' - inputs: - containerRegistry: $(AcrBaseUrl) - repository: 'mock-register' - command: tag - # arguments: 'mock-register-for-testing $(AcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)' - arguments: 'mock-register $(AcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)' - -- task: Docker@2 - displayName: 'Re-Tag Register API image with :latest (for develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - containerRegistry: $(AcrBaseUrl) - repository: 'mock-register' - command: tag - # arguments: 'mock-register-for-testing $(AcrBaseUrl).azurecr.io/mock-register:latest' - arguments: 'mock-register $(AcrBaseUrl).azurecr.io/mock-register:latest' - -# Shared SP ACR -# Pipeline variables are required : SharedAcrBaseUrl and SpSharedAcr - -# develop branch tags -- task: Docker@2 - displayName: 'Re-Tag Register API image with develop-latest (for develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:develop-latest' - -# develop-latest-obligation-date tag -- task: Docker@2 - displayName: 'Re-Tag Register API image with develop-latest-{obligation-date} (for develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:develop-latest-$(ObligationDateTag)' - -# develop branch> build number tag only -- task: Docker@2 - displayName: 'Re-Tag Register API image with build number (for develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.BuildId)' - -# main branch tags -# Pipeline variables are required : SharedAcrBaseUrl -- task: Docker@2 - displayName: 'Re-Tag Register API image with main-latest (for main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:main-latest' - -# main-latest-obligation-date tag -- task: Docker@2 - displayName: 'Re-Tag Register API image with main-latest-{obligation-date} (for main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:main-latest-$(ObligationDateTag)' - -# Build number tag -- task: Docker@2 - displayName: 'Re-Tag Register API image with build number (for main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.BuildId)' - -# Re tag for full version for releases branch -- task: Docker@2 - displayName: 'Re-Tag Register API image with full version for releases branch' - # Cannot use releases/* wildcard - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)' - -# Re tag release full version and obligation date -- task: Docker@2 - displayName: 'Re-Tag Register API image with releases-{obligation-date} for releases branch' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)-$(ObligationDateTag)' - -# Re tag Major and Mnior Tags using above variables -- task: Docker@2 - displayName: 'Re-Tag Register image with major and minor tags for releases branch' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:"$(majorMinorVersion)"' - -# Re tag Major release Tag using above variables -- task: Docker@2 - displayName: 'Re-Tag Register image with major tag for releases branch' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - containerRegistry: $(SpSharedAcr) - repository: 'mock-register' - command: tag - arguments: 'mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:"$(majorVersion)"' - -# Pushing develop branch tags changes to the ACR -- task: CmdLine@2 - displayName: 'Push Register container image with :branch-name tag to ACR' - inputs: - script: 'docker push $(AcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)' - -- task: CmdLine@2 - displayName: 'Push Register container image with :latest tag to ACR (develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) +- task: Bash@3 + displayName: 'Tag and push Mock Register image with Source Branch Name for any successful builds.' + condition: succeeded() inputs: - script: 'docker push $(AcrBaseUrl).azurecr.io/mock-register:latest' + targetType: inline + script: | + echo Tagging mock-register with Source Branch Name: $(Build.SourceBranchName) + docker tag mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName) -- task: CmdLine@2 - displayName: 'Push Register container image with :develop-latest tag to ACR (develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:develop-latest' + echo Pushing all tags to $(SharedAcrBaseUrl).azurecr.io/mock-register + docker image push --all-tags $(SharedAcrBaseUrl).azurecr.io/mock-register -- task: CmdLine@2 - displayName: 'Push Register container image with :develop-latest-obligation-date tag to ACR (develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:develop-latest-$(ObligationDateTag)' - -- task: CmdLine@2 - displayName: 'Push Register container image with :build id tag to ACR (develop branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) +- task: Bash@3 + displayName: 'Tag and Push Mock Register images for develop, main and release branches.' + condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'))) inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.BuildId)' + targetType: inline + script: | + echo Tagging mock-register with latest tag + docker tag mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:latest -# Pushing main branch tags changes to the ACR -- task: CmdLine@2 - displayName: 'Push Register container image with :main-latest tag to ACR (main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:main-latest' + echo Tagging mock-register with Source Branch Name-latest: $(Build.SourceBranchName)-latest + docker tag mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)-latest -- task: CmdLine@2 - displayName: 'Push Register container image with :main-latest-obligation-date tag to ACR (main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:main-latest-$(ObligationDateTag)' - -- task: CmdLine@2 - displayName: 'Push Register container image with :build id tag to ACR (main branch only)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.BuildId)' - -# Push release full version Tag -- task: CmdLine@2 - displayName: 'Push Register container image with releases tags to ACR (releases branch only)' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)' + if [ -n "$(ObligationDate)" ]; then + echo Tagging mock-register with obligation date: $(Build.SourceBranchName)-$(ObligationDate) + docker tag mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)-$(ObligationDate) + fi -# Push release full version Tag and obligation date -- task: CmdLine@2 - displayName: 'Push Register container image with releases tags to ACR (releases branch only)' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.SourceBranchName)-$(ObligationDateTag)' + echo Tagging mock-register with build id: $(Build.BuildId) + docker tag mock-register $(SharedAcrBaseUrl).azurecr.io/mock-register:$(Build.BuildId) -# Push Major and Minor release Tags using above variables -- task: CmdLine@2 - displayName: 'Push Register container image with releases tags to ACR (releases branch only)' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:"$(majorMinorVersion)"' - -# Push Major release Tag using above variables -- task: CmdLine@2 - displayName: 'Push Register container image with releases tags to ACR (releases branch only)' - condition: eq(startsWith(variables['Build.SourceBranch'], 'refs/heads/releases/'), 'True') - inputs: - script: 'docker push $(SharedAcrBaseUrl).azurecr.io/mock-register:"$(majorVersion)"' \ No newline at end of file + echo Pushing all tags to $(SharedAcrBaseUrl).azurecr.io/mock-register + docker image push --all-tags $(SharedAcrBaseUrl).azurecr.io/mock-register \ No newline at end of file diff --git a/.azuredevops/pipelines/code-scanning.yml b/.azuredevops/pipelines/code-scanning.yml new file mode 100644 index 0000000..ea3fe11 --- /dev/null +++ b/.azuredevops/pipelines/code-scanning.yml @@ -0,0 +1,24 @@ +resources: + repositories: + - repository: shared-code-scanning + type: git + name: Common/shared-code-scanning + ref: refs/heads/main + trigger: none + +schedules: +- cron: '0 17 * * 0' # Run at 17:00 Sunday night UTC (03:00+10:00 Monday Morning) + displayName: 'Weekly code scan' + branches: + include: + - develop + always: true + +# Disable standard CI build +trigger: none + +pool: + vmImage: 'ubuntu-latest' + +extends: + template: pipeline-templates/code-scanning.yml@shared-code-scanning \ No newline at end of file diff --git a/.azuredevops/pull_request_template.md b/.azuredevops/pull_request_template.md index 0c8d12c..0b62ba5 100644 --- a/.azuredevops/pull_request_template.md +++ b/.azuredevops/pull_request_template.md @@ -1,27 +1,16 @@ -**Checklist:** (Put an `x` in all the boxes that apply) -- [ ] My code follows the code style of this project. -- [ ] I have set this Pull Request to Auto Complete with the delete source branch option selected. -- [ ] Commented out code has been removed or will be removed. -- [ ] I have updated the documentation accordingly. -- [ ] I have added tests to cover my changes. -- [ ] All new and existing tests passed. -- [ ] I have updated the `CHANGELOG.md` file as appropriate. - - -**What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) - - +**What is the new behaviour?** -**What is the current behavior?** (You can also link to an open issue here) +(if this is a feature change) +**Does this PR introduce a breaking change?** -**What is the new behavior?** (if this is a feature change) - - - -**Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) +(What changes might users need to make in their application due to this PR?) **Other information**: + +**Checklist:** (Put an `x` in all the boxes that apply) +- [ ] I have set this Pull Request to auto complete with the delete source branch option selected. +- [ ] I have updated the documentation in confluence or relevant readme.md text accordingly. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ff9afa6..cbeb201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.1] - 2025-06-19 +### Changed +- Fixed multiple build warnings to improve code quality and maintainability + ## [2.2.0] - 2025-03-19 ### Changed - Updated NuGet packages diff --git a/README.md b/README.md index c124a03..9d50f32 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Consumer Data Right Logo](./cdr-logo.png?raw=true) -[![Consumer Data Standards v1.33.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.33.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.33.0/#introduction) +[![Consumer Data Standards v1.34.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.34.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction) [![made-with-dotnet](https://img.shields.io/badge/Made%20with-.NET-1f425Ff.svg)](https://dotnet.microsoft.com/) [![made-with-csharp](https://img.shields.io/badge/Made%20with-C%23-1f425Ff.svg)](https://docs.microsoft.com/en-us/dotnet/csharp/) [![MIT License](https://img.shields.io/github/license/ConsumerDataRight/mock-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.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). +The Mock Register aligns to [v1.34.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.34.0/#introduction). ## Getting Started There are a number of ways that the artefacts within this project can be used: diff --git a/Source/.editorconfig b/Source/.editorconfig index 6487a11..33cd81c 100644 --- a/Source/.editorconfig +++ b/Source/.editorconfig @@ -145,142 +145,31 @@ 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 - +csharp_prefer_system_threading_lock = true:suggestion #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 04ecd02..c01e066 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 @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.Register.API.Gateway.TLS/Startup.cs b/Source/CDR.Register.API.Gateway.TLS/Startup.cs index 729b5b3..cb2e0b7 100644 --- a/Source/CDR.Register.API.Gateway.TLS/Startup.cs +++ b/Source/CDR.Register.API.Gateway.TLS/Startup.cs @@ -12,15 +12,15 @@ namespace CDR.Register.API.Gateway.TLS { public class Startup { - public IConfiguration Configuration { get; } - public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public static void ConfigureServices(IServiceCollection services) { services.AddOcelot(); } @@ -45,7 +45,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) httpContext.Request.Headers.Append("X-Forwarded-Host", httpContext.Request.Host.ToString()); await next.Invoke(); - } + }, }; app.UseOcelot(pipelineConfiguration).Wait(); 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 8e98bcb..cada86a 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 @@ -44,7 +44,7 @@ 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 d506ace..ca00014 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/Program.cs +++ b/Source/CDR.Register.API.Gateway.mTLS/Program.cs @@ -90,7 +90,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) => 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 + 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 e5a6eaf..4cf882b 100644 --- a/Source/CDR.Register.API.Gateway.mTLS/Startup.cs +++ b/Source/CDR.Register.API.Gateway.mTLS/Startup.cs @@ -21,13 +21,13 @@ namespace CDR.Register.API.Gateway.Mtls { public class Startup { - public IConfiguration Configuration { get; } - public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { @@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services) { context.Fail("invalid client certificate"); throw context.Exception; - } + }, }; }) @@ -109,8 +109,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) // The thumbprint and common name from the client certificate are extracted and added as headers for the downstream services. if (clientCert != null) { - var certThumbprintNameHttpHeaderName = Configuration.GetValue(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; - var certCommonNameHttpHeaderName = Configuration.GetValue(Constants.ConfigurationKeys.CertCommonNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_COMMON_NAME; + var certThumbprintNameHttpHeaderName = this.Configuration.GetValue(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; + var certCommonNameHttpHeaderName = this.Configuration.GetValue(Constants.ConfigurationKeys.CertCommonNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_COMMON_NAME; httpContext.Request.Headers.Append(certThumbprintNameHttpHeaderName, clientCert.Thumbprint); httpContext.Request.Headers.Append(certCommonNameHttpHeaderName, clientCert.GetNameInfo(X509NameType.SimpleName, false)); @@ -120,7 +120,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) httpContext.Request.Headers.Append("X-Forwarded-Host", httpContext.Request.Host.ToString()); await next.Invoke(); - } + }, }; app.UseOcelot(pipelineConfiguration).Wait(); 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 8c867b3..c9d7f5e 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 @@ -20,13 +20,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - all + + 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 725c2d9..c55e05c 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Certificates/CertificateValidatorTests.cs @@ -21,7 +21,7 @@ public void IsValid_ValidCertificate_ShouldReturnTrue() var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); var inMemorySettings = new Dictionary { - { "RootCACertificatePath", rootCaPath } + { "RootCACertificatePath", rootCaPath }, }; IConfiguration configuration = new ConfigurationBuilder() @@ -47,7 +47,7 @@ public void IsValid_NullCertificate_ShouldThrowException() var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); var inMemorySettings = new Dictionary { - { "RootCACertificatePath", rootCaPath } + { "RootCACertificatePath", rootCaPath }, }; IConfiguration configuration = new ConfigurationBuilder() @@ -69,7 +69,7 @@ public void IsValid_SelfSignedCertificate_ShouldReturnFalse() var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); var inMemorySettings = new Dictionary { - { "RootCACertificatePath", rootCaPath } + { "RootCACertificatePath", rootCaPath }, }; IConfiguration configuration = new ConfigurationBuilder() @@ -91,7 +91,7 @@ public void IsValid_FakeMockCDRCACertificate_ShouldReturnFalse() var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); var inMemorySettings = new Dictionary { - { "RootCACertificatePath", rootCaPath } + { "RootCACertificatePath", rootCaPath }, }; IConfiguration configuration = new ConfigurationBuilder() @@ -115,7 +115,7 @@ public void IsValid_NonMockCDRCACertificate_ShouldReturnFalse() var rootCaPath = Path.Combine(Directory.GetCurrentDirectory(), "Certificates", "ca.pem"); var inMemorySettings = new Dictionary { - { "RootCACertificatePath", rootCaPath } + { "RootCACertificatePath", rootCaPath }, }; IConfiguration configuration = new ConfigurationBuilder() 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 4ca5982..3ec7fad 100644 --- a/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs +++ b/Source/CDR.Register.API.Infrastructure.Tests.UnitTests/Versioning/CdrVersionReaderTests.cs @@ -1,9 +1,9 @@ -using CDR.Register.API.Infrastructure.Versioning; +using System; +using System.Collections.Generic; +using CDR.Register.API.Infrastructure.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using NSubstitute; -using System; -using System.Collections.Generic; using Xunit; namespace CDR.Register.API.Infrastructure.Tests.UnitTests.Versioning diff --git a/Source/CDR.Register.API.Infrastructure/Attributes/ValidateContentTypeFilterAttribute.cs b/Source/CDR.Register.API.Infrastructure/Attributes/ValidateContentTypeFilterAttribute.cs new file mode 100644 index 0000000..d0b1880 --- /dev/null +++ b/Source/CDR.Register.API.Infrastructure/Attributes/ValidateContentTypeFilterAttribute.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace CDR.Register.API.Infrastructure.Attributes +{ + // This attribute is to validate the media type and send customer error message as expected by the tests. + // This attribute replaces [Consumes("application/x-www-form-urlencoded")] attribute but with custom error message. + // This attribute also avoid the consumer to access the http request, validate and send custom error message. + [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + public class ValidateContentTypeFilterAttribute : ActionFilterAttribute + { + private readonly string _expectedContentType; + + public ValidateContentTypeFilterAttribute(string expectedContentType) + { + this._expectedContentType = expectedContentType; + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + var contentType = context.HttpContext.Request.ContentType; + + if (contentType == null) + { + context.Result = new ObjectResult(new + { + error = "invalid_request", + error_description = $"Content-Type is not {this._expectedContentType}", + }) + { + StatusCode = StatusCodes.Status400BadRequest, + }; + } + else if (!contentType.StartsWith(this._expectedContentType, StringComparison.OrdinalIgnoreCase)) + { + context.Result = new ObjectResult(new + { + error = "invalid_request", + error_description = $"Content-Type is not {this._expectedContentType}", + }) + { + StatusCode = StatusCodes.Status415UnsupportedMediaType, + }; + } + } + } +} diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs index 75649ce..471217c 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/AuthorisationPolicyAttribute.cs @@ -7,11 +7,11 @@ public class AuthorisationPolicyAttribute : Attribute { public AuthorisationPolicyAttribute(string name, string? scopeRequirement, bool hasMtlsRequirement, bool hasHolderOfKeyRequirement, bool hasAccessTokenRequirement) { - Name = name; - ScopeRequirement = scopeRequirement; - HasMtlsRequirement = hasMtlsRequirement; - HasHolderOfKeyRequirement = hasHolderOfKeyRequirement; - HasAccessTokenRequirement = hasAccessTokenRequirement; + this.Name = name; + this.ScopeRequirement = scopeRequirement; + this.HasMtlsRequirement = hasMtlsRequirement; + this.HasHolderOfKeyRequirement = hasHolderOfKeyRequirement; + this.HasAccessTokenRequirement = hasAccessTokenRequirement; } public string Name { get; private set; } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs index ca6e4a5..33e5d0b 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdHandler.cs @@ -1,8 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Serilog.Context; -using System.Threading.Tasks; namespace CDR.Register.API.Infrastructure.Authorization { @@ -13,8 +13,8 @@ public class DataRecipientSoftwareProductIdHandler : AuthorizationHandler logger) { - _httpContextAccessor = httpContextAccessor; - _logger = logger; + this._httpContextAccessor = httpContextAccessor; + this._logger = logger; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DataRecipientSoftwareProductIdRequirement requirement) @@ -30,7 +30,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. Access token is missing 'client_id' claim for issuer '{Issuer}'.", requirement.Issuer); + this._logger.LogError("Unauthorized request. Access token is missing 'client_id' claim for issuer '{Issuer}'.", requirement.Issuer); } return Task.CompletedTask; @@ -41,20 +41,20 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. Access token is missing 'client_id' claim."); + this._logger.LogError("Unauthorized request. Access token is missing 'client_id' claim."); } return Task.CompletedTask; } - string? requestDataRecipientProductId = _httpContextAccessor.HttpContext?.Request.RouteValues["softwareProductId"]?.ToString(); + string? requestDataRecipientProductId = this._httpContextAccessor.HttpContext?.Request.RouteValues["softwareProductId"]?.ToString(); // Token ClientId should match the ProductId. if (!accessTokenClientId.Equals(requestDataRecipientProductId, System.StringComparison.InvariantCultureIgnoreCase)) { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. Access token client_id '{AccessTokenClientId}' does not match request softwareProductId '{RequestDataRecipientProductId}'", accessTokenClientId, requestDataRecipientProductId); + this._logger.LogError("Unauthorized request. Access token client_id '{AccessTokenClientId}' does not match request softwareProductId '{RequestDataRecipientProductId}'", accessTokenClientId, requestDataRecipientProductId); } return Task.CompletedTask; diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdRequirement.cs b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdRequirement.cs index 6db3fd1..bd28f6f 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdRequirement.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/DataRecipientSoftwareProductIdRequirement.cs @@ -5,11 +5,11 @@ namespace CDR.Register.API.Infrastructure.Authorization { public class DataRecipientSoftwareProductIdRequirement : IAuthorizationRequirement { - public string Issuer { get; } - public DataRecipientSoftwareProductIdRequirement(string issuer) { - Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer)); + this.Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer)); } + + public string Issuer { get; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs index eef6041..67ba782 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/MTLSHandler.cs @@ -1,11 +1,11 @@ -using Microsoft.AspNetCore.Authorization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Newtonsoft.Json.Linq; using Serilog.Context; -using System.Threading.Tasks; namespace CDR.Register.API.Infrastructure.Authorization { @@ -17,9 +17,9 @@ public class MtlsHandler : AuthorizationHandler public MtlsHandler(IHttpContextAccessor httpContextAccessor, ILogger logger, IConfiguration config) { - _httpContextAccessor = httpContextAccessor; - _logger = logger; - _configuration = config; + this._httpContextAccessor = httpContextAccessor; + this._logger = logger; + this._configuration = config; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MtlsRequirement requirement) @@ -34,8 +34,8 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte // as the one expected by the cnf:x5t#S256 claim in the access token string? requestHeaderClientCertThumprint = null; - var certThumbprintNameHttpHeaderName = _configuration.GetValue(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; - if (_httpContextAccessor.HttpContext?.Request.Headers.TryGetValue(certThumbprintNameHttpHeaderName, out StringValues headerThumbprints) == true) + var certThumbprintNameHttpHeaderName = this._configuration.GetValue(Constants.ConfigurationKeys.CertThumbprintNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_THUMBPRINT; + if (this._httpContextAccessor.HttpContext?.Request.Headers.TryGetValue(certThumbprintNameHttpHeaderName, out StringValues headerThumbprints) == true) { requestHeaderClientCertThumprint = headerThumbprints[0]; } @@ -44,7 +44,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. Request header 'X-TlsClientCertThumbprint' is missing."); + this._logger.LogError("Unauthorized request. Request header 'X-TlsClientCertThumbprint' is missing."); } return Task.CompletedTask; @@ -62,7 +62,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. cnf:x5t#S256 claim is missing from access token."); + this._logger.LogError("Unauthorized request. cnf:x5t#S256 claim is missing from access token."); } return Task.CompletedTask; @@ -72,7 +72,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. X-TlsClientCertThumbprint request header value '{RequestHeaderClientCertThumprint}' does not match access token cnf:x5t#S256 claim value '{AccessTokenClientCertThumbprint}'", requestHeaderClientCertThumprint, accessTokenClientCertThumbprint); + this._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; diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs b/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs index 34f9d3f..49e92e5 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/PolicyAuthorizeAttribute.cs @@ -1,24 +1,24 @@ -using CDR.Register.Domain.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Filters; -using System; +using System; using System.Linq; using System.Net; using System.Threading.Tasks; +using CDR.Register.Domain.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; namespace CDR.Register.API.Infrastructure.Authorization { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class PolicyAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter { - public RegisterAuthorisationPolicy RegisterAuthorisationPolicy { get; private set; } - public PolicyAuthorizeAttribute(RegisterAuthorisationPolicy policy) { this.RegisterAuthorisationPolicy = policy; } + public RegisterAuthorisationPolicy RegisterAuthorisationPolicy { get; private set; } + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { var authorizationService = (IAuthorizationService?)context.HttpContext.RequestServices.GetService(typeof(IAuthorizationService)); @@ -28,7 +28,7 @@ public async Task OnAuthorizationAsync(AuthorizationFilterContext context) return; } - var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, RegisterAuthorisationPolicy.ToString()); + var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, this.RegisterAuthorisationPolicy.ToString()); if (authorizationResult.Succeeded) { diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs index 0914518..5944091 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/RegisterAuthorisationPolicy.cs @@ -7,6 +7,6 @@ public enum RegisterAuthorisationPolicy [AuthorisationPolicy("DataHolderBrandsApiMultiIndustry", CdsRegistrationScopes.Read, true, false, false)] DataHolderBrandsApiMultiIndustry, [AuthorisationPolicy("GetSSAMultiIndustry", CdsRegistrationScopes.Read, true, false, false)] - GetSSAMultiIndustry + GetSSAMultiIndustry, } } diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs index ae33ef4..3a5af08 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeHandler.cs @@ -1,8 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Serilog.Context; -using System.Linq; -using System.Threading.Tasks; namespace CDR.Register.API.Infrastructure.Authorization { @@ -12,7 +12,7 @@ public class ScopeHandler : AuthorizationHandler public ScopeHandler(ILogger logger) { - _logger = logger; + this._logger = logger; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopeRequirement requirement) @@ -28,7 +28,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte { using (LogContext.PushProperty("MethodName", "HandleRequirementAsync")) { - _logger.LogError("Unauthorized request. Access token is missing 'scope' claim."); + this._logger.LogError("Unauthorized request. Access token is missing 'scope' claim."); } return Task.CompletedTask; diff --git a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs index cf4bf5c..625bf4e 100644 --- a/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs +++ b/Source/CDR.Register.API.Infrastructure/Authorization/ScopeRequirement.cs @@ -5,11 +5,11 @@ namespace CDR.Register.API.Infrastructure.Authorization { public class ScopeRequirement : IAuthorizationRequirement { - public string Scope { get; } - public ScopeRequirement(string? scope) { - Scope = scope ?? throw new ArgumentNullException(nameof(scope)); + this.Scope = scope ?? throw new ArgumentNullException(nameof(scope)); } + + public string Scope { get; } } } 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 e525df7..e2c57fe 100644 --- a/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj +++ b/Source/CDR.Register.API.Infrastructure/CDR.Register.API.Infrastructure.csproj @@ -12,6 +12,7 @@ + @@ -22,14 +23,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 7f2ab1f..ccc7b08 100644 --- a/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs +++ b/Source/CDR.Register.API.Infrastructure/Certificates/CertificateValidator.cs @@ -1,8 +1,8 @@ -using CDR.Register.API.Infrastructure.Exceptions; +using System; +using System.Security.Cryptography.X509Certificates; +using CDR.Register.API.Infrastructure.Exceptions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System; -using System.Security.Cryptography.X509Certificates; namespace CDR.Register.API.Infrastructure { @@ -16,20 +16,20 @@ public class CertificateValidator : ICertificateValidator public CertificateValidator(ILogger logger, IConfiguration config) { - _logger = logger; - _config = config; + this._logger = logger; + this._config = config; } public void ValidateClientCertificate(X509Certificate2 clientCert) { - _logger.LogInformation("Validating certificate within the {Name}", nameof(CertificateValidator)); + this._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 = this._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/Configuration/ConfigureSwaggerOptions.cs b/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureSwaggerOptions.cs index ca3fb79..ba9275b 100644 --- a/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureSwaggerOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureSwaggerOptions.cs @@ -13,19 +13,19 @@ public class ConfigureSwaggerOptions : IConfigureOptions public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IOptions options) { - _provider = provider; - _options = options.Value; + this._provider = provider; + this._options = options.Value; } public void Configure(SwaggerGenOptions options) { - foreach (var description in _provider.ApiVersionDescriptions) + foreach (var description in this._provider.ApiVersionDescriptions) { options.SwaggerDoc( description.GroupName, new Microsoft.OpenApi.Models.OpenApiInfo() { - Title = _options.SwaggerTitle, + Title = this._options.SwaggerTitle, Version = description.ApiVersion.ToString(), }); options.UseInlineDefinitionsForEnums(); diff --git a/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureUnversionedSwaggerOptions.cs b/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureUnversionedSwaggerOptions.cs index 89db471..afa062d 100644 --- a/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureUnversionedSwaggerOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Configuration/ConfigureUnversionedSwaggerOptions.cs @@ -13,7 +13,7 @@ public class ConfigureUnversionedSwaggerOptions : IConfigureOptions options) { - _options = options.Value; + this._options = options.Value; } public void Configure(SwaggerGenOptions options) @@ -22,7 +22,7 @@ public void Configure(SwaggerGenOptions options) _defaultVersion, // This name is used to direct to the path new Microsoft.OpenApi.Models.OpenApiInfo() { - Title = _options.SwaggerTitle, + Title = this._options.SwaggerTitle, Version = _defaultVersion, }); options.UseInlineDefinitionsForEnums(); diff --git a/Source/CDR.Register.API.Infrastructure/Extensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions.cs index 5868be0..d6551f8 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions.cs @@ -1,4 +1,13 @@ -using CDR.Register.API.Infrastructure.Authorization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using CDR.Register.API.Infrastructure.Authorization; using CDR.Register.API.Infrastructure.Configuration; using CDR.Register.API.Infrastructure.Models; using CDR.Register.API.Infrastructure.SwaggerFilters; @@ -18,16 +27,10 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.OpenApi.Models; using Newtonsoft.Json; +using Polly; +using Polly.Extensions.Http; +using Serilog; using Swashbuckle.AspNetCore.SwaggerGen; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration; namespace CDR.Register.API.Infrastructure @@ -52,6 +55,7 @@ public static string GetInfosecBaseUrl(this IConfiguration configuration, HttpCo /// /// CTS conformance ids must be validated. /// + /// if issuer is valid. public static bool ValidateIssuer(this HttpContext context) { if (context.Request != null && context.Request.PathBase.HasValue) @@ -137,7 +141,7 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic options.Configuration = new OpenIdConnectConfiguration() { JwksUri = $"{metadataAddress}/jwks", - JsonWebKeySet = jwks + JsonWebKeySet = jwks, }; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() @@ -148,7 +152,7 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic RequireSignedTokens = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(clockSkew), - IssuerSigningKeys = options.Configuration.JsonWebKeySet?.Keys + IssuerSigningKeys = options.Configuration.JsonWebKeySet?.Keys, }; // Ignore server certificate issues when retrieving OIDC configuration and JWKS. @@ -180,17 +184,6 @@ public static void AddAuthenticationAuthorization(this IServiceCollection servic services.AddSingleton(); } - private static async Task LoadJwks(string jwksUri, IConfiguration configuration) - { - var handler = new HttpClientHandler(); - handler.SetServerCertificateValidation(configuration); - var httpClient = new HttpClient(handler); - var httpResponse = await httpClient.GetAsync(jwksUri); - - var contentAsString = await httpResponse.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(contentAsString); - } - public static string GetHostName(this string url) { return url.Replace("https://", string.Empty).Replace("http://", string.Empty).Split('/')[0]; @@ -210,7 +203,7 @@ public static LinksPaginated GetPaginated( var currentUrl = controller.Request.GetDisplayUrl(); var links = new LinksPaginated { - Self = new Uri(currentUrl) + Self = new Uri(currentUrl), }; links.Self = ReplaceUriHost(currentUrl, controller.GetHostNameAsUri(configuration, hostName, isSecure)); @@ -247,7 +240,7 @@ public static Links GetSelf(this ControllerBase controller, IConfiguration confi var currentUrl = controller.Request.GetDisplayUrl(); var links = new Links { - Self = new Uri(currentUrl) + Self = new Uri(currentUrl), }; links.Self = ReplaceUriHost(currentUrl, controller.GetHostNameAsUri(configuration, hostName)); return links; @@ -323,21 +316,6 @@ public static string GetHostNameAsUri(this ControllerBase controller, IConfigura return new Uri(url.Replace(currentHost, newHostName)); } - private static Uri ReplaceUriHost(string url, string? newHost = null) - { - Uri originalUri = new Uri(url); - Uri replaceUri = new Uri(newHost ?? string.Empty); - - // Update the Uri components - UriBuilder modifiedUriBuilder = new UriBuilder(originalUri) - { - Host = replaceUri.Host, - Port = replaceUri.IsDefaultPort ? -1 : replaceUri.Port, - }; - - return modifiedUriBuilder.Uri; - } - public static Industry ToIndustry(this string industry) { if (Enum.IsDefined(typeof(Industry), industry.ToUpper())) @@ -373,7 +351,7 @@ public static string GetClientCertificateThumbprint(this HttpContext context, IC return string.Empty; } - public static string GetClientCertificateCommonName(this HttpContext context, ILogger logger, IConfiguration configuration) + public static string GetClientCertificateCommonName(this HttpContext context, Microsoft.Extensions.Logging.ILogger logger, IConfiguration configuration) { string? headerCommonName; var certCommonNameHttpHeaderName = configuration.GetValue(Constants.ConfigurationKeys.CertCommonNameHttpHeaderName) ?? Constants.Headers.X_TLS_CLIENT_CERT_COMMON_NAME; @@ -511,5 +489,54 @@ public static IServiceCollection AddCdrSwaggerGen(this IServiceCollection servic return services; } + + private static async Task LoadJwks(string jwksUri, IConfiguration configuration) + { + var retryPolicy = GetRetryPolicy(); + var handler = new HttpClientHandler(); + handler.SetServerCertificateValidation(configuration); + var httpClient = new HttpClient(handler); + + var httpResponse = await retryPolicy.ExecuteAsync(async () => + { + return await httpClient.GetAsync(jwksUri); + }); + + var contentAsString = await httpResponse.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(contentAsString); + } + + private static Polly.Retry.AsyncRetryPolicy GetRetryPolicy() + { + // Handles HttpRequestException, Http status codes >= 500 (server errors) and status code 408 (request timeout) + int maxRetryCount = 5; + int retryDelaySeconds = 5; + return HttpPolicyExtensions + .HandleTransientHttpError() + .WaitAndRetryAsync( + maxRetryCount, + (retryAttempt) => TimeSpan.FromSeconds(retryAttempt * retryDelaySeconds), + (exception, timeSpan, retryCount, context) => + Log.Logger.Warning( + "Request failed. Retrying in {Seconds}s (attempt {RetryCount} of {MaxRetryCount}).", + timeSpan.TotalSeconds, + retryCount, + maxRetryCount)); + } + + private static Uri ReplaceUriHost(string url, string? newHost = null) + { + Uri originalUri = new Uri(url); + Uri replaceUri = new Uri(newHost ?? string.Empty); + + // Update the Uri components + UriBuilder modifiedUriBuilder = new UriBuilder(originalUri) + { + Host = replaceUri.Host, + Port = replaceUri.IsDefaultPort ? -1 : replaceUri.Port, + }; + + return modifiedUriBuilder.Uri; + } } } diff --git a/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs index 2fadc6a..3fd94ac 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions/CdrSwaggerMiddlewareExtensions.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Builder; +using System.Linq; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection; -using System.Linq; namespace CDR.Register.API.Infrastructure { diff --git a/Source/CDR.Register.API.Infrastructure/Extensions/HttpClientHandlerExtensions.cs b/Source/CDR.Register.API.Infrastructure/Extensions/HttpClientHandlerExtensions.cs index 00b3874..982c7b8 100644 --- a/Source/CDR.Register.API.Infrastructure/Extensions/HttpClientHandlerExtensions.cs +++ b/Source/CDR.Register.API.Infrastructure/Extensions/HttpClientHandlerExtensions.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Configuration; -using System; +using System; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Configuration; namespace CDR.Register.API.Infrastructure { @@ -34,4 +34,4 @@ public static void SetServerCertificateValidation(this HttpClientHandler httpCli }; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs index 0a24a95..ebb615f 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/CheckDateAttribute.cs @@ -11,7 +11,7 @@ public class CheckDateAttribute : ValidationAttribute { protected override ValidationResult? IsValid(object? value, ValidationContext? validationContext) { - if (!DateTime.TryParse(value?.ToString(), CultureInfo.InvariantCulture, out _)) + 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 660a9df..4ed0ec8 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/CheckIndustryAttribute.cs @@ -1,8 +1,8 @@ -using CDR.Register.Domain.Models; +using System; +using CDR.Register.Domain.Models; using CDR.Register.Repository.Infrastructure; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using System; namespace CDR.Register.API.Infrastructure.Filters { @@ -16,17 +16,17 @@ public class CheckIndustryAttribute : ActionFilterAttribute public CheckIndustryAttribute() { - _industryRestriction = string.Empty; + this._industryRestriction = string.Empty; } public CheckIndustryAttribute(Industry industryRestriction) { - _industryRestriction = industryRestriction.ToString().ToUpper(); + this._industryRestriction = industryRestriction.ToString().ToUpper(); } public override void OnActionExecuting(ActionExecutingContext context) { - if (context.ActionArguments["industry"] is string industry && !IsValidIndustry(industry)) + if (context.ActionArguments["industry"] is string industry && !this.IsValidIndustry(industry)) { context.Result = new BadRequestObjectResult(new ResponseErrorList().AddInvalidIndustry()); } @@ -49,7 +49,7 @@ private bool IsValidIndustry(string industry) } // Check that the incoming industry matches the industry restriction, if set. - if (!string.IsNullOrEmpty(_industryRestriction) && !string.Equals(_industryRestriction, industryItem.ToString(), StringComparison.CurrentCultureIgnoreCase)) + if (!string.IsNullOrEmpty(this._industryRestriction) && !string.Equals(this._industryRestriction, industryItem.ToString(), StringComparison.CurrentCultureIgnoreCase)) { return false; } diff --git a/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs index 23cd18e..81c9bed 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/CheckPageSizeAttribute.cs @@ -1,7 +1,7 @@ -using CDR.Register.Domain.Models; -using Newtonsoft.Json; -using System; +using System; using System.ComponentModel.DataAnnotations; +using CDR.Register.Domain.Models; +using Newtonsoft.Json; namespace CDR.Register.API.Infrastructure.Filters { diff --git a/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs index 268768e..079b13e 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/LogActionEntryAttribute.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Mvc.Filters; +using System; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using Serilog.Context; -using System; namespace CDR.Register.API.Infrastructure.Filters { @@ -12,7 +12,7 @@ public class LogActionEntryAttribute : ActionFilterAttribute public LogActionEntryAttribute(ILogger logger) { - _logger = logger; + this._logger = logger; } public override void OnActionExecuting(ActionExecutingContext context) @@ -21,7 +21,7 @@ public override void OnActionExecuting(ActionExecutingContext context) var action = context.RouteData.Values["action"]?.ToString(); using (LogContext.PushProperty("MethodName", action)) { - _logger.LogInformation("Request received to {Controller}.{Action}", controller, action); + this._logger.LogInformation("Request received to {Controller}.{Action}", controller, action); } base.OnActionExecuting(context); diff --git a/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs b/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs index 09e067a..e86ac57 100644 --- a/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs +++ b/Source/CDR.Register.API.Infrastructure/Filters/ReturnXVAttribute.cs @@ -13,13 +13,13 @@ public class ReturnXVAttribute : ActionFilterAttribute public ReturnXVAttribute(string version) { - _version = version; + this._version = version; } public override void OnActionExecuted(ActionExecutedContext context) { // Set version (x-v) we are responding with in the response header to the parsed in version - context.HttpContext.Response.Headers[Constants.Headers.X_V] = _version; + context.HttpContext.Response.Headers[Constants.Headers.X_V] = this._version; base.OnActionExecuted(context); } diff --git a/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs b/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs index d060e3e..714fa3f 100644 --- a/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs +++ b/Source/CDR.Register.API.Infrastructure/Middleware/ApiExceptionHandler.cs @@ -1,11 +1,11 @@ -using CDR.Register.API.Infrastructure.Versioning; +using System.Net; +using System.Threading.Tasks; +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.Net; -using System.Threading.Tasks; using static CDR.Register.API.Infrastructure.Constants; namespace CDR.Register.API.Infrastructure.Middleware @@ -27,8 +27,8 @@ public static async Task Handle(HttpContext context) NullValueHandling = NullValueHandling.Ignore, ContractResolver = new DefaultContractResolver { - NamingStrategy = new CamelCaseNamingStrategy() - } + NamingStrategy = new CamelCaseNamingStrategy(), + }, }; if (ex is InvalidVersionException) diff --git a/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs b/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs index 78002e3..b922c5e 100644 --- a/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs +++ b/Source/CDR.Register.API.Infrastructure/Middleware/ModelStateErrorMiddleware.cs @@ -1,9 +1,9 @@ -using CDR.Register.Domain.Models; +using System.Linq; +using System.Net; +using CDR.Register.Domain.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; -using System.Linq; -using System.Net; namespace CDR.Register.API.Infrastructure.Middleware { diff --git a/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs b/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs index 2a743c4..a89184b 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/CdrApiEndpointVersionOptions.cs @@ -2,30 +2,18 @@ { public class CdrApiEndpointVersionOptions { - 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;}. - /// /// 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; - IsVersioned = true; - IsXVHeaderMandatory = isXvMandatory; - CurrentMinVersion = minVersion; - CurrentMaxVersion = maxVersion; - MinVerForResponseErrorListV2 = minVersionForErrorListV2; + this.Path = path; + this.IsVersioned = true; + this.IsXVHeaderMandatory = isXvMandatory; + this.CurrentMinVersion = minVersion; + this.CurrentMaxVersion = maxVersion; + this.MinVerForResponseErrorListV2 = minVersionForErrorListV2; } /// @@ -34,12 +22,12 @@ public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int minVers /// public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int version) { - Path = path; - IsVersioned = true; - IsXVHeaderMandatory = isXvMandatory; - CurrentMinVersion = version; - CurrentMaxVersion = version; - MinVerForResponseErrorListV2 = version; + this.Path = path; + this.IsVersioned = true; + this.IsXVHeaderMandatory = isXvMandatory; + this.CurrentMinVersion = version; + this.CurrentMaxVersion = version; + this.MinVerForResponseErrorListV2 = version; } /// @@ -48,9 +36,21 @@ public CdrApiEndpointVersionOptions(string path, bool isXvMandatory, int version /// public CdrApiEndpointVersionOptions(string path) { - Path = path; - IsVersioned = false; - IsXVHeaderMandatory = false; + this.Path = path; + this.IsVersioned = false; + this.IsXVHeaderMandatory = false; } + + 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;}. } } diff --git a/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs b/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs index 54fc001..56fb2bc 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/CdrApiOptions.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Http; namespace CDR.Register.API.Infrastructure.Models { @@ -30,7 +30,7 @@ public class CdrApiOptions public CdrApiEndpointVersionOptions? GetApiEndpointVersionOption(PathString path) { - foreach (var supportedApi in EndpointVersionOptions.OrderByDescending(v => v.Path.Length)) + foreach (var supportedApi in this.EndpointVersionOptions.OrderByDescending(v => v.Path.Length)) { var regEx = new System.Text.RegularExpressions.Regex(supportedApi.Path); if (regEx.IsMatch(path)) diff --git a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs index fb05509..182393a 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKey.cs @@ -1,25 +1,35 @@ using System; +using Newtonsoft.Json; namespace CDR.Register.API.Infrastructure.Models { public class JsonWebKey { - public string alg { get; set; } = string.Empty; + [JsonProperty("alg")] + public string Alg { get; set; } = string.Empty; - public string e { get; set; } = string.Empty; + [JsonProperty("e")] + public string E { get; set; } = string.Empty; - public string[] key_ops { get; set; } = []; + [JsonProperty("key_ops")] + public string[] Key_ops { get; set; } = []; - public string kid { get; set; } = string.Empty; + [JsonProperty("kid")] + public string Kid { get; set; } = string.Empty; - public string kty { get; set; } = string.Empty; + [JsonProperty("kty")] + public string Kty { get; set; } = string.Empty; - public string n { get; set; } = string.Empty; + [JsonProperty("n")] + public string N { get; set; } = string.Empty; - public string use { get; set; } = string.Empty; + [JsonProperty("use")] + public string Use { get; set; } = string.Empty; - public string x5t { get; set; } = string.Empty; + [JsonProperty("x5t")] + public string X5t { get; set; } = string.Empty; - public string[] x5c { get; set; } = []; + [JsonProperty("x5c")] + 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 830d3db..d991e14 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/JsonWebKeySet.cs @@ -1,7 +1,10 @@ -namespace CDR.Register.API.Infrastructure.Models +using Newtonsoft.Json; + +namespace CDR.Register.API.Infrastructure.Models { public class JsonWebKeySet { - public JsonWebKey[] keys { get; set; } = []; + [JsonProperty("keys")] + 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 c4ae64c..2b28ecd 100644 --- a/Source/CDR.Register.API.Infrastructure/Models/Links.cs +++ b/Source/CDR.Register.API.Infrastructure/Models/Links.cs @@ -5,7 +5,7 @@ namespace CDR.Register.API.Infrastructure.Models public class Links { /// - /// Fully qualified link to this API call. + /// Gets or sets 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 aea26ce..c3eb80c 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. + /// Gets or sets 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. + /// Gets or sets 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. + /// Gets or sets 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. + /// Gets or sets 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. + /// Gets or sets Fully qualified link to this API call. public Uri? Self { get; set; } } } diff --git a/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs b/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs index fbda1ae..9c21282 100644 --- a/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs +++ b/Source/CDR.Register.API.Infrastructure/Services/DataRecipientStatusCheckService.cs @@ -20,13 +20,13 @@ public class DataRecipientStatusCheckService : IDataRecipientStatusCheckService public DataRecipientStatusCheckService(IRegisterDiscoveryRepository registerDiscoveryRepository) { - _registerDiscoveryRepository = registerDiscoveryRepository; + this._registerDiscoveryRepository = registerDiscoveryRepository; } public async Task ValidateSoftwareProductStatus(Guid softwareProductId) { // Get the latest data recipient details from the repository - var softwareProduct = await _registerDiscoveryRepository.GetSoftwareProductIdAsync(softwareProductId); + var softwareProduct = await this._registerDiscoveryRepository.GetSoftwareProductIdAsync(softwareProductId); // Perform validations ResponseErrorList errorList = new ResponseErrorList(); diff --git a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs index 7d19160..c0f0a90 100644 --- a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs +++ b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/AuthorizationOperationFilter.cs @@ -1,11 +1,11 @@ -using CDR.Register.API.Infrastructure.Authorization; +using System; +using System.Collections.Generic; +using System.Linq; +using CDR.Register.API.Infrastructure.Authorization; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; -using System; -using System.Collections.Generic; -using System.Linq; namespace CDR.Register.API.Infrastructure.SwaggerFilters { diff --git a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/SetupApiVersionParamsOperationFilter.cs b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/SetupApiVersionParamsOperationFilter.cs index efdf4e5..508f3ce 100644 --- a/Source/CDR.Register.API.Infrastructure/SwaggerFilters/SetupApiVersionParamsOperationFilter.cs +++ b/Source/CDR.Register.API.Infrastructure/SwaggerFilters/SetupApiVersionParamsOperationFilter.cs @@ -12,12 +12,12 @@ public class SetupApiVersionParamsOperationFilter : IOperationFilter public SetupApiVersionParamsOperationFilter(IOptions options) { - _options = options.Value; + this._options = options.Value; } public void Apply(OpenApiOperation operation, OperationFilterContext context) { - var versionOption = _options.GetApiEndpointVersionOption($"/{context.ApiDescription.RelativePath}"); + var versionOption = this._options.GetApiEndpointVersionOption($"/{context.ApiDescription.RelativePath}"); foreach (var s in operation.Parameters.Where(o => o.Name == "x-v" || o.Name == "x-min-v")) { diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/ApiVersionErrorResponse.cs b/Source/CDR.Register.API.Infrastructure/Versioning/ApiVersionErrorResponse.cs index aeb6561..640c461 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/ApiVersionErrorResponse.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/ApiVersionErrorResponse.cs @@ -12,7 +12,7 @@ public class ApiVersionErrorResponse : DefaultErrorResponseProvider public ApiVersionErrorResponse() { - _logger = new LoggerFactory().CreateLogger(); + this._logger = new LoggerFactory().CreateLogger(); } public override IActionResult CreateResponse(ErrorResponseContext context) @@ -42,7 +42,7 @@ public override IActionResult CreateResponse(ErrorResponseContext context) statusCode = StatusCodes.Status500InternalServerError; } - _logger.LogError("Error detail {Detail}", errorList.Errors[0].Detail); + this._logger.LogError("Error detail {Detail}", errorList.Errors[0].Detail); return new ObjectResult(errorList) { StatusCode = statusCode }; } diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs b/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs index c4d945d..d349259 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/CdrVersionReader.cs @@ -14,7 +14,7 @@ public class CdrVersionReader : IApiVersionReader public CdrVersionReader(CdrApiOptions options) { - _options = options; + this._options = options; } public void AddParameters(IApiVersionParameterDescriptionContext context) @@ -25,16 +25,16 @@ public void AddParameters(IApiVersionParameterDescriptionContext context) public string? Read(HttpRequest request) { - var endpointOption = _options.GetApiEndpointVersionOption(request.Path); + var endpointOption = this._options.GetApiEndpointVersionOption(request.Path); if (endpointOption == null) { // handle any endpoint that hasn't been defined in options - endpointOption = new CdrApiEndpointVersionOptions(string.Empty, false, int.Parse(_options.DefaultVersion)); + endpointOption = new CdrApiEndpointVersionOptions(string.Empty, false, int.Parse(this._options.DefaultVersion)); } else if (!endpointOption.IsVersioned) { - return _options.DefaultVersion; + return this._options.DefaultVersion; } // If x-min-v is passed in, we expect it to be a Positive Integer, the x-v value is parsed out of the header and will be validated by the Package itself diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs b/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs index 59822e6..a7d6ed8 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/InvalidVersionException.cs @@ -4,8 +4,6 @@ namespace CDR.Register.API.Infrastructure.Versioning { public class InvalidVersionException : Exception { - public string HeaderName { get; set; } = string.Empty; - public InvalidVersionException() : base() { @@ -16,5 +14,7 @@ public InvalidVersionException(string headerName) { this.HeaderName = headerName; } + + public string HeaderName { get; set; } = string.Empty; } } diff --git a/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs b/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs index 12be6f3..ee02033 100644 --- a/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs +++ b/Source/CDR.Register.API.Infrastructure/Versioning/MissingRequiredHeaderException.cs @@ -4,12 +4,10 @@ namespace CDR.Register.API.Infrastructure.Versioning { public class MissingRequiredHeaderException : Exception { - public string HeaderName { get; set; } - public MissingRequiredHeaderException() : base() { - HeaderName = string.Empty; + this.HeaderName = string.Empty; } public MissingRequiredHeaderException(string headerName) @@ -17,5 +15,7 @@ public MissingRequiredHeaderException(string headerName) { this.HeaderName = headerName; } + + public string HeaderName { get; set; } } } 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 29ba5c4..394912e 100644 --- a/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj +++ b/Source/CDR.Register.API.Logger/CDR.Register.API.Logger.csproj @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.Register.API.Logger/IRequestResponseLogger.cs b/Source/CDR.Register.API.Logger/IRequestResponseLogger.cs index e8235fe..536239b 100644 --- a/Source/CDR.Register.API.Logger/IRequestResponseLogger.cs +++ b/Source/CDR.Register.API.Logger/IRequestResponseLogger.cs @@ -1,9 +1,9 @@ -namespace CDR.Register.API.Logger -{ - using Serilog; +using Serilog; +namespace CDR.Register.API.Logger +{ public interface IRequestResponseLogger { ILogger Log { get; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.API.Logger/LoggerExtensions.cs b/Source/CDR.Register.API.Logger/LoggerExtensions.cs index bbd9f55..da37ee4 100644 --- a/Source/CDR.Register.API.Logger/LoggerExtensions.cs +++ b/Source/CDR.Register.API.Logger/LoggerExtensions.cs @@ -1,7 +1,7 @@ -namespace CDR.Register.API.Logger -{ - using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +namespace CDR.Register.API.Logger +{ public static class LoggerExtensions { public static IServiceCollection AddRequestResponseLogging(this IServiceCollection services) @@ -10,4 +10,4 @@ public static IServiceCollection AddRequestResponseLogging(this IServiceCollecti return services; } } -} \ 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 e9da2b3..dc1fdd9 100644 --- a/Source/CDR.Register.API.Logger/RequestResponseLogger.cs +++ b/Source/CDR.Register.API.Logger/RequestResponseLogger.cs @@ -1,22 +1,16 @@ -namespace CDR.Register.API.Logger -{ - using Microsoft.Extensions.Configuration; - using Serilog; - using Serilog.Core; - using Serilog.Settings.Configuration; +using Microsoft.Extensions.Configuration; +using Serilog; +using Serilog.Settings.Configuration; +namespace CDR.Register.API.Logger +{ public class RequestResponseLogger : IRequestResponseLogger, IDisposable { - private readonly Logger _logger; - - public ILogger Log - { - get { return _logger; } - } + private readonly Serilog.Core.Logger _logger; public RequestResponseLogger(IConfiguration configuration) { - _logger = new LoggerConfiguration() + this._logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration, new ConfigurationReaderOptions { SectionName = "SerilogRequestResponseLogger" }) .Enrich.WithProperty("RequestMethod", string.Empty) .Enrich.WithProperty("RequestBody", string.Empty) @@ -36,9 +30,14 @@ public RequestResponseLogger(IConfiguration configuration) .CreateLogger(); } + public ILogger Log + { + get { return this._logger; } + } + public void Dispose() { - Dispose(true); + this.Dispose(true); GC.SuppressFinalize(this); } diff --git a/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs b/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs index 9c8e81d..1c48674 100644 --- a/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs +++ b/Source/CDR.Register.API.Logger/RequestResponseLoggingMiddleware.cs @@ -47,53 +47,65 @@ public class RequestResponseLoggingMiddleware public RequestResponseLoggingMiddleware(RequestDelegate next, IRequestResponseLogger requestResponseLogger, IConfiguration configuration) { - _requestResponseLogger = requestResponseLogger.Log.ForContext(); - _next = next ?? throw new ArgumentNullException(nameof(next)); - _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); - _currentProcessName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name; - _configuration = configuration; + this._requestResponseLogger = requestResponseLogger.Log.ForContext(); + this._next = next ?? throw new ArgumentNullException(nameof(next)); + this._recyclableMemoryStreamManager = new RecyclableMemoryStreamManager(); + this._currentProcessName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name; + this._configuration = configuration; } public async Task InvokeAsync(HttpContext context) { - InitMembers(); - await ExtractRequestProperties(context); - await ExtractResponseProperties(context); + this.InitMembers(); + await this.ExtractRequestProperties(context); + await this.ExtractResponseProperties(context); + } + + private static void SetIdFromJwt(string jwt, string identifierType, ref string idToSet) + { + var handler = new JwtSecurityTokenHandler(); + if (handler.CanReadToken(jwt)) + { + var decodedJwt = handler.ReadJwtToken(jwt); + var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; + + idToSet = id; + } } private void InitMembers() { - _requestMethod = _requestBody = _requestHeaders = _requestPath = _requestQueryString = - _statusCode = _elapsedTime = _responseHeaders = _responseBody = _requestHost = _requestScheme = - _exceptionMessage = _requestPathBase = _clientId = _softwareId = _fapiInteractionId = _requestIpAddress = _dataHolderBrandId = string.Empty; + this._requestMethod = this._requestBody = this._requestHeaders = this._requestPath = this._requestQueryString = + this._statusCode = this._elapsedTime = this._responseHeaders = this._responseBody = this._requestHost = this._requestScheme = + this._exceptionMessage = this._requestPathBase = this._clientId = this._softwareId = this._fapiInteractionId = this._requestIpAddress = this._dataHolderBrandId = string.Empty; } private void LogWithContext() { - var logger = _requestResponseLogger - .ForContext("SourceContext", GetSourceContext()) - .ForContext("RequestMethod", _requestMethod) - .ForContext("RequestBody", _requestBody) - .ForContext("RequestHeaders", _requestHeaders) - .ForContext("RequestPath", _requestPath) - .ForContext("RequestQueryString", _requestQueryString) - .ForContext("StatusCode", _statusCode) - .ForContext("RequestHost", _requestHost) - .ForContext("RequestIpAddress", _requestIpAddress) - .ForContext("ResponseHeaders", _responseHeaders) - .ForContext("ResponseBody", _responseBody) - .ForContext("ClientId", _clientId) - .ForContext("SoftwareId", _softwareId) - .ForContext("FapiInteractionId", _fapiInteractionId) - .ForContext("DataHolderBrandId", _dataHolderBrandId); - - if (!string.IsNullOrEmpty(_exceptionMessage)) + var logger = this._requestResponseLogger + .ForContext("SourceContext", this.GetSourceContext()) + .ForContext("RequestMethod", this._requestMethod) + .ForContext("RequestBody", this._requestBody) + .ForContext("RequestHeaders", this._requestHeaders) + .ForContext("RequestPath", this._requestPath) + .ForContext("RequestQueryString", this._requestQueryString) + .ForContext("StatusCode", this._statusCode) + .ForContext("RequestHost", this._requestHost) + .ForContext("RequestIpAddress", this._requestIpAddress) + .ForContext("ResponseHeaders", this._responseHeaders) + .ForContext("ResponseBody", this._responseBody) + .ForContext("ClientId", this._clientId) + .ForContext("SoftwareId", this._softwareId) + .ForContext("FapiInteractionId", this._fapiInteractionId) + .ForContext("DataHolderBrandId", this._dataHolderBrandId); + + if (!string.IsNullOrEmpty(this._exceptionMessage)) { - logger.Error(HttpSummaryExceptionMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _exceptionMessage); + logger.Error(HttpSummaryExceptionMessageTemplate, this._requestMethod, this._requestScheme, this._requestHost, this._requestPathBase, this._requestPath, this._exceptionMessage); } else { - logger.Write(LogEventLevel.Information, HttpSummaryMessageTemplate, _requestMethod, _requestScheme, _requestHost, _requestPathBase, _requestPath, _statusCode, _elapsedTime); + logger.Write(LogEventLevel.Information, HttpSummaryMessageTemplate, this._requestMethod, this._requestScheme, this._requestHost, this._requestPathBase, this._requestPath, this._statusCode, this._elapsedTime); } } @@ -102,46 +114,28 @@ private async Task ExtractRequestProperties(HttpContext context) try { context.Request.EnableBuffering(); - await using var requestStream = _recyclableMemoryStreamManager.GetStream(); + await using var requestStream = this._recyclableMemoryStreamManager.GetStream(); await context.Request.Body.CopyToAsync(requestStream); - _requestBody = ReadStreamInChunks(requestStream); + this._requestBody = this.ReadStreamInChunks(requestStream); context.Request.Body.Position = 0; - _requestHost = GetHost(context.Request); - _requestIpAddress = GetIpAddress(context); - _requestMethod = context.Request.Method; - _requestScheme = context.Request.Scheme; - _requestPath = context.Request.Path; - _requestQueryString = context.Request.QueryString.ToString(); - _requestPathBase = context.Request.PathBase.ToString(); + this._requestHost = this.GetHost(context.Request); + this._requestIpAddress = this.GetIpAddress(context); + this._requestMethod = context.Request.Method; + this._requestScheme = context.Request.Scheme; + this._requestPath = context.Request.Path; + this._requestQueryString = context.Request.QueryString.ToString(); + this._requestPathBase = context.Request.PathBase.ToString(); IEnumerable keyValues = context.Request.Headers.Keys.Select(key => key + ": " + string.Join(",", context.Request.Headers[key].ToArray())); - _requestHeaders = string.Join(Environment.NewLine, keyValues); + this._requestHeaders = string.Join(Environment.NewLine, keyValues); - ExtractIdFromRequest(context.Request); + this.ExtractIdFromRequest(context.Request); } catch (Exception ex) { - _exceptionMessage = ex.Message; - } - } - - private static class ClaimIdentifiers - { - public const string ClientId = "client_id"; - public const string Iss = "iss"; - } - - private static void SetIdFromJwt(string jwt, string identifierType, ref string idToSet) - { - var handler = new JwtSecurityTokenHandler(); - if (handler.CanReadToken(jwt)) - { - var decodedJwt = handler.ReadJwtToken(jwt); - var id = decodedJwt.Claims.FirstOrDefault(x => x.Type == identifierType)?.Value ?? string.Empty; - - idToSet = id; + this._exceptionMessage = ex.Message; } } @@ -150,9 +144,9 @@ private void ExtractIdFromRequest(HttpRequest request) try { // try fetching from the clientid in the body for connect/par - if (!string.IsNullOrEmpty(_requestBody) && _requestBody.Contains("client_assertion=") && string.IsNullOrEmpty(_clientId)) + if (!string.IsNullOrEmpty(this._requestBody) && this._requestBody.Contains("client_assertion=") && string.IsNullOrEmpty(this._clientId)) { - var nameValueCollection = HttpUtility.ParseQueryString(_requestBody); + var nameValueCollection = HttpUtility.ParseQueryString(this._requestBody); if (nameValueCollection != null) { var assertion = nameValueCollection["client_assertion"]; @@ -160,36 +154,36 @@ private void ExtractIdFromRequest(HttpRequest request) if (assertion != null) { // in this case we set the iss to clientid - _softwareId = string.Empty; - SetIdFromJwt(assertion, ClaimIdentifiers.Iss, ref _softwareId); + this._softwareId = string.Empty; + SetIdFromJwt(assertion, ClaimIdentifiers.Iss, ref this._softwareId); } } } // try fetching x-fapi-interaction-id. After fetching we don't return as we need other important ids. - _fapiInteractionId = string.Empty; + this._fapiInteractionId = string.Empty; if (request.Headers.TryGetValue("x-fapi-interaction-id", out var interactionid)) { - _fapiInteractionId = interactionid; + this._fapiInteractionId = interactionid; } // 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)) + if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue) && string.IsNullOrEmpty(this._softwareId)) { var scheme = headerValue.Scheme; var parameter = headerValue.Parameter; if (scheme == JwtBearerDefaults.AuthenticationScheme && parameter != null) { - _softwareId = string.Empty; - SetIdFromJwt(parameter, ClaimIdentifiers.ClientId, ref _softwareId); + this._softwareId = string.Empty; + SetIdFromJwt(parameter, ClaimIdentifiers.ClientId, ref this._softwareId); } } } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; } } @@ -213,7 +207,7 @@ private string ReadStreamInChunks(Stream stream) } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; } return string.Empty; @@ -222,35 +216,35 @@ private string ReadStreamInChunks(Stream stream) private async Task ExtractResponseProperties(HttpContext httpContext) { var originalBodyStream = httpContext.Response.Body; - await using var responseBody = _recyclableMemoryStreamManager.GetStream(); + await using var responseBody = this._recyclableMemoryStreamManager.GetStream(); httpContext.Response.Body = responseBody; var sw = Stopwatch.StartNew(); try { - await _next(httpContext); + await this._next(httpContext); } catch (Exception ex) { - _exceptionMessage = ex.Message; + this._exceptionMessage = ex.Message; throw; } finally { sw.Stop(); - _elapsedTime = sw.ElapsedMilliseconds.ToString(); + this._elapsedTime = sw.ElapsedMilliseconds.ToString(); responseBody.Seek(0, SeekOrigin.Begin); - _responseBody = await new StreamReader(responseBody).ReadToEndAsync(); + this._responseBody = await new StreamReader(responseBody).ReadToEndAsync(); responseBody.Seek(0, SeekOrigin.Begin); IEnumerable keyValues = httpContext.Response.Headers.Keys.Select(key => key + ": " + string.Join(",", httpContext.Response.Headers[key].ToArray())); - _responseHeaders = string.Join(System.Environment.NewLine, keyValues); + this._responseHeaders = string.Join(System.Environment.NewLine, keyValues); - _statusCode = httpContext.Response.StatusCode.ToString(); + this._statusCode = httpContext.Response.StatusCode.ToString(); - LogWithContext(); + this.LogWithContext(); // This is for middleware hooked before us to see our changes. // Otherwise the original stream would be seen which cannot be read again. @@ -263,7 +257,7 @@ private string GetHost(HttpRequest request) { // 1. check if the X-Forwarded-Host header has been provided -> use that // 2. If not, use the request.Host - string hostHeaderKey = _configuration.GetValue("SerilogRequestResponseLogger:HostNameHeaderKey") ?? "X-Forwarded-Host"; + string hostHeaderKey = this._configuration.GetValue("SerilogRequestResponseLogger:HostNameHeaderKey") ?? "X-Forwarded-Host"; if (!request.Headers.TryGetValue(hostHeaderKey, out var keys)) { @@ -275,7 +269,7 @@ private string GetHost(HttpRequest request) private string? GetIpAddress(HttpContext context) { - string ipHeaderKey = _configuration.GetValue("SerilogRequestResponseLogger:IPAddressHeaderKey") ?? "X-Forwarded-For"; + string ipHeaderKey = this._configuration.GetValue("SerilogRequestResponseLogger:IPAddressHeaderKey") ?? "X-Forwarded-For"; if (!context.Request.Headers.TryGetValue(ipHeaderKey, out var keys)) { @@ -292,7 +286,7 @@ private string GetHost(HttpRequest request) private string GetSourceContext() { - switch (_currentProcessName) + switch (this._currentProcessName) { case "CDR.Register.Discovery.API": return "SB-REG-DISC"; @@ -306,5 +300,11 @@ private string GetSourceContext() return string.Empty; } + + private static class ClaimIdentifiers + { + public const string ClientId = "client_id"; + public const string Iss = "iss"; + } } } diff --git a/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs b/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs index ea11d2d..08c85da 100644 --- a/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs +++ b/Source/CDR.Register.Admin.API/Business/AdminMappingProfile.cs @@ -1,8 +1,9 @@ -using AutoMapper; +using System.Collections.Generic; +using AutoMapper; using CDR.Register.Admin.API.Business.Model; using CDR.Register.Domain.Entities; +using CDR.Register.Repository.Enums; using Microsoft.Extensions.Configuration; -using System.Collections.Generic; using DomainEntities = CDR.Register.Domain.Entities; namespace CDR.Register.Admin.API.Business @@ -11,30 +12,30 @@ public class AdminMappingProfile : Profile { public AdminMappingProfile() { - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source)) .ForMember(dest => dest.DataRecipientBrands, source => source.MapFrom(source => source.DataRecipientBrands)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.AccreditationLevelId, source => source.MapFrom(source => source.AccreditationLevel)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandStatus, source => source.MapFrom(source => source.Status)) .ForMember(dest => dest.BrandId, source => source.MapFrom(source => source.DataRecipientBrandId)); - CreateMap() + this.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 ?? string.Empty)); - CreateMap(); + this.CreateMap(); // DH Brand Mappings - CreateMap(); - CreateMap(); - CreateMap(); + this.CreateMap(); + this.CreateMap(); + this.CreateMap(); - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.LegalEntityId)) .ForMember(dest => dest.LegalEntityName, source => source.MapFrom(source => source.LegalEntityName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) @@ -48,18 +49,18 @@ public AdminMappingProfile() .ForMember(dest => dest.OrganisationType, source => source.MapFrom(source => source.OrganisationType)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status.ToUpper())); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Industries, (IMemberConfigurationExpression> source) => source.MapFrom(source => source.Industries)) .ForMember(dest => dest.Industry, source => source.MapFrom(source => source.Industries.Length > 0 ? source.Industries[0] : string.Empty)) .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source == null ? null : source.LegalEntity)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.LegalEntity == null ? string.Empty : source.LegalEntity.Status.ToUpper())); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandId, source => source.MapFrom(source => source.DataHolderBrandId)) .ForMember(dest => dest.BrandName, source => source.MapFrom(source => source.BrandName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) .ForMember(dest => dest.BrandStatus, source => source.MapFrom(source => source.Status.ToUpper())) - .ForMember(dest => dest.IsActive, source => source.MapFrom(source => string.Compare(source.Status, Repository.Entities.BrandStatusType.Active.ToString(), true))) + .ForMember(dest => dest.IsActive, source => source.MapFrom(source => string.Compare(source.Status, BrandStatusType.Active.ToString(), true))) .ForMember(dest => dest.DataHolderAuthentications, source => source.MapFrom(source => new[] { source.AuthDetails })) .ForMember(dest => dest.DataHolderBrandServiceEndpoint, source => source.MapFrom(source => source.EndpointDetail)) .ForMember(dest => dest.DataHolder, source => source.MapFrom(source => source)); diff --git a/Source/CDR.Register.Admin.API/Business/Model/Brand.cs b/Source/CDR.Register.Admin.API/Business/Model/Brand.cs index c67fb1a..d4857b1 100644 --- a/Source/CDR.Register.Admin.API/Business/Model/Brand.cs +++ b/Source/CDR.Register.Admin.API/Business/Model/Brand.cs @@ -1,7 +1,7 @@ -using CDR.Register.Repository.Entities; -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Entities; namespace CDR.Register.Admin.API.Business.Model { diff --git a/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs b/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs index bc16faf..18e7370 100644 --- a/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs +++ b/Source/CDR.Register.Admin.API/Business/Model/DataHolderBrandModel.cs @@ -1,8 +1,8 @@ -using CDR.Register.Admin.API.Business.Validators; +using System; +using System.Collections.Generic; +using CDR.Register.Admin.API.Business.Validators; using CDR.Register.Domain.Entities; using CDR.Register.Domain.Models; -using System; -using System.Collections.Generic; namespace CDR.Register.Admin.API.Business.Model { @@ -49,7 +49,7 @@ public ResponseErrorList Validate(DataHolderBrand existingDataHolderBrand) } // Validate against the existing data - var existingDataValidationErrors = ValidateWithExisting(existingDataHolderBrand); + var existingDataValidationErrors = this.ValidateWithExisting(existingDataHolderBrand); if (existingDataValidationErrors.Count > 0) { responseErrorList.Errors.AddRange(existingDataValidationErrors); @@ -67,7 +67,9 @@ private List ValidateWithExisting(DataHolderBrand existingDataHolderBrand } // Validate all the parent IDs. - if (existingDataHolderBrand.DataHolder == null) // This ensures it is a DH Participation + + // This ensures it is a DH Participation + if (existingDataHolderBrand.DataHolder == null) { errorList.Add(new Error( Domain.Constants.ErrorCodes.Cds.InvalidField, diff --git a/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs b/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs index af72346..9bb63ea 100644 --- a/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs +++ b/Source/CDR.Register.Admin.API/Business/SoftwareScopeResolver.cs @@ -17,7 +17,7 @@ public string Resolve(Model.SoftwareProduct source, DomainEntities.SoftwareProdu { if (source.Scope == null) { - return config["SoftwareProductDefaultScopes"] ?? string.Empty; + return this.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 ba50288..2d6ef2b 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/BrandValidator.cs @@ -1,8 +1,9 @@ -using CDR.Register.Admin.API.Business.Model; -using FluentValidation; -using System; +using System; using System.Collections.Generic; using System.Linq; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Repository.Enums; +using FluentValidation; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -12,30 +13,30 @@ public class BrandValidator : AbstractValidator public BrandValidator() { // 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); + this.RuleFor(x => x.DataRecipientBrandId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.BrandName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // 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"); + this.RuleFor(x => x.BrandName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.BrandName}' is not allowed for BrandName"); + this.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 _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.Status}' is not allowed for Status"); + this.RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out 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()); + this.RuleForEach(x => x.SoftwareProducts).SetValidator(new SoftwareProductValidator()); // duplicate check - RuleFor(x => x.SoftwareProducts).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); + this.RuleFor(x => x.SoftwareProducts).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); } - private bool HaveUniqueIds(ICollection? softwareProducts) + private static bool HaveUniqueIds(ICollection? softwareProducts) { return softwareProducts?.Select(s => s.SoftwareProductId).Distinct().Count() == softwareProducts?.Count; } - private object? GetDuplicateId(Brand brand) + private static object? GetDuplicateId(Brand brand) { var distinctIds = new HashSet(); for (var i = 0; i < brand.SoftwareProducts?.Count; i++) diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs index c8e6ab8..49c3568 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderAuthenticationValidator.cs @@ -1,7 +1,7 @@ -using CDR.Register.Admin.API.Business.Model; -using CDR.Register.Domain.Entities; +using System; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Domain.Enums; using FluentValidation; -using System; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -11,14 +11,14 @@ public class DataHolderAuthenticationValidator : AbstractValidator x.RegisterUType).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.JwksEndpoint).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.RegisterUType).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.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 _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.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); + this.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 8da9192..066c095 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderBrandValidator.cs @@ -1,7 +1,7 @@ -using CDR.Register.Admin.API.Business.Model; -using CDR.Register.Domain.Entities; +using System; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Domain.Enums; using FluentValidation; -using System; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -11,35 +11,35 @@ public class DataHolderBrandValidator : AbstractValidator public DataHolderBrandValidator() { // Mandatory Field Validations - RuleFor(x => x.DataHolderBrandId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.BrandName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.Industries).Must(x => x != null && x.Length > 0).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.LegalEntity).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.EndpointDetail).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.AuthDetails).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.DataHolderBrandId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.BrandName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Industries).Must(x => x != null && x.Length > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LegalEntity).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.EndpointDetail).Must(x => x != null).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.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 _)).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); + this.RuleForEach(x => x.Industries).Must(x => Enum.TryParse(x, true, out Industry _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.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); + this.RuleFor(x => x.BrandName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); // Child Validation - When(x => x != null, () => + this.When(x => x != null, () => { - RuleFor(x => x.EndpointDetail!).SetValidator(new DataHolderEndpointValidator()); + this.RuleFor(x => x.EndpointDetail!).SetValidator(new DataHolderEndpointValidator()); }); - When(x => x != null, () => + this.When(x => x != null, () => { - RuleFor(x => x.AuthDetails!).SetValidator(new DataHolderAuthenticationValidator()); + this.RuleFor(x => x.AuthDetails!).SetValidator(new DataHolderAuthenticationValidator()); }); - When(x => x != null, () => + this.When(x => x != null, () => { - RuleFor(x => x.LegalEntity!).SetValidator(new DataHolderLegalEntityValidator()); + this.RuleFor(x => x.LegalEntity!).SetValidator(new DataHolderLegalEntityValidator()); }); } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderEndpointValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderEndpointValidator.cs index b948f31..e6925d4 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderEndpointValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderEndpointValidator.cs @@ -9,19 +9,19 @@ public class DataHolderEndpointValidator : AbstractValidator x.Version).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.PublicBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.ResourceBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.InfosecBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.WebsiteUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Version).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.PublicBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.ResourceBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.InfosecBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.WebsiteUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // Length Validations - RuleFor(x => x.Version).MaximumLength(25).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.PublicBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.ResourceBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.InfosecBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.ExtensionBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.WebsiteUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.Version).MaximumLength(25).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.PublicBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.ResourceBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.InfosecBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.ExtensionBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.WebsiteUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); } } } diff --git a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs index 38116d5..2ad25b6 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/DataHolderLegalEntityValidator.cs @@ -1,7 +1,7 @@ -using CDR.Register.Admin.API.Business.Model; -using CDR.Register.Domain.Entities; +using System; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Domain.Enums; using FluentValidation; -using System; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -11,24 +11,24 @@ public class DataHolderLegalEntityValidator : AbstractValidator 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.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LegalEntityId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LegalEntityName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.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 _)).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); + this.RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out DhStatus _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.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); - RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.RegistrationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.RegisteredCountry).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.Abn).MaximumLength(11).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.Acn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.Arbn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); - RuleFor(x => x.AnzsicDivision).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.LegalEntityName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.RegistrationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.RegisteredCountry).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.Abn).MaximumLength(11).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.Acn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.Arbn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField); + this.RuleFor(x => x.AnzsicDivision).MaximumLength(100).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 c4f8bf0..4a41096 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/LegalEntityValidator.cs @@ -1,8 +1,9 @@ -using CDR.Register.Admin.API.Business.Model; -using FluentValidation; -using System; +using System; using System.Collections.Generic; using System.Linq; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Repository.Enums; +using FluentValidation; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -12,43 +13,43 @@ public class LegalEntityValidator : AbstractValidator public LegalEntityValidator() { // 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); + this.RuleFor(x => x.LegalEntityId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LegalEntityName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.AccreditationNumber).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.AccreditationLevel).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.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 _)).WithErrorCode(ErrorCodes.Cds.InvalidField). + this.RuleFor(x => x.AccreditationLevel).Must(x => Enum.TryParse(x, true, out 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 _)). + this.RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out 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("_", string.Empty), true, out Repository.Entities.OrganisationTypes _)). + this.RuleFor(x => x.OrganisationType).Must(x => x == null || Enum.TryParse(x?.Replace("_", string.Empty), true, out 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"); - 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"); - RuleFor(x => x.RegistrationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.RegistrationNumber}' is not allowed for RegistrationNumber"); - RuleFor(x => x.RegisteredCountry).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.RegisteredCountry}' is not allowed for RegisteredCountry"); - RuleFor(x => x.Abn).MaximumLength(11).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Abn}' is not allowed for Abn"); - 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"); + this.RuleFor(x => x.LegalEntityName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.LegalEntityName}' is not allowed for LegalEntityName"); + this.RuleFor(x => x.AccreditationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.AccreditationNumber}' is not allowed for AccreditationNumber"); + this.RuleFor(x => x.AccreditationLevel).MaximumLength(13).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.AccreditationLevel}' is not allowed for AccreditationLevel"); + this.RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.LogoUri}' is not allowed for LogoUri"); + this.RuleFor(x => x.RegistrationNumber).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.RegistrationNumber}' is not allowed for RegistrationNumber"); + this.RuleFor(x => x.RegisteredCountry).MaximumLength(100).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.RegisteredCountry}' is not allowed for RegisteredCountry"); + this.RuleFor(x => x.Abn).MaximumLength(11).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Abn}' is not allowed for Abn"); + this.RuleFor(x => x.Acn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Acn}' is not allowed for Acn"); + this.RuleFor(x => x.Arbn).MaximumLength(9).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(le => $"Value '{le.Arbn}' is not allowed for Arbn"); + this.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()); + this.RuleForEach(x => x.DataRecipientBrands).SetValidator(new BrandValidator()); - RuleFor(x => x.DataRecipientBrands).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); + this.RuleFor(x => x.DataRecipientBrands).Must(HaveUniqueIds).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(GetDuplicateId); } - private object? GetDuplicateId(LegalEntity legalEntity) + private static object? GetDuplicateId(LegalEntity legalEntity) { var distinctIds = new HashSet(); for (var i = 0; i < legalEntity.DataRecipientBrands?.Count; i++) @@ -63,7 +64,7 @@ public LegalEntityValidator() return null; } - private bool HaveUniqueIds(ICollection? brands) + private static 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 cd7c4fe..16bc27a 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductCertificateValidator.cs @@ -9,11 +9,11 @@ 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); + this.RuleFor(x => x.CommonName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.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"); + this.RuleFor(x => x.CommonName).MaximumLength(2000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(b => $"Value '{b.CommonName}' is not allowed for CommonName"); + this.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 239c70d..c33d262 100644 --- a/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs +++ b/Source/CDR.Register.Admin.API/Business/Validators/SoftwareProductValidator.cs @@ -1,6 +1,7 @@ -using CDR.Register.Admin.API.Business.Model; +using System; +using CDR.Register.Admin.API.Business.Model; +using CDR.Register.Repository.Enums; using FluentValidation; -using System; using static CDR.Register.Domain.Constants; namespace CDR.Register.Admin.API.Business.Validators @@ -9,37 +10,37 @@ public class SoftwareProductValidator : AbstractValidator { public SoftwareProductValidator() { - RuleFor(x => x.SoftwareProductId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.SoftwareProductName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.SoftwareProductDescription).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); - 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.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); + this.RuleFor(x => x.SoftwareProductId).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.SoftwareProductName).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.SoftwareProductDescription).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.LogoUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.ClientUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.RecipientBaseUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.RevocationUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.RedirectUris).Must(x => x != null && x.Length > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.JwksUri).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Status).NotEmpty().WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); + this.RuleFor(x => x.Certificates).Must(x => x != null && x.Count > 0).WithErrorCode(ErrorCodes.Cds.MissingRequiredField).WithMessage(ErrorTitles.MissingRequiredField); // 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"); - RuleFor(x => x.ClientUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.ClientUri}' is not allowed for ClientUri"); - RuleFor(x => x.SectorIdentifierUri).MaximumLength(2048).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SectorIdentifierUri}' is not allowed for SectorIdentifierUri"); - RuleFor(x => x.TosUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.TosUri}' is not allowed for TosUri"); - RuleFor(x => x.PolicyUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.PolicyUri}' is not allowed for PolicyUri"); - RuleFor(x => x.RecipientBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RecipientBaseUri}' is not allowed for RecipientBaseUri"); - RuleFor(x => x.RevocationUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RevocationUri}' is not allowed for RevocationUri"); - RuleForEach(x => x.RedirectUris).MaximumLength(2000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RedirectUris}' is not allowed for RedirectUris"); - RuleFor(x => x.JwksUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.JwksUri}' is not allowed for JwksUri"); - 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"); + this.RuleFor(x => x.SoftwareProductName).MaximumLength(200).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SoftwareProductName}' is not allowed for SoftwareProductName"); + this.RuleFor(x => x.SoftwareProductDescription).MaximumLength(4000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SoftwareProductDescription}' is not allowed for SoftwareProductDescription"); + this.RuleFor(x => x.LogoUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.LogoUri}' is not allowed for LogoUri"); + this.RuleFor(x => x.ClientUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.ClientUri}' is not allowed for ClientUri"); + this.RuleFor(x => x.SectorIdentifierUri).MaximumLength(2048).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.SectorIdentifierUri}' is not allowed for SectorIdentifierUri"); + this.RuleFor(x => x.TosUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.TosUri}' is not allowed for TosUri"); + this.RuleFor(x => x.PolicyUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.PolicyUri}' is not allowed for PolicyUri"); + this.RuleFor(x => x.RecipientBaseUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RecipientBaseUri}' is not allowed for RecipientBaseUri"); + this.RuleFor(x => x.RevocationUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RevocationUri}' is not allowed for RevocationUri"); + this.RuleForEach(x => x.RedirectUris).MaximumLength(2000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.RedirectUris}' is not allowed for RedirectUris"); + this.RuleFor(x => x.JwksUri).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.JwksUri}' is not allowed for JwksUri"); + this.RuleFor(x => x.Scope).MaximumLength(1000).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Scope}' is not allowed for Scope"); + this.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 _)).WithErrorCode(ErrorCodes.Cds.InvalidField).WithMessage(ErrorTitles.InvalidField).WithState(x => $"Value '{x.Status}' is not allowed for Status"); + this.RuleFor(x => x.Status).Must(x => Enum.TryParse(x, true, out 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()); + this.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 960b0fe..2a26911 100644 --- a/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj +++ b/Source/CDR.Register.Admin.API/CDR.Register.Admin.API.csproj @@ -50,7 +50,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.Register.Admin.API/Common/Constants.cs b/Source/CDR.Register.Admin.API/Common/Constants.cs index 2c2bb1c..60a6be9 100644 --- a/Source/CDR.Register.Admin.API/Common/Constants.cs +++ b/Source/CDR.Register.Admin.API/Common/Constants.cs @@ -12,4 +12,4 @@ public static class Authorization public const string ScopeValue = "Authorization:Scope"; } } -} \ No newline at end of file +} 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 104b87a..68708da 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 @@ -10,7 +10,7 @@ JWKS. - + This controller action produces a client assertion for a mock data recipient. diff --git a/Source/CDR.Register.Admin.API/Controllers/AdminController.cs b/Source/CDR.Register.Admin.API/Controllers/AdminController.cs index 6f5a000..be0a655 100644 --- a/Source/CDR.Register.Admin.API/Controllers/AdminController.cs +++ b/Source/CDR.Register.Admin.API/Controllers/AdminController.cs @@ -1,4 +1,8 @@ -using AutoMapper; +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using AutoMapper; using CDR.Register.Admin.API.Business.Model; using CDR.Register.Admin.API.Business.Validators; using CDR.Register.Admin.API.Extensions; @@ -13,10 +17,6 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; namespace CDR.Register.Admin.API.Controllers { @@ -31,10 +31,10 @@ public class AdminController : ControllerBase public AdminController(ILogger logger, RegisterDatabaseContext dbContext, IRegisterAdminRepository registerAdminRepository, IMapper mapper) { - _logger = logger; - _dbContext = dbContext; - _adminRepository = registerAdminRepository; - _mapper = mapper; + this._logger = logger; + this._dbContext = dbContext; + this._adminRepository = registerAdminRepository; + this._mapper = mapper; } [HttpPost] @@ -43,33 +43,33 @@ public AdminController(ILogger logger, RegisterDatabaseContext [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task LoadData() { - using var reader = new StreamReader(Request.Body); + using var reader = new StreamReader(this.Request.Body); string json = await reader.ReadToEndAsync(); string respMsg = string.Empty; try { - bool updated = await _dbContext.SeedDatabaseFromJson(json, _logger, true); + bool updated = await this._dbContext.SeedDatabaseFromJson(json, this._logger, true); if (updated) { - Response.StatusCode = StatusCodes.Status200OK; + this.Response.StatusCode = StatusCodes.Status200OK; } else { - Response.StatusCode = StatusCodes.Status400BadRequest; + this.Response.StatusCode = StatusCodes.Status400BadRequest; respMsg = "Database not updated"; } } catch { // SeedDatabaseFromJson doesn't throw specific error exceptions, so lets just consider any exception a BadRequest - Response.StatusCode = StatusCodes.Status400BadRequest; + this.Response.StatusCode = StatusCodes.Status400BadRequest; respMsg = "UnexpectedError, An error occurred loading the database."; } finally { - Response.ContentType = "application/json"; - await Response.BodyWriter.WriteAsync(System.Text.Encoding.UTF8.GetBytes(respMsg)); + this.Response.ContentType = "application/json"; + await this.Response.BodyWriter.WriteAsync(System.Text.Encoding.UTF8.GetBytes(respMsg)); } } @@ -89,11 +89,11 @@ public async Task GetData() JsonConvert.DefaultSettings = () => apiSpecificSettings; - var metadata = await _dbContext.GetJsonFromDatabase(); + var metadata = await this._dbContext.GetJsonFromDatabase(); // Return the raw JSON response. - Response.ContentType = "application/json"; - await Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes($"{{ \"legalEntities\": {metadata} }}")); + this.Response.ContentType = "application/json"; + await this.Response.BodyWriter.WriteAsync(System.Text.UTF8Encoding.UTF8.GetBytes($"{{ \"legalEntities\": {metadata} }}")); } [HttpPost] @@ -107,29 +107,29 @@ public async Task SaveDataHolderBrand([FromBody] DataHolderBrandM try { // Get the existing data holder record - var existingDataHolderBrand = await _adminRepository.GetDataHolderBrandAsync(dataHolderBrandModel.DataHolderBrandId); + var existingDataHolderBrand = await this._adminRepository.GetDataHolderBrandAsync(dataHolderBrandModel.DataHolderBrandId); // Validate the incoming data holder model. var validationErrors = dataHolderBrandModel.Validate(existingDataHolderBrand); if (validationErrors != null && validationErrors.Errors.Count > 0) { - return BadRequest(validationErrors); + return this.BadRequest(validationErrors); } - var dataHolderBrandToSave = _mapper.Map(dataHolderBrandModel); - var isDhBrandSaved = await _adminRepository.SaveDataHolderBrand(dataHolderBrandToSave.DataHolder.LegalEntity.LegalEntityId, dataHolderBrandToSave); + var dataHolderBrandToSave = this._mapper.Map(dataHolderBrandModel); + var isDhBrandSaved = await this._adminRepository.SaveDataHolderBrand(dataHolderBrandToSave.DataHolder.LegalEntity.LegalEntityId, dataHolderBrandToSave); if (!isDhBrandSaved) { // Return error message here - return BadRequest(); + return this.BadRequest(); } - return Ok(); + return this.Ok(); } catch (Exception ex) { - _logger.LogError(ex, "An error occurred while trying to the save data holder."); - return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + this._logger.LogError(ex, "An error occurred while trying to the save data holder."); + return this.StatusCode(StatusCodes.Status500InternalServerError, ex.Message); } } @@ -143,12 +143,12 @@ public async Task SaveDataRecipient() { try { - using var reader = new StreamReader(Request.Body); + using var reader = new StreamReader(this.Request.Body); string json = await reader.ReadToEndAsync(); if (string.IsNullOrWhiteSpace(json)) { - return BadRequest(new Error(Domain.Constants.ErrorTitles.InvalidField, Domain.Constants.ErrorCodes.Cds.InvalidField, "Empty LegalEntity received")); + return this.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 @@ -156,21 +156,21 @@ public async Task SaveDataRecipient() var errors = legalEntity?.GetValidationErrors(new LegalEntityValidator()); if (errors?.Errors.Count > 0) { - return BadRequest(errors); + return this.BadRequest(errors); } - var dataRecipient = _mapper.Map(legalEntity); - var businessRuleError = await _adminRepository.AddOrUpdateDataRecipient(dataRecipient); + var dataRecipient = this._mapper.Map(legalEntity); + var businessRuleError = await this._adminRepository.AddOrUpdateDataRecipient(dataRecipient); return businessRuleError switch { - null => Ok(), - _ => BadRequest(businessRuleError.ToResponseErrorList()) + null => this.Ok(), + _ => this.BadRequest(businessRuleError.ToResponseErrorList()), }; } catch (Exception ex) { - _logger.LogError(ex, "An error occured in SaveDataRecipient"); - return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); + this._logger.LogError(ex, "An error occured in SaveDataRecipient"); + return this.StatusCode(StatusCodes.Status500InternalServerError, ex.Message); } } } diff --git a/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs b/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs index 6f9c80c..38f58e7 100644 --- a/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs +++ b/Source/CDR.Register.Admin.API/Controllers/LoopbackController.cs @@ -1,14 +1,14 @@ -using CDR.Register.API.Infrastructure.Filters; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using CDR.Register.API.Infrastructure.Filters; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; namespace CDR.Register.Admin.API.Controllers { @@ -21,7 +21,7 @@ public class LoopbackController : ControllerBase public LoopbackController(IConfiguration config) { - _config = config; + this._config = config; } /// @@ -39,17 +39,17 @@ public IActionResult MockDataRecipientJwks() var kid = GenerateKid(rsaParams, out var e, out var n); var jwk = new CDR.Register.API.Infrastructure.Models.JsonWebKey() { - alg = "PS256", - kid = kid, - kty = "RSA", - n = n, - e = e, - key_ops = new string[] { "sign", "verify" } + Alg = "PS256", + Kid = kid, + Kty = "RSA", + N = n, + E = e, + Key_ops = ["sign", "verify"], }; return new OkObjectResult(new CDR.Register.API.Infrastructure.Models.JsonWebKeySet() { - keys = new CDR.Register.API.Infrastructure.Models.JsonWebKey[] { jwk } + Keys = [jwk], }); } @@ -63,53 +63,45 @@ public IActionResult MockDataRecipientJwks() [HttpGet] [Route("MockDataRecipientClientAssertion")] [ServiceFilter(typeof(LogActionEntryAttribute))] - public IActionResult MockDataRecipientClientAssertion() + public IActionResult MockDataRecipientClientAssertion([FromQuery(Name = "iss")] string? iss = null, [FromQuery(Name = "aud")] string? aud = null) { var privateKeyRaw = System.IO.File.ReadAllText("Certificates/client.key"); 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"; - string softwareProductId = _config.GetValue("LoopbackDefaultSoftwareProductId") ?? "6F7A1B8E-8799-48A8-9011-E3920391F713"; - if (Request.Query.TryGetValue("iss", out var iss)) - { - softwareProductId = iss.ToString(); - } + string audience = this._config.GetValue("IdentityServerTokenUri") ?? "https://localhost:7001/idp/connect/token"; + string softwareProductId = this._config.GetValue("LoopbackDefaultSoftwareProductId") ?? "6F7A1B8E-8799-48A8-9011-E3920391F713"; - if (Request.Query.TryGetValue("aud", out var aud)) - { - audience = aud.ToString(); - } + softwareProductId = iss ?? softwareProductId; + audience = aud ?? audience; - using (var rsa = RSA.Create()) + using var rsa = RSA.Create(); + rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); + var kid = GenerateKid(rsa); + var privateSecurityKey = new RsaSecurityKey(rsa) { - rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _); - var kid = GenerateKid(rsa); - var privateSecurityKey = new RsaSecurityKey(rsa) + KeyId = kid, + CryptoProviderFactory = new CryptoProviderFactory() { - KeyId = kid, - CryptoProviderFactory = new CryptoProviderFactory() - { - CacheSignatureProviders = false - } - }; + CacheSignatureProviders = false, + }, + }; - var descriptor = new SecurityTokenDescriptor - { - Issuer = softwareProductId, - Audience = audience, - Expires = DateTime.UtcNow.AddMinutes(10), - Subject = new ClaimsIdentity(new List { new Claim("sub", softwareProductId) }), - SigningCredentials = new SigningCredentials(privateSecurityKey, SecurityAlgorithms.RsaSsaPssSha256), - NotBefore = null, - IssuedAt = null, - Claims = new Dictionary() - }; - descriptor.Claims.Add("jti", Guid.NewGuid().ToString()); + var descriptor = new SecurityTokenDescriptor + { + Issuer = softwareProductId, + Audience = audience, + Expires = DateTime.UtcNow.AddMinutes(10), + Subject = new ClaimsIdentity(new List { new Claim("sub", softwareProductId) }), + SigningCredentials = new SigningCredentials(privateSecurityKey, SecurityAlgorithms.RsaSsaPssSha256), + NotBefore = null, + IssuedAt = null, + Claims = new Dictionary(), + }; + descriptor.Claims.Add("jti", Guid.NewGuid().ToString()); - var tokenHandler = new JsonWebTokenHandler(); - return new OkObjectResult(tokenHandler.CreateToken(descriptor)); - } + var tokenHandler = new JsonWebTokenHandler(); + return new OkObjectResult(tokenHandler.CreateToken(descriptor)); } /// @@ -122,7 +114,7 @@ public IActionResult MockDataRecipientClientAssertion() public IActionResult RegisterSelfSignedJwt( [FromQuery] string aud) { - var cert = new X509Certificate2(_config.GetValue("SigningCertificate:Path") ?? string.Empty, _config.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); + var cert = new X509Certificate2(this._config.GetValue("SigningCertificate:Path") ?? string.Empty, this._config.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); var descriptor = new SecurityTokenDescriptor @@ -134,7 +126,7 @@ public IActionResult RegisterSelfSignedJwt( SigningCredentials = signingCredentials, NotBefore = null, IssuedAt = DateTime.UtcNow, - Claims = new Dictionary() + Claims = new Dictionary(), }; descriptor.Claims.Add("jti", Guid.NewGuid().ToString()); @@ -156,7 +148,7 @@ private static string GenerateKid(RSAParameters rsaParams, out string e, out str { { "e", e }, { "kty", "RSA" }, - { "n", n } + { "n", n }, }; var hash = SHA256.Create(); var hashBytes = hash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict))); diff --git a/Source/CDR.Register.Admin.API/Extensions/ErrorHandlingExtensions.cs b/Source/CDR.Register.Admin.API/Extensions/ErrorHandlingExtensions.cs index c9f684d..4de5c71 100644 --- a/Source/CDR.Register.Admin.API/Extensions/ErrorHandlingExtensions.cs +++ b/Source/CDR.Register.Admin.API/Extensions/ErrorHandlingExtensions.cs @@ -1,9 +1,9 @@ -using CDR.Register.Admin.API.Business.Model; +using System.Linq; +using CDR.Register.Admin.API.Business.Model; using CDR.Register.Admin.API.Business.Validators; using CDR.Register.API.Infrastructure.Models; using CDR.Register.Domain.Models; using CDR.Register.Domain.ValueObjects; -using System.Linq; namespace CDR.Register.Admin.API.Extensions { diff --git a/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs b/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs index 3d65691..2c4dd64 100644 --- a/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs +++ b/Source/CDR.Register.Admin.API/Filters/ApiAuthorizeAttribute.cs @@ -1,11 +1,11 @@ -using CDR.Register.Admin.API.Common; +using System; +using System.Linq; +using CDR.Register.Admin.API.Common; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using Microsoft.Net.Http.Headers; -using System; -using System.Linq; namespace CDR.Register.Admin.API.Filters { diff --git a/Source/CDR.Register.Admin.API/Program.cs b/Source/CDR.Register.Admin.API/Program.cs index f746670..3bcc18d 100644 --- a/Source/CDR.Register.Admin.API/Program.cs +++ b/Source/CDR.Register.Admin.API/Program.cs @@ -1,11 +1,11 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.IO; +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; namespace CDR.Register.Admin.API { diff --git a/Source/CDR.Register.Admin.API/Startup.cs b/Source/CDR.Register.Admin.API/Startup.cs index 68b4722..2a67e7b 100644 --- a/Source/CDR.Register.Admin.API/Startup.cs +++ b/Source/CDR.Register.Admin.API/Startup.cs @@ -35,22 +35,22 @@ public partial class Startup private bool healthCheckSeedData = false; private string healthCheckSeedDataMessage = string.Empty; - public IConfiguration Configuration { get; } - public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() - .AddCheck("migration", () => healthCheckMigration ? HealthCheckResult.Healthy(healthCheckMigrationMessage) : HealthCheckResult.Unhealthy(healthCheckMigrationMessage)) - .AddCheck("seed-data", () => healthCheckSeedData ? HealthCheckResult.Healthy(healthCheckSeedDataMessage) : HealthCheckResult.Unhealthy(healthCheckSeedDataMessage)); + .AddCheck("migration", () => this.healthCheckMigration ? HealthCheckResult.Healthy(this.healthCheckMigrationMessage) : HealthCheckResult.Unhealthy(this.healthCheckMigrationMessage)) + .AddCheck("seed-data", () => this.healthCheckSeedData ? HealthCheckResult.Healthy(this.healthCheckSeedDataMessage) : HealthCheckResult.Unhealthy(this.healthCheckSeedDataMessage)); - services.AddRegisterAdmin(Configuration); - services.AddRegisterAdminAuth(Configuration); + services.AddRegisterAdmin(this.Configuration); + services.AddRegisterAdminAuth(this.Configuration); services.AddControllers(); @@ -61,10 +61,10 @@ public void ConfigureServices(IServiceCollection services) options.ReportApiVersions = true; }); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { - var issuer = Configuration.GetValue(Constants.Authorization.Issuer); + var issuer = this.Configuration.GetValue(Constants.Authorization.Issuer); services.AddCdrSwaggerGen(opt => { opt.SwaggerTitle = "Consumer Data Right (CDR) Participant Tooling - Mock Register - Admin API"; @@ -82,7 +82,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< { app.UseHealthChecks("/health", new HealthCheckOptions() { - ResponseWriter = CustomResponseWriter + ResponseWriter = CustomResponseWriter, }); if (env.IsDevelopment()) @@ -96,7 +96,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< app.UseRouting(); - var issuer = Configuration.GetValue(Constants.Authorization.Issuer); + var issuer = this.Configuration.GetValue(Constants.Authorization.Issuer); if (!string.IsNullOrEmpty(issuer)) { app.UseAuthentication(); @@ -104,7 +104,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< app.UseAuthorization(); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { app.UseCdrSwagger(); @@ -125,9 +125,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< } // Run EF database migrations. - if (RunMigrations()) + if (this.RunMigrations()) { - healthCheckMigrationMessage = "Migration in progress"; + this.healthCheckMigrationMessage = "Migration in progress"; var context = serviceScope.ServiceProvider.GetRequiredService(); if (context == null) { @@ -136,36 +136,26 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< } context?.Database.Migrate(); - healthCheckMigrationMessage = "Migration completed"; + this.healthCheckMigrationMessage = "Migration completed"; // Seed the database using the sample data JSON. - var seedDataFilePath = Configuration.GetValue("SeedData:FilePath"); - var seedDataOverwrite = Configuration.GetValue("SeedData:OverwriteExistingData", false); + var seedDataFilePath = this.Configuration.GetValue("SeedData:FilePath"); + var seedDataOverwrite = this.Configuration.GetValue("SeedData:OverwriteExistingData", false); if (!string.IsNullOrEmpty(seedDataFilePath)) { - healthCheckSeedDataMessage = "Seeding of data in progress"; + this.healthCheckSeedDataMessage = "Seeding of data in progress"; logger.LogInformation("Seed data file found within configuration. Attempting to seed the repository from the seed data..."); Task.Run(() => context.SeedDatabaseFromJsonFile(seedDataFilePath, logger, seedDataOverwrite)).Wait(); - healthCheckSeedDataMessage = "Seeding of data completed"; + this.healthCheckSeedDataMessage = "Seeding of data completed"; } // Re-configure logger with the DB now. - Program.ConfigureSerilog(Configuration, true); + Program.ConfigureSerilog(this.Configuration, true); } // If we get here migration (if required) and seeding (if required) has completed - healthCheckMigration = true; - healthCheckSeedData = true; - } - - /// - /// Determine if EF Migrations should run. - /// - private bool RunMigrations() - { - // Run migrations if the DBO connection string is set. - var dbo = Configuration.GetConnectionString("Register_DBO"); - return !string.IsNullOrEmpty(dbo); + this.healthCheckMigration = true; + this.healthCheckSeedData = true; } private static Task CustomResponseWriter(HttpContext context, HealthReport healthReport) @@ -177,9 +167,19 @@ private static Task CustomResponseWriter(HttpContext context, HealthReport healt { key = e.Key, value = e.Value.Status.ToString(), - }) + }), }); return context.Response.WriteAsync(result); } + + /// + /// Determine if EF Migrations should run. + /// + private bool RunMigrations() + { + // Run migrations if the DBO connection string is set. + var dbo = this.Configuration.GetConnectionString("Register_DBO"); + return !string.IsNullOrEmpty(dbo); + } } } diff --git a/Source/CDR.Register.Discovery.API/Business/DiscoveryService.cs b/Source/CDR.Register.Discovery.API/Business/DiscoveryService.cs index 94ae62f..a789a90 100644 --- a/Source/CDR.Register.Discovery.API/Business/DiscoveryService.cs +++ b/Source/CDR.Register.Discovery.API/Business/DiscoveryService.cs @@ -1,9 +1,9 @@ +using System; +using System.Threading.Tasks; using AutoMapper; using CDR.Register.Discovery.API.Business.Responses; using CDR.Register.Repository.Infrastructure; using CDR.Register.Repository.Interfaces; -using System; -using System.Threading.Tasks; namespace CDR.Register.Discovery.API.Business { @@ -16,20 +16,20 @@ public DiscoveryService( IRegisterDiscoveryRepository registerDiscoveryRepository, IMapper mapper) { - _registerDiscoveryRepository = registerDiscoveryRepository; - _mapper = mapper; + this._registerDiscoveryRepository = registerDiscoveryRepository; + this._mapper = mapper; } public async Task GetDataHolderBrandsAsync(Industry industry, DateTime? updatedSince, int page, int pageSize) { - var entity = await _registerDiscoveryRepository.GetDataHolderBrandsAsync(industry, updatedSince, page, pageSize); - return _mapper.Map(entity); + var entity = await this._registerDiscoveryRepository.GetDataHolderBrandsAsync(industry, updatedSince, page, pageSize); + return this._mapper.Map(entity); } public async Task GetDataRecipientsAsync(Industry industry) { - var entity = await _registerDiscoveryRepository.GetDataRecipientsAsync(industry); - return _mapper.Map(entity); + var entity = await this._registerDiscoveryRepository.GetDataRecipientsAsync(industry); + return this._mapper.Map(entity); } } -} \ 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 877b070..4091824 100644 --- a/Source/CDR.Register.Discovery.API/Business/IDiscoveryService.cs +++ b/Source/CDR.Register.Discovery.API/Business/IDiscoveryService.cs @@ -1,7 +1,7 @@ -using CDR.Register.Discovery.API.Business.Responses; -using CDR.Register.Repository.Infrastructure; -using System; +using System; using System.Threading.Tasks; +using CDR.Register.Discovery.API.Business.Responses; +using CDR.Register.Repository.Infrastructure; namespace CDR.Register.Discovery.API.Business { diff --git a/Source/CDR.Register.Discovery.API/Business/MappingProfile.cs b/Source/CDR.Register.Discovery.API/Business/MappingProfile.cs index b5d64c0..bead95f 100644 --- a/Source/CDR.Register.Discovery.API/Business/MappingProfile.cs +++ b/Source/CDR.Register.Discovery.API/Business/MappingProfile.cs @@ -1,10 +1,10 @@ -using AutoMapper; +using System.Collections.Generic; +using AutoMapper; using CDR.Register.API.Infrastructure.Models; using CDR.Register.Discovery.API.Business.Models; using CDR.Register.Discovery.API.Business.Responses; using CDR.Register.Domain.Entities; using CDR.Register.Domain.ValueObjects; -using System.Collections.Generic; namespace CDR.Register.Discovery.API.Business { @@ -12,13 +12,13 @@ public class MappingProfile : Profile { public MappingProfile() { - CreateMap(typeof(Page<>), typeof(MetaPaginated)); + this.CreateMap(typeof(Page<>), typeof(MetaPaginated)); - CreateMap(); + this.CreateMap(); - CreateMap(); + this.CreateMap(); - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.LegalEntityId)) .ForMember(dest => dest.LegalEntityName, source => source.MapFrom(source => source.LegalEntityName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) @@ -32,11 +32,11 @@ public MappingProfile() .ForMember(dest => dest.OrganisationType, source => source.MapFrom(source => source.OrganisationType)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status.ToUpper())); - CreateMap, ResponseRegisterDataHolderBrandList>() + this.CreateMap, ResponseRegisterDataHolderBrandList>() .ForMember(dest => dest.Data, source => source.MapFrom(source => source.Data)) .ForMember(dest => dest.Meta, source => source.MapFrom(source => source)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.DataHolderBrandId, source => source.MapFrom(source => source.BrandId)) .ForMember(dest => dest.Industries, source => source.MapFrom(source => new List { source.DataHolder.Industry.ToLower() })) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.BrandStatus)) @@ -44,17 +44,17 @@ public MappingProfile() .ForMember(dest => dest.EndpointDetail, source => source.MapFrom(source => source.DataHolderBrandServiceEndpoint)) .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source.DataHolder.LegalEntity)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.DataRecipientBrandId, source => source.MapFrom(source => source.BrandId)) .ForMember(dest => dest.BrandName, source => source.MapFrom(source => source.BrandName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.BrandStatus)) .ForMember(dest => dest.SoftwareProducts, source => source.MapFrom(source => source.SoftwareProducts)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Data, source => source.MapFrom(source => source)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.LegalEntity.LegalEntityId)) .ForMember(dest => dest.LegalEntityName, source => source.MapFrom(source => source.LegalEntity.LegalEntityName)) .ForMember(dest => dest.AccreditationNumber, source => source.MapFrom(source => source.LegalEntity.AccreditationNumber)) @@ -64,11 +64,11 @@ public MappingProfile() .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status)) .ForMember(dest => dest.LastUpdated, source => source.MapFrom(source => source.LastUpdated)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.SoftwareProductId, source => source.MapFrom(source => source.SoftwareProductId)) .ForMember(dest => dest.SoftwareProductName, source => source.MapFrom(source => source.SoftwareProductName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status)); } } -} \ 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 6000221..523d441 100644 --- a/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj +++ b/Source/CDR.Register.Discovery.API/CDR.Register.Discovery.API.csproj @@ -30,7 +30,7 @@ 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 309c243..f8f8f4b 100644 --- a/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs +++ b/Source/CDR.Register.Discovery.API/Controllers/DiscoveryController.cs @@ -1,4 +1,8 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Authorization; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.API.Infrastructure.Services; @@ -7,10 +11,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System; -using System.Globalization; -using System.Net; -using System.Threading.Tasks; namespace CDR.Register.Discovery.API.Controllers { @@ -33,9 +33,9 @@ public DiscoveryController( IConfiguration configuration, IDataRecipientStatusCheckService statusCheckService) { - _discoveryService = discoveryService; - _configuration = configuration; - _statusCheckService = statusCheckService; + this._discoveryService = discoveryService; + this._configuration = configuration; + this._statusCheckService = statusCheckService; } [HttpGet("v1/{industry}/data-holders/brands", Name = ROUTE_GET_DATA_HOLDER_BRANDS_XV2)] @@ -52,18 +52,18 @@ public async Task GetDataHolderBrandsXV2( [FromQuery(Name = "page-size"), CheckPageSize] string pageSize) { // CTS conformance ID validations - var basePathExpression = _configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); + var basePathExpression = this._configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); if (!string.IsNullOrEmpty(basePathExpression)) { - var validIssuer = HttpContext.ValidateIssuer(); + var validIssuer = this.HttpContext.ValidateIssuer(); if (!validIssuer) { - return Unauthorized(new ResponseErrorList(StatusCodes.Status401Unauthorized.ToString(), HttpStatusCode.Unauthorized.ToString(), "invalid_token")); + return this.Unauthorized(new ResponseErrorList(StatusCodes.Status401Unauthorized.ToString(), HttpStatusCode.Unauthorized.ToString(), "invalid_token")); } } // Check if the data recipient is active - var result = await CheckSoftwareProduct(); + var result = await this.CheckSoftwareProduct(); if (result != null) { return result; @@ -73,7 +73,7 @@ public async Task GetDataHolderBrandsXV2( 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); + var response = await this._discoveryService.GetDataHolderBrandsAsync(industry.ToIndustry(), updatedSinceDate, pageNumber, pageSizeNumber); // Check if the given page number is out of range if (pageNumber != 1 && pageNumber > response.Meta.TotalPages) @@ -84,7 +84,7 @@ public async Task GetDataHolderBrandsXV2( // Set pagination meta data response.Links = this.GetPaginated( ROUTE_GET_DATA_HOLDER_BRANDS_XV2, - _configuration, + this._configuration, updatedSinceDate, pageNumber, response.Meta.TotalPages.Value, @@ -92,7 +92,7 @@ public async Task GetDataHolderBrandsXV2( string.Empty, true); - return Ok(response); + return this.Ok(response); } [HttpGet("v1/{industry}/data-recipients", Name = ROUTE_GET_DATA_RECIPIENTS_XV3)] @@ -103,9 +103,9 @@ public async Task GetDataHolderBrandsXV2( [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetDataRecipientsXV3(string industry) { - var response = await _discoveryService.GetDataRecipientsAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); - return Ok(response); + var response = await this._discoveryService.GetDataRecipientsAsync(industry.ToIndustry()); + response.Links = this.GetSelf(this._configuration, this.HttpContext, string.Empty); + return this.Ok(response); } /// @@ -115,14 +115,14 @@ public async Task GetDataRecipientsXV3(string industry) private async Task CheckSoftwareProduct() { // Get the software product id based on the access token. - var softwareProductIdAsGuid = GetSoftwareProductIdFromAccessToken(); + var softwareProductIdAsGuid = this.GetSoftwareProductIdFromAccessToken(); if (softwareProductIdAsGuid == null) { return new BadRequestObjectResult(new ResponseErrorList().AddUnexpectedError()); } // Check the status of the data recipient making the SSA request. - var statusErrors = await CheckStatus(softwareProductIdAsGuid.Value); + var statusErrors = await this.CheckStatus(softwareProductIdAsGuid.Value); if (statusErrors.HasErrors()) { return new RegisterForbidResult(statusErrors); @@ -133,7 +133,7 @@ private async Task CheckSoftwareProduct() private Guid? GetSoftwareProductIdFromAccessToken() { - string clientId = User.FindFirst("client_id")?.Value; + string clientId = this.User.FindFirst("client_id")?.Value; if (Guid.TryParse(clientId, out Guid softwareProductId)) { return softwareProductId; @@ -144,7 +144,7 @@ private async Task CheckSoftwareProduct() private async Task CheckStatus(Guid softwareProductId) { - return await _statusCheckService.ValidateSoftwareProductStatus(softwareProductId); + return await this._statusCheckService.ValidateSoftwareProductStatus(softwareProductId); } } } diff --git a/Source/CDR.Register.Discovery.API/Program.cs b/Source/CDR.Register.Discovery.API/Program.cs index 87fb76a..6635e01 100644 --- a/Source/CDR.Register.Discovery.API/Program.cs +++ b/Source/CDR.Register.Discovery.API/Program.cs @@ -1,10 +1,10 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.IO; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using System; -using System.IO; namespace CDR.Register.Discovery.API { diff --git a/Source/CDR.Register.Discovery.API/Startup.cs b/Source/CDR.Register.Discovery.API/Startup.cs index 1171c56..afc471a 100644 --- a/Source/CDR.Register.Discovery.API/Startup.cs +++ b/Source/CDR.Register.Discovery.API/Startup.cs @@ -22,7 +22,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -32,7 +32,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); - services.AddRegisterDiscovery(Configuration); + services.AddRegisterDiscovery(this.Configuration); services.AddControllers() .ConfigureApiBehaviorOptions(options => @@ -46,7 +46,7 @@ public void ConfigureServices(IServiceCollection services) options.ErrorResponses = new ApiVersionErrorResponse(); }); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { services.AddCdrSwaggerGen(opt => @@ -60,13 +60,13 @@ public void ConfigureServices(IServiceCollection services) // This is to manage the EF database context through the web API DI. // If this is to be done inside the repository project itself, we need to manage the context life-cycle explicitly. - services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("Register_DB"))); + services.AddDbContext(options => options.UseSqlServer(this.Configuration.GetConnectionString("Register_DB"))); services.AddAutoMapper(typeof(Startup), typeof(RegisterDatabaseContext)); services.AddScoped(); - if (Configuration.GetSection("SerilogRequestResponseLogger") != null) + if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) { Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); @@ -82,7 +82,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); }); - app.UseBasePathOrExpression(Configuration); + app.UseBasePathOrExpression(this.Configuration); app.UseSerilogRequestLogging(); @@ -93,7 +93,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthentication(); app.UseAuthorization(); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { app.UseCdrSwagger(); 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 51f445f..3d905dc 100644 --- a/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj +++ b/Source/CDR.Register.Domain.UnitTests/CDR.Register.Domain.UnitTests.csproj @@ -23,7 +23,7 @@ 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 adc0332..507922b 100644 --- a/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs +++ b/Source/CDR.Register.Domain.UnitTests/DataRecipientTests.cs @@ -10,10 +10,11 @@ public class DataRecipientTests /// /// When the data recipient has no brands, the last updated date should be null. /// + /// empty list of data recipient brands. public static IEnumerable GetEmptyDataRecipientBrands() => new List { new object[] { Array.Empty() }, - new object[] { null } + new object[] { null }, }; [Theory] @@ -23,7 +24,7 @@ public void LastUpdated_NoBrands_ShouldReturnNull(DataRecipientBrand[] brands) // Arrange var sut = new DataRecipient() { - DataRecipientBrands = brands + DataRecipientBrands = brands, }; // Act @@ -47,7 +48,7 @@ public void LastUpdated_HasBrands_ShouldReturnLastUpdatedDateFromLatestBrand() { new DataRecipientBrand() { LastUpdated = latestLastUpdated.AddDays(-1) }, new DataRecipientBrand() { LastUpdated = latestLastUpdated }, - } + }, }; // Act diff --git a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj index 82241ec..87f9164 100644 --- a/Source/CDR.Register.Domain/CDR.Register.Domain.csproj +++ b/Source/CDR.Register.Domain/CDR.Register.Domain.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.Register.Domain/Entities/Brand.cs b/Source/CDR.Register.Domain/Entities/Brand.cs index 0602624..9b10eed 100644 --- a/Source/CDR.Register.Domain/Entities/Brand.cs +++ b/Source/CDR.Register.Domain/Entities/Brand.cs @@ -16,6 +16,6 @@ public abstract class Brand public bool IsActive { get; set; } - public DateTime LastUpdated { get => DateTime.SpecifyKind(lastUpdated, DateTimeKind.Utc); set => lastUpdated = value; } + public DateTime LastUpdated { get => DateTime.SpecifyKind(this.lastUpdated, DateTimeKind.Utc); set => this.lastUpdated = value; } } } diff --git a/Source/CDR.Register.Domain/Entities/DataHolder.cs b/Source/CDR.Register.Domain/Entities/DataHolder.cs index 07aee85..b998bf0 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolder.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolder.cs @@ -30,20 +30,4 @@ public DateTime? LastUpdated } } } - - public enum Industry - { - All = 0, - Banking, - Energy, - Telco - } - - public enum DhParticipationStatus - { - Unknown = 0, - Active = 1, - Removed = 2, - Inactive = 6 - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs b/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs index a463e8a..e0e2446 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderAuthentication.cs @@ -6,10 +6,4 @@ public class DataHolderAuthentication public string JwksEndpoint { get; set; } } - - public enum RegisterUType - { - Unknown = 0, - SignedJwt = 1 - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs b/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs index 9f6f6ff..2604f9c 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderBrand.cs @@ -10,4 +10,4 @@ public class DataHolderBrand : Brand 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 20c5cf6..d2045cf 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderBrandServiceEndpoint.cs @@ -14,4 +14,4 @@ public class DataHolderBrandServiceEndpoint 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 1bb8a9c..46efaf4 100644 --- a/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs +++ b/Source/CDR.Register.Domain/Entities/DataHolderLegalEntity.cs @@ -14,7 +14,7 @@ public class DataHolderLegalEntity public string RegistrationNumber { get; set; } - public DateTime? RegistrationDate { get => _registrationDate?.Date; set => _registrationDate = value; } + public DateTime? RegistrationDate { get => this._registrationDate?.Date; set => this._registrationDate = value; } public string RegisteredCountry { get; set; } @@ -30,22 +30,4 @@ public class DataHolderLegalEntity public string Status { get; set; } } - - public enum DhStatus - { - Active = 1, - Removed = 2, - Inactive = 6 - } - - public enum OrganisationType - { - Unknown = 0, - SoleTrader = 1, - Company = 2, - Partnership = 3, - Trust = 4, - GovernmentEntity = 5, - Other = 6 - } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Domain/Entities/DataRecipient.cs b/Source/CDR.Register.Domain/Entities/DataRecipient.cs index c3a53bb..098a7be 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipient.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipient.cs @@ -6,14 +6,14 @@ namespace CDR.Register.Domain.Entities { public class DataRecipient { + public static string Industry => "banking"; + 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; } @@ -28,4 +28,4 @@ public DateTime? LastUpdated } } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs b/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs index 80ff115..6b9d827 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipientLegalEntity.cs @@ -4,6 +4,8 @@ namespace CDR.Register.Domain.Entities { public class DataRecipientLegalEntity { + private DateTime? _registrationDate; + public Guid LegalEntityId { get; set; } public string LegalEntityName { get; set; } @@ -18,11 +20,9 @@ public class DataRecipientLegalEntity public string AccreditationLevelId { get; set; } - private DateTime? _registrationDate; - public string RegistrationNumber { get; set; } - public DateTime? RegistrationDate { get => _registrationDate?.Date; set => _registrationDate = value; } + public DateTime? RegistrationDate { get => this._registrationDate?.Date; set => this._registrationDate = value; } public string RegisteredCountry { get; set; } @@ -34,4 +34,4 @@ public class DataRecipientLegalEntity 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 225b891..6218b13 100644 --- a/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs +++ b/Source/CDR.Register.Domain/Entities/DataRecipientStatus.cs @@ -8,11 +8,4 @@ public class DataRecipientStatus 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/DataRecipientStatusV2.cs b/Source/CDR.Register.Domain/Entities/DataRecipientStatusV2.cs new file mode 100644 index 0000000..17c86bd --- /dev/null +++ b/Source/CDR.Register.Domain/Entities/DataRecipientStatusV2.cs @@ -0,0 +1,11 @@ +using System; + +namespace CDR.Register.Domain.Entities +{ + public class DataRecipientStatusV2 + { + public Guid LegalEntityId { get; set; } + + public string Status { get; set; } + } +} diff --git a/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs b/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs index 3fa266f..8a433ad 100644 --- a/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs +++ b/Source/CDR.Register.Domain/Entities/SoftwareProduct.cs @@ -27,7 +27,7 @@ public class SoftwareProduct public string RedirectUri { get; set; } - public IEnumerable RedirectUris => RedirectUri?.Split(" "); + public IEnumerable RedirectUris => this.RedirectUri?.Split(" "); public string JwksUri { get; set; } @@ -41,4 +41,4 @@ public class SoftwareProduct public DataRecipientBrand DataRecipientBrand { get; set; } } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Domain/Enums/DhParticipationStatus.cs b/Source/CDR.Register.Domain/Enums/DhParticipationStatus.cs new file mode 100644 index 0000000..97ae08c --- /dev/null +++ b/Source/CDR.Register.Domain/Enums/DhParticipationStatus.cs @@ -0,0 +1,10 @@ +namespace CDR.Register.Domain.Enums +{ + public enum DhParticipationStatus + { + Unknown = 0, + Active = 1, + Removed = 2, + Inactive = 6, + } +} diff --git a/Source/CDR.Register.Domain/Enums/DhStatus.cs b/Source/CDR.Register.Domain/Enums/DhStatus.cs new file mode 100644 index 0000000..80b4296 --- /dev/null +++ b/Source/CDR.Register.Domain/Enums/DhStatus.cs @@ -0,0 +1,9 @@ +namespace CDR.Register.Domain.Enums +{ + public enum DhStatus + { + Active = 1, + Removed = 2, + Inactive = 6, + } +} diff --git a/Source/CDR.Register.Domain/Enums/Industry.cs b/Source/CDR.Register.Domain/Enums/Industry.cs new file mode 100644 index 0000000..5164956 --- /dev/null +++ b/Source/CDR.Register.Domain/Enums/Industry.cs @@ -0,0 +1,10 @@ +namespace CDR.Register.Domain.Enums +{ + public enum Industry + { + All = 0, + Banking, + Energy, + Telco, + } +} diff --git a/Source/CDR.Register.Domain/Enums/OrganisationType.cs b/Source/CDR.Register.Domain/Enums/OrganisationType.cs new file mode 100644 index 0000000..503b063 --- /dev/null +++ b/Source/CDR.Register.Domain/Enums/OrganisationType.cs @@ -0,0 +1,13 @@ +namespace CDR.Register.Domain.Enums +{ + public enum OrganisationType + { + Unknown = 0, + SoleTrader = 1, + Company = 2, + Partnership = 3, + Trust = 4, + GovernmentEntity = 5, + Other = 6, + } +} diff --git a/Source/CDR.Register.Domain/Enums/RegisterUType.cs b/Source/CDR.Register.Domain/Enums/RegisterUType.cs new file mode 100644 index 0000000..6dd9022 --- /dev/null +++ b/Source/CDR.Register.Domain/Enums/RegisterUType.cs @@ -0,0 +1,8 @@ +namespace CDR.Register.Domain.Enums +{ + public enum RegisterUType + { + Unknown = 0, + SignedJwt = 1, + } +} diff --git a/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs b/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs index 217c553..748936b 100644 --- a/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs +++ b/Source/CDR.Register.Domain/Models/CdrJsonSerializerSettings.cs @@ -1,7 +1,7 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; -using System.Collections.Generic; namespace CDR.Register.Domain.Models { @@ -10,11 +10,11 @@ public class CdrJsonSerializerSettings : JsonSerializerSettings public CdrJsonSerializerSettings() : base() { - ContractResolver = new CamelCasePropertyNamesContractResolver(); - DefaultValueHandling = DefaultValueHandling.Include; - NullValueHandling = NullValueHandling.Ignore; - Formatting = Formatting.Indented; - Converters = new List() { new StringEnumConverter() }; + this.ContractResolver = new CamelCasePropertyNamesContractResolver(); + this.DefaultValueHandling = DefaultValueHandling.Include; + this.NullValueHandling = NullValueHandling.Ignore; + this.Formatting = Formatting.Indented; + this.Converters = new List() { new StringEnumConverter() }; } } } diff --git a/Source/CDR.Register.Domain/Models/Error.cs b/Source/CDR.Register.Domain/Models/Error.cs index edf9bbf..404a9e2 100644 --- a/Source/CDR.Register.Domain/Models/Error.cs +++ b/Source/CDR.Register.Domain/Models/Error.cs @@ -1,5 +1,5 @@ -using CDR.Register.Domain.Extensions; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using CDR.Register.Domain.Extensions; namespace CDR.Register.Domain.Models { @@ -11,39 +11,39 @@ public Error() public Error(string code, string title, string detail) { - Code = code; - Title = title; - Detail = detail; + this.Code = code; + this.Title = title; + this.Detail = detail; } public Error(string code, string title, string detail, string metaUrn) { - Code = code; - Title = title; - Detail = detail; - Meta = metaUrn.IsNullOrWhiteSpace() ? null : new MetaError(metaUrn); + this.Code = code; + this.Title = title; + this.Detail = detail; + this.Meta = metaUrn.IsNullOrWhiteSpace() ? null : new MetaError(metaUrn); } /// - /// Error code. + /// Gets or sets Error code. /// [Required] public string Code { get; set; } /// - /// Error title. + /// Gets or sets Error title. /// [Required] public string Title { get; set; } /// - /// Error detail. + /// Gets or sets Error detail. /// [Required] public string Detail { get; set; } /// - /// Optional additional data for specific error types. + /// Gets or sets Optional additional data for specific error types. /// public MetaError Meta { get; set; } } diff --git a/Source/CDR.Register.Domain/Models/MetaError.cs b/Source/CDR.Register.Domain/Models/MetaError.cs index 809e353..3b4c35f 100644 --- a/Source/CDR.Register.Domain/Models/MetaError.cs +++ b/Source/CDR.Register.Domain/Models/MetaError.cs @@ -4,7 +4,7 @@ public class MetaError { public MetaError(string urn) { - Urn = urn; + this.Urn = urn; } public string Urn { get; set; } diff --git a/Source/CDR.Register.Domain/Models/ResponseErrorList.cs b/Source/CDR.Register.Domain/Models/ResponseErrorList.cs index e9d45b8..890554d 100644 --- a/Source/CDR.Register.Domain/Models/ResponseErrorList.cs +++ b/Source/CDR.Register.Domain/Models/ResponseErrorList.cs @@ -5,158 +5,160 @@ namespace CDR.Register.Domain.Models { public class ResponseErrorList { + public ResponseErrorList() + { + this.Errors = []; + } + + public ResponseErrorList(Error error) + { + this.Errors = [error]; + } + + public ResponseErrorList(string errorCode, string errorTitle, string errorDetail) + { + var error = new Error(errorCode, errorTitle, errorDetail); + this.Errors = [error]; + } + [Required] public List Errors { get; set; } - public bool HasErrors() + public static Error InvalidDateTime() { - return Errors != null && Errors.Count > 0; + return new Error(Constants.ErrorCodes.Cds.InvalidDateTime, Constants.ErrorTitles.InvalidDateTime, "{0} should be valid DateTimeString"); } - public ResponseErrorList() + public static Error InvalidPageSize() // Should be looked at compared to CDS { - Errors = []; + return new Error(Constants.ErrorCodes.Cds.InvalidPageSize, Constants.ErrorTitles.InvalidPageSize, "Page size not a positive Integer"); } - public ResponseErrorList(Error error) + public static Error PageSizeTooLarge() // Should be looked at compared to CDS { - Errors = [error]; + return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page size too large"); } - public ResponseErrorList(string errorCode, string errorTitle, string errorDetail) + public static Error InvalidPage() // Should be looked at compared to CDS { - var error = new Error(errorCode, errorTitle, errorDetail); - Errors = [error]; + 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 + { + return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page is out of range"); + } + + public static Error DataRecipientParticipationNotActive() + { + return new Error(Constants.ErrorCodes.Cds.AdrStatusNotActive, Constants.ErrorTitles.ADRStatusNotActive, string.Empty); + } + + public static Error DataRecipientSoftwareProductNotActive() + { + return new Error(Constants.ErrorCodes.Cds.AdrStatusNotActive, Constants.ErrorTitles.ADRStatusNotActive, string.Empty); + } + + public static Error InvalidResource(string softwareProductId) + { + return new Error(Constants.ErrorCodes.Cds.InvalidResource, Constants.ErrorTitles.InvalidResource, softwareProductId); + } + + public static Error InvalidSoftwareProduct(string softwareProductId) + { + return new Error(Constants.ErrorCodes.Cds.InvalidSoftwareProduct, Constants.ErrorTitles.InvalidSoftwareProduct, softwareProductId); + } + + public static Error NotFound() + { + return new Error(Constants.ErrorCodes.Cds.ResourceNotFound, Constants.ErrorTitles.ResourceNotFound, string.Empty); + } + + public bool HasErrors() + { + return this.Errors != null && this.Errors.Count > 0; } /// /// Add unexpected error to the response error list. /// + /// ErrorList for response. public ResponseErrorList AddUnexpectedError(string message) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.UnexpectedError, Constants.ErrorTitles.UnexpectedError, message)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.UnexpectedError, Constants.ErrorTitles.UnexpectedError, message)); return this; } public ResponseErrorList AddUnexpectedError() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.UnexpectedError, Constants.ErrorTitles.UnexpectedError, "An unexpected exception occurred while processing the request.")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.UnexpectedError, Constants.ErrorTitles.UnexpectedError, "An unexpected exception occurred while processing the request.")); return this; } /// /// Add invalid industry error to the response error list. /// + /// Errorlist for response. public ResponseErrorList AddInvalidIndustry() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "industry")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "industry")); return this; } // Return Unsupported Version public ResponseErrorList AddInvalidXVUnsupportedVersion() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.UnsupportedVersion, Constants.ErrorTitles.UnsupportedVersion, "Requested version is lower than the minimum version or greater than maximum version.")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.UnsupportedVersion, Constants.ErrorTitles.UnsupportedVersion, "Requested version is lower than the minimum version or greater than maximum version.")); return this; } // Return Invalid Version public ResponseErrorList AddInvalidXVInvalidVersion() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidVersion, Constants.ErrorTitles.InvalidVersion, "Version is not a positive Integer.")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidVersion, Constants.ErrorTitles.InvalidVersion, "Version is not a positive Integer.")); return this; } public ResponseErrorList AddInvalidXVMissingRequiredHeader() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredHeader, Constants.ErrorTitles.MissingRequiredHeader, "An API version x-v header is required, but was not specified.")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredHeader, Constants.ErrorTitles.MissingRequiredHeader, "An API version x-v header is required, but was not specified.")); return this; } public ResponseErrorList AddInvalidConsentArrangement(string arrangementId) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidConsentArrangement, Constants.ErrorTitles.InvalidConsentArrangement, arrangementId)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidConsentArrangement, Constants.ErrorTitles.InvalidConsentArrangement, arrangementId)); return this; } public ResponseErrorList AddMissingRequiredHeader(string headerName) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredHeader, Constants.ErrorTitles.MissingRequiredHeader, headerName)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredHeader, Constants.ErrorTitles.MissingRequiredHeader, headerName)); return this; } public ResponseErrorList AddMissingRequiredField(string headerName) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredField, Constants.ErrorTitles.MissingRequiredField, headerName)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.MissingRequiredField, Constants.ErrorTitles.MissingRequiredField, headerName)); return this; } public ResponseErrorList AddInvalidField(string fieldName) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, fieldName)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, fieldName)); return this; } public ResponseErrorList AddInvalidHeader(string headerName) { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidHeader, Constants.ErrorTitles.InvalidHeader, headerName)); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidHeader, Constants.ErrorTitles.InvalidHeader, headerName)); return this; } public ResponseErrorList AddInvalidDateTime() { - Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidDateTime, Constants.ErrorTitles.InvalidDateTime, "{0} should be valid DateTimeString")); + this.Errors.Add(new Error(Constants.ErrorCodes.Cds.InvalidDateTime, Constants.ErrorTitles.InvalidDateTime, "{0} should be valid DateTimeString")); return this; } - - 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 - { - 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 - { - 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 - { - 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 - { - return new Error(Constants.ErrorCodes.Cds.InvalidField, Constants.ErrorTitles.InvalidField, "Page is out of range"); - } - - public static Error DataRecipientParticipationNotActive() - { - return new Error(Constants.ErrorCodes.Cds.AdrStatusNotActive, Constants.ErrorTitles.ADRStatusNotActive, string.Empty); - } - - public static Error DataRecipientSoftwareProductNotActive() - { - return new Error(Constants.ErrorCodes.Cds.AdrStatusNotActive, Constants.ErrorTitles.ADRStatusNotActive, string.Empty); - } - - public static Error InvalidResource(string softwareProductId) - { - return new Error(Constants.ErrorCodes.Cds.InvalidResource, Constants.ErrorTitles.InvalidResource, softwareProductId); - } - - public static Error InvalidSoftwareProduct(string softwareProductId) - { - return new Error(Constants.ErrorCodes.Cds.InvalidSoftwareProduct, Constants.ErrorTitles.InvalidSoftwareProduct, softwareProductId); - } - - public static Error NotFound() - { - return new Error(Constants.ErrorCodes.Cds.ResourceNotFound, Constants.ErrorTitles.ResourceNotFound, string.Empty); - } } } diff --git a/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs b/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs index ca446f7..a5e43e4 100644 --- a/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs +++ b/Source/CDR.Register.Domain/ValueObjects/BusinessRuleError.cs @@ -2,17 +2,17 @@ { public class BusinessRuleError { + public BusinessRuleError(string errorCode, string errorTitle, string errorDetail) + { + this.Code = errorCode; + this.Title = errorTitle; + this.Detail = errorDetail; + } + public string Code { get; set; } public string Title { get; set; } public string Detail { get; set; } - - public BusinessRuleError(string errorCode, string errorTitle, string errorDetail) - { - Code = errorCode; - Title = errorTitle; - Detail = errorDetail; - } } } diff --git a/Source/CDR.Register.Domain/ValueObjects/Page.cs b/Source/CDR.Register.Domain/ValueObjects/Page.cs index f6c3398..aecd7bf 100644 --- a/Source/CDR.Register.Domain/ValueObjects/Page.cs +++ b/Source/CDR.Register.Domain/ValueObjects/Page.cs @@ -18,12 +18,12 @@ public int TotalPages { get { - if (TotalRecords == 0 || PageSize == 0) + if (this.TotalRecords == 0 || this.PageSize == 0) { return 0; } - return (int)Math.Ceiling((decimal)TotalRecords / (decimal)PageSize); + return (int)Math.Ceiling((decimal)this.TotalRecords / (decimal)this.PageSize); } } } diff --git a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj index 4dde46a..7ebdf7f 100644 --- a/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj +++ b/Source/CDR.Register.Infosec/CDR.Register.Infosec.csproj @@ -40,7 +40,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive 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 6ea86c8..aacb0dc 100644 --- a/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml +++ b/Source/CDR.Register.Infosec/ConsumerDataRight.ParticipantTooling.MockRegister.API.Infosec.xml @@ -10,7 +10,7 @@ client_id (form param) when provided and must match client assertion issuer and subject. client Assertion. - + Tuple of IsValid, ValidationMessage, Client. diff --git a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs index 1d82a77..f414753 100644 --- a/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs +++ b/Source/CDR.Register.Infosec/Controllers/DiscoveryController.cs @@ -1,9 +1,9 @@ -using CDR.Register.API.Infrastructure; +using System.Security.Cryptography.X509Certificates; +using CDR.Register.API.Infrastructure; using CDR.Register.Infosec.Models; using IdentityModel; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; -using System.Security.Cryptography.X509Certificates; namespace CDR.Register.Infosec.Controllers { @@ -16,15 +16,15 @@ public class DiscoveryController : ControllerBase public DiscoveryController( IConfiguration configuration) { - _configuration = configuration; + this._configuration = configuration; } [HttpGet(Name = "GetOIDD")] [Route("openid-configuration")] public DiscoveryDocument Get() { - var baseUrl = _configuration.GetInfosecBaseUrl(HttpContext); - var secureBaseUrl = _configuration.GetInfosecBaseUrl(HttpContext, true); + var baseUrl = this._configuration.GetInfosecBaseUrl(this.HttpContext); + var secureBaseUrl = this._configuration.GetInfosecBaseUrl(this.HttpContext, true); return new DiscoveryDocument() { @@ -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") ?? string.Empty, _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable); + var cert = new X509Certificate2(this._configuration.GetValue("SigningCertificate:Path") ?? string.Empty, this._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()); @@ -62,21 +62,21 @@ public DiscoveryDocument Get() var jwks = new API.Infrastructure.Models.JsonWebKeySet { - keys = + Keys = [ new API.Infrastructure.Models.JsonWebKey { - kty = "RSA", - use = "sig", - kid = signingCredentials.Kid, - x5t = thumbprint, - e = exponent, - n = modulus, - x5c = [cert64], - alg = "PS256" + Kty = "RSA", + Use = "sig", + Kid = signingCredentials.Kid, + X5t = thumbprint, + E = exponent, + N = modulus, + X5c = [cert64], + Alg = "PS256", } - ] + ], }; return jwks; } diff --git a/Source/CDR.Register.Infosec/Controllers/TokenController.cs b/Source/CDR.Register.Infosec/Controllers/TokenController.cs index c190397..18a1cdc 100644 --- a/Source/CDR.Register.Infosec/Controllers/TokenController.cs +++ b/Source/CDR.Register.Infosec/Controllers/TokenController.cs @@ -1,10 +1,11 @@ -using CDR.Register.API.Infrastructure; +using System.IdentityModel.Tokens.Jwt; +using CDR.Register.API.Infrastructure; +using CDR.Register.API.Infrastructure.Attributes; using CDR.Register.Domain.Entities; using CDR.Register.Infosec.Interfaces; using CDR.Register.Infosec.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc; -using System.IdentityModel.Tokens.Jwt; using static CDR.Register.Domain.Constants; namespace CDR.Register.Infosec.Controllers @@ -22,173 +23,161 @@ public TokenController( IClientService clientService, ITokenService tokenService) { - _logger = logger; - _configuration = configuration; - _tokenService = tokenService; + this._logger = logger; + this._configuration = configuration; + this._tokenService = tokenService; } [HttpPost("token", Name = "GetAccessToken")] - [Consumes("application/x-www-form-urlencoded")] + [ValidateContentTypeFilter("application/x-www-form-urlencoded")] public async Task GetAccessToken([FromForm] ClientAssertionRequest clientAssertion) { - var (isValid, error, errorDescription, client) = await Validate(clientAssertion, this.Request); + var (isValid, error, errorDescription, client) = await this.Validate(clientAssertion); if (!isValid || client == null) { - return BadRequest(new ErrorResponse() { Error = error, ErrorDescription = errorDescription }); + return this.BadRequest(new ErrorResponse() { Error = error, ErrorDescription = errorDescription }); } - var expiry = _configuration.GetValue("AccessTokenExpiryInSeconds", 300); - var defaultScope = _configuration.GetValue("DefaultScope") ?? $"{Constants.Scopes.RegisterRead}"; - var scope = clientAssertion.scope ?? defaultScope; - var cnf = HttpContext.GetClientCertificateThumbprint(_configuration); + var expiry = this._configuration.GetValue("AccessTokenExpiryInSeconds", 300); + var defaultScope = this._configuration.GetValue("DefaultScope") ?? $"{Constants.Scopes.RegisterRead}"; + var scope = clientAssertion.Scope ?? defaultScope; + var cnf = this.HttpContext.GetClientCertificateThumbprint(this._configuration); - return Ok(new AccessTokenResponse() + return this.Ok(new AccessTokenResponse() { - AccessToken = await _tokenService.CreateAccessToken(client, expiry, scope, cnf), + AccessToken = await this._tokenService.CreateAccessToken(client, expiry, scope, cnf), ExpiresIn = expiry, Scope = scope, - TokenType = JwtBearerDefaults.AuthenticationScheme + TokenType = JwtBearerDefaults.AuthenticationScheme, }); } - private async Task<(bool isValid, string? error, string? errorDescription, SoftwareProductInfosec? client)> Validate(ClientAssertionRequest clientAssertion, HttpRequest request) + private static (bool IsValid, string? Error, string? ErrorDescription, SoftwareProductInfosec? Client) ValidateBasicParameters(ClientAssertionRequest clientAssertion) { - if (request.ContentType == null || !request.ContentType.Contains("application/x-www-form-urlencoded")) + if (string.IsNullOrEmpty(clientAssertion.Grant_type)) { - return (false, "invalid_request", "Content-Type is not application/x-www-form-urlencoded", null); + return (false, ErrorCodes.Generic.InvalidClient, "grant_type not provided", null); } - // Basic validation. - var basicValidationResult = ValidateBasicParameters(clientAssertion); - if (!basicValidationResult.isValid) + if (string.IsNullOrEmpty(clientAssertion.Client_assertion)) { - return basicValidationResult; + return (false, ErrorCodes.Generic.InvalidClient, "client_assertion not provided", null); } - // Grant type needs to be client_credentials. - if (clientAssertion.grant_type != null && !clientAssertion.grant_type.Equals("client_credentials", StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(clientAssertion.Client_assertion_type)) { - return (false, "unsupported_grant_type", "grant_type must be client_credentials", null); + return (false, ErrorCodes.Generic.InvalidClient, "client_assertion_type not provided", null); } - // Client assertion type needs to be urn:ietf:params:oauth:client-assertion-type:jwt-bearer. - if (clientAssertion.client_assertion_type != null && !clientAssertion.client_assertion_type.Equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer", StringComparison.OrdinalIgnoreCase)) - { - return (false, ErrorCodes.Generic.InvalidClient, "client_assertion_type must be urn:ietf:params:oauth:client-assertion-type:jwt-bearer", null); - } + return (true, null, null, null); + } - // If scope is provided it must include "cdr-register:read" - var scopeValidationResult = ValidateScope(clientAssertion.scope, request); - if (!scopeValidationResult.isValid) + private static (bool IsValid, string? Error, string? ErrorDescription, SoftwareProductInfosec? Client) ValidateScope(string? scope) + { + if (string.IsNullOrEmpty(scope)) { - return scopeValidationResult; + return (false, ErrorCodes.Generic.InvalidClient, "empty scope", null); } - // 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); - if (!tokenValidationResult.isValid) + var scopes = scope.Split(' '); + foreach (var s in scopes) { - return tokenValidationResult; + if (!s.Equals(Constants.Scopes.RegisterRead)) + { + return (false, ErrorCodes.Generic.InvalidClient, "invalid scope", null); + } } - // Validate the client assertion. - var clientAssertionResult = await _tokenService.ValidateClientAssertion(clientAssertion.client_id ?? string.Empty, clientAssertion.client_assertion ?? string.Empty); + return (true, null, null, null); + } - if (!clientAssertionResult.isValid) - { - return (false, ErrorCodes.Generic.InvalidClient, clientAssertionResult.message, null); - } + private static (bool IsValid, string? Error, string? ErrorDescription, SoftwareProductInfosec? Client) ValidateClientAssertionToken(string? clientAssertion) + { + var handler = new JwtSecurityTokenHandler(); - var client = clientAssertionResult.client; - if (client == null) + if (clientAssertion == null || !handler.CanReadToken(clientAssertion)) { - return (false, ErrorCodes.Generic.InvalidClient, "invalid client_id", null); + return (false, ErrorCodes.Generic.InvalidClient, "Invalid client_assertion - token validation error", null); } - if (!IsValidCertificate(client)) + var token = handler.ReadJwtToken(clientAssertion); + if (token == null) { - return (false, ErrorCodes.Generic.InvalidClient, "Client certificate validation failed", null); + return (false, ErrorCodes.Generic.InvalidClient, "Invalid client_assertion - token validation error", null); } - return (true, null, null, client); + return (true, null, null, null); } - private (bool isValid, string? error, string? errorDescription, SoftwareProductInfosec? client) ValidateBasicParameters(ClientAssertionRequest clientAssertion) + private async Task<(bool IsValid, string? Error, string? ErrorDescription, SoftwareProductInfosec? Client)> Validate(ClientAssertionRequest clientAssertion) { - if (string.IsNullOrEmpty(clientAssertion.grant_type)) + // Basic validation. + var basicValidationResult = ValidateBasicParameters(clientAssertion); + if (!basicValidationResult.IsValid) { - return (false, ErrorCodes.Generic.InvalidClient, "grant_type not provided", null); + return basicValidationResult; } - if (string.IsNullOrEmpty(clientAssertion.client_assertion)) + // Grant type needs to be client_credentials. + if (clientAssertion.Grant_type != null && !clientAssertion.Grant_type.Equals("client_credentials", StringComparison.OrdinalIgnoreCase)) { - return (false, ErrorCodes.Generic.InvalidClient, "client_assertion not provided", null); + return (false, "unsupported_grant_type", "grant_type must be client_credentials", null); } - if (string.IsNullOrEmpty(clientAssertion.client_assertion_type)) + // Client assertion type needs to be urn:ietf:params:oauth:client-assertion-type:jwt-bearer. + if (clientAssertion.Client_assertion_type != null && !clientAssertion.Client_assertion_type.Equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer", StringComparison.OrdinalIgnoreCase)) { - return (false, ErrorCodes.Generic.InvalidClient, "client_assertion_type not provided", null); + return (false, ErrorCodes.Generic.InvalidClient, "client_assertion_type must be urn:ietf:params:oauth:client-assertion-type:jwt-bearer", null); } - return (true, null, null, null); - } - - private (bool isValid, string? error, string? errorDescription, SoftwareProductInfosec? client) ValidateScope(string? scope, HttpRequest request) - { - if (scope != null) + // If scope is provided it must include "cdr-register:read" + var scopeValidationResult = ValidateScope(clientAssertion.Scope); + if (!scopeValidationResult.IsValid) { - if (scope.Trim().Length == 0) - { - return (false, ErrorCodes.Generic.InvalidClient, "empty scope", null); - } - - var scopes = scope.Split(' '); - foreach (var s in scopes) - { - if (!s.Equals(Constants.Scopes.RegisterRead)) - { - return (false, ErrorCodes.Generic.InvalidClient, "invalid scope", null); - } - } + return scopeValidationResult; } - else if (request.Form.ContainsKey("scope")) + + // 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); + if (!tokenValidationResult.IsValid) { - return (false, ErrorCodes.Generic.InvalidClient, "empty scope", null); + return tokenValidationResult; } - return (true, null, null, null); - } + // Validate the client assertion. + var clientAssertionResult = await this._tokenService.ValidateClientAssertion(clientAssertion.Client_id ?? string.Empty, clientAssertion.Client_assertion ?? string.Empty); - private (bool isValid, string? error, string? errorDescription, SoftwareProductInfosec? client) ValidateClientAssertionToken(string? clientAssertion) - { - var handler = new JwtSecurityTokenHandler(); + if (!clientAssertionResult.IsValid) + { + return (false, ErrorCodes.Generic.InvalidClient, clientAssertionResult.Message, null); + } - if (clientAssertion == null || !handler.CanReadToken(clientAssertion)) + var client = clientAssertionResult.Client; + if (client == null) { - return (false, ErrorCodes.Generic.InvalidClient, "Invalid client_assertion - token validation error", null); + return (false, ErrorCodes.Generic.InvalidClient, "invalid client_id", null); } - var token = handler.ReadJwtToken(clientAssertion); - if (token == null) + if (!this.IsValidCertificate(client)) { - return (false, ErrorCodes.Generic.InvalidClient, "Invalid client_assertion - token validation error", null); + return (false, ErrorCodes.Generic.InvalidClient, "Client certificate validation failed", null); } - return (true, null, null, null); + return (true, null, null, client); } private bool IsValidCertificate( SoftwareProductInfosec client) { // Get the certificate thumbprint and common name from the headers. - var thumbprint = this.HttpContext.GetClientCertificateThumbprint(_configuration); - var httpHeaderCommonName = this.HttpContext.GetClientCertificateCommonName(_logger, _configuration); + var thumbprint = this.HttpContext.GetClientCertificateThumbprint(this._configuration); + var httpHeaderCommonName = this.HttpContext.GetClientCertificateCommonName(this._logger, this._configuration); if (string.IsNullOrEmpty(thumbprint) || string.IsNullOrEmpty(httpHeaderCommonName)) { - _logger.LogError("Thumbprint {Thumbprint} and/or common name {CommonName} not found.", thumbprint, httpHeaderCommonName); + this._logger.LogError("Thumbprint {Thumbprint} and/or common name {CommonName} not found.", thumbprint, httpHeaderCommonName); return false; } @@ -199,7 +188,7 @@ private bool IsValidCertificate( var matchingCert = certs.Find(c => c.CommonName.GetCommonName().Equals(httpHeaderCommonName, StringComparison.OrdinalIgnoreCase)); - _logger.LogInformation("Matching cert = {MatchingCert}", matchingCert != null); + this._logger.LogInformation("Matching cert = {MatchingCert}", matchingCert != null); return matchingCert != null; } diff --git a/Source/CDR.Register.Infosec/Models/ClientAssertionRequest.cs b/Source/CDR.Register.Infosec/Models/ClientAssertionRequest.cs index eb0c565..47597c3 100644 --- a/Source/CDR.Register.Infosec/Models/ClientAssertionRequest.cs +++ b/Source/CDR.Register.Infosec/Models/ClientAssertionRequest.cs @@ -1,19 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CDR.Register.Infosec.Models { public class ClientAssertionRequest { - public string? grant_type { get; set; } + [JsonPropertyName("grant_type")] + public string? Grant_type { get; set; } - public string? client_id { get; set; } + [JsonPropertyName("client_id")] + public string? Client_id { get; set; } - public string? client_assertion_type { get; set; } + [JsonPropertyName("client_assertion_type")] + public string? Client_assertion_type { get; set; } - [JsonProperty(nameof(client_assertion))] - public string? client_assertion { get; set; } + [JsonPropertyName("client_assertion")] + public string? Client_assertion { get; set; } - [JsonProperty(nameof(scope))] - public string? scope { get; set; } + [JsonPropertyName("scope")] + public string? Scope { get; set; } } } diff --git a/Source/CDR.Register.Infosec/Services/ClientService.cs b/Source/CDR.Register.Infosec/Services/ClientService.cs index 81a1e71..6e9172c 100644 --- a/Source/CDR.Register.Infosec/Services/ClientService.cs +++ b/Source/CDR.Register.Infosec/Services/ClientService.cs @@ -10,7 +10,7 @@ public class ClientService : IClientService public ClientService(IRegisterInfosecRepository infosecRepository) { - _infosecRepository = infosecRepository; + this._infosecRepository = infosecRepository; } public async Task GetClientAsync(string? clientId) @@ -25,7 +25,7 @@ public ClientService(IRegisterInfosecRepository infosecRepository) return null; } - var softwareProduct = await _infosecRepository.GetSoftwareProductAsync(softwareProductId); + var softwareProduct = await this._infosecRepository.GetSoftwareProductAsync(softwareProductId); return softwareProduct; } } diff --git a/Source/CDR.Register.Infosec/Services/ITokenService.cs b/Source/CDR.Register.Infosec/Services/ITokenService.cs index 3875519..0515a5e 100644 --- a/Source/CDR.Register.Infosec/Services/ITokenService.cs +++ b/Source/CDR.Register.Infosec/Services/ITokenService.cs @@ -5,7 +5,7 @@ namespace CDR.Register.Infosec.Interfaces { public interface ITokenService { - Task<(bool isValid, string? message, SoftwareProductInfosec? client)> ValidateClientAssertion(string client_id, string clientAssertion); + Task<(bool IsValid, string? Message, SoftwareProductInfosec? Client)> ValidateClientAssertion(string client_id, string clientAssertion); Task CreateAccessToken( SoftwareProductInfosec client, diff --git a/Source/CDR.Register.Infosec/Services/TokenService.cs b/Source/CDR.Register.Infosec/Services/TokenService.cs index 5e3d9b0..9bda5d2 100644 --- a/Source/CDR.Register.Infosec/Services/TokenService.cs +++ b/Source/CDR.Register.Infosec/Services/TokenService.cs @@ -1,12 +1,12 @@ -using CDR.Register.API.Infrastructure; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; +using CDR.Register.API.Infrastructure; using CDR.Register.Domain.Entities; using CDR.Register.Infosec.Interfaces; using Microsoft.Extensions.Caching.Distributed; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Security.Cryptography.X509Certificates; namespace CDR.Register.Infosec.Services { @@ -25,11 +25,11 @@ public TokenService( IHttpContextAccessor httpContextAccessor, IClientService clientService) { - _logger = logger; - _configuration = configuration; - _cache = cache; - _httpContextAccessor = httpContextAccessor; - _clientService = clientService; + this._logger = logger; + this._configuration = configuration; + this._cache = cache; + this._httpContextAccessor = httpContextAccessor; + this._clientService = clientService; } /// @@ -37,8 +37,8 @@ public TokenService( /// /// 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) + /// Tuple of IsValid, ValidationMessage, Client. + public async Task<(bool IsValid, string? Message, SoftwareProductInfosec? Client)> ValidateClientAssertion(string? client_id, string clientAssertion) { JwtSecurityToken? validatedSecurityToken; SoftwareProductInfosec? client; @@ -62,13 +62,13 @@ public TokenService( return (false, "Invalid client_assertion - 'sub' and 'iss' must be set to the client_id", null); } - client = await _clientService.GetClientAsync(clientId); + client = await this._clientService.GetClientAsync(clientId); if (client == null) { return (false, "invalid client_id", null); } - var tokenValidationParameters = await BuildTokenValidationParameters(client); + var tokenValidationParameters = await this.BuildTokenValidationParameters(client); handler.ValidateToken(clientAssertion, tokenValidationParameters, out var token); @@ -95,82 +95,32 @@ public TokenService( } // Has this jti already been used? - if (IsBlacklisted(validatedSecurityToken.Issuer, validatedSecurityToken.Id)) + if (this.IsBlacklisted(validatedSecurityToken.Issuer, validatedSecurityToken.Id)) { return (false, "Invalid client_assertion - 'jti' in the client assertion token must be unique", null); } } catch (Exception ex) { - _logger.LogError(ex, "Invalid client_assertion - token validation error"); + this._logger.LogError(ex, "Invalid client_assertion - token validation error"); return (false, "Invalid client_assertion - token validation error", null); } // Add the jti into the blacklist so that the same jti cannot be re-used until at least after it has expired. - Blacklist(validatedSecurityToken.Issuer, validatedSecurityToken.Id, validatedSecurityToken.ValidTo.AddMinutes(5)); + this.Blacklist(validatedSecurityToken.Issuer, validatedSecurityToken.Id, validatedSecurityToken.ValidTo.AddMinutes(5)); return (true, null, client); } - private async Task BuildTokenValidationParameters( - SoftwareProductInfosec client, - int clockSkew = 120) - { - var validAudiences = BuildValidAudiences(); - - return new TokenValidationParameters - { - ValidateIssuer = false, - 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)); - - if (!isValid) - { - string errorMessage = $"IDX10214: Audience validation failed. Audiences: '{string.Join(',', audiences)}'. Did not match: '{string.Join(',', validationParameters.ValidAudiences)}'."; - throw new SecurityTokenInvalidAudienceException(errorMessage) - { - InvalidAudience = string.Join(',', audiences) - }; - } - - return isValid; - }, - - RequireSignedTokens = true, - RequireExpirationTime = true, - - ValidateLifetime = true, - ClockSkew = TimeSpan.FromSeconds(clockSkew), - }; - } - - private List BuildValidAudiences() - { - var baseUri = _configuration.GetInfosecBaseUrl(_httpContextAccessor.HttpContext); - var secureBaseUri = _configuration.GetInfosecBaseUrl(_httpContextAccessor.HttpContext, true); - - return new List() - { - baseUri, - $"{secureBaseUri}/connect/token" - }; - } - public async Task CreateAccessToken( SoftwareProductInfosec client, int expiryInSeconds, string scope, string cnf) { - var cert = await Task.Run(() => new X509Certificate2(_configuration.GetValue("SigningCertificate:Path") ?? string.Empty, _configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable)); + var cert = await Task.Run(() => new X509Certificate2(this._configuration.GetValue("SigningCertificate:Path") ?? string.Empty, this._configuration.GetValue("SigningCertificate:Password"), X509KeyStorageFlags.Exportable)); var signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); - var issuer = _configuration.GetInfosecBaseUrl(_httpContextAccessor.HttpContext); + var issuer = this._configuration.GetInfosecBaseUrl(this._httpContextAccessor.HttpContext); List claims = [new Claim("client_id", client.Id), new Claim("jti", Guid.NewGuid().ToString()), @@ -180,7 +130,7 @@ public async Task CreateAccessToken( "cnf", JsonConvert.SerializeObject(new Dictionary { - { "x5t#S256", cnf } + { "x5t#S256", cnf }, }), JsonClaimValueTypes.Json)); @@ -208,12 +158,12 @@ public async Task> GetClientKeys(SoftwareProductInfosec clien var trustedKeys = new List(); try { - var jwks = await GetClientJwks(client); + var jwks = await this.GetClientJwks(client); trustedKeys.AddRange(jwks); } catch (Exception ex) { - _logger.LogError(ex, "Error retrieving jwks from client {ClientId}", client.Id); + this._logger.LogError(ex, "Error retrieving jwks from client {ClientId}", client.Id); return trustedKeys; } @@ -223,10 +173,10 @@ public async Task> GetClientKeys(SoftwareProductInfosec clien public async Task> GetClientJwks(SoftwareProductInfosec client) { var handler = new HttpClientHandler(); - handler.SetServerCertificateValidation(_configuration); + handler.SetServerCertificateValidation(this._configuration); var httpClient = new HttpClient(handler); - var passUserAgent = _configuration.GetValue("PassUserAgent"); // allows CTS to attach a header for request filtering + var passUserAgent = this._configuration.GetValue("PassUserAgent"); // allows CTS to attach a header for request filtering if (passUserAgent) { httpClient.DefaultRequestHeaders.Add("User-Agent", "mock-register"); @@ -237,14 +187,14 @@ public async Task> GetClientJwks(SoftwareProductInfosec client if (!httpResponse.IsSuccessStatusCode) { - _logger.LogError("{Method}: {JwksUri} returned {StatusCode}", nameof(GetClientJwks), client.JwksUri, httpResponse.StatusCode); + this._logger.LogError("{Method}: {JwksUri} returned {StatusCode}", nameof(this.GetClientJwks), client.JwksUri, httpResponse.StatusCode); return new List(); } var keys = JsonConvert.DeserializeObject(responseContent); if (keys == null || keys.Keys == null || !keys.Keys.Any()) { - _logger.LogError("{Method}: No keys found at {JwksUri}", nameof(GetClientJwks), client.JwksUri); + this._logger.LogError("{Method}: No keys found at {JwksUri}", nameof(this.GetClientJwks), client.JwksUri); return new List(); } @@ -253,19 +203,69 @@ public async Task> GetClientJwks(SoftwareProductInfosec client public bool IsBlacklisted(string clientId, string id) { - return !string.IsNullOrEmpty(_cache.GetString($"{clientId}::{id}")); + return !string.IsNullOrEmpty(this._cache.GetString($"{clientId}::{id}")); } public void Blacklist(string clientId, string id, DateTime expiresOn) { try { - _cache.SetString($"{clientId}::{id}", id, new DistributedCacheEntryOptions() { AbsoluteExpiration = new DateTimeOffset(expiresOn) }); + this._cache.SetString($"{clientId}::{id}", id, new DistributedCacheEntryOptions() { AbsoluteExpiration = new DateTimeOffset(expiresOn) }); } catch (Exception ex) { - _logger.LogError(ex, "Exception while adding security token to cache"); + this._logger.LogError(ex, "Exception while adding security token to cache"); } } + + private async Task BuildTokenValidationParameters( + SoftwareProductInfosec client, + int clockSkew = 120) + { + var validAudiences = this.BuildValidAudiences(); + + return new TokenValidationParameters + { + ValidateIssuer = false, + IssuerSigningKeys = await this.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)); + + if (!isValid) + { + string errorMessage = $"IDX10214: Audience validation failed. Audiences: '{string.Join(',', audiences)}'. Did not match: '{string.Join(',', validationParameters.ValidAudiences)}'."; + throw new SecurityTokenInvalidAudienceException(errorMessage) + { + InvalidAudience = string.Join(',', audiences), + }; + } + + return isValid; + }, + + RequireSignedTokens = true, + RequireExpirationTime = true, + + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(clockSkew), + }; + } + + private List BuildValidAudiences() + { + var baseUri = this._configuration.GetInfosecBaseUrl(this._httpContextAccessor.HttpContext); + var secureBaseUri = this._configuration.GetInfosecBaseUrl(this._httpContextAccessor.HttpContext, true); + + return new List() + { + baseUri, + $"{secureBaseUri}/connect/token", + }; + } } } diff --git a/Source/CDR.Register.Infosec/Startup.cs b/Source/CDR.Register.Infosec/Startup.cs index f12ba5a..d1e6da0 100644 --- a/Source/CDR.Register.Infosec/Startup.cs +++ b/Source/CDR.Register.Infosec/Startup.cs @@ -19,7 +19,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -28,7 +28,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); - services.AddRegisterInfosec(Configuration); + services.AddRegisterInfosec(this.Configuration); services.AddControllers() .ConfigureApiBehaviorOptions(options => @@ -40,25 +40,25 @@ public void ConfigureServices(IServiceCollection services) options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; }); - if (Configuration.GetSection("SerilogRequestResponseLogger") != null) + if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) { Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); } // if the distributed cache connection string has been set then use it, otherwise fall back to in-memory caching. - if (UseDistributedCache()) + if (this.UseDistributedCache()) { services.AddStackExchangeRedisCache(options => { - options.Configuration = Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache); + options.Configuration = this.Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache); options.InstanceName = "register-cache-"; }); services.AddDataProtection() .SetApplicationName("reg-infosec") .PersistKeysToStackExchangeRedis( - StackExchange.Redis.ConnectionMultiplexer.Connect(Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache) ?? string.Empty), + StackExchange.Redis.ConnectionMultiplexer.Connect(this.Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache) ?? string.Empty), "register-cache-dp-keys"); } else @@ -76,7 +76,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); }); - app.UseBasePathOrExpression(Configuration); + app.UseBasePathOrExpression(this.Configuration); app.UseSerilogRequestLogging(); app.UseMiddleware(); @@ -96,7 +96,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) private bool UseDistributedCache() { - var cacheConnectionString = Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache); + var cacheConnectionString = this.Configuration.GetConnectionString(Constants.ConnectionStringNames.Cache); return !string.IsNullOrEmpty(cacheConnectionString); } } 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 a229f63..a7ce3ae 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 @@ -1,15 +1,15 @@ -using CDR.Register.Repository.Entities; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -27,71 +27,6 @@ public US27560_GetDataRecipients_MultiIndustry_Tests(ITestOutputHelper outputHel { } - // Get expected data recipients - private static string GetExpectedDataRecipients(string url) - { - try - { - using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - - var expectedDataRecipients = new - { - data = dbContext.Participations.AsNoTracking() - .Include(participation => participation.Status) - .Include(participation => participation.Industry) - .Include(participation => participation.LegalEntity) - .Include(participation => participation.Brands) - .ThenInclude(brand => brand.BrandStatus) - .Include(participation => participation.Brands) - .ThenInclude(brand => brand.SoftwareProducts) - .ThenInclude(softwareProduct => softwareProduct.Status) - .Where(participation => participation.ParticipationTypeId == ParticipationTypes.Dr) - .OrderBy(participation => participation.LegalEntityId) - .Select(participation => new - { - legalEntityId = participation.LegalEntityId, - legalEntityName = participation.LegalEntity.LegalEntityName, - accreditationNumber = participation.LegalEntity.AccreditationNumber, - accreditationLevel = participation.LegalEntity.AccreditationLevel.AccreditationLevelCode.ToUpper(), // DF: accreditation level should be uppercase. - logoUri = participation.LegalEntity.LogoUri, - dataRecipientBrands = participation.Brands.OrderBy(b => b.BrandId).Select(brand => new - { - dataRecipientBrandId = brand.BrandId, - brandName = brand.BrandName, - logoUri = brand.LogoUri, - softwareProducts = brand.SoftwareProducts.OrderBy(sp => sp.SoftwareProductId.ToString()).Select(softwareProduct => new - { - softwareProductId = softwareProduct.SoftwareProductId, - softwareProductName = softwareProduct.SoftwareProductName, - softwareProductDescription = softwareProduct.SoftwareProductDescription, - logoUri = softwareProduct.LogoUri, - status = softwareProduct.Status.SoftwareProductStatusCode - }), - status = brand.BrandStatus.BrandStatusCode, - }), - status = participation.Status.ParticipationStatusCode, - 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 - { - self = url - }, - meta = new object() - }; - - string result = JsonConvert.SerializeObject(expectedDataRecipients); - - return result; - } - catch (Exception ex) - { - throw new Exception($"Error getting expected data recipients - {ex.Message}"); - } - } - [Theory] [InlineData(3, "all")] [InlineData(3, "banking")] @@ -108,7 +43,7 @@ public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_DataRecipients(int xv, { HttpMethod = HttpMethod.Get, URL = url, - XV = xv.ToString() + XV = xv.ToString(), }.SendAsync(); // Assert @@ -145,7 +80,7 @@ public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_DataRecipients { HttpMethod = HttpMethod.Get, URL = url, - XV = xv.ToString() + XV = xv.ToString(), }.SendAsync(); // Assert @@ -236,23 +171,23 @@ 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", "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("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("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) { @@ -262,7 +197,7 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? minXv, strin HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients", XV = xv, - XMinV = minXv + XMinV = minXv, }.SendAsync(); // Assert @@ -286,5 +221,70 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? minXv, strin } } } + + // Get expected data recipients + private static string GetExpectedDataRecipients(string url) + { + try + { + using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); + + var expectedDataRecipients = new + { + data = dbContext.Participations.AsNoTracking() + .Include(participation => participation.Status) + .Include(participation => participation.Industry) + .Include(participation => participation.LegalEntity) + .Include(participation => participation.Brands) + .ThenInclude(brand => brand.BrandStatus) + .Include(participation => participation.Brands) + .ThenInclude(brand => brand.SoftwareProducts) + .ThenInclude(softwareProduct => softwareProduct.Status) + .Where(participation => participation.ParticipationTypeId == ParticipationTypes.Dr) + .OrderBy(participation => participation.LegalEntityId) + .Select(participation => new + { + legalEntityId = participation.LegalEntityId, + legalEntityName = participation.LegalEntity.LegalEntityName, + accreditationNumber = participation.LegalEntity.AccreditationNumber, + accreditationLevel = participation.LegalEntity.AccreditationLevel.AccreditationLevelCode.ToUpper(), // DF: accreditation level should be uppercase. + logoUri = participation.LegalEntity.LogoUri, + dataRecipientBrands = participation.Brands.OrderBy(b => b.BrandId).Select(brand => new + { + dataRecipientBrandId = brand.BrandId, + brandName = brand.BrandName, + logoUri = brand.LogoUri, + softwareProducts = brand.SoftwareProducts.OrderBy(sp => sp.SoftwareProductId.ToString()).Select(softwareProduct => new + { + softwareProductId = softwareProduct.SoftwareProductId, + softwareProductName = softwareProduct.SoftwareProductName, + softwareProductDescription = softwareProduct.SoftwareProductDescription, + logoUri = softwareProduct.LogoUri, + status = softwareProduct.Status.SoftwareProductStatusCode, + }), + status = brand.BrandStatus.BrandStatusCode, + }), + status = participation.Status.ParticipationStatusCode, + 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 + { + self = url, + }, + meta = new object(), + }; + + string result = JsonConvert.SerializeObject(expectedDataRecipients); + + return result; + } + catch (Exception ex) + { + throw new Exception($"Error getting expected data recipients - {ex.Message}"); + } + } } } 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 0fee2aa..efb6a88 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,16 +1,16 @@ -using CDR.Register.IntegrationTests.Infrastructure; -using CDR.Register.Repository.Entities; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.IntegrationTests.Infrastructure; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -23,230 +23,20 @@ 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) - { - } - - // Participation/Brand/SoftwareProduct Ids - 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"; - private static string GetExpectedResponse(string baseUrl, string selfUrl, DateTime? updatedSince, int? requestedPage, int? requestedPageSize, string? industry = null) + public US27562_GetDataHolderBrands_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) { - static string Link(string baseUrl, DateTime? updatedSince, int? page = null, int? pageSize = null) - { - var query = new KeyValuePairBuilder(); - - if (updatedSince != null) - { - query.Add("updated-since", ((DateTime)updatedSince).ToString("yyyy-MM-ddTHH\\%3Amm\\%3Ass.fffffffZ")); - } - - if (page != null) - { - query.Add("page", page.Value); - } - - if (pageSize != null) - { - query.Add("page-size", pageSize.Value); - } - - return query.Count == 0 ? - baseUrl : - $"{baseUrl}?{query.Value}"; - } - - if (industry == "all") - { - industry = null; // treat "all" same as no industry - } - - var page = requestedPage ?? 1; - var pageSize = requestedPageSize ?? 25; - - using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - - var allData = dbContext.Brands.AsNoTracking() - .Include(brand => brand.Endpoint) - .Include(brand => brand.BrandStatus) - .Include(brand => brand.AuthDetails) - .ThenInclude(authDetail => authDetail.RegisterUType) - .Include(brand => brand.Participation.LegalEntity.OrganisationType) - .Include(brand => brand.Participation.Industry) - .Where(brand => - brand.Participation.ParticipationTypeId == ParticipationTypes.Dh && - (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); - - var totalRecords = allData.Count(); - var totalPages = (int)Math.Ceiling((double)totalRecords / pageSize); - const int MINPAGE = 1; - if (page < MINPAGE) - { - throw new Exception($"Page {page} out of range. Min Page is {MINPAGE}"); - } - - var maxPage = ((totalRecords - 1) / pageSize) + 1; - if (page > maxPage) - { - throw new Exception($"Page {page} out of range. Max Page is {maxPage} (Records={totalRecords}, PageSize={pageSize})"); - } - - var data = allData - .OrderBy(brand => brand.BrandName).ThenBy(brand => brand.BrandId) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .Select(brand => new - { - dataHolderBrandId = brand.BrandId, - brandName = brand.BrandName, - industries = new string[] { brand.Participation.Industry.IndustryTypeCode.ToLower() }, - logoUri = brand.LogoUri, - legalEntity = new - { - legalEntityId = brand.Participation.LegalEntity.LegalEntityId, - legalEntityName = brand.Participation.LegalEntity.LegalEntityName, - logoUri = brand.Participation.LegalEntity.LogoUri, - registrationNumber = brand.Participation.LegalEntity.RegistrationNumber, - registrationDate = brand.Participation.LegalEntity.RegistrationDate == null ? null : brand.Participation.LegalEntity.RegistrationDate.Value.ToString("yyyy-MM-dd"), - registeredCountry = brand.Participation.LegalEntity.RegisteredCountry, - abn = brand.Participation.LegalEntity.Abn, - acn = brand.Participation.LegalEntity.Acn, - arbn = brand.Participation.LegalEntity.Arbn, - anzsicDivision = brand.Participation.LegalEntity.AnzsicDivision, - organisationType = brand.Participation.LegalEntity.OrganisationType.OrganisationTypeCode, - status = brand.Participation.Status.ParticipationStatusCode.ToUpper() - }, - status = brand.BrandStatus.BrandStatusCode, - endpointDetail = new - { - version = brand.Endpoint.Version, - publicBaseUri = brand.Endpoint.PublicBaseUri, - resourceBaseUri = brand.Endpoint.ResourceBaseUri, - infosecBaseUri = brand.Endpoint.InfosecBaseUri, - extensionBaseUri = brand.Endpoint.ExtensionBaseUri, - websiteUri = brand.Endpoint.WebsiteUri - }, - authDetails = brand.AuthDetails.Select(authDetails => new - { - registerUType = authDetails.RegisterUType.RegisterUTypeCode, - jwksEndpoint = authDetails.JwksEndpoint - }), - lastUpdated = brand.LastUpdated.ToString("yyyy-MM-ddTHH:mm:ssZ") - }) - .ToList(); - - var expectedResponse = new - { - data, - links = new - { - first = totalPages == 0 ? - null : - Link(baseUrl, updatedSince, 1, pageSize), - last = totalPages == 0 ? - null : - Link(baseUrl, updatedSince, totalPages, pageSize), - next = totalPages == 0 || page == totalPages ? - null : - Link(baseUrl, updatedSince, page + 1, pageSize), - prev = totalPages == 0 || page == 1 ? - null : - Link(baseUrl, updatedSince, page - 1, pageSize), - self = selfUrl, - }, - meta = new - { - totalRecords, - totalPages - } - }; - - return JsonConvert.SerializeObject(expectedResponse); } - private static async Task Test_AC01_AC02_AC03_AC04_AC05_AC06( - DateTime? updatedSince, - int? queryPage, - int? queryPageSize, - string? industry = null, - HttpStatusCode expectedStatusCode = HttpStatusCode.OK) - { - static string GetUrl(string baseUrl, DateTime? updatedSince, int? queryPage, int? queryPageSize) - { - // Build query - var query = new KeyValuePairBuilder(); - - if (updatedSince != null) - { - query.Add("updated-since", ((DateTime)updatedSince).ToString("yyyy-MM-ddTHH:mm:ssZ")); - } - - if (queryPage != null) - { - query.Add("page", queryPage.Value); - } - - if (queryPageSize != null) - { - query.Add("page-size", queryPageSize.Value); - } - - return query.Count > 0 ? - $"{baseUrl}?{query.Value}" : - baseUrl; - } - - // Arrange - var baseUrl = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands"; - var url = GetUrl(baseUrl, updatedSince, queryPage, queryPageSize); - - var expectedResponse = GetExpectedResponse(baseUrl, url, updatedSince, queryPage, queryPageSize, industry); - - var accessToken = await new Infrastructure.AccessToken - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD - }.GetAsync(); - - var api = new Infrastructure.Api - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD, - HttpMethod = HttpMethod.Get, - URL = url, - AccessToken = accessToken, - XV = "2" - }; - - // Act - var response = await api.SendAsync(); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - if (response.StatusCode == HttpStatusCode.OK) - { - // Assert - Check content type - Assert_HasContentType_ApplicationJson(response.Content); + private delegate void BeforeTestAC181920(); - // Assert - Check XV - Assert_HasHeader(api.XV, response.Headers, "x-v"); + private delegate void AfterTestAC181920Request(); - // Assert - Check json - await Assert_HasContent_Json(expectedResponse, response.Content); - } - } - } + // Participation/Brand/SoftwareProduct Ids + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup [Theory] [InlineData(null, HttpStatusCode.NotFound)] @@ -283,7 +73,7 @@ public async Task AC01_CTS_URL_Get_WithNoQueryString_ShouldRespondWith_200OK_Fir Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }.GetAsync(addCertificateToRequest: false); var api = new Infrastructure.Api @@ -295,7 +85,7 @@ public async Task AC01_CTS_URL_Get_WithNoQueryString_ShouldRespondWith_200OK_Fir AccessToken = accessToken, XV = "2", CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }; // Act @@ -381,7 +171,7 @@ public async Task AC07_Get_WithUpdatedSinceInvalidDate_ShouldRespondWith_400BadR var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); var api = new Infrastructure.Api @@ -391,7 +181,7 @@ public async Task AC07_Get_WithUpdatedSinceInvalidDate_ShouldRespondWith_400BadR HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands?updated-since={updatedSince}", AccessToken = accessToken, - XV = "2" + XV = "2", }; // Act @@ -425,29 +215,6 @@ 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 - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD, - HttpMethod = HttpMethod.Get, - URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", - AccessToken = accessToken, - XV = "2" - }; - - // Act - var response = await api.SendAsync(); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - } - } - [Theory] [InlineData(null, HttpStatusCode.NotFound)] // DF: this will be a 404 now. [InlineData("banking")] @@ -486,7 +253,7 @@ public async Task AC11_Get_WithExpiredAccessToken_ShouldRespondWith_401Unauthori HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", AccessToken = accessToken, - XV = "2" + XV = "2", }; // Act @@ -511,7 +278,7 @@ public async Task AC12_Get_WithDifferentHolderOfKey_ShouldRespondWith_401Unautho var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); var api = new Infrastructure.Api @@ -521,7 +288,7 @@ public async Task AC12_Get_WithDifferentHolderOfKey_ShouldRespondWith_401Unautho HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", AccessToken = accessToken, - XV = "2" + XV = "2", }; // Act @@ -535,46 +302,6 @@ public async Task AC12_Get_WithDifferentHolderOfKey_ShouldRespondWith_401Unautho } } - private static async Task Test_AC13_AC14_AC15_AC16(string queryString, HttpStatusCode expectedStatusCode, string expectedContent, string? industry) - { - // Arrange - var accessToken = await new Infrastructure.AccessToken - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD - }.GetAsync(); - - var api = new Infrastructure.Api - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD, - HttpMethod = HttpMethod.Get, - URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands?{queryString}", - AccessToken = accessToken, - XV = "2" - }; - - // Act - var response = await api.SendAsync(); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - // Assert - Check error response - if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) - { - // Assert - Check content type - Assert_HasContentType_ApplicationJson(response.Content); - - // Assert - Check error response - await Assert_HasContent_Json(expectedContent, response.Content); - } - } - } - [Theory] [InlineData("", HttpStatusCode.NotFound, null)] // "" is effectively not providing a page-size, so it will default to 25. [InlineData("0", HttpStatusCode.NotFound, null)] @@ -598,9 +325,7 @@ 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, + var expectedContent = @" { ""errors"": [ @@ -610,7 +335,11 @@ await Test_AC13_AC14_AC15_AC16( ""detail"": ""Page size not a positive Integer"", } ] - }", + }"; + await Test_AC13_AC14_AC15_AC16( + $"page-size={pageSize}", + expectedStatusCode, + expectedContent, industry); } @@ -637,9 +366,7 @@ await Test_AC13_AC14_AC15_AC16( [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, + var expectedContent = @" { ""errors"": [ @@ -649,7 +376,11 @@ await Test_AC13_AC14_AC15_AC16( ""detail"": ""Page not a positive integer"", } ] - }", + }"; + await Test_AC13_AC14_AC15_AC16( + $"page={page}", + expectedStatusCode, + expectedContent, industry); } @@ -660,9 +391,7 @@ await Test_AC13_AC14_AC15_AC16( [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, + var expectedContent = @" { ""errors"": [ @@ -672,7 +401,11 @@ await Test_AC13_AC14_AC15_AC16( ""detail"": ""Page is out of range"", } ] - }", + }"; + await Test_AC13_AC14_AC15_AC16( + $"page={page}", + expectedStatusCode, + expectedContent, industry); } @@ -683,9 +416,7 @@ await Test_AC13_AC14_AC15_AC16( [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, + var expectedContent = @" { ""errors"": [ @@ -695,75 +426,14 @@ await Test_AC13_AC14_AC15_AC16( ""detail"": ""Page size too large"", } ] - }", + }"; + await Test_AC13_AC14_AC15_AC16( + $"page-size={pageSize}", + expectedStatusCode, + expectedContent, industry); } - private delegate void BeforeTestAC181920(); - - private delegate void AfterTestAC181920Request(); - - private static async Task Test_AC17_AC18_AC19( - HttpStatusCode expectedStatusCode, - BeforeTestAC181920? beforeRequest, - AfterTestAC181920Request? afterRequest, - string? industry) - { - var accessToken = await new Infrastructure.AccessToken - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD - }.GetAsync(); - - var api = new Infrastructure.Api - { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD, - HttpMethod = HttpMethod.Get, - URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", - AccessToken = accessToken, - XV = "2" - }; - - beforeRequest?.Invoke(); - try - { - // Act - var response = await api.SendAsync(); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - // Assert - Check error response - if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) - { - // Assert - Check content type - Assert_HasContentType_ApplicationJson(response.Content); - - // Assert - Check error response - var expectedContent = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", - ""title"": ""ADR Status Is Not Active"", - ""detail"": """", - } - ] - }"; - await Assert_HasContent_Json(expectedContent, response.Content); - } - } - } - finally - { - afterRequest?.Invoke(); - } - } - [Theory] [InlineData(1, HttpStatusCode.NotFound, null)] // Active [InlineData(2, HttpStatusCode.NotFound, null)] // Removed @@ -864,7 +534,7 @@ public async Task ACX20_Get_WithIfNoneMatchKnownETAG_ShouldRespondWith_304NotMod var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); // Arrange - Get brands and save the ETag @@ -910,7 +580,7 @@ public async Task ACX01_Get_WithInvalidIndustry_ShouldRespondWith_400BadRequest( var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); var api = new Infrastructure.Api @@ -920,7 +590,7 @@ public async Task ACX01_Get_WithInvalidIndustry_ShouldRespondWith_400BadRequest( HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", AccessToken = accessToken, - XV = "2" + XV = "2", }; // Act @@ -966,7 +636,7 @@ public async Task ACX02_Get_WithScope_ShouldRespondWith_200OK(string? industry, { CertificateFilename = CERTIFICATE_FILENAME, CertificatePassword = CERTIFICATE_PASSWORD, - Scope = scope + Scope = scope, }.GetAsync(); var api = new Infrastructure.Api @@ -991,33 +661,33 @@ 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 + [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("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") { @@ -1036,7 +706,7 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", AccessToken = accessToken, XV = xv, - XMinV = minXv + XMinV = minXv, }; // Act @@ -1063,5 +733,343 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string } } } + + private static async Task Test_AC01_AC02_AC03_AC04_AC05_AC06( + DateTime? updatedSince, + int? queryPage, + int? queryPageSize, + string? industry = null, + HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + { + static string GetUrl(string baseUrl, DateTime? updatedSince, int? queryPage, int? queryPageSize) + { + // Build query + var query = new KeyValuePairBuilder(); + + if (updatedSince != null) + { + query.Add("updated-since", ((DateTime)updatedSince).ToString("yyyy-MM-ddTHH:mm:ssZ")); + } + + if (queryPage != null) + { + query.Add("page", queryPage.Value); + } + + if (queryPageSize != null) + { + query.Add("page-size", queryPageSize.Value); + } + + return query.Count > 0 ? + $"{baseUrl}?{query.Value}" : + baseUrl; + } + + // Arrange + var baseUrl = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands"; + var url = GetUrl(baseUrl, updatedSince, queryPage, queryPageSize); + + var expectedResponse = GetExpectedResponse(baseUrl, url, updatedSince, queryPage, queryPageSize, industry); + + var accessToken = await new Infrastructure.AccessToken + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + }.GetAsync(); + + var api = new Infrastructure.Api + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + HttpMethod = HttpMethod.Get, + URL = url, + AccessToken = accessToken, + XV = "2", + }; + + // Act + var response = await api.SendAsync(); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + if (response.StatusCode == HttpStatusCode.OK) + { + // Assert - Check content type + Assert_HasContentType_ApplicationJson(response.Content); + + // Assert - Check XV + Assert_HasHeader(api.XV, response.Headers, "x-v"); + + // Assert - Check json + await Assert_HasContent_Json(expectedResponse, response.Content); + } + } + } + + private static string GetExpectedResponse(string baseUrl, string selfUrl, DateTime? updatedSince, int? requestedPage, int? requestedPageSize, string? industry = null) + { + static string Link(string baseUrl, DateTime? updatedSince, int? page = null, int? pageSize = null) + { + var query = new KeyValuePairBuilder(); + + if (updatedSince != null) + { + query.Add("updated-since", ((DateTime)updatedSince).ToString("yyyy-MM-ddTHH\\%3Amm\\%3Ass.fffffffZ")); + } + + if (page != null) + { + query.Add("page", page.Value); + } + + if (pageSize != null) + { + query.Add("page-size", pageSize.Value); + } + + return query.Count == 0 ? + baseUrl : + $"{baseUrl}?{query.Value}"; + } + + if (industry == "all") + { + industry = null; // treat "all" same as no industry + } + + var page = requestedPage ?? 1; + var pageSize = requestedPageSize ?? 25; + + using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); + + var allData = dbContext.Brands.AsNoTracking() + .Include(brand => brand.Endpoint) + .Include(brand => brand.BrandStatus) + .Include(brand => brand.AuthDetails) + .ThenInclude(authDetail => authDetail.RegisterUType) + .Include(brand => brand.Participation.LegalEntity.OrganisationType) + .Include(brand => brand.Participation.Industry) + .Where(brand => + brand.Participation.ParticipationTypeId == ParticipationTypes.Dh && + (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); + + var totalRecords = allData.Count(); + var totalPages = (int)Math.Ceiling((double)totalRecords / pageSize); + const int MINPAGE = 1; + if (page < MINPAGE) + { + throw new Exception($"Page {page} out of range. Min Page is {MINPAGE}"); + } + + var maxPage = ((totalRecords - 1) / pageSize) + 1; + if (page > maxPage) + { + throw new Exception($"Page {page} out of range. Max Page is {maxPage} (Records={totalRecords}, PageSize={pageSize})"); + } + + var data = allData + .OrderBy(brand => brand.BrandName).ThenBy(brand => brand.BrandId) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(brand => new + { + dataHolderBrandId = brand.BrandId, + brandName = brand.BrandName, + industries = new string[] { brand.Participation.Industry.IndustryTypeCode.ToLower() }, + logoUri = brand.LogoUri, + legalEntity = new + { + legalEntityId = brand.Participation.LegalEntity.LegalEntityId, + legalEntityName = brand.Participation.LegalEntity.LegalEntityName, + logoUri = brand.Participation.LegalEntity.LogoUri, + registrationNumber = brand.Participation.LegalEntity.RegistrationNumber, + registrationDate = brand.Participation.LegalEntity.RegistrationDate == null ? null : brand.Participation.LegalEntity.RegistrationDate.Value.ToString("yyyy-MM-dd"), + registeredCountry = brand.Participation.LegalEntity.RegisteredCountry, + abn = brand.Participation.LegalEntity.Abn, + acn = brand.Participation.LegalEntity.Acn, + arbn = brand.Participation.LegalEntity.Arbn, + anzsicDivision = brand.Participation.LegalEntity.AnzsicDivision, + organisationType = brand.Participation.LegalEntity.OrganisationType.OrganisationTypeCode, + status = brand.Participation.Status.ParticipationStatusCode.ToUpper(), + }, + status = brand.BrandStatus.BrandStatusCode, + endpointDetail = new + { + version = brand.Endpoint.Version, + publicBaseUri = brand.Endpoint.PublicBaseUri, + resourceBaseUri = brand.Endpoint.ResourceBaseUri, + infosecBaseUri = brand.Endpoint.InfosecBaseUri, + extensionBaseUri = brand.Endpoint.ExtensionBaseUri, + websiteUri = brand.Endpoint.WebsiteUri, + }, + authDetails = brand.AuthDetails.Select(authDetails => new + { + registerUType = authDetails.RegisterUType.RegisterUTypeCode, + jwksEndpoint = authDetails.JwksEndpoint, + }), + lastUpdated = brand.LastUpdated.ToString("yyyy-MM-ddTHH:mm:ssZ"), + }) + .ToList(); + + var expectedResponse = new + { + data, + links = new + { + first = totalPages == 0 ? + null : + Link(baseUrl, updatedSince, 1, pageSize), + last = totalPages == 0 ? + null : + Link(baseUrl, updatedSince, totalPages, pageSize), + next = totalPages == 0 || page == totalPages ? + null : + Link(baseUrl, updatedSince, page + 1, pageSize), + prev = totalPages == 0 || page == 1 ? + null : + Link(baseUrl, updatedSince, page - 1, pageSize), + self = selfUrl, + }, + meta = new + { + totalRecords, + totalPages, + }, + }; + + return JsonConvert.SerializeObject(expectedResponse); + } + + private static async Task Test_AC09_AC10(string? accessToken, string? industry, HttpStatusCode expectedStatusCode = HttpStatusCode.Unauthorized) + { + var api = new Infrastructure.Api + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + HttpMethod = HttpMethod.Get, + URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", + AccessToken = accessToken, + XV = "2", + }; + + // Act + var response = await api.SendAsync(); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + } + } + + private static async Task Test_AC13_AC14_AC15_AC16(string queryString, HttpStatusCode expectedStatusCode, string expectedContent, string? industry) + { + // Arrange + var accessToken = await new Infrastructure.AccessToken + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + }.GetAsync(); + + var api = new Infrastructure.Api + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + HttpMethod = HttpMethod.Get, + URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands?{queryString}", + AccessToken = accessToken, + XV = "2", + }; + + // Act + var response = await api.SendAsync(); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + // Assert - Check error response + if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) + { + // Assert - Check content type + Assert_HasContentType_ApplicationJson(response.Content); + + // Assert - Check error response + await Assert_HasContent_Json(expectedContent, response.Content); + } + } + } + + private static async Task Test_AC17_AC18_AC19( + HttpStatusCode expectedStatusCode, + BeforeTestAC181920? beforeRequest, + AfterTestAC181920Request? afterRequest, + string? industry) + { + var accessToken = await new Infrastructure.AccessToken + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + }.GetAsync(); + + var api = new Infrastructure.Api + { + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + HttpMethod = HttpMethod.Get, + URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-holders/brands", + AccessToken = accessToken, + XV = "2", + }; + + beforeRequest?.Invoke(); + try + { + // Act + var response = await api.SendAsync(); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + // Assert - Check error response + if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NotFound) + { + // Assert - Check content type + Assert_HasContentType_ApplicationJson(response.Content); + + // Assert - Check error response + var expectedContent = @" + { + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", + ""title"": ""ADR Status Is Not Active"", + ""detail"": """", + } + ] + }"; + await Assert_HasContent_Json(expectedContent, response.Content); + } + } + } + finally + { + afterRequest?.Invoke(); + } + } } } diff --git a/Source/CDR.Register.IntegrationTests/API/SSA/AccessTokenType.cs b/Source/CDR.Register.IntegrationTests/API/SSA/AccessTokenType.cs new file mode 100644 index 0000000..12cc7ad --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/API/SSA/AccessTokenType.cs @@ -0,0 +1,15 @@ +#nullable enable + +namespace CDR.Register.IntegrationTests.API.SSA +{ +public partial class US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests + { + 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 + } + } +} 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 4e61bc1..6801377 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,16 +1,16 @@ -using CDR.Register.IntegrationTests.Extensions; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.IntegrationTests.Extensions; using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; -using System; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -21,134 +21,33 @@ namespace CDR.Register.IntegrationTests.API.SSA /// /// Integration tests for GetSoftwareStatementAssertion. /// - public class US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests : BaseTest + public partial class US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests : BaseTest { - public US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) - : base(outputHelper, testFixture) - { - } - - // Participation/Brand/SoftwareProduct Ids - 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"; - private enum AccessTokenType + private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" + { + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", + ""title"": ""ADR Status Is Not Active"", + ""detail"": """", + } + ] + }"; + + public US27564_GetSoftwareStatementAssertion_MultiIndustry_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) { - 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 } 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, - string certificatePassword, - HttpStatusCode expectedStatusCode, - AccessTokenType accessTokenType = AccessTokenType.ValidAccessToken, - BeforeSSARequest? beforeRequest = null, - AfterSSARequest? afterRequest = null, - string? x_v = "3", - string? x_min_v = null, - string? expectedContent = null, - string? getAccessTokenCertificateFilename = null, - string? getAccessTokenCertificatePassword = null, - string? expectedXV = null, - string? industry = "all", - string brandId = BRANDID, - string softwareProductId = SOFTWAREPRODUCTID) - { - // Arrange - string url = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-recipients/brands/{brandId}/software-products/{softwareProductId}/ssa"; - - var accessToken = await GetAccessToken( - accessTokenType, - getAccessTokenCertificateFilename, - getAccessTokenCertificatePassword, - certificateFilename, - certificatePassword); - - var api = new Infrastructure.Api - { - CertificateFilename = certificateFilename, - CertificatePassword = certificatePassword, - HttpMethod = HttpMethod.Get, - URL = url, - AccessToken = accessToken, - XV = x_v, - XMinV = x_min_v - }; - - beforeRequest?.Invoke(); - try - { - // Act - var response = await api.SendAsync(); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - if (response.StatusCode == HttpStatusCode.OK) - { - // Assert - Check XV - Assert_HasHeader(expectedXV ?? x_v, response.Headers, "x-v"); - } - - // Assert - Check expected content - if (expectedContent != null) - { - await Assert_HasContent_Json(expectedContent, response.Content); - } - } - } - finally - { - afterRequest?.Invoke(); - } - } + // Participation/Brand/SoftwareProduct Ids + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup. [Theory] [InlineData(3)] @@ -165,7 +64,7 @@ public async Task AC01_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int xv) var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); // Act - Send request to SSA API @@ -176,7 +75,7 @@ public async Task AC01_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(int xv) HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/all/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa", XV = xv.ToString(), - AccessToken = accessToken + AccessToken = accessToken, }.SendAsync(); await AssertSsa(response, softwareProduct, xv); @@ -200,7 +99,7 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different var accessToken = await new Infrastructure.AccessToken { CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD + CertificatePassword = CERTIFICATE_PASSWORD, }.GetAsync(); // Act - Send request to SSA API @@ -211,25 +110,14 @@ public async Task ACX99_GetSSA_WithDifferentIndustry_ShouldRespondWith_Different HttpMethod = HttpMethod.Get, URL = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-recipients/brands/{BRANDID}/software-products/{SOFTWAREPRODUCTID}/ssa", XV = "3", - AccessToken = accessToken + AccessToken = accessToken, }.SendAsync(); await AssertSsa(response, softwareProduct, 3); } - private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", - ""title"": ""ADR Status Is Not Active"", - ""detail"": """", - } - ] - }"; - [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Removed [InlineData(3, HttpStatusCode.Forbidden)] // Suspended [InlineData(4, HttpStatusCode.Forbidden)] // Revoked @@ -251,7 +139,7 @@ await Test_GetSSA( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC04_GetSSA_WithBrandNotActive_ShouldRespondWith_403Forbidden( @@ -270,7 +158,7 @@ await Test_GetSSA( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC05_GetSSA_WithSoftwareProductNotActive_ShouldRespondWith_403Forbidden( @@ -339,13 +227,8 @@ await Test_GetSSA( [InlineData("3", "foo")] public async Task AC11_GetSSA_InvalidSoftwareProductId_ShouldRespondWith_404NotFound(string xv, string softwareProductId) { - await Test_GetSSA( - CERTIFICATE_FILENAME, - CERTIFICATE_PASSWORD, - HttpStatusCode.NotFound, - x_v: xv, - softwareProductId: softwareProductId, - expectedContent: $@" + var expectedContent = + $@" {{ ""errors"": [ {{ @@ -354,7 +237,14 @@ await Test_GetSSA( ""detail"": ""{softwareProductId}"", }} ] - }}"); + }}"; + await Test_GetSSA( + CERTIFICATE_FILENAME, + CERTIFICATE_PASSWORD, + HttpStatusCode.NotFound, + x_v: xv, + softwareProductId: softwareProductId, + expectedContent: expectedContent); } [Theory] @@ -379,34 +269,24 @@ await Test_GetSSA( brandId: Guid.NewGuid().ToString()); } - private static async Task GetSsaJwks() - { - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - - 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", "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("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("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) { @@ -427,7 +307,7 @@ public async Task ACX01_VersionHeaderValidation(string? xv, string? xminv, strin URL = url, AccessToken = accessToken, XV = xv, - XMinV = xminv + XMinV = xminv, }; // Act @@ -477,7 +357,7 @@ public async Task AC01_CTS_URL_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(in Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }.GetAsync(addCertificateToRequest: false); // Act - Send request to SSA API @@ -488,7 +368,7 @@ public async Task AC01_CTS_URL_GetSSA_WithXV1_ShouldRespondWith_200OK_V3ofSSA(in XV = xv.ToString(), AccessToken = accessToken, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }.SendAsync(); await AssertSsa(response, softwareProduct, xv); @@ -569,5 +449,119 @@ private static async Task AssertSsa(HttpResponseMessage response, Repository.Ent } } } + + private static async Task Test_GetSSA( + string certificateFilename, + string certificatePassword, + HttpStatusCode expectedStatusCode, + AccessTokenType accessTokenType = AccessTokenType.ValidAccessToken, + BeforeSSARequest? beforeRequest = null, + AfterSSARequest? afterRequest = null, + string? x_v = "3", + string? x_min_v = null, + string? expectedContent = null, + string? getAccessTokenCertificateFilename = null, + string? getAccessTokenCertificatePassword = null, + string? expectedXV = null, + string? industry = "all", + string brandId = BRANDID, + string softwareProductId = SOFTWAREPRODUCTID) + { + // Arrange + string url = $"{MTLS_BaseURL}/cdr-register/v1/{industry}/data-recipients/brands/{brandId}/software-products/{softwareProductId}/ssa"; + + var accessToken = await GetAccessToken( + accessTokenType, + getAccessTokenCertificateFilename, + getAccessTokenCertificatePassword, + certificateFilename, + certificatePassword); + + var api = new Infrastructure.Api + { + CertificateFilename = certificateFilename, + CertificatePassword = certificatePassword, + HttpMethod = HttpMethod.Get, + URL = url, + AccessToken = accessToken, + XV = x_v, + XMinV = x_min_v, + }; + + beforeRequest?.Invoke(); + try + { + // Act + var response = await api.SendAsync(); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + if (response.StatusCode == HttpStatusCode.OK) + { + // Assert - Check XV + Assert_HasHeader(expectedXV ?? x_v, response.Headers, "x-v"); + } + + // Assert - Check expected content + if (expectedContent != null) + { + await Assert_HasContent_Json(expectedContent, response.Content); + } + } + } + finally + { + afterRequest?.Invoke(); + } + } + + 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 GetSsaJwks() + { + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + var jwksClient = new HttpClient(clientHandler); + var jwksResponse = await jwksClient.GetAsync($"{TLS_BaseURL}/cdr-register/v1/jwks"); + return new JsonWebKeySet(await jwksResponse.Content.ReadAsStringAsync()); + } } } 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 87ad299..6d0fb10 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 @@ -1,15 +1,15 @@ -using CDR.Register.Repository.Entities; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -27,32 +27,6 @@ public US27556_GetDataRecipientStatus_MultiIndustry_Tests(ITestOutputHelper outp { } - private static string GetExpectedDataRecipientsStatus(string url) - { - using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - - var expectedDataRecipientsStatus = new - { - data = dbContext.Participations.AsNoTracking() - .Include(p => p.Status) - .Where(p => p.ParticipationTypeId == ParticipationTypes.Dr) - .Select(p => new - { - legalEntityId = p.LegalEntityId, - status = p.Status.ParticipationStatusCode - }) - .OrderBy(p => p.legalEntityId.ToString()) - .ToList(), - links = new - { - self = url - }, - meta = new object() - }; - - return JsonConvert.SerializeObject(expectedDataRecipientsStatus); - } - [Theory] [InlineData("2", "2")] public async Task AC01_AC02_Get_ShouldRespondWith_200OK_DataRecipientsStatus(string? xv, string expectedXV) @@ -66,7 +40,7 @@ public async Task AC01_AC02_Get_ShouldRespondWith_200OK_DataRecipientsStatus(str { HttpMethod = HttpMethod.Get, URL = url, - XV = xv + XV = xv, }.SendAsync(); // Assert @@ -113,7 +87,7 @@ public async Task AC01_AC02_CTS_URL_Get_ShouldRespondWith_200OK_DataRecipientsSt { HttpMethod = HttpMethod.Get, URL = url, - XV = xv + XV = xv, }.SendAsync(); // Assert @@ -204,22 +178,22 @@ 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("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("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("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(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) { @@ -229,7 +203,7 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients/status", XV = xv, - XMinV = minXv + XMinV = minXv, }.SendAsync(); // Assert @@ -253,5 +227,31 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string } } } + + private static string GetExpectedDataRecipientsStatus(string url) + { + using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); + + var expectedDataRecipientsStatus = new + { + data = dbContext.Participations.AsNoTracking() + .Include(p => p.Status) + .Where(p => p.ParticipationTypeId == ParticipationTypes.Dr) + .Select(p => new + { + legalEntityId = p.LegalEntityId, + status = p.Status.ParticipationStatusCode, + }) + .OrderBy(p => p.legalEntityId.ToString()) + .ToList(), + links = new + { + self = url, + }, + meta = new object(), + }; + + return JsonConvert.SerializeObject(expectedDataRecipientsStatus); + } } } 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 d6d51ff..d4325fa 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 @@ -1,13 +1,13 @@ -using CDR.Register.Repository.Infrastructure; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.Repository.Infrastructure; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -25,31 +25,6 @@ public US27558_GetSoftwareProductStatus_MultiIndustry_Tests(ITestOutputHelper ou { } - private static string GetExpectedSoftwareProductStatus(string url) - { - using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); - - var expectedSoftwareProductsStatus = new - { - data = dbContext.SoftwareProducts.AsNoTracking() - .Include(sp => sp.Status) - .Select(sp => new - { - softwareProductId = sp.SoftwareProductId, - status = sp.Status.SoftwareProductStatusCode - }) - .OrderBy(sp => sp.softwareProductId.ToString()) - .ToList(), - links = new - { - self = url - }, - meta = new object() - }; - - return JsonConvert.SerializeObject(expectedSoftwareProductsStatus); - } - [Theory] [InlineData("2", "2")] public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(string? xv, string expectedXV) @@ -63,7 +38,7 @@ public async Task AC01_Get_WithXV_ShouldRespondWith_200OK_SoftwareProducts(strin { HttpMethod = HttpMethod.Get, URL = url, - XV = xv + XV = xv, }.SendAsync(); // Assert @@ -97,7 +72,7 @@ public async Task AC01_CTS_URL_Get_WithXV_ShouldRespondWith_200OK_SoftwareProduc { HttpMethod = HttpMethod.Get, URL = url, - XV = xv + XV = xv, }.SendAsync(); // Assert @@ -188,22 +163,22 @@ 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("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("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("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) { @@ -213,7 +188,7 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string HttpMethod = HttpMethod.Get, URL = $"{TLS_BaseURL}/cdr-register/v1/banking/data-recipients/brands/software-products/status", XV = xv, - XMinV = minXv + XMinV = minXv, }.SendAsync(); // Assert @@ -237,5 +212,30 @@ public async Task ACXX_VersionHeaderValidation(string? xv, string? minXv, string } } } + + private static string GetExpectedSoftwareProductStatus(string url) + { + using var dbContext = new RegisterDatabaseContext(new DbContextOptionsBuilder().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).Options); + + var expectedSoftwareProductsStatus = new + { + data = dbContext.SoftwareProducts.AsNoTracking() + .Include(sp => sp.Status) + .Select(sp => new + { + softwareProductId = sp.SoftwareProductId, + status = sp.Status.SoftwareProductStatusCode, + }) + .OrderBy(sp => sp.softwareProductId.ToString()) + .ToList(), + links = new + { + self = url, + }, + meta = new object(), + }; + + return JsonConvert.SerializeObject(expectedSoftwareProductsStatus); + } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Update/ErrorType.cs b/Source/CDR.Register.IntegrationTests/API/Update/ErrorType.cs new file mode 100644 index 0000000..55654de --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/API/Update/ErrorType.cs @@ -0,0 +1,15 @@ +namespace CDR.Register.IntegrationTests.API.Update +{ + public partial class ExpectedErrors + { + public enum ErrorType + { + MissingField, + MissingHeader, + InvalidField, + InvalidVersion, + UnsupportedVersion, + Unauthorized, + } + } +} diff --git a/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs b/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs index 3915cef..f5361d7 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/ExpectedErrors.cs @@ -1,43 +1,33 @@ -using CDR.Register.IntegrationTests.Models; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; +using CDR.Register.IntegrationTests.Models; +using Newtonsoft.Json; namespace CDR.Register.IntegrationTests.API.Update { - public class ExpectedErrors + public partial class ExpectedErrors { - public enum ErrorType - { - MissingField, - MissingHeader, - InvalidField, - InvalidVersion, - UnsupportedVersion, - Unauthorized - } - private const string CDS_ERROR_PREFIX = "urn:au-cds:error:cds-all:"; private readonly List _expectedErrors; - /// - /// 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() { - _expectedErrors = new List(); + this._expectedErrors = new List(); } + /// + /// Gets expected errors. This is used in serialised JSON to compare against the actual JSON. Do not remove. + /// + [JsonProperty(PropertyName = "errors")] + public List Errors { get => this._expectedErrors.ToList(); } + public void AddExpectedError(ErrorType errorType, string detail) { switch (errorType) { case ErrorType.MissingField: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"{CDS_ERROR_PREFIX}Field/Missing", Title = "Missing Required Field", @@ -45,7 +35,7 @@ public void AddExpectedError(ErrorType errorType, string detail) }); return; case ErrorType.MissingHeader: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"{CDS_ERROR_PREFIX}Header/Missing", Title = "Missing Required Header", @@ -53,7 +43,7 @@ public void AddExpectedError(ErrorType errorType, string detail) }); return; case ErrorType.InvalidField: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"{CDS_ERROR_PREFIX}Field/Invalid", Title = "Invalid Field", @@ -61,7 +51,7 @@ public void AddExpectedError(ErrorType errorType, string detail) }); return; case ErrorType.InvalidVersion: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"{CDS_ERROR_PREFIX}Header/InvalidVersion", Title = "Invalid Version", @@ -69,7 +59,7 @@ public void AddExpectedError(ErrorType errorType, string detail) }); return; case ErrorType.UnsupportedVersion: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"{CDS_ERROR_PREFIX}Header/UnsupportedVersion", Title = "Unsupported Version", @@ -77,7 +67,7 @@ public void AddExpectedError(ErrorType errorType, string detail) }); return; case ErrorType.Unauthorized: - _expectedErrors.Add(new ExpectedApiErrors() + this._expectedErrors.Add(new ExpectedApiErrors() { Code = $"401", Title = "Unauthorized", diff --git a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs index b915ec7..6d267b1 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataHolders.cs @@ -1,4 +1,12 @@ -using CDR.Register.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using CDR.Register.IntegrationTests.Extensions; using CDR.Register.IntegrationTests.Models; using CDR.Register.Repository.Infrastructure; using FluentAssertions; @@ -8,14 +16,6 @@ using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Serilog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -23,19 +23,19 @@ namespace CDR.Register.IntegrationTests.API.Update { public class US50480_UpdateDataHolders : BaseTest { + private const string UPDATE_DATA_HOLDER_CURRENT_API_VERSION = "1"; + + private const string TEST_DATA_BASE_URI = "https://TestAumationLogoUri.gov.au"; + public US50480_UpdateDataHolders(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } - private const string UPDATE_DATA_HOLDER_CURRENT_API_VERSION = "1"; - - private const string TEST_DATA_BASE_URI = "https://TestAumationLogoUri.gov.au"; - public enum Industry { Banking, - Energy + Energy, } [Theory] @@ -188,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 @@ -205,7 +205,7 @@ 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 @@ -221,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 @@ -705,13 +705,13 @@ private static string GetActualDataHolderFromDatabase(string legalEntityId, stri resourceBaseUri = brand.Endpoint.ResourceBaseUri, infosecBaseUri = brand.Endpoint.InfosecBaseUri, extensionBaseUri = brand.Endpoint.ExtensionBaseUri, - websiteUri = brand.Endpoint.WebsiteUri + websiteUri = brand.Endpoint.WebsiteUri, }, authDetails = new { registerUType = brand.AuthDetails.First().RegisterUType.RegisterUTypeCode, - jwksEndpoint = brand.AuthDetails.First().JwksEndpoint - } + jwksEndpoint = brand.AuthDetails.First().JwksEndpoint, + }, })); return JsonConvert.SerializeObject(expectedDataHolder.First().First(), Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); @@ -730,7 +730,7 @@ private static async Task GetAzureAdAccessToken() new KeyValuePair("client_id", AZURE_AD_CLIENT_ID), new KeyValuePair("scope", AZURE_AD_SCOPE), new KeyValuePair("client_secret", AZURE_AD_CLIENT_SECRET), - new KeyValuePair("grant_type", AZURE_AD_GRANT_TYPE) + new KeyValuePair("grant_type", AZURE_AD_GRANT_TYPE), }); var response = await client.PostAsync(AZURE_AD_TOKEN_ENDPOINT_URL, content); @@ -741,7 +741,7 @@ private static async Task GetAzureAdAccessToken() } var tokenResnse = JsonConvert.DeserializeObject(responseBody); - return tokenResnse.access_token; + return tokenResnse.Access_token; } private static async Task GetInvalidAzureAdAccessToken() @@ -752,7 +752,7 @@ private static async Task GetInvalidAzureAdAccessToken() new KeyValuePair("client_id", AZURE_AD_UNAUTHORISED_CLIENT_ID), new KeyValuePair("client_secret", AZURE_AD_UNAUTHORISED_CLIENT_SECRET), new KeyValuePair("scope", AZURE_AD_SCOPE), - new KeyValuePair("grant_type", AZURE_AD_GRANT_TYPE) + new KeyValuePair("grant_type", AZURE_AD_GRANT_TYPE), }); var response = await client.PostAsync(AZURE_AD_TOKEN_ENDPOINT_URL, content); @@ -763,7 +763,7 @@ private static async Task GetInvalidAzureAdAccessToken() } var tokenResnse = JsonConvert.DeserializeObject(responseBody); - return tokenResnse.access_token; + return tokenResnse.Access_token; } } } diff --git a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs index aec0c69..fe7cc2d 100644 --- a/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs +++ b/Source/CDR.Register.IntegrationTests/API/Update/US50480_UpdateDataRecipients.cs @@ -1,4 +1,12 @@ -using CDR.Register.IntegrationTests.Extensions; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using CDR.Register.IntegrationTests.Extensions; using CDR.Register.IntegrationTests.Models; using CDR.Register.Repository.Infrastructure; using FluentAssertions; @@ -8,14 +16,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -23,11 +23,6 @@ namespace CDR.Register.IntegrationTests.API.Update { public class US50480_UpdateDataRecipients : BaseTest { - 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 " + "energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:accounts.concessions:read energy:billing:read common:customer.basic:read common:customer.detail:read cdr:registration cdr-register:read"; @@ -38,6 +33,11 @@ public US50480_UpdateDataRecipients(ITestOutputHelper outputHelper, TestFixture private const string TEST_DATA_BASE_URI = "https://TestAumationLogoUri.gov.au"; private const string TEST_DATA_THUMBPRINT = "52ec9233a5fcb690de8582c18223b4fee2fe3989"; + public US50480_UpdateDataRecipients(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } + [Fact] public async Task AC02_Add_New_Legal_Entity_With_All_Fields_Fields_Http_200() { @@ -141,7 +141,7 @@ public async Task AC08_Add_Multiple_Duplicate_Certificates() dataRecipient.DataRecipientBrands[0].SoftwareProducts[0].Certificates.Add(new DataRecipientMetadata.Certificate() { CommonName = "Test Automation Certificate Common Name", - Thumbprint = TEST_DATA_THUMBPRINT + Thumbprint = TEST_DATA_THUMBPRINT, }); // Send Updated Data Recipient to Register @@ -182,9 +182,9 @@ public async Task ACXX_Add_New_Legal_Entity_With_Multiple_Software_Products_Http new DataRecipientMetadata.Certificate() { CommonName = "Test Automation Certificate Common Name", - Thumbprint = TEST_DATA_THUMBPRINT - } - } + Thumbprint = TEST_DATA_THUMBPRINT, + }, + }, }); // Send to Register @@ -791,10 +791,10 @@ private static DataRecipientMetadata GenerateValidDataRecipient(bool includeOpti Status = "ACTIVE", SoftwareProducts = new List { - GenerateNewSofwareProduct() - } - } - } + GenerateNewSofwareProduct(), + }, + }, + }, }; if (includeOptionalFields) @@ -836,9 +836,9 @@ private static DataRecipientMetadata.SoftwareProduct GenerateNewSofwareProduct() new DataRecipientMetadata.Certificate() { CommonName = "Test Automation Certificate Common Name", - Thumbprint = TEST_DATA_THUMBPRINT - } - } + Thumbprint = TEST_DATA_THUMBPRINT, + }, + }, }; return softwareProduct; } @@ -905,7 +905,7 @@ private static string GetActualDataRecipientFromDatabase(string legalEntityId) { commonName = certificates.CommonName, thumbprint = certificates.Thumbprint, - }) + }), }), }), }); diff --git a/Source/CDR.Register.IntegrationTests/BaseTest.cs b/Source/CDR.Register.IntegrationTests/BaseTest.cs index 8bf4e23..293ee38 100644 --- a/Source/CDR.Register.IntegrationTests/BaseTest.cs +++ b/Source/CDR.Register.IntegrationTests/BaseTest.cs @@ -1,4 +1,14 @@ -using CDR.Register.Domain.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Security.Claims; +using System.Threading.Tasks; +using CDR.Register.Domain.Models; using CDR.Register.IntegrationTests.API.Update; using CDR.Register.IntegrationTests.Extensions; using CDR.Register.Repository.Infrastructure; @@ -13,16 +23,6 @@ using Serilog; using Serilog.Exceptions; using Serilog.Sinks.SystemConsole.Themes; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Reflection; -using System.Security.Claims; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -33,6 +33,60 @@ namespace CDR.Register.IntegrationTests { public abstract class BaseTest : BaseTest0, IClassFixture { + // START CTS Settings + public const string EXPIRED_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyJ9.eyJhdWQiOiI3YzVmZmE2Yy1jN2ZhLTRlNDktODMyZi1lZWQ0MzBmODE1MjUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYWZiYmE3ZDAtNjc2Zi00MGI3LTkwYmQtOGY0NjM4MDM0YjgyL3YyLjAiLCJpYXQiOjE2ODM3ODIyMTgsIm5iZiI6MTY4Mzc4MjIxOCwiZXhwIjoxNjgzNzg2MTE4LCJhaW8iOiJFMlpnWUdnS2VoSXp6ODFpMWFYazA2NG5jcU15ZU90dUNhOUs4NTB6dGNiYjhvR1g5UVVBIiwiYXpwIjoiMzA5MjJhZWUtMDc5OS00NzkxLWFkNDUtMjI2NTUwZjg2OGJlIiwiYXpwYWNyIjoiMSIsIm9pZCI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInJoIjoiMC5BVUVBMEtlN3IyOW50MENRdlk5R09BTkxnbXo2WDN6NngwbE9neV91MURENEZTVkJBQUEuIiwicm9sZXMiOlsiQXBpLkFkbWluLlBhcnRpY2lwYW50TWV0YURhdGEuV3JpdGUiLCJBcGkuQWRtaW4uUGFydGljaXBhbnRNZXRhRGF0YS5SZWFkIl0sInN1YiI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInRpZCI6ImFmYmJhN2QwLTY3NmYtNDBiNy05MGJkLThmNDYzODAzNGI4MiIsInV0aSI6IlB6SEF3aTlXY2t5dXRkbGRSSkVTQUEiLCJ2ZXIiOiIyLjAifQ.VjMh6-FRMLWAIkYloADH--fTgUVfNgN2XYx3yeJUew1JiCRpiABj4JYkieBxkQ4vrWfj79F3O1ggf2SEOy49nym037CdA3TfW83kpw7MOVHH38-VG-LR_sobAMsS40N4dwNrvsfRQxjQha8gcnskPvtYAWYOII2vfMFxrxAeChwsDGd6A-b5-vo26GyQjebZLcfhMAgu79HFKgIrQRg9MYQ5ZI2wISi2T_d43RYyluXHBtVyCRIfEmUy3aTyJBo6ZHW5omhbUgDp9otwUmwFkv4xrmdrz5ADgqaMelEVllyJrUD9de_wvAh9V5q5Bu6bJhufQoKWXgO-dKIx6baJOA"; + + // 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"; + + public static readonly string ADMIN_URL = ADMIN_BaseURL + "/admin/metadata"; + + public static readonly string IDENTITYSERVER_URL = MTLS_BaseURL + "/idp/connect/token"; + + // Client certificates + protected const string CERTIFICATE_FILENAME = "Certificates/client.pfx"; + protected const string CERTIFICATE_PASSWORD = "#M0ckDataRecipient#"; + protected const string ADDITIONAL_CERTIFICATE_FILENAME = "Certificates/client-additional.pfx"; + protected const string ADDITIONAL_CERTIFICATE_PASSWORD = CERTIFICATE_PASSWORD; + protected const string DEFAULT_CERTIFICATE_THUMBPRINT = "f0e5146a51f16e236844cf0353d791f11865e405"; + protected const string DEFAULT_CERTIFICATE_COMMON_NAME = "MockDataRecipient"; + + protected const string EXPECTED_UNSUPPORTED_ERROR = @" + { + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Header/UnsupportedVersion"", + ""title"": ""Unsupported Version"", + ""detail"": ""Requested version is lower than the minimum version or greater than maximum version."", + } + ] + }"; + + protected const string EXPECTED_INVALID_VERSION_ERROR = @" + { + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Header/InvalidVersion"", + ""title"": ""Invalid Version"", + ""detail"": ""Version is not a positive Integer."" + } + ] + }"; + + protected const string EXPECTED_MISSING_X_V_ERROR = @" + { + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Header/Missing"", + ""title"": ""Missing Required Header"", + ""detail"": ""An API version x-v header is required, but was not specified."", + } + ] + }"; + + private const string REGISTER_RW = "DefaultConnection"; + private static IConfigurationRoot? configuration; + protected BaseTest(ITestOutputHelper output, TestFixture testFixture) { Log.Logger = new LoggerConfiguration() @@ -46,11 +100,9 @@ protected BaseTest(ITestOutputHelper output, TestFixture testFixture) .CreateLogger(); JsonConvert.DefaultSettings = () => new CdrJsonSerializerSettings(); - TestFixture = testFixture; + this.TestFixture = testFixture; } - private const string REGISTER_RW = "DefaultConnection"; - public static string CONNECTIONSTRING_REGISTER_RW { get @@ -65,8 +117,6 @@ public static string CONNECTIONSTRING_REGISTER_RW } } - private static IConfigurationRoot? configuration; - public static IConfigurationRoot Configuration { get @@ -85,20 +135,6 @@ public static IConfigurationRoot Configuration } } - // Client certificates - protected const string CERTIFICATE_FILENAME = "Certificates/client.pfx"; - protected const string CERTIFICATE_PASSWORD = "#M0ckDataRecipient#"; - protected const string ADDITIONAL_CERTIFICATE_FILENAME = "Certificates/client-additional.pfx"; - protected const string ADDITIONAL_CERTIFICATE_PASSWORD = CERTIFICATE_PASSWORD; - protected const string DEFAULT_CERTIFICATE_THUMBPRINT = "f0e5146a51f16e236844cf0353d791f11865e405"; - protected const string DEFAULT_CERTIFICATE_COMMON_NAME = "MockDataRecipient"; - - // 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"; - - // START CTS Settings - public const string EXPIRED_ACCESS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyJ9.eyJhdWQiOiI3YzVmZmE2Yy1jN2ZhLTRlNDktODMyZi1lZWQ0MzBmODE1MjUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vYWZiYmE3ZDAtNjc2Zi00MGI3LTkwYmQtOGY0NjM4MDM0YjgyL3YyLjAiLCJpYXQiOjE2ODM3ODIyMTgsIm5iZiI6MTY4Mzc4MjIxOCwiZXhwIjoxNjgzNzg2MTE4LCJhaW8iOiJFMlpnWUdnS2VoSXp6ODFpMWFYazA2NG5jcU15ZU90dUNhOUs4NTB6dGNiYjhvR1g5UVVBIiwiYXpwIjoiMzA5MjJhZWUtMDc5OS00NzkxLWFkNDUtMjI2NTUwZjg2OGJlIiwiYXpwYWNyIjoiMSIsIm9pZCI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInJoIjoiMC5BVUVBMEtlN3IyOW50MENRdlk5R09BTkxnbXo2WDN6NngwbE9neV91MURENEZTVkJBQUEuIiwicm9sZXMiOlsiQXBpLkFkbWluLlBhcnRpY2lwYW50TWV0YURhdGEuV3JpdGUiLCJBcGkuQWRtaW4uUGFydGljaXBhbnRNZXRhRGF0YS5SZWFkIl0sInN1YiI6ImY0ZGZmMGU2LTQxYjMtNDFlNy1iNWFiLWQ0MzNjZTg4MTY5NiIsInRpZCI6ImFmYmJhN2QwLTY3NmYtNDBiNy05MGJkLThmNDYzODAzNGI4MiIsInV0aSI6IlB6SEF3aTlXY2t5dXRkbGRSSkVTQUEiLCJ2ZXIiOiIyLjAifQ.VjMh6-FRMLWAIkYloADH--fTgUVfNgN2XYx3yeJUew1JiCRpiABj4JYkieBxkQ4vrWfj79F3O1ggf2SEOy49nym037CdA3TfW83kpw7MOVHH38-VG-LR_sobAMsS40N4dwNrvsfRQxjQha8gcnskPvtYAWYOII2vfMFxrxAeChwsDGd6A-b5-vo26GyQjebZLcfhMAgu79HFKgIrQRg9MYQ5ZI2wISi2T_d43RYyluXHBtVyCRIfEmUy3aTyJBo6ZHW5omhbUgDp9otwUmwFkv4xrmdrz5ADgqaMelEVllyJrUD9de_wvAh9V5q5Bu6bJhufQoKWXgO-dKIx6baJOA"; - 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"); @@ -121,8 +157,6 @@ public static IConfigurationRoot Configuration 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 // URLs @@ -135,48 +169,14 @@ public static IConfigurationRoot Configuration 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 = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Header/UnsupportedVersion"", - ""title"": ""Unsupported Version"", - ""detail"": ""Requested version is lower than the minimum version or greater than maximum version."", - } - ] - }"; - - protected const string EXPECTED_INVALID_VERSION_ERROR = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Header/InvalidVersion"", - ""title"": ""Invalid Version"", - ""detail"": ""Version is not a positive Integer."" - } - ] - }"; - - protected const string EXPECTED_MISSING_X_V_ERROR = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Header/Missing"", - ""title"": ""Missing Required Header"", - ""detail"": ""An API version x-v header is required, but was not specified."", - } - ] - }"; + public TestFixture TestFixture { get; } /// /// Assert response content and expectedJson are equivalent. /// /// The expected json. /// The response content. + /// representing the asynchronous operation. public static async Task Assert_HasContent_Json(string expectedJson, HttpContent content) { var actualJson = await content.ReadAsStringAsync(); @@ -249,14 +249,10 @@ public static void AssertClaim(IEnumerable claims, string claimType, stri } } - protected static string GetJsonFromModel(T model) - { - return JsonConvert.SerializeObject(model); - } - /// /// Get status of SoftwareProduct. /// + /// statusid. public static int GetSoftwareProductStatusId(string softwareProductId) { using var connection = new SqlConnection(Configuration.GetConnectionString("DefaultConnection")); @@ -295,6 +291,7 @@ public static void SetSoftwareProductStatusId(string softwareProductId, int stat /// /// Get status of Brand. /// + /// brand status. public static int GetBrandStatusId(string brandId) { using var connection = new SqlConnection(Configuration.GetConnectionString("DefaultConnection")); @@ -333,6 +330,7 @@ public static void SetBrandStatusId(string brandId, int statusId) /// /// Get participationid for brand. /// + /// participation id. public static string GetParticipationId(string brandId) { using var connection = new SqlConnection(Configuration.GetConnectionString("DefaultConnection")); @@ -347,6 +345,7 @@ public static string GetParticipationId(string brandId) /// /// Get status of Participation. /// + /// participation status. public static int GetParticipationStatusId(string participationId) { using var connection = new SqlConnection(Configuration.GetConnectionString("DefaultConnection")); @@ -382,6 +381,18 @@ public static void SetParticipationStatusId(string participationId, int statusId } } + public static string GenerateDynamicCtsUrl(string baseUrl, string? conformanceId = null) + { + if (conformanceId == null) + { + return $"{baseUrl}/cts/{Guid.NewGuid()}/register"; + } + else + { + return $"{baseUrl}/cts/{conformanceId}/register"; + } + } + protected static void VerifyParticipationRecord(string legalEntiryId, string expectedParticipationType, string expectedIndustryType, string expectedStatus) { using SqlConnection registerConnection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); @@ -451,6 +462,11 @@ Participation p } } + protected static string GetJsonFromModel(T model) + { + return JsonConvert.SerializeObject(model); + } + protected static void VerifyBrandLastUpdatedDateRecord(string legalEntiryId) { var brands = GetActualBrandsFromDatabase(legalEntiryId); @@ -575,18 +591,6 @@ protected static string ConvertJsonPathToPascalCase(string jsonPath) return mainJoken.ToObject(); } - public static string GenerateDynamicCtsUrl(string baseUrl, string? conformanceId = null) - { - if (conformanceId == null) - { - return $"{baseUrl}/cts/{Guid.NewGuid()}/register"; - } - else - { - return $"{baseUrl}/cts/{conformanceId}/register"; - } - } - protected static string ReplaceSecureHostName(string url, string hostNamedToReplace) { string secureHostname = Configuration["SecureHostName"] ?? string.Empty; @@ -615,24 +619,4 @@ protected static string ReplacePublicHostName(string url, string hostNamedToRepl } } } - - [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 - { - } } diff --git a/Source/CDR.Register.IntegrationTests/BaseTest0.cs b/Source/CDR.Register.IntegrationTests/BaseTest0.cs new file mode 100644 index 0000000..2b5b282 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/BaseTest0.cs @@ -0,0 +1,14 @@ +using Xunit; + +#nullable enable + +namespace CDR.Register.IntegrationTests +{ + // 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 + { + } +} diff --git a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj index 66df957..b6fe9bf 100644 --- a/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj +++ b/Source/CDR.Register.IntegrationTests/CDR.Register.IntegrationTests.csproj @@ -64,7 +64,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Source/CDR.Register.IntegrationTests/DisplayTestMethodNameAttribute.cs b/Source/CDR.Register.IntegrationTests/DisplayTestMethodNameAttribute.cs new file mode 100644 index 0000000..5bbd41b --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/DisplayTestMethodNameAttribute.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Security.Claims; +using System.Threading.Tasks; +using CDR.Register.Domain.Models; +using CDR.Register.IntegrationTests.API.Update; +using CDR.Register.IntegrationTests.Extensions; +using CDR.Register.Repository.Infrastructure; +using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Json; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serilog; +using Serilog.Exceptions; +using Serilog.Sinks.SystemConsole.Themes; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +#nullable enable + +namespace CDR.Register.IntegrationTests +{ + [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}"); + } + } +} diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs index 7bb84b4..8a8f26f 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JTokenExtensions.cs @@ -1,12 +1,20 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using System.Linq; +using Newtonsoft.Json.Linq; namespace CDR.Register.IntegrationTests.Extensions { public static class JTokenExtensions { + /// + /// Should a JToken be removed?. + /// + /// validation result. + public delegate bool ShouldRemove(JToken jToken); + + public delegate JToken Replace(JToken token); + /// /// Remove child JToken from JToken. /// @@ -31,11 +39,6 @@ public static void Remove(this JToken jToken, string key) } } - /// - /// Should a JToken be removed?. - /// - public delegate bool ShouldRemove(JToken jToken); - /// /// Recursively remove child JTokens from a JToken. /// @@ -101,8 +104,6 @@ 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); diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs index 4fbf405..00d8d5e 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JsonExtensions.cs @@ -9,12 +9,13 @@ public static class JsonExtensions /// Strip comments from json string. /// The json will be reserialized so it's formatting may change (ie whitespace/indentation etc). /// + /// json string. public static string JsonStripComments(this string json) { var options = new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, - WriteIndented = true + WriteIndented = true, }; var jsonObject = JsonSerializer.Deserialize(json, options); @@ -27,6 +28,7 @@ public static string JsonStripComments(this string json) /// Json is converted to JTokens prior to comparision, thus formatting is ignore. /// Returns true if json is equivalent, otherwise false. /// + /// comparision result. public static bool JsonCompare(this string json, string jsonToCompare) { var jsonToken = JToken.Parse(json); diff --git a/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs index 3a8a61d..c0e9185 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/JwtSecurityTokenExtensions.cs @@ -12,7 +12,7 @@ public static class JwtSecurityTokenExtensions /// /// Get claim for claimType. Throws exception if no claim or multiple claims (ie must be a single claim for claimType). /// - /// + /// Claim. public static Claim Claim(this JwtSecurityToken jwt, string claimType) => jwt.Claims.Single(claim => claim.Type == claimType); diff --git a/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs b/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs index 1775f06..0923152 100644 --- a/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs +++ b/Source/CDR.Register.IntegrationTests/Extensions/SqlExtensions.cs @@ -1,5 +1,5 @@ -using Microsoft.Data.SqlClient; -using System; +using System; +using Microsoft.Data.SqlClient; namespace CDR.Register.IntegrationTests.Extensions { @@ -8,6 +8,7 @@ public static class SqlExtensions /// /// Execute scalar command and return result as Int32. Throw error if no results or conversion error. /// + /// Value in the first column of the first row in the result set. public static int ExecuteScalarInt32(this SqlCommand command) { var res = command.ExecuteScalar(); @@ -23,6 +24,7 @@ public static int ExecuteScalarInt32(this SqlCommand command) /// /// Execute scalar command and return result as string. Throw error if no results or conversion error. /// + /// Value in the first column of the first row in the result set. public static string ExecuteScalarString(this SqlCommand command) { var res = command.ExecuteScalar(); diff --git a/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs b/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs index ffeb60a..53192cf 100644 --- a/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs +++ b/Source/CDR.Register.IntegrationTests/Fixtures/TestFixture.cs @@ -1,13 +1,13 @@ -using CDR.Register.IntegrationTests.Extensions; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Configuration; -using System; +using System; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using CDR.Register.IntegrationTests.Extensions; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; using Xunit; #nullable enable @@ -43,6 +43,17 @@ public static async Task Seeddata() } } + public async Task InitializeAsync() + { + await Seeddata(); + await PatchRegister(); + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } + // Patch JWKSURI to be the Register loopback URI private static async Task PatchRegister() { @@ -54,16 +65,5 @@ private static async Task PatchRegister() await updateCommand.ExecuteNonQueryAsync(); } - - public async Task InitializeAsync() - { - await Seeddata(); - await PatchRegister(); - } - - public Task DisposeAsync() - { - return Task.CompletedTask; - } } -} \ No newline at end of file +} 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 2b6a1aa..634f8f4 100644 --- a/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Gateway/US12841_Gateway_MTLS_Tests.cs @@ -21,11 +21,6 @@ namespace CDR.Register.IntegrationTests.Gateway /// public class US12841_Gateway_MTLS_Tests : BaseTest { - public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) - : base(outputHelper, testFixture) - { - } - // Client certificates private const string INVALID_CERTIFICATE_FILENAME = "Certificates/client-invalid.pfx"; @@ -35,7 +30,6 @@ public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture te // Client assertion private const string SCOPE = "cdr-register:read"; - private static readonly string AUDIENCE = IDENTITYSERVER_URL; // Token request private const string GRANT_TYPE = "client_credentials"; @@ -43,79 +37,37 @@ public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture te private const string CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; private const string ISSUER = CLIENT_ID; - // Participation/Brand/SoftwareProduct Ids - 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. - /// - private static HttpClient GetClient(string certificateFilename, string certificatePassword) - { - 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); - - return new HttpClient(clientHandler); - } - - /// - /// Get HttpRequestMessage for access token request. - /// - private static HttpRequestMessage GetAccessTokenRequest(string certificateFilename, string certificatePassword) - { - static string BuildContent(string scope, string grant_type, string client_id, string client_assertion_type, string client_assertion) - { - var kvp = new KeyValuePairBuilder(); - - if (scope != null) - { - kvp.Add("scope", scope); - } - - if (grant_type != null) - { - kvp.Add("grant_type", grant_type); - } - - if (client_id != null) + private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" { - kvp.Add("client_id", client_id); - } + ""errors"": [ + { + ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", + ""title"": ""ADR Status Is Not Active"", + ""detail"": """", + } + ] + }"; - if (client_assertion_type != null) - { - kvp.Add("client_assertion_type", client_assertion_type); - } + private static readonly string AUDIENCE = IDENTITYSERVER_URL; - if (client_assertion != null) - { - kvp.Add("client_assertion", client_assertion); - } + public US12841_Gateway_MTLS_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) + { + } - return kvp.Value; - } + private delegate void BeforeDataHolderBrandsRequest(); - var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, Guid.NewGuid().ToString()); - var client_assertion = tokenizer.Generate(ISSUER, AUDIENCE, ISSUER); + private delegate void AfterDataHolderBrandsRequest(); - var request = new HttpRequestMessage(HttpMethod.Post, IDENTITYSERVER_URL) - { - Content = new StringContent( - BuildContent(SCOPE, GRANT_TYPE, CLIENT_ID, CLIENT_ASSERTION_TYPE, client_assertion), - Encoding.UTF8, - "application/json") - }; + private delegate void BeforeSSARequest(); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + private delegate void AfterSSARequest(); - return request; - } + // Participation/Brand/SoftwareProduct Ids + private static string PARTICIPATIONID => GetParticipationId(BRANDID); // lookup [Fact] public async Task AC01_PostAccessTokenRequest_WithClientCert_ShouldRespondWith_200OK_AccessToken() @@ -151,16 +103,16 @@ public async Task AC01_PostAccessTokenRequest_WithClientCert_ShouldRespondWith_2 var accessToken = JsonSerializer.Deserialize(await accessTokenResponse.Content.ReadAsStringAsync()); // Assert - Check expires_in - accessToken.expires_in.Should().Be(ACCESSTOKEN_EXPIRESIN); + accessToken.Expires_in.Should().Be(ACCESSTOKEN_EXPIRESIN); // Assert - Check token_type - accessToken.token_type.Should().Be(ACCESSTOKEN_TOKENTYPE); + accessToken.Token_type.Should().Be(ACCESSTOKEN_TOKENTYPE); // Assert - Check scope - accessToken.scope.Should().Contain("cdr-register:read"); + accessToken.Scope.Should().Contain("cdr-register:read"); // Assert - Check the JWT access_token - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken.access_token); + var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken.Access_token); AssertClaim(jwt.Claims, "iss", jwtClaimIss); AssertClaim(jwt.Claims, "aud", JWT_CLAIM_AUD); AssertClaim(jwt.Claims, "client_id", JWT_CLAIM_CLIENT_ID); @@ -221,64 +173,6 @@ public async Task AC04_PostAccessTokenRequest_WithServerCert_ShouldThrow_HttpReq } } - private delegate void BeforeDataHolderBrandsRequest(); - - private delegate void AfterDataHolderBrandsRequest(); - - private static async Task Test_GetDataHolderBrands( - string certificateFilename, - string certificatePassword, - HttpStatusCode expectedStatusCode, - bool withAccessToken = true, - BeforeDataHolderBrandsRequest beforeRequest = null, - AfterDataHolderBrandsRequest afterRequest = null, - string expectedContent = null) - { - // DataHolderBrands - 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); - request.Headers.Add("x-v", XV); - - // Supply access token with request? - if (withAccessToken) - { - var accessTokenRequest = GetAccessTokenRequest(certificateFilename, certificatePassword); - var accessTokenResponse = await client.SendAsync(accessTokenRequest); - var accessToken = JsonSerializer.Deserialize(await accessTokenResponse.Content.ReadAsStringAsync()); - request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, accessToken.access_token); - } - - beforeRequest?.Invoke(); - try - { - // Act - var response = await client.SendAsync(request); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - // Assert - Check error response - if (expectedContent != null) - { - await Assert_HasContent_Json(expectedContent, response.Content); - } - } - } - finally - { - afterRequest?.Invoke(); - } - } - [Fact] public async Task AC05_GetDataHolderBrands_WithAccessToken_AndClientCert_ShouldRespondWith_200OK() { @@ -311,19 +205,8 @@ public async Task AC09_GetDataHolderBrands_WithNoAccessToken_AndClientCert_Shoul await Test_GetDataHolderBrands(CERTIFICATE_FILENAME, CERTIFICATE_PASSWORD, HttpStatusCode.Unauthorized, false); } - private const string EXPECTEDCONTENT_ADRSTATUSNOTACTIVE = @" - { - ""errors"": [ - { - ""code"": ""urn:au-cds:error:cds-all:Authorisation/AdrStatusNotActive"", - ""title"": ""ADR Status Is Not Active"", - ""detail"": """", - } - ] - }"; - [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC10_GetDataHolderBrands_WithAccessToken_AndSoftwareProductStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( @@ -342,7 +225,7 @@ await Test_GetDataHolderBrands( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC11_GetDataHolderBrands_WithAccessToken_AndBrandStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( @@ -361,7 +244,7 @@ await Test_GetDataHolderBrands( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Removed [InlineData(3, HttpStatusCode.Forbidden)] // Suspended [InlineData(4, HttpStatusCode.Forbidden)] // Revoked @@ -382,63 +265,6 @@ await Test_GetDataHolderBrands( expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } - private delegate void BeforeSSARequest(); - - private delegate void AfterSSARequest(); - - private static async Task Test_GetSSA( - string certificateFilename, - string certificatePassword, - HttpStatusCode expectedStatusCode, - bool withAccessToken = true, - BeforeSSARequest beforeRequest = null, - AfterSSARequest afterRequest = null, - string expectedContent = null) - { - 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); - request.Headers.Add("x-v", XV); - - // Supply access token with request? - if (withAccessToken) - { - var accessTokenRequest = GetAccessTokenRequest(certificateFilename, certificatePassword); - var accessTokenResponse = await client.SendAsync(accessTokenRequest); - var accessToken = JsonSerializer.Deserialize(await accessTokenResponse.Content.ReadAsStringAsync()); - - request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, accessToken.access_token); - } - - beforeRequest?.Invoke(); - try - { - // Act - var response = await client.SendAsync(request); - - // Assert - using (new AssertionScope()) - { - // Assert - Check status code - response.StatusCode.Should().Be(expectedStatusCode); - - // Assert - Check error response - if (expectedContent != null) - { - await Assert_HasContent_Json(expectedContent, response.Content); - } - } - } - finally - { - afterRequest?.Invoke(); - } - } - [Fact] public async Task AC13_GetSSA_WithAccessToken_AndClientCert_ShouldRespondWith_200OK() { @@ -472,7 +298,7 @@ public async Task AC17_GetSSA_WithNoAccessToken_AndClientCert_ShouldRespondWith_ } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC18_GetSSA_WithAccessToken_AndSoftwareProductStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( @@ -491,7 +317,7 @@ await Test_GetSSA( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Inactive [InlineData(3, HttpStatusCode.Forbidden)] // Removed public async Task AC19_GetSSA_WithAccessToken_AndBrandStatusSetInactiveSinceTokenProvisioned_ShouldRespondWith_403Forbidden( @@ -510,7 +336,7 @@ await Test_GetSSA( } [Theory] - [InlineData(1, HttpStatusCode.OK)] // Active + [InlineData(1, HttpStatusCode.OK)] // Active [InlineData(2, HttpStatusCode.Forbidden)] // Removed [InlineData(3, HttpStatusCode.Forbidden)] // Suspended [InlineData(4, HttpStatusCode.Forbidden)] // Revoked @@ -530,5 +356,180 @@ await Test_GetSSA( afterRequest: () => SetParticipationStatusId(PARTICIPATIONID, saveParticipationStatusId), expectedContent: expectedStatusCode == HttpStatusCode.OK ? null : EXPECTEDCONTENT_ADRSTATUSNOTACTIVE); } + + private static async Task Test_GetDataHolderBrands( + string certificateFilename, + string certificatePassword, + HttpStatusCode expectedStatusCode, + bool withAccessToken = true, + BeforeDataHolderBrandsRequest beforeRequest = null, + AfterDataHolderBrandsRequest afterRequest = null, + string expectedContent = null) + { + // DataHolderBrands + 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); + request.Headers.Add("x-v", XV); + + // Supply access token with request? + if (withAccessToken) + { + var accessTokenRequest = GetAccessTokenRequest(certificateFilename, certificatePassword); + var accessTokenResponse = await client.SendAsync(accessTokenRequest); + var accessToken = JsonSerializer.Deserialize(await accessTokenResponse.Content.ReadAsStringAsync()); + request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, accessToken.Access_token); + } + + beforeRequest?.Invoke(); + try + { + // Act + var response = await client.SendAsync(request); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + // Assert - Check error response + if (expectedContent != null) + { + await Assert_HasContent_Json(expectedContent, response.Content); + } + } + } + finally + { + afterRequest?.Invoke(); + } + } + + private static async Task Test_GetSSA( + string certificateFilename, + string certificatePassword, + HttpStatusCode expectedStatusCode, + bool withAccessToken = true, + BeforeSSARequest beforeRequest = null, + AfterSSARequest afterRequest = null, + string expectedContent = null) + { + 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); + request.Headers.Add("x-v", XV); + + // Supply access token with request? + if (withAccessToken) + { + var accessTokenRequest = GetAccessTokenRequest(certificateFilename, certificatePassword); + var accessTokenResponse = await client.SendAsync(accessTokenRequest); + var accessToken = JsonSerializer.Deserialize(await accessTokenResponse.Content.ReadAsStringAsync()); + + request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, accessToken.Access_token); + } + + beforeRequest?.Invoke(); + try + { + // Act + var response = await client.SendAsync(request); + + // Assert + using (new AssertionScope()) + { + // Assert - Check status code + response.StatusCode.Should().Be(expectedStatusCode); + + // Assert - Check error response + if (expectedContent != null) + { + await Assert_HasContent_Json(expectedContent, response.Content); + } + } + } + finally + { + afterRequest?.Invoke(); + } + } + + /// + /// Get HttpClient with client certificate. + /// + private static HttpClient GetClient(string certificateFilename, string certificatePassword) + { + 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); + + return new HttpClient(clientHandler); + } + + /// + /// Get HttpRequestMessage for access token request. + /// + private static HttpRequestMessage GetAccessTokenRequest(string certificateFilename, string certificatePassword) + { + static string BuildContent(string scope, string grant_type, string client_id, string client_assertion_type, string client_assertion) + { + var kvp = new KeyValuePairBuilder(); + + if (scope != null) + { + kvp.Add("scope", scope); + } + + if (grant_type != null) + { + kvp.Add("grant_type", grant_type); + } + + if (client_id != null) + { + kvp.Add("client_id", client_id); + } + + if (client_assertion_type != null) + { + kvp.Add("client_assertion_type", client_assertion_type); + } + + if (client_assertion != null) + { + kvp.Add("client_assertion", client_assertion); + } + + return kvp.Value; + } + + var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, Guid.NewGuid().ToString()); + var client_assertion = tokenizer.Generate(ISSUER, AUDIENCE, ISSUER); + + var request = new HttpRequestMessage(HttpMethod.Post, IDENTITYSERVER_URL) + { + Content = new StringContent( + BuildContent(SCOPE, GRANT_TYPE, CLIENT_ID, CLIENT_ASSERTION_TYPE, client_assertion), + Encoding.UTF8, + "application/json"), + }; + + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + + return request; + } } } 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 6ce50d0..abe999c 100644 --- a/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/IdentityServer/US14045_IdentityServer_Token_Tests.cs @@ -28,126 +28,102 @@ namespace CDR.Register.IntegrationTests.IdentityServer /// public class US14045_IdentityServer_Token_Tests : BaseTest { - 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"; - protected const string CLIENTASSERTION_GRANT_TYPE = "client_credentials"; protected const string CLIENTASSERTION_CLIENT_ID = "86ecb655-9eba-409c-9be3-59e7adf7080d"; protected const string CLIENTASSERTION_SCOPE = "cdr-register:read"; protected const string CLIENTASSERTION_CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - private static HttpClient GetClient() + public US14045_IdentityServer_Token_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) + : base(outputHelper, testFixture) { - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - - return new HttpClient(clientHandler); } - private static HttpClient GetClientWithCertificate( - string certificateFilename = CERTIFICATE_FILENAME, - string certificatePassword = CERTIFICATE_PASSWORD) + public static IEnumerable ValidCtsUrlAudienceScenarios { - 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); - - return new HttpClient(clientHandler); + get + { + string conformanceId = Guid.NewGuid().ToString(); + string tokenEndpointUrl = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; + string validIssuer = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp"; + yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}", "Audience matches Token Endpoint", tokenEndpointUrl }; + yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}", "Audience matches OIDC Issuer", tokenEndpointUrl }; + } } - private static string GetClientAssertion( - string certificateFilename = CERTIFICATE_FILENAME, - string certificatePassword = CERTIFICATE_PASSWORD, - string clientAssertionIssuer = CLIENTASSERTION_ISSUER, - string? clientAssetionAudience = null, - string subClaim = CLIENTASSERTION_ISSUER, - string? jtiClaim = null, - string? signingAlgorithm = SecurityAlgorithms.RsaSsaPssSha256) + public static IEnumerable ValidAudienceScenarios { - if (clientAssetionAudience == null) + get { - clientAssetionAudience = CLIENTASSERTION_AUDIENCE; + yield return new string[] { $"{MTLS_BaseURL}/idp/connect/token", "Audience matches Token Endpoint" }; + yield return new string[] { $"{TLS_BaseURL}/idp", "Audience matches OIDC Issuer" }; } + } - if (jtiClaim == null) + public static IEnumerable InvalidAudienceScenarios + { + get { - jtiClaim = Guid.NewGuid().ToString(); + 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[] { $"{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'" }; + yield return new string[] { $"{MTLS_BaseURL}/idp", "Audience is only partial OICD issuer match - Ends with /idp'" }; } - - var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, jtiClaim); - return tokenizer.Generate(clientAssertionIssuer, clientAssetionAudience, subClaim, signingAlgorithm: signingAlgorithm!); } - private static HttpRequestMessage GetAccessTokenRequest( - string? grant_type, - string? client_id, - string? client_assertion_type, - string? client_assertion, - string? content_type_header = "application/x-www-form-urlencoded", - string scope = CLIENTASSERTION_SCOPE, - string? tokenEndpointUrl = null) + public static IEnumerable InvalidCtsUrlAudienceScenarios { - static string BuildContent(string? grant_type, string? client_id, string? client_assertion_type, string? client_assertion, string? scope) + get { - var kvp = new KeyValuePairBuilder(); - - kvp.Add("scope", scope); - - if (grant_type != null) - { - kvp.Add("grant_type", grant_type); - } - - if (client_id != null) - { - kvp.Add("client_id", client_id); - } - - if (client_assertion_type != null) - { - kvp.Add("client_assertion_type", client_assertion_type); - } - - if (client_assertion != null) - { - kvp.Add("client_assertion", client_assertion); - } - - return kvp.Value; + string conformanceId = Guid.NewGuid().ToString(); + string tokenEndpointUrl = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; + string validIssuer = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp"; + yield return new string[] { "foo", "Audience is 'foo'", tokenEndpointUrl }; + yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/idp", "Audience start has Token Endpoint but ends with '/idp'", tokenEndpointUrl }; + yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/idp/connect/token", "Audience start has OIDC Issuer but ends with '/idp/connect/token'", tokenEndpointUrl }; + yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/foo", "Audience start has Token Endpoint but ends with '/foo'", tokenEndpointUrl }; + yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/foo", "Audience start has OIDC Issuer but ends with '/foo'", tokenEndpointUrl }; } + } - if (tokenEndpointUrl == null) - { - tokenEndpointUrl = IDENTITYSERVER_URL; - } + protected static string CLIENTASSERTION_AUDIENCE => MTLS_BaseURL + "/idp/connect/token"; - var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpointUrl) + // Use MemberData due to dynamic aud strings + [Trait("Category", "CTSONLY")] + [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 + var accessToken = new Infrastructure.AccessToken { - Content = new StringContent( - BuildContent(grant_type, client_id, client_assertion_type, client_assertion, scope), - Encoding.UTF8, - content_type_header!) + CertificateFilename = CERTIFICATE_FILENAME, + CertificatePassword = CERTIFICATE_PASSWORD, + Scope = "cdr-register:read", + Audience = aud, + TokenEndPoint = tokenEndpointUrl, + CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }; - if (string.IsNullOrEmpty(content_type_header)) - { - request.Content.Headers.ContentType = null; - } - else + HttpResponseMessage response = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); + + // Assert + using (new AssertionScope()) { - request.Content.Headers.ContentType = new MediaTypeHeaderValue(content_type_header); - } + // Assert - Check status code + response.StatusCode.Should().Be(HttpStatusCode.OK); - return request; + // Assert that access token in response + string responseString = await response.Content.ReadAsStringAsync(); + responseString.Should().Contain("access_token", because: aud + " - " + scenarioDescription); + } } [Theory] @@ -180,7 +156,7 @@ static async Task GetValidatedToken(AccessToken accessToken) }; // Validate token (throws exception if token fails to validate) - new JwtSecurityTokenHandler().ValidateToken(accessToken.access_token, validationParameters, out var validatedToken); + new JwtSecurityTokenHandler().ValidateToken(accessToken.Access_token, validationParameters, out var validatedToken); return validatedToken; } @@ -214,13 +190,13 @@ static async Task GetValidatedToken(AccessToken accessToken) if (accessToken != null) { // Assert - Check expires in - accessToken.expires_in.Should().Be(300); + accessToken.Expires_in.Should().Be(300); // Assert - Check token type - accessToken.token_type.Should().Be(JwtBearerDefaults.AuthenticationScheme); + accessToken.Token_type.Should().Be(JwtBearerDefaults.AuthenticationScheme); // Assert - Check scope - accessToken.scope.Should().Contain("cdr-register:read"); + accessToken.Scope.Should().Contain("cdr-register:read"); // Assert - Validate access token SecurityToken? validatedToken = null; @@ -619,15 +595,6 @@ public async Task AC00_TokenRequest_ValidAudience(string aud, string scenarioDes } } - public static IEnumerable ValidAudienceScenarios - { - get - { - yield return new string[] { $"{MTLS_BaseURL}/idp/connect/token", "Audience matches Token Endpoint" }; - yield return new string[] { $"{TLS_BaseURL}/idp", "Audience matches OIDC Issuer" }; - } - } - // Use MemberData due to dynamic aud strings [Theory] [MemberData(nameof(InvalidAudienceScenarios))] @@ -660,27 +627,13 @@ public async Task AC00_TokenRequest_InvalidAudience(string aud, string scenarioD } } - public static IEnumerable InvalidAudienceScenarios - { - get - { - 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[] { $"{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'" }; - yield return new string[] { $"{MTLS_BaseURL}/idp", "Audience is only partial OICD issuer match - Ends with /idp'" }; - } - } - // Use MemberData due to dynamic aud strings [Trait("Category", "CTSONLY")] [Theory] - [MemberData(nameof(ValidCtsUrlAudienceScenarios))] - public static async Task AC00_TokenRequest_ValidAudience_DynamicBasePath(string aud, string scenarioDescription, string tokenEndpointUrl) + [MemberData(nameof(InvalidCtsUrlAudienceScenarios))] + public async Task AC00_TokenRequest_InvalidAudience_DynamicBasePath(string aud, string scenarioDescription, string tokenEndpointUrl) { - Log.Information("Running positive test case for valid audience of {aud} - {scenarioDescription}", aud, scenarioDescription); + Log.Information("Running negative test case for audience of {aud} - {scenarioDescription}", aud, scenarioDescription); // Arrange - Get access token var accessToken = new Infrastructure.AccessToken @@ -691,7 +644,7 @@ public static async Task AC00_TokenRequest_ValidAudience_DynamicBasePath(string Audience = aud, TokenEndPoint = tokenEndpointUrl, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }; HttpResponseMessage response = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); @@ -700,72 +653,119 @@ public static async Task AC00_TokenRequest_ValidAudience_DynamicBasePath(string using (new AssertionScope()) { // Assert - Check status code - response.StatusCode.Should().Be(HttpStatusCode.OK); + response.StatusCode.Should().Be(HttpStatusCode.BadRequest, because: aud + " - " + scenarioDescription); - // Assert that access token in response - string responseString = await response.Content.ReadAsStringAsync(); - responseString.Should().Contain("access_token", because: aud + " - " + scenarioDescription); + // Assert - Check error response + await Assert_HasContent_Json(@"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - token validation error""}", response.Content); } } - public static IEnumerable ValidCtsUrlAudienceScenarios + private static HttpRequestMessage GetAccessTokenRequest( + string? grant_type, + string? client_id, + string? client_assertion_type, + string? client_assertion, + string? content_type_header = "application/x-www-form-urlencoded", + string scope = CLIENTASSERTION_SCOPE, + string? tokenEndpointUrl = null) { - get + static string BuildContent(string? grant_type, string? client_id, string? client_assertion_type, string? client_assertion, string? scope) { - string conformanceId = Guid.NewGuid().ToString(); - string tokenEndpointUrl = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; - string validIssuer = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp"; - yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}", "Audience matches Token Endpoint", tokenEndpointUrl }; - yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}", "Audience matches OIDC Issuer", tokenEndpointUrl }; + var kvp = new KeyValuePairBuilder(); + + kvp.Add("scope", scope); + + if (grant_type != null) + { + kvp.Add("grant_type", grant_type); + } + + if (client_id != null) + { + kvp.Add("client_id", client_id); + } + + if (client_assertion_type != null) + { + kvp.Add("client_assertion_type", client_assertion_type); + } + + if (client_assertion != null) + { + kvp.Add("client_assertion", client_assertion); + } + + return kvp.Value; } - } - // Use MemberData due to dynamic aud strings - [Trait("Category", "CTSONLY")] - [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); + if (tokenEndpointUrl == null) + { + tokenEndpointUrl = IDENTITYSERVER_URL; + } - // Arrange - Get access token - var accessToken = new Infrastructure.AccessToken + var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpointUrl) { - CertificateFilename = CERTIFICATE_FILENAME, - CertificatePassword = CERTIFICATE_PASSWORD, - Scope = "cdr-register:read", - Audience = aud, - TokenEndPoint = tokenEndpointUrl, - CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + Content = new StringContent( + BuildContent(grant_type, client_id, client_assertion_type, client_assertion, scope), + Encoding.UTF8, + content_type_header!), }; - HttpResponseMessage response = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); - - // Assert - using (new AssertionScope()) + if (string.IsNullOrEmpty(content_type_header)) { - // Assert - Check status code - response.StatusCode.Should().Be(HttpStatusCode.BadRequest, because: aud + " - " + scenarioDescription); - - // Assert - Check error response - await Assert_HasContent_Json(@"{""error"":""invalid_client"",""error_description"":""Invalid client_assertion - token validation error""}", response.Content); + request.Content.Headers.ContentType = null; + } + else + { + request.Content.Headers.ContentType = new MediaTypeHeaderValue(content_type_header); } + + return request; } - public static IEnumerable InvalidCtsUrlAudienceScenarios + private static HttpClient GetClientWithCertificate( + string certificateFilename = CERTIFICATE_FILENAME, + string certificatePassword = CERTIFICATE_PASSWORD) { - get + 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); + + return new HttpClient(clientHandler); + } + + private static string GetClientAssertion( + string certificateFilename = CERTIFICATE_FILENAME, + string certificatePassword = CERTIFICATE_PASSWORD, + string clientAssertionIssuer = CLIENTASSERTION_ISSUER, + string? clientAssetionAudience = null, + string subClaim = CLIENTASSERTION_ISSUER, + string? jtiClaim = null, + string? signingAlgorithm = SecurityAlgorithms.RsaSsaPssSha256) + { + if (clientAssetionAudience == null) { - string conformanceId = Guid.NewGuid().ToString(); - string tokenEndpointUrl = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp/connect/token"; - string validIssuer = $"{GenerateDynamicCtsUrl(IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL, conformanceId)}/idp"; - yield return new string[] { "foo", "Audience is 'foo'", tokenEndpointUrl }; - yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/idp", "Audience start has Token Endpoint but ends with '/idp'", tokenEndpointUrl }; - yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/idp/connect/token", "Audience start has OIDC Issuer but ends with '/idp/connect/token'", tokenEndpointUrl }; - yield return new string[] { $"{ReplaceSecureHostName(tokenEndpointUrl, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/foo", "Audience start has Token Endpoint but ends with '/foo'", tokenEndpointUrl }; - yield return new string[] { $"{ReplacePublicHostName(validIssuer, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL)}/foo", "Audience start has OIDC Issuer but ends with '/foo'", tokenEndpointUrl }; + clientAssetionAudience = CLIENTASSERTION_AUDIENCE; + } + + if (jtiClaim == null) + { + jtiClaim = Guid.NewGuid().ToString(); } + + var tokenizer = new PrivateKeyJwt(certificateFilename, certificatePassword, jtiClaim); + return tokenizer.Generate(clientAssertionIssuer, clientAssetionAudience, subClaim, signingAlgorithm: signingAlgorithm!); + } + + private static HttpClient GetClient() + { + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + return new HttpClient(clientHandler); } } } diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs index e7b5fa5..aedead1 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/API.cs @@ -1,9 +1,9 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using System; +using System; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; #nullable enable @@ -18,48 +18,48 @@ public class Api private const string CERTIFICATE_CN_HEADER_NAME = "X-SSLClientCertCN"; /// - /// Filename of certificate to use. + /// Gets or sets Filename of certificate to use. /// If null then no certificate will be attached to the request. /// public string? CertificateFilename { get; set; } /// - /// Password for certificate. + /// Gets or sets Password for certificate. /// If null then no certificate password will be set. /// public string? CertificatePassword { get; set; } /// - /// Access token. + /// Gets or sets Access token. /// If null then no access token will be attached to the request. /// See the AccessToken class to generate an access token. /// public string? AccessToken { get; set; } /// - /// The HttpMethod of the request. + /// Gets or sets the HttpMethod of the request. /// public HttpMethod? HttpMethod { get; set; } /// - /// The URL of the request. + /// Gets or sets URL of the request. /// public string? URL { get; set; } /// - /// The x-v header. + /// Gets or sets x-v header. /// If null then no x-v header will be set. /// public string? XV { get; set; } /// - /// The x-min-v header. + /// Gets or sets x-min-v header. /// If null then no x-min-v header will be set. /// public string? XMinV { get; set; } /// - /// The If-None-Match header (an ETag). + /// Gets or sets If-None-Match header (an ETag). /// If null then no If-None-Match header will be set. /// public string? IfNoneMatch { get; set; } @@ -77,41 +77,41 @@ public async Task SendAsync() // Build request HttpRequestMessage BuildRequest() { - if (HttpMethod == null) + if (this.HttpMethod == null) { - throw new Exception($"{nameof(Api)}.{nameof(SendAsync)}.{nameof(BuildRequest)} - {nameof(HttpMethod)} not set"); + throw new Exception($"{nameof(Api)}.{nameof(this.SendAsync)}.{nameof(BuildRequest)} - {nameof(this.HttpMethod)} not set"); } - var request = new HttpRequestMessage(HttpMethod, URL); + var request = new HttpRequestMessage(this.HttpMethod, this.URL); // Attach access token if provided - if (AccessToken != null) + if (this.AccessToken != null) { - request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, AccessToken); + request.Headers.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, this.AccessToken); } // Set x-v header if provided - if (XV != null) + if (this.XV != null) { - request.Headers.Add("x-v", XV); + request.Headers.Add("x-v", this.XV); } // Set x-min-v header if provided - if (XMinV != null) + if (this.XMinV != null) { - request.Headers.Add("x-min-v", XMinV); + request.Headers.Add("x-min-v", this.XMinV); } // Set If-None-Match header if provided - if (IfNoneMatch != null) + if (this.IfNoneMatch != null) { - request.Headers.Add("If-None-Match", $"\"{IfNoneMatch}\""); + request.Headers.Add("If-None-Match", $"\"{this.IfNoneMatch}\""); } - if (CertificateThumbprint != null && CertificateCn != null) + if (this.CertificateThumbprint != null && this.CertificateCn != null) { - request.Headers.Add(CERTIFICATE_THUMBPRINT_HEADER_NAME, CertificateThumbprint); - request.Headers.Add(CERTIFICATE_CN_HEADER_NAME, CertificateCn); + request.Headers.Add(CERTIFICATE_THUMBPRINT_HEADER_NAME, this.CertificateThumbprint); + request.Headers.Add(CERTIFICATE_CN_HEADER_NAME, this.CertificateCn); } return request; @@ -123,15 +123,15 @@ async Task SendRequest(HttpRequestMessage request) HttpClient GetClient() { // Attach client certificate - if (CertificateFilename != null) + if (this.CertificateFilename != null) { var clientHandler = new HttpClientHandler(); clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; clientHandler.ClientCertificates.Add(new X509Certificate2( - CertificateFilename, - CertificatePassword, + this.CertificateFilename, + this.CertificatePassword, 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 818847f..351adaa 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/AccessToken.cs @@ -24,13 +24,13 @@ public class AccessToken private static readonly string AUDIENCE = IDENTITYSERVER_URL; /// - /// Filename of certificate to use. + /// Gets or sets Filename of certificate to use. /// If null then no certificate will be attached to the request. /// public string? CertificateFilename { get; set; } /// - /// Password for certificate. + /// Gets or sets Password for certificate. /// If null then no certificate password will be set. /// public string? CertificatePassword { get; set; } @@ -53,14 +53,73 @@ public class AccessToken public string? CertificateCn { get; set; } = null; + /// + /// Get an access token from Identity Server. + /// + /// representing the asynchronous operation. + public async Task GetAsync(bool addCertificateToRequest = true) + { + HttpResponseMessage response = await this.SendAccessTokenRequest(addCertificateToRequest); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new Exception($"{nameof(AccessToken)}.{nameof(this.GetAsync)} - Error getting access token"); + } + + // Deserialize the access token from the response + var accessToken = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + // And return the access token + return accessToken?.Access_token; + } + + public async Task SendAccessTokenRequest(bool addCertificateToRequest = true) + { + // Create ClientHandler + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + // Attach client certificate to handler + if (this.CertificateFilename != null && addCertificateToRequest) + { + var clientCertificate = new X509Certificate2(this.CertificateFilename, this.CertificatePassword, X509KeyStorageFlags.Exportable); + clientHandler.ClientCertificates.Add(clientCertificate); + } + + // Create HttpClient + using var client = new HttpClient(clientHandler); + + // Create an access token request + var request = this.CreateAccessTokenRequest( + this.CertificateFilename!, + this.CertificatePassword!, + this.Issuer, + this.Audience, + this.Scope, + this.GrantType, + this.ClientId!, + this.ClientAssertionType, + this.CertificateThumbprint, + this.CertificateCn); + + // Request the access token + return await client.SendAsync(request); + } + /// /// Get HttpRequestMessage for access token request. /// private HttpRequestMessage CreateAccessTokenRequest( - string certificateFilename, string certificatePassword, + string certificateFilename, + string certificatePassword, string? issuer, string audience, - string scope, string grant_type, string client_id, string client_assertion_type, string? certificateThumprint, string? certificateCn) + 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) { @@ -97,12 +156,12 @@ 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 ?? string.Empty); - var request = new HttpRequestMessage(HttpMethod.Post, TokenEndPoint) + var request = new HttpRequestMessage(HttpMethod.Post, this.TokenEndPoint) { Content = new StringContent( BuildContent(scope, grant_type, client_id, client_assertion_type, client_assertion), Encoding.UTF8, - "application/json") + "application/json"), }; request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); @@ -115,50 +174,5 @@ static string BuildContent(string scope, string grant_type, string client_id, st return request; } - - /// - /// Get an access token from Identity Server. - /// - public async Task GetAsync(bool addCertificateToRequest = true) - { - HttpResponseMessage response = await SendAccessTokenRequest(addCertificateToRequest); - - if (response.StatusCode != HttpStatusCode.OK) - { - throw new Exception($"{nameof(AccessToken)}.{nameof(GetAsync)} - Error getting access token"); - } - - // Deserialize the access token from the response - var accessToken = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); - - // And return the access token - return accessToken?.access_token; - } - - public async Task SendAccessTokenRequest(bool addCertificateToRequest = 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); - } - - // Create HttpClient - using var client = new HttpClient(clientHandler); - - // Create an access token request - var request = CreateAccessTokenRequest( - CertificateFilename!, CertificatePassword!, - Issuer, Audience, - 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 57616e8..a1a52ba 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/KeyValuePairBuilder.cs @@ -11,23 +11,23 @@ public class KeyValuePairBuilder private readonly StringBuilder _sb = new StringBuilder(); - /// - /// Get key/value pairs as string. - /// - public string Value => _sb.ToString(); - private int _count = 0; - /// - /// Number of key/value pairs. - /// - public int Count => _count; - public KeyValuePairBuilder(string delimiter = "&") { - _delimiter = delimiter; + this._delimiter = delimiter; } + /// + /// Gets key/value pairs as string. + /// + public string Value => this._sb.ToString(); + + /// + /// Gets number of key/value pairs. + /// + public int Count => this._count; + /// /// Append key/value pair. /// @@ -35,14 +35,14 @@ public KeyValuePairBuilder(string delimiter = "&") /// Value to add. public void Add(string key, string value) { - if (_sb.Length > 0) + if (this._sb.Length > 0) { - _sb.Append(_delimiter); + this._sb.Append(this._delimiter); } - _sb.Append($"{key}={value}"); + this._sb.Append($"{key}={value}"); - _count++; + this._count++; } /// @@ -52,7 +52,7 @@ public void Add(string key, string value) /// Value to add. public void Add(string key, int value) { - Add(key, value.ToString()); + this.Add(key, value.ToString()); } /// @@ -62,7 +62,7 @@ public void Add(string key, int value) /// Value to add. public void Add(string key, long value) { - Add(key, value.ToString()); + this.Add(key, value.ToString()); } } } diff --git a/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs b/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs index 6ace4b9..65f3e75 100644 --- a/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs +++ b/Source/CDR.Register.IntegrationTests/Infrastructure/PrivateKeyJwt.cs @@ -17,8 +17,6 @@ namespace CDR.Register.IntegrationTests.Infrastructure /// public class PrivateKeyJwt { - public string PrivateKeyBase64 { get; private set; } - private readonly string jtiClaim; /// @@ -26,8 +24,8 @@ public class PrivateKeyJwt /// public PrivateKeyJwt() { - PrivateKeyBase64 = string.Empty; - jtiClaim = string.Empty; + this.PrivateKeyBase64 = string.Empty; + this.jtiClaim = string.Empty; } /// @@ -42,14 +40,14 @@ public PrivateKeyJwt(string privateKey) { if (!string.IsNullOrEmpty(privateKey)) { - PrivateKeyBase64 = FormatKey(privateKey); + this.PrivateKeyBase64 = FormatKey(privateKey); } else { - PrivateKeyBase64 = string.Empty; + this.PrivateKeyBase64 = string.Empty; } - jtiClaim = string.Empty; + this.jtiClaim = string.Empty; } /// @@ -65,7 +63,7 @@ public PrivateKeyJwt(string certFilePath, string pwd, string jtiClaim) var rsa = cert.GetRSAPrivateKey(); if (rsa == null) { - PrivateKeyBase64 = string.Empty; + this.PrivateKeyBase64 = string.Empty; jtiClaim = string.Empty; } @@ -74,6 +72,8 @@ public PrivateKeyJwt(string certFilePath, string pwd, string jtiClaim) this.jtiClaim = jtiClaim; } + public string PrivateKeyBase64 { get; private set; } + /// /// Load the private key from a file. /// @@ -91,7 +91,7 @@ public void LoadPrivateKeyFromFile(string filePath) } var privateKey = File.ReadAllText(filePath); - PrivateKeyBase64 = FormatKey(privateKey); + this.PrivateKeyBase64 = FormatKey(privateKey); } /// @@ -100,10 +100,11 @@ public void LoadPrivateKeyFromFile(string filePath) /// 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. + /// The signingAlgorithm of the JWT, default is RsaSsaPssSha256. /// A base64 encoded JWT. public string Generate(string? issuer, string audience, string subject, string signingAlgorithm = SecurityAlgorithms.RsaSsaPssSha256) { - if (string.IsNullOrEmpty(PrivateKeyBase64)) + if (string.IsNullOrEmpty(this.PrivateKeyBase64)) { throw new ArgumentException("privateKey must be set"); } @@ -118,7 +119,7 @@ public string Generate(string? issuer, string audience, string subject, string s throw new ArgumentException("audience must be provided"); } - var privateKeyBytes = Convert.FromBase64String(PrivateKeyBase64); + var privateKeyBytes = Convert.FromBase64String(this.PrivateKeyBase64); using (var rsa = RSA.Create()) { @@ -129,8 +130,8 @@ public string Generate(string? issuer, string audience, string subject, string s KeyId = kid, CryptoProviderFactory = new CryptoProviderFactory() { - CacheSignatureProviders = false - } + CacheSignatureProviders = false, + }, }; var descriptor = new SecurityTokenDescriptor @@ -142,12 +143,12 @@ public string Generate(string? issuer, string audience, string subject, string s SigningCredentials = new SigningCredentials(privateSecurityKey, signingAlgorithm), NotBefore = null, IssuedAt = null, - Claims = new Dictionary() + Claims = new Dictionary(), }; - if (!string.IsNullOrEmpty(jtiClaim)) + if (!string.IsNullOrEmpty(this.jtiClaim)) { - descriptor.Claims.Add("jti", jtiClaim); + descriptor.Claims.Add("jti", this.jtiClaim); } var tokenHandler = new JsonWebTokenHandler(); @@ -173,7 +174,7 @@ private static string GenerateKeyId(RSAParameters rsaParams, out string e, out s { { "e", e }, { "kty", "RSA" }, - { "n", n } + { "n", n }, }; var hash = SHA256.Create(); var hashBytes = hash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(dict))); diff --git a/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs b/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs index a62ac87..184431b 100644 --- a/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Miscellaneous/US12677_RegisterEnrolment_Tests.cs @@ -23,38 +23,6 @@ public US12677_RegisterEnrolment_Tests(ITestOutputHelper outputHelper, TestFixtu { } - /// - /// Get the repository as json. - /// - private static async Task GetJson() - { - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - var client = new HttpClient(clientHandler); - var request = new HttpRequestMessage(HttpMethod.Get, ADMIN_URL); - var response = await client.SendAsync(request); - return response; - } - - /// - /// Load (replace) the repository from json. - /// - private static async Task PostJson(string json) - { - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - var client = new HttpClient(clientHandler); - var request = new HttpRequestMessage(HttpMethod.Post, ADMIN_URL); - if (json != null) - { - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - } - - var response = await client.SendAsync(request); - return response; - } - [Fact] public async Task AC01_Get_ShouldRespondWith_200OK_RepositoryAsJson() { @@ -129,54 +97,6 @@ public async Task AC04_Post_WithInvalidJson_ShouldRespondWith_400BadRequest() } } - // Remove "foreign keys" (LegalEntityId, ParticipationId, etc) from child collections as their values can be dynamic. - private static void RemoveForeignKeys(JToken jToken) - { - 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; - } - } - } - - ClearField(jToken, "legalEntityId"); - ClearField(jToken, "participationId"); - ClearField(jToken, "brandId"); - ClearField(jToken, "softwareProductId"); - ClearField(jToken, "softwareProductCertificateId"); - - ClearField(jToken, "legalEntityStatusId"); - } - - private static JToken Cleanup(JToken jToken) - { - // Sort - jToken["legalEntities"].SortArray("legalEntityId"); - jToken.SortArray("$..legalEntities..participations", "participationId"); - jToken.SortArray("$..legalEntities..participations..brands", "brandId"); - jToken.SortArray("$..legalEntities..participations..brands..softwareProducts", "softwareProductId"); - - RemoveForeignKeys(jToken); - - jToken.RemoveNulls(); - jToken.RemoveEmptyArrays(); - - // Fix mismatch in version, convert to string - 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 - jToken = JToken.Parse(jToken.ToString().ToUpper()); - - return jToken; - } - [Fact] public async Task AC05_AC06_Post_WithValidJson_ThenGet_ShouldRespondWith_200OK_ReturnedJsonMatchesPostedJson() { @@ -268,5 +188,85 @@ static void UpdateNames(JToken jToken) JToken.DeepEquals(jToken, jTokenResponse).Should().BeTrue(); } } + + /// + /// Get the repository as json. + /// + private static async Task GetJson() + { + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + var client = new HttpClient(clientHandler); + var request = new HttpRequestMessage(HttpMethod.Get, ADMIN_URL); + var response = await client.SendAsync(request); + return response; + } + + /// + /// Load (replace) the repository from json. + /// + private static async Task PostJson(string json) + { + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + var client = new HttpClient(clientHandler); + var request = new HttpRequestMessage(HttpMethod.Post, ADMIN_URL); + if (json != null) + { + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + + var response = await client.SendAsync(request); + return response; + } + + private static JToken Cleanup(JToken jToken) + { + // Sort + jToken["legalEntities"].SortArray("legalEntityId"); + jToken.SortArray("$..legalEntities..participations", "participationId"); + jToken.SortArray("$..legalEntities..participations..brands", "brandId"); + jToken.SortArray("$..legalEntities..participations..brands..softwareProducts", "softwareProductId"); + + RemoveForeignKeys(jToken); + + jToken.RemoveNulls(); + jToken.RemoveEmptyArrays(); + + // Fix mismatch in version, convert to string + 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 + jToken = JToken.Parse(jToken.ToString().ToUpper()); + + return jToken; + } + + // Remove "foreign keys" (LegalEntityId, ParticipationId, etc) from child collections as their values can be dynamic. + private static void RemoveForeignKeys(JToken jToken) + { + 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; + } + } + } + + ClearField(jToken, "legalEntityId"); + ClearField(jToken, "participationId"); + ClearField(jToken, "brandId"); + ClearField(jToken, "softwareProductId"); + ClearField(jToken, "softwareProductCertificateId"); + + ClearField(jToken, "legalEntityStatusId"); + } } } diff --git a/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs b/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs index 55b293d..f776a2f 100644 --- a/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs +++ b/Source/CDR.Register.IntegrationTests/Miscellaneous/US50483_DynamicUrl_Tests.cs @@ -1,4 +1,8 @@ -using CDR.Register.IntegrationTests.API.Update; +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CDR.Register.IntegrationTests.API.Update; using CDR.Register.IntegrationTests.Extensions; using CDR.Register.IntegrationTests.Infrastructure; using FluentAssertions; @@ -6,10 +10,6 @@ using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Serilog; -using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -17,13 +17,13 @@ namespace CDR.Register.IntegrationTests.Miscellaneous { public class US50483_DynamicUrl_Tests : BaseTest { + private const string DEFAULT_SOFTWAREPRODUCT_ID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; + public US50483_DynamicUrl_Tests(ITestOutputHelper outputHelper, TestFixture testFixture) : base(outputHelper, testFixture) { } - private const string DEFAULT_SOFTWAREPRODUCT_ID = "86ECB655-9EBA-409C-9BE3-59E7ADF7080D"; - [Trait("Category", "CTSONLY")] [Theory] [InlineData("Invalid Certificate Thumbprint", "foo", DEFAULT_CERTIFICATE_COMMON_NAME)] @@ -45,7 +45,7 @@ public async Task AC01_Get_Data_Holder_Brands_Invalid_Certificate_Header_For_Acc Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }.GetAsync(addCertificateToRequest: false); var api = new Infrastructure.Api @@ -55,7 +55,7 @@ public async Task AC01_Get_Data_Holder_Brands_Invalid_Certificate_Header_For_Acc AccessToken = accessToken, XV = "2", CertificateThumbprint = certificateThumbPrint, - CertificateCn = certificateCommonName + CertificateCn = certificateCommonName, }; // Act @@ -88,7 +88,7 @@ public async Task AC02_Get_Access_Token_Invalid_Certificate_Header_For_Access_To Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = certificateThumbPrint, - CertificateCn = certificateCommonName + CertificateCn = certificateCommonName, }; HttpResponseMessage response = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); @@ -117,7 +117,7 @@ public async Task AC03_Get_Access_Token_Invalid_Url_Regular_Expression_For_Acces Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }; HttpResponseMessage response = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); @@ -145,7 +145,7 @@ public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }.GetAsync(addCertificateToRequest: false); var api = new Infrastructure.Api @@ -155,7 +155,7 @@ public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() AccessToken = accessToken, XV = "2", CertificateThumbprint = DEFAULT_CERTIFICATE_THUMBPRINT, - CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME + CertificateCn = DEFAULT_CERTIFICATE_COMMON_NAME, }; // Act @@ -167,21 +167,6 @@ public async Task AC04_Get_Data_Holder_Brands_Invalid_Access_Token_Issuer() await VerifyInvalidTokenRepsonse(response); } - private static async Task VerifyInvalidTokenRepsonse(HttpResponseMessage response) - { - using (new AssertionScope()) - { - response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); - - ExpectedErrors expectedErrors = new ExpectedErrors(); - expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.Unauthorized, "invalid_token"); - - 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")] @@ -208,7 +193,7 @@ public async Task AC05_Get_Access_Token_With_Valid_Certificate_Header(string tes Audience = ReplaceSecureHostName(tokenEndpoint, IDENTITY_PROVIDER_DOWNSTREAM_BASE_URL), TokenEndPoint = tokenEndpoint, CertificateThumbprint = certificateThumbPrint, - CertificateCn = certificateCommonName + CertificateCn = certificateCommonName, }; HttpResponseMessage accessTokenResponse = await accessToken.SendAccessTokenRequest(addCertificateToRequest: false); @@ -226,7 +211,7 @@ public async Task AC05_Get_Access_Token_With_Valid_Certificate_Header(string tes AccessToken = validAccessToken, XV = "2", CertificateThumbprint = certificateThumbPrint, - CertificateCn = certificateCommonName + CertificateCn = certificateCommonName, }; // Act @@ -257,5 +242,20 @@ private static void SetCertificateCommonName(string softwareProductId, string co throw new Exception($"Common name was not updated for Software Product: {softwareProductId}"); } } + + private static async Task VerifyInvalidTokenRepsonse(HttpResponseMessage response) + { + using (new AssertionScope()) + { + response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + + ExpectedErrors expectedErrors = new ExpectedErrors(); + expectedErrors.AddExpectedError(ExpectedErrors.ErrorType.Unauthorized, "invalid_token"); + + string actualErrors = await response.Content.ReadAsStringAsync(); + + Assert_Json(GetJsonFromModel(expectedErrors), actualErrors); + } + } } } diff --git a/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs b/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs index 8756cd1..e09301e 100644 --- a/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs +++ b/Source/CDR.Register.IntegrationTests/Models/AccessToken.cs @@ -1,16 +1,22 @@ -namespace CDR.Register.IntegrationTests.Models +using System.Text.Json.Serialization; + +namespace CDR.Register.IntegrationTests.Models { /// /// Access token. /// public class AccessToken { - public string access_token { get; set; } + [JsonPropertyName("access_token")] + public string Access_token { get; set; } - public int expires_in { get; set; } + [JsonPropertyName("expires_in")] + public int Expires_in { get; set; } - public string token_type { get; set; } + [JsonPropertyName("token_type")] + public string Token_type { get; set; } - public string scope { get; set; } + [JsonPropertyName("scope")] + public string Scope { get; set; } } } diff --git a/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs b/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs index 284ec98..d5b2d37 100644 --- a/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs +++ b/Source/CDR.Register.IntegrationTests/Models/DataHolderMetadata.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using Newtonsoft.Json; namespace CDR.Register.IntegrationTests.Models { diff --git a/Source/CDR.Register.IntegrationTests/Models/DataRecipient.cs b/Source/CDR.Register.IntegrationTests/Models/DataRecipient.cs new file mode 100644 index 0000000..a87ff6a --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/Models/DataRecipient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace CDR.Register.IntegrationTests.Models +{ + public class DataRecipient + { + public DataRecipient() + { + this.DataRecipientBrands = new List(); + } + + [JsonProperty("accreditationNumber")] + public string AccreditationNumber { get; set; } + + [JsonProperty("legalEntityId")] + public string LegalEntityId { get; set; } + + [JsonProperty("legalEntityName")] + public string LegalEntityName { get; set; } + + [JsonProperty("industry")] + public string Industry { get; set; } + + [JsonProperty("logoUri")] + public string LogoUri { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("dataRecipientBrands")] + public List DataRecipientBrands { get; set; } + + [JsonProperty("lastUpdated")] + public DateTime LastUpdated { get; set; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/Models/DataRecipientBrand.cs b/Source/CDR.Register.IntegrationTests/Models/DataRecipientBrand.cs new file mode 100644 index 0000000..72c797e --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/Models/DataRecipientBrand.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace CDR.Register.IntegrationTests.Models +{ + public class DataRecipientBrand + { + public DataRecipientBrand() + { + this.SoftwareProducts = new List(); + } + + [JsonProperty("dataRecipientBrandId")] + public string DataRecipientBrandId { get; set; } + + [JsonProperty("brandName")] + public string BrandName { get; set; } + + [JsonProperty("logoUri")] + public string LogoUri { get; set; } + + [JsonProperty("softwareProducts")] + public List SoftwareProducts { get; set; } + + [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 3206879..e7b265a 100644 --- a/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs +++ b/Source/CDR.Register.IntegrationTests/Models/DataRecipientMetadata.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using Newtonsoft.Json; namespace CDR.Register.IntegrationTests.Models { diff --git a/Source/CDR.Register.IntegrationTests/Models/DataRecipient_V2.cs b/Source/CDR.Register.IntegrationTests/Models/DataRecipient_V2.cs new file mode 100644 index 0000000..79d78eb --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/Models/DataRecipient_V2.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace CDR.Register.IntegrationTests.Models +{ + public class DataRecipient_V2 + { + public DataRecipient_V2() + { + this.DataRecipientBrands = new List(); + } + + [JsonProperty("accreditationNumber")] + public string AccreditationNumber { get; set; } + + [JsonProperty("legalEntityId")] + public string LegalEntityId { get; set; } + + [JsonProperty("legalEntityName")] + public string LegalEntityName { get; set; } + + [JsonProperty("industry")] + public string[] Industry { get; set; } + + [JsonProperty("logoUri")] + public string LogoUri { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("dataRecipientBrands")] + public List DataRecipientBrands { get; set; } + + [JsonProperty("lastUpdated")] + public DateTime LastUpdated { get; set; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs b/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs index cb4aecd..3964337 100644 --- a/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs +++ b/Source/CDR.Register.IntegrationTests/Models/ExpectedApiErrors.cs @@ -16,7 +16,7 @@ public class ExpectedApiErrors [JsonProperty("meta")] public MetaData Meta { get; set; } - public class MetaData + public partial 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 4566a09..d0d1d60 100644 --- a/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs +++ b/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; +using Newtonsoft.Json; namespace CDR.Register.IntegrationTests.Models { @@ -8,138 +8,10 @@ public class ExpectedDataRecipients { public ExpectedDataRecipients() { - Data = new List(); + this.Data = new List(); } [JsonProperty("data")] public List Data { get; set; } } - - public class ExpectedDataRecipients_V2 - { - public ExpectedDataRecipients_V2() - { - Data = new List(); - } - - [JsonProperty("data")] - public List Data { get; set; } - } - - public class DataRecipient - { - public DataRecipient() - { - DataRecipientBrands = new List(); - } - - [JsonProperty("accreditationNumber")] - public string AccreditationNumber { get; set; } - - [JsonProperty("legalEntityId")] - public string LegalEntityId { get; set; } - - [JsonProperty("legalEntityName")] - public string LegalEntityName { get; set; } - - [JsonProperty("industry")] - public string Industry { get; set; } - - [JsonProperty("logoUri")] - public string LogoUri { get; set; } - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("dataRecipientBrands")] - public List DataRecipientBrands { get; set; } - - [JsonProperty("lastUpdated")] - public DateTime LastUpdated { get; set; } - } - - public class DataRecipient_V2 - { - public DataRecipient_V2() - { - DataRecipientBrands = new List(); - } - - [JsonProperty("accreditationNumber")] - public string AccreditationNumber { get; set; } - - [JsonProperty("legalEntityId")] - public string LegalEntityId { get; set; } - - [JsonProperty("legalEntityName")] - public string LegalEntityName { get; set; } - - [JsonProperty("industry")] - public string[] Industry { get; set; } - - [JsonProperty("logoUri")] - public string LogoUri { get; set; } - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("dataRecipientBrands")] - public List DataRecipientBrands { get; set; } - - [JsonProperty("lastUpdated")] - public DateTime LastUpdated { get; set; } - } - - public class DataRecipientBrand - { - public DataRecipientBrand() - { - SoftwareProducts = new List(); - } - - [JsonProperty("dataRecipientBrandId")] - public string DataRecipientBrandId { get; set; } - - [JsonProperty("brandName")] - public string BrandName { get; set; } - - [JsonProperty("logoUri")] - public string LogoUri { get; set; } - - [JsonProperty("softwareProducts")] - public List SoftwareProducts { get; set; } - - [JsonProperty("status")] - public string Status { get; set; } - } - - public class SoftwareProduct - { - [JsonProperty("softwareProductId")] - public string SoftwareProductId { get; set; } - - [JsonProperty("softwareProductName")] - public string SoftwareProductName { get; set; } - - [JsonProperty("softwareProductDescription")] - public string SoftwareProductDescription { get; set; } - - [JsonProperty("logoUri")] - public string LogoUri { get; set; } - - [JsonProperty("recipientBaseUri")] - public string RecipientBaseUri { get; set; } - - [JsonProperty("redirectUri")] - public string RedirectUri { get; set; } - - [JsonProperty("jwksUri")] - public string JwksUri { get; set; } - - [JsonProperty("scope")] - public string Scope { get; set; } - - [JsonProperty("status")] - public string Status { get; set; } - } } diff --git a/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients_V2.cs b/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients_V2.cs new file mode 100644 index 0000000..66c3afb --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/Models/ExpectedDataRecipients_V2.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace CDR.Register.IntegrationTests.Models +{ + public class ExpectedDataRecipients_V2 + { + public ExpectedDataRecipients_V2() + { + this.Data = new List(); + } + + [JsonProperty("data")] + public List Data { get; set; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/Models/SoftwareProduct.cs b/Source/CDR.Register.IntegrationTests/Models/SoftwareProduct.cs new file mode 100644 index 0000000..8dc1994 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/Models/SoftwareProduct.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; + +namespace CDR.Register.IntegrationTests.Models +{ + public class SoftwareProduct + { + [JsonProperty("softwareProductId")] + public string SoftwareProductId { get; set; } + + [JsonProperty("softwareProductName")] + public string SoftwareProductName { get; set; } + + [JsonProperty("softwareProductDescription")] + public string SoftwareProductDescription { get; set; } + + [JsonProperty("logoUri")] + public string LogoUri { get; set; } + + [JsonProperty("recipientBaseUri")] + public string RecipientBaseUri { get; set; } + + [JsonProperty("redirectUri")] + public string RedirectUri { get; set; } + + [JsonProperty("jwksUri")] + public string JwksUri { get; set; } + + [JsonProperty("scope")] + public string Scope { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/AuthDetail.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/AuthDetail.cs new file mode 100644 index 0000000..46238d3 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/AuthDetail.cs @@ -0,0 +1,20 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [Table("AuthDetail")] + internal class AuthDetail + { + [ExplicitKey] + public Guid BrandId { get; set; } + + public int? RegisterUTypeId { get; set; } = 1; + + public string? JwksEndpoint { get; set; } = "foo"; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/Brand.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/Brand.cs new file mode 100644 index 0000000..aa0d054 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/Brand.cs @@ -0,0 +1,26 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/DatabaseSeeder.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/DatabaseSeeder.cs new file mode 100644 index 0000000..4538666 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/DatabaseSeeder.cs @@ -0,0 +1,52 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System.Threading.Tasks; +using Dapper; +using Dapper.Contrib.Extensions; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + internal static class DatabaseSeeder + { + 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" }); + } + + 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"); + } + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/Endpoint.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/Endpoint.cs new file mode 100644 index 0000000..4b7675a --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/Endpoint.cs @@ -0,0 +1,28 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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"; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/LegalEntity.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/LegalEntity.cs new file mode 100644 index 0000000..246fdb3 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/LegalEntity.cs @@ -0,0 +1,20 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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"; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/Participation.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/Participation.cs new file mode 100644 index 0000000..516baca --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/Participation.cs @@ -0,0 +1,24 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/ParticipationStatus.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/ParticipationStatus.cs new file mode 100644 index 0000000..34c925c --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/ParticipationStatus.cs @@ -0,0 +1,17 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [Table("ParticipationStatus")] + internal class ParticipationStatus + { + [ExplicitKey] + public int? ParticipationStatusId { get; set; } + + public string? ParticipationStatusCode { get; set; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProduct.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProduct.cs new file mode 100644 index 0000000..78ae0fb --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProduct.cs @@ -0,0 +1,44 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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; } + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProductCertificate.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProductCertificate.cs new file mode 100644 index 0000000..c5119b8 --- /dev/null +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/SoftwareProductCertificate.cs @@ -0,0 +1,22 @@ +// #define DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + +using System; +using Dapper.Contrib.Extensions; + +#nullable enable + +namespace CDR.Register.IntegrationTests.TemporalTables +{ + [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"; + } +} diff --git a/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs b/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs index e3b7666..33b810e 100644 --- a/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs +++ b/Source/CDR.Register.IntegrationTests/TemporalTables/US29068_TemporalTableTests.cs @@ -18,95 +18,8 @@ namespace CDR.Register.IntegrationTests.TemporalTables { public class US29068_TemporalTableTests : BaseTest0 { - private static async Task GetTableJson(SqlConnection connection, string tableName, DateTime? pointInTimeUTC = null) - { - string orderby = tableName.ToUpper() switch - { - "AUTHDETAIL" => "BrandID", - "ENDPOINT" => "BrandID", - _ => $"{tableName}ID" - }; - - IEnumerable? data; - - string sql = @$" - select * from {tableName} - {(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 { }); - } - private delegate Task Mutate(SqlConnection connection); - private async Task Test(string tableName, Mutate mutate) - { - await Test(new string[] { tableName }, mutate); - } - - private static async Task Test(string[] tableNames, Mutate mutate) - { - // Arrange - await DatabaseSeeder.Execute(); - - using var registerConnection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); - await registerConnection.OpenAsync(); - - var pointInTimeUTC = DateTime.UtcNow; - - var initialData = new Dictionary(); - foreach (string tableName in tableNames) - { - initialData.Add(tableName, await GetTableJson(registerConnection, tableName)); - } - - // Act - Mutate data - await Task.Delay(500); - if (mutate != null) - { - await mutate(registerConnection); - } - - var modifiedData = new Dictionary(); - foreach (string tableName in tableNames) - { - modifiedData.Add(tableName, await GetTableJson(registerConnection, tableName)); - } - - // Assert - var pointInTimeData = new Dictionary(); - foreach (string tableName in tableNames) - { - pointInTimeData.Add(tableName, await GetTableJson(registerConnection, tableName, pointInTimeUTC)); - } - -#if DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON - foreach (string tableName in tableNames) - { - File.WriteAllText($"c:/temp/{tableName}_initial.json", initialData[tableName]); - File.WriteAllText($"c:/temp/{tableName}_modified.json", modifiedData[tableName]); - File.WriteAllText($"c:/temp/{tableName}_pointintime.json", pointInTimeData[tableName]); - } -#endif - - using (new AssertionScope()) - { - // Check data was actually modified - foreach (string tableName in tableNames) - { - modifiedData[tableName].Should().NotBe(initialData[tableName]); - } - - // Check point in time data matches initial data - foreach (string tableName in tableNames) - { - pointInTimeData[tableName].Should().Be(initialData[tableName]); - } - } - } - // [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] @@ -124,29 +37,29 @@ await Test(tableName, async (connection) => "LegalEntity" => connection.InsertAsync(new LegalEntity()), "Participation" => connection.InsertAsync(new Participation() { - LegalEntityId = await connection.ExecuteScalarAsync("select top 1 legalentityid from legalentity") + LegalEntityId = await connection.ExecuteScalarAsync("select top 1 legalentityid from legalentity"), }), "Brand" => connection.InsertAsync(new Brand() { - ParticipationId = await connection.ExecuteScalarAsync("select top 1 participationid from participation") + ParticipationId = await connection.ExecuteScalarAsync("select top 1 participationid from participation"), }), "AuthDetail" => connection.InsertAsync(new AuthDetail() { - BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand"), }), "Endpoint" => connection.InsertAsync(new Endpoint() { - BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand"), }), "SoftwareProduct" => connection.InsertAsync(new SoftwareProduct() { - BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand") + BrandId = await connection.ExecuteScalarAsync("select top 1 brandid from brand"), }), "SoftwareProductCertificate" => connection.InsertAsync(new SoftwareProductCertificate() { - SoftwareProductId = await connection.ExecuteScalarAsync("select top 1 softwareProductid from softwareProduct") + SoftwareProductId = await connection.ExecuteScalarAsync("select top 1 softwareProductid from softwareProduct"), }), - _ => throw new NotSupportedException() + _ => throw new NotSupportedException(), }; await insert; }); @@ -168,175 +81,92 @@ await Test( await connection.ExecuteAsync("delete LegalEntity"); }); } - } - - [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"; + private static async Task Test(string tableName, Mutate mutate) + { + await Test(new string[] { tableName }, mutate); + } - public int? StatusId { get; set; } = 1; + private static async Task Test(string[] tableNames, Mutate mutate) + { + // Arrange + await DatabaseSeeder.Execute(); - public Guid? BrandId { get; set; } - } + using var registerConnection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); + await registerConnection.OpenAsync(); - [Table("SoftwareProductCertificate")] - internal class SoftwareProductCertificate - { - [ExplicitKey] - public Guid SoftwareProductCertificateId { get; set; } = Guid.NewGuid(); + var pointInTimeUTC = DateTime.UtcNow; - public Guid SoftwareProductId { get; set; } + var initialData = new Dictionary(); + foreach (string tableName in tableNames) + { + initialData.Add(tableName, await GetTableJson(registerConnection, tableName)); + } - public string? CommonName { get; set; } = "foo"; + // Act - Mutate data + await Task.Delay(500); + if (mutate != null) + { + await mutate(registerConnection); + } - public string? Thumbprint { get; set; } = "foo"; - } + var modifiedData = new Dictionary(); + foreach (string tableName in tableNames) + { + modifiedData.Add(tableName, await GetTableJson(registerConnection, tableName)); + } - [Table("ParticipationStatus")] - internal class ParticipationStatus - { - [ExplicitKey] - public int? ParticipationStatusId { get; set; } + // Assert + var pointInTimeData = new Dictionary(); + foreach (string tableName in tableNames) + { + pointInTimeData.Add(tableName, await GetTableJson(registerConnection, tableName, pointInTimeUTC)); + } - public string? ParticipationStatusCode { get; set; } - } +#if DEBUG_WRITE_EXPECTED_AND_ACTUAL_JSON + foreach (string tableName in tableNames) + { + File.WriteAllText($"c:/temp/{tableName}_initial.json", initialData[tableName]); + File.WriteAllText($"c:/temp/{tableName}_modified.json", modifiedData[tableName]); + File.WriteAllText($"c:/temp/{tableName}_pointintime.json", pointInTimeData[tableName]); + } +#endif - internal static class DatabaseSeeder - { - private static async Task Purge(SqlConnection connection) - { - static async Task PurgeTable(SqlConnection connection, string tableName, bool temporal = true) + using (new AssertionScope()) { - // Delete data - await connection.ExecuteAsync($"delete {tableName}"); + // Check data was actually modified + foreach (string tableName in tableNames) + { + modifiedData[tableName].Should().NotBe(initialData[tableName]); + } - // Now cleanup temporal data too - if (temporal) + // Check point in time data matches initial data + foreach (string tableName in tableNames) { - 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))"); + pointInTimeData[tableName].Should().Be(initialData[tableName]); } } - - 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() + private static async Task GetTableJson(SqlConnection connection, string tableName, DateTime? pointInTimeUTC = null) { - using var connection = new SqlConnection(BaseTest.CONNECTIONSTRING_REGISTER_RW); - await connection.OpenAsync(); + string orderby = tableName.ToUpper() switch + { + "AUTHDETAIL" => "BrandID", + "ENDPOINT" => "BrandID", + _ => $"{tableName}ID", + }; + + IEnumerable? data; + + string sql = @$" + select * from {tableName} + {(pointInTimeUTC == null ? string.Empty : $"for system_time as of '{pointInTimeUTC:yyyy-MM-dd HH:mm:ss.fffffff}'")} + order by {orderby}"; - await Purge(connection); - await TestFixture.Seeddata(); + data = await connection.QueryAsync(sql); - await connection.ExecuteAsync("delete ParticipationStatus where ParticipationStatusId = -999"); - await connection.InsertAsync(new ParticipationStatus { ParticipationStatusId = -999, ParticipationStatusCode = "foo" }); + return JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings { }); } } } diff --git a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj index de7bde9..4b1120c 100644 --- a/Source/CDR.Register.Repository/CDR.Register.Repository.csproj +++ b/Source/CDR.Register.Repository/CDR.Register.Repository.csproj @@ -31,7 +31,7 @@ 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 92f6335..6cda23e 100644 --- a/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs +++ b/Source/CDR.Register.Repository/Entities/AccreditationLevel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,11 +12,4 @@ public class AccreditationLevel [Required] public string AccreditationLevelCode { get; set; } } - - public enum AccreditationLevelType - { - // 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 1f76a02..706ea3a 100644 --- a/Source/CDR.Register.Repository/Entities/AuthDetail.cs +++ b/Source/CDR.Register.Repository/Entities/AuthDetail.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/Brand.cs b/Source/CDR.Register.Repository/Entities/Brand.cs index 6e46d8f..38ee51f 100644 --- a/Source/CDR.Register.Repository/Entities/Brand.cs +++ b/Source/CDR.Register.Repository/Entities/Brand.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/BrandStatus.cs b/Source/CDR.Register.Repository/Entities/BrandStatus.cs index 661e319..3fa952f 100644 --- a/Source/CDR.Register.Repository/Entities/BrandStatus.cs +++ b/Source/CDR.Register.Repository/Entities/BrandStatus.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,12 +12,4 @@ public class BrandStatus [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/Enumerations.cs b/Source/CDR.Register.Repository/Entities/Enumerations.cs index 5d50c16..f26c4e8 100644 --- a/Source/CDR.Register.Repository/Entities/Enumerations.cs +++ b/Source/CDR.Register.Repository/Entities/Enumerations.cs @@ -5,7 +5,7 @@ public enum Industry ALL = 0, BANKING, ENERGY, - TELCO + TELCO, } public enum OrganisationType @@ -16,14 +16,14 @@ public enum OrganisationType Partnership = 3, Trust = 4, GovernmentEntity = 5, - Other = 6 + Other = 6, } public enum AccreditationLevel { // Sponsored by Default Sponsored = 0, - Unrestricted = 1 + Unrestricted = 1, } public enum ParticipationStatus @@ -34,13 +34,13 @@ public enum ParticipationStatus Suspended = 3, Revoked = 4, Surrendered = 5, - Inactive = 6 + Inactive = 6, } public enum LegalEntityStatus { Active = 1, - Removed = 2 + Removed = 2, } public enum BrandStatus @@ -48,20 +48,20 @@ public enum BrandStatus Unknown = 0, Active = 1, Inactive = 2, - Removed = 3 + Removed = 3, } public enum ParticipationType { Unknown = 0, Dh = 1, - Dr = 2 + Dr = 2, } public enum RegisterUType { Unknown = 0, - SignedJwt = 1 + SignedJwt = 1, } public enum SoftwareProductStatus @@ -69,6 +69,6 @@ public enum SoftwareProductStatus Unknown = 0, Active = 1, Inactive = 2, - Removed = 3 + Removed = 3, } } diff --git a/Source/CDR.Register.Repository/Entities/IndustryType.cs b/Source/CDR.Register.Repository/Entities/IndustryType.cs index ac5533e..8f062cc 100644 --- a/Source/CDR.Register.Repository/Entities/IndustryType.cs +++ b/Source/CDR.Register.Repository/Entities/IndustryType.cs @@ -1,5 +1,5 @@ -using CDR.Register.Repository.Infrastructure; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Infrastructure; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/LegalEntity.cs b/Source/CDR.Register.Repository/Entities/LegalEntity.cs index 3ccb294..cd32226 100644 --- a/Source/CDR.Register.Repository/Entities/LegalEntity.cs +++ b/Source/CDR.Register.Repository/Entities/LegalEntity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/OrganisationType.cs b/Source/CDR.Register.Repository/Entities/OrganisationType.cs index 75b3d5a..3dcd20d 100644 --- a/Source/CDR.Register.Repository/Entities/OrganisationType.cs +++ b/Source/CDR.Register.Repository/Entities/OrganisationType.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,15 +12,4 @@ public class OrganisationType [Required] public string OrganisationTypeCode { get; set; } } - - public enum OrganisationTypes - { - Unknown = 0, - SoleTrader = 1, - Company = 2, - Partnership = 3, - Trust = 4, - GovernmentEntity = 5, - Other = 6 - } } diff --git a/Source/CDR.Register.Repository/Entities/Participation.cs b/Source/CDR.Register.Repository/Entities/Participation.cs index b2dd679..2347528 100644 --- a/Source/CDR.Register.Repository/Entities/Participation.cs +++ b/Source/CDR.Register.Repository/Entities/Participation.cs @@ -1,7 +1,8 @@ -using CDR.Register.Repository.Infrastructure; -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; +using CDR.Register.Repository.Infrastructure; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs b/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs index 96d7e2d..3f95e57 100644 --- a/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs +++ b/Source/CDR.Register.Repository/Entities/ParticipationStatus.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -12,19 +13,8 @@ public class ParticipationStatus public string ParticipationStatusCode { get; set; } /// - /// Applicable participation types. If null or Unknown, it's available for all participation types. + /// Gets or sets applicable participation types. If null or Unknown, it's available for all participation types. /// public ParticipationTypes? ParticipationTypeId { get; set; } } - - public enum ParticipationStatusType - { - Unknown = 0, - Active = 1, - Removed = 2, - Suspended = 3, - Revoked = 4, - Surrendered = 5, - Inactive = 6 - } } diff --git a/Source/CDR.Register.Repository/Entities/ParticipationType.cs b/Source/CDR.Register.Repository/Entities/ParticipationType.cs index 23866e6..43c65c5 100644 --- a/Source/CDR.Register.Repository/Entities/ParticipationType.cs +++ b/Source/CDR.Register.Repository/Entities/ParticipationType.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,11 +12,4 @@ public class ParticipationType [Required] public string ParticipationTypeCode { get; set; } } - - public enum ParticipationTypes - { - Unknown = 0, - Dh = 1, - Dr = 2 - } } diff --git a/Source/CDR.Register.Repository/Entities/RegisterUType.cs b/Source/CDR.Register.Repository/Entities/RegisterUType.cs index 1980d12..ef37838 100644 --- a/Source/CDR.Register.Repository/Entities/RegisterUType.cs +++ b/Source/CDR.Register.Repository/Entities/RegisterUType.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,10 +12,4 @@ public class RegisterUType [Required] public string RegisterUTypeCode { get; set; } } - - public enum RegisterUTypes - { - Unknown = 0, - SignedJwt = 1 - } } diff --git a/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs b/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs index 4746314..fce097b 100644 --- a/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs +++ b/Source/CDR.Register.Repository/Entities/SoftwareProduct.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { diff --git a/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs b/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs index 5cd67a9..d9bcc25 100644 --- a/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs +++ b/Source/CDR.Register.Repository/Entities/SoftwareProductStatus.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using CDR.Register.Repository.Enums; namespace CDR.Register.Repository.Entities { @@ -11,12 +12,4 @@ public class SoftwareProductStatus [Required] public string SoftwareProductStatusCode { get; set; } } - - public enum SoftwareProductStatusType - { - Unknown = 0, - Active = 1, - Inactive = 2, - Removed = 3 - } } diff --git a/Source/CDR.Register.Repository/Enums/AccreditationLevelType.cs b/Source/CDR.Register.Repository/Enums/AccreditationLevelType.cs new file mode 100644 index 0000000..60547b2 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/AccreditationLevelType.cs @@ -0,0 +1,9 @@ +namespace CDR.Register.Repository.Enums +{ + public enum AccreditationLevelType + { + // Sponsored by Default + Sponsored = 0, + Unrestricted = 1, + } +} diff --git a/Source/CDR.Register.Repository/Enums/BrandStatusType.cs b/Source/CDR.Register.Repository/Enums/BrandStatusType.cs new file mode 100644 index 0000000..6713eb8 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/BrandStatusType.cs @@ -0,0 +1,10 @@ +namespace CDR.Register.Repository.Enums +{ + public enum BrandStatusType + { + Unknown = 0, + Active = 1, + Inactive = 2, + Removed = 3, + } +} diff --git a/Source/CDR.Register.Repository/Enums/OrganisationTypes.cs b/Source/CDR.Register.Repository/Enums/OrganisationTypes.cs new file mode 100644 index 0000000..72a4768 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/OrganisationTypes.cs @@ -0,0 +1,13 @@ +namespace CDR.Register.Repository.Enums +{ + public enum OrganisationTypes + { + Unknown = 0, + SoleTrader = 1, + Company = 2, + Partnership = 3, + Trust = 4, + GovernmentEntity = 5, + Other = 6, + } +} diff --git a/Source/CDR.Register.Repository/Enums/ParticipationStatusType.cs b/Source/CDR.Register.Repository/Enums/ParticipationStatusType.cs new file mode 100644 index 0000000..e8343e5 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/ParticipationStatusType.cs @@ -0,0 +1,13 @@ +namespace CDR.Register.Repository.Enums +{ + public enum ParticipationStatusType + { + Unknown = 0, + Active = 1, + Removed = 2, + Suspended = 3, + Revoked = 4, + Surrendered = 5, + Inactive = 6, + } +} diff --git a/Source/CDR.Register.Repository/Enums/ParticipationTypes.cs b/Source/CDR.Register.Repository/Enums/ParticipationTypes.cs new file mode 100644 index 0000000..ecede53 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/ParticipationTypes.cs @@ -0,0 +1,9 @@ +namespace CDR.Register.Repository.Enums +{ + public enum ParticipationTypes + { + Unknown = 0, + Dh = 1, + Dr = 2, + } +} diff --git a/Source/CDR.Register.Repository/Enums/RegisterUTypes.cs b/Source/CDR.Register.Repository/Enums/RegisterUTypes.cs new file mode 100644 index 0000000..240ee20 --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/RegisterUTypes.cs @@ -0,0 +1,8 @@ +namespace CDR.Register.Repository.Enums +{ + public enum RegisterUTypes + { + Unknown = 0, + SignedJwt = 1, + } +} diff --git a/Source/CDR.Register.Repository/Enums/SoftwareProductStatusType.cs b/Source/CDR.Register.Repository/Enums/SoftwareProductStatusType.cs new file mode 100644 index 0000000..96df2dc --- /dev/null +++ b/Source/CDR.Register.Repository/Enums/SoftwareProductStatusType.cs @@ -0,0 +1,10 @@ +namespace CDR.Register.Repository.Enums +{ + public enum SoftwareProductStatusType + { + Unknown = 0, + Active = 1, + Inactive = 2, + Removed = 3, + } +} diff --git a/Source/CDR.Register.Repository/IRegisterAdminRepository.cs b/Source/CDR.Register.Repository/IRegisterAdminRepository.cs index 806b9a2..1e9bae6 100644 --- a/Source/CDR.Register.Repository/IRegisterAdminRepository.cs +++ b/Source/CDR.Register.Repository/IRegisterAdminRepository.cs @@ -1,7 +1,7 @@ -using CDR.Register.Domain.Entities; -using CDR.Register.Domain.ValueObjects; -using System; +using System; using System.Threading.Tasks; +using CDR.Register.Domain.Entities; +using CDR.Register.Domain.ValueObjects; namespace CDR.Register.Repository { diff --git a/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs b/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs index 971734f..2ea0ddf 100644 --- a/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs +++ b/Source/CDR.Register.Repository/IRegisterDiscoveryRepository.cs @@ -1,7 +1,7 @@ -using CDR.Register.Domain.Entities; -using CDR.Register.Domain.ValueObjects; -using System; +using System; using System.Threading.Tasks; +using CDR.Register.Domain.Entities; +using CDR.Register.Domain.ValueObjects; namespace CDR.Register.Repository.Interfaces { diff --git a/Source/CDR.Register.Repository/IRegisterStatusRepository.cs b/Source/CDR.Register.Repository/IRegisterStatusRepository.cs index 77e1377..96dfa7f 100644 --- a/Source/CDR.Register.Repository/IRegisterStatusRepository.cs +++ b/Source/CDR.Register.Repository/IRegisterStatusRepository.cs @@ -1,5 +1,5 @@ -using CDR.Register.Domain.Entities; -using System.Threading.Tasks; +using System.Threading.Tasks; +using CDR.Register.Domain.Entities; namespace CDR.Register.Repository.Interfaces { diff --git a/Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs b/Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs index 35b282c..2643522 100644 --- a/Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs +++ b/Source/CDR.Register.Repository/Infrastructure/CdsRegistrationScopes.cs @@ -4,4 +4,4 @@ public static class CdsRegistrationScopes { public const string Read = "cdr-register:read"; } -} \ No newline at end of file +} diff --git a/Source/CDR.Register.Repository/Infrastructure/Extensions.cs b/Source/CDR.Register.Repository/Infrastructure/Extensions.cs index 3b70765..258a4eb 100644 --- a/Source/CDR.Register.Repository/Infrastructure/Extensions.cs +++ b/Source/CDR.Register.Repository/Infrastructure/Extensions.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CDR.Register.Domain.ValueObjects; using CDR.Register.Repository.Entities; +using CDR.Register.Repository.Enums; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -59,6 +60,7 @@ 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. /// + /// representing the asynchronous operation. public static async Task SeedDatabaseFromJsonFile( this RegisterDatabaseContext registerDatabaseContext, string jsonFileFullPath, @@ -78,6 +80,7 @@ public static async Task SeedDatabaseFromJsonFile( /// /// This is the initial database seed. If there are records in the database, this will not re-seed the database. /// + /// representing the asynchronous operation. public static async Task SeedDatabaseFromJson( this RegisterDatabaseContext registerDatabaseContext, string json, @@ -106,6 +109,7 @@ public static async Task SeedDatabaseFromJson( /// /// Retrieves all participant metadata from the database, serialises to JSON and return as a string. /// + /// representing the asynchronous operation. public static async Task GetJsonFromDatabase( this RegisterDatabaseContext registerDatabaseContext) { @@ -135,6 +139,7 @@ public static async 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. /// + /// representing the asynchronous operation. public static async Task ReSeedDatabaseFromJson(this RegisterDatabaseContext registerDatabaseContext, string json, ILogger logger) { using (var transaction = await registerDatabaseContext.Database.BeginTransactionAsync()) @@ -314,7 +319,7 @@ public static async Task AddOrUpdateDataRecipientParticipatio Include(x => x.LegalEntity). 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); + var participationStatus = (ParticipationStatusType)Enum.Parse(typeof(ParticipationStatusType), dataRecipient.LegalEntity.Status, true); if (existingParticipant != null) { @@ -335,7 +340,7 @@ public static async Task AddOrUpdateDataRecipientParticipatio { LegalEntityId = legalEntity.LegalEntityId, ParticipationTypeId = ParticipationTypes.Dr, - StatusId = participationStatus + StatusId = participationStatus, }; legalEntity.Participations ??= new List(); diff --git a/Source/CDR.Register.Repository/Infrastructure/Industry.cs b/Source/CDR.Register.Repository/Infrastructure/Industry.cs index 1dc473b..ccb8e76 100644 --- a/Source/CDR.Register.Repository/Infrastructure/Industry.cs +++ b/Source/CDR.Register.Repository/Infrastructure/Industry.cs @@ -5,6 +5,6 @@ public enum Industry ALL = 0, BANKING, ENERGY, - TELCO + TELCO, } -} \ 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 d17390b..3e72c35 100644 --- a/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs +++ b/Source/CDR.Register.Repository/Infrastructure/MappingProfile.cs @@ -1,7 +1,8 @@ -using AutoMapper; -using CDR.Register.Repository.Entities; -using System; +using System; using System.Linq; +using AutoMapper; +using CDR.Register.Repository.Entities; +using CDR.Register.Repository.Enums; using DomainEntities = CDR.Register.Domain.Entities; namespace CDR.Register.Repository.Infrastructure @@ -10,30 +11,30 @@ public class MappingProfile : Profile { public MappingProfile() { - CreateMap() + this.CreateMap() .ForMember(dest => dest.OrganisationType, source => source.MapFrom(source => source.OrganisationType.OrganisationTypeCode)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Participations.FirstOrDefault().Status.ParticipationStatusCode.ToUpper())); - CreateMap() + this.CreateMap() .ForMember(dest => dest.OrganisationTypeId, source => source.MapFrom(source => source.OrganisationType == null ? null : Enum.Parse(typeof(OrganisationTypes), source.OrganisationType.Replace("_", string.Empty), true))) .ForMember(dest => dest.OrganisationType, opt => opt.Ignore()); - CreateMap() + this.CreateMap() .ForMember(dest => dest.OrganisationType, source => source.MapFrom(source => source.OrganisationType.OrganisationTypeCode)) .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("_", string.Empty), true))) + this.CreateMap() + .ForMember(dest => dest.OrganisationTypeId, source => source.MapFrom(source => source.OrganisationType == null ? null : Enum.Parse(typeof(OrganisationTypes), source.OrganisationType.Replace("_", string.Empty), true))) .ForMember(dest => dest.OrganisationType, opt => opt.Ignore()); - CreateMap() + this.CreateMap() .ForMember(dest => dest.DataHolderId, source => source.MapFrom(source => source.ParticipationId)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status.ParticipationStatusCode)) .ForMember(dest => dest.IsActive, source => source.MapFrom(source => source.Status.ParticipationStatusId == ParticipationStatusType.Active)) .ForMember(dest => dest.Industry, source => source.MapFrom(source => source.Industry.IndustryTypeCode)) .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source.LegalEntity)) .ForMember(dest => dest.Brands, source => source.MapFrom(source => source.Brands)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.StatusId, source => source.MapFrom(source => Enum.Parse(typeof(ParticipationStatusType), source.Status, true))) .ForMember(dest => dest.IndustryId, source => source.MapFrom(source => Enum.Parse(typeof(Industry), source.Industry, true))) .ForMember(dest => dest.ParticipationTypeId, source => source.MapFrom(source => ParticipationTypes.Dh)) // This is a Dh Participation @@ -41,7 +42,7 @@ public MappingProfile() .ForMember(dest => dest.Status, opt => opt.Ignore()) .ForMember(dest => dest.ParticipationType, opt => opt.Ignore()); - CreateMap() + this.CreateMap() .ForMember(dest => dest.DataRecipientId, source => source.MapFrom(source => source.ParticipationId)) .ForMember(dest => dest.IsActive, source => source.MapFrom(source => source.Status.ParticipationStatusId == ParticipationStatusType.Active)) .ForMember(dest => dest.LegalEntity, source => source.MapFrom(source => source.LegalEntity)) @@ -49,57 +50,57 @@ public MappingProfile() .ForMember(dest => dest.DataRecipientBrands, source => source.MapFrom(source => source.Brands)) .ReverseMap(); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Status, source => source.MapFrom(source => source.StatusId.ToString().ToUpper())) .ReverseMap(); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandStatus, source => source.MapFrom(source => source.BrandStatus.BrandStatusCode)) .ForMember(dest => dest.IsActive, source => source.MapFrom(source => source.BrandStatus.BrandStatusId == BrandStatusType.Active)) .ForMember(dest => dest.DataHolderAuthentications, source => source.MapFrom(source => source.AuthDetails)) .ForMember(dest => dest.DataHolderBrandServiceEndpoint, source => source.MapFrom(source => source.Endpoint)) .ForMember(dest => dest.DataHolder, source => source.MapFrom(source => source.Participation)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandName, source => source.MapFrom(source => source.BrandName)) .ForMember(dest => dest.LogoUri, source => source.MapFrom(source => source.LogoUri)) .ForMember(dest => dest.BrandStatusId, source => source.MapFrom(source => Enum.Parse(typeof(BrandStatusType), source.BrandStatus, true))) .ForMember(dest => dest.BrandStatus, opt => opt.Ignore()); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandStatus, source => source.MapFrom(source => source.BrandStatus.BrandStatusCode)) .ForMember(dest => dest.IsActive, source => source.MapFrom(source => source.BrandStatus.BrandStatusId == BrandStatusType.Active)) .ForMember(dest => dest.SoftwareProducts, source => source.MapFrom(source => source.SoftwareProducts)) .ForMember(dest => dest.DataRecipient, source => source.MapFrom(source => source.Participation)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.BrandStatusId, source => source.MapFrom(source => Enum.Parse(typeof(BrandStatusType), source.BrandStatus, true))) .ForMember(dest => dest.BrandStatus, opts => opts.Ignore()) .ForMember(dest => dest.SoftwareProducts, opts => opts.Ignore()); - CreateMap() + this.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.DataRecipientBrand, source => source.MapFrom(s => s.Brand)); - CreateMap() - .ForMember(dest => dest.StatusId, source => source.MapFrom(source => Enum.Parse(typeof(Entities.SoftwareProductStatusType), source.Status, true))) + this.CreateMap() + .ForMember(dest => dest.StatusId, source => source.MapFrom(source => Enum.Parse(typeof(SoftwareProductStatusType), source.Status, true))) .ForMember(dest => dest.RedirectUris, source => source.MapFrom(src => src.RedirectUris != null ? string.Join(" ", src.RedirectUris) : string.Empty)) .ForMember(dest => dest.Status, opts => opts.Ignore()) .ForMember(dest => dest.Certificates, opts => opts.Ignore()); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Id, source => source.MapFrom(s => s.SoftwareProductId)) .ForMember(dest => dest.Name, source => source.MapFrom(s => s.SoftwareProductName)) .ForMember(dest => dest.JwksUri, source => source.MapFrom(s => s.JwksUri)) .ForMember(dest => dest.X509Certificates, source => source.MapFrom(s => s.Certificates)); - CreateMap().ReverseMap(); + this.CreateMap().ReverseMap(); - CreateMap() + this.CreateMap() .ForMember(dest => dest.RegisterUType, source => source.MapFrom(source => source.RegisterUType.RegisterUTypeCode)); - CreateMap() + this.CreateMap() .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)) @@ -107,7 +108,7 @@ public MappingProfile() .ForMember(dest => dest.Brand, opt => opt.Ignore()) .ForMember(dest => dest.BrandId, opt => opt.Ignore()); - CreateMap() + this.CreateMap() .ReverseMap(); } } diff --git a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs index bb0e3d8..e5dbddf 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RegisterDatabaseContext.cs @@ -1,6 +1,6 @@ -using CDR.Register.Repository.Entities; +using System.Linq; +using CDR.Register.Repository.Entities; using Microsoft.EntityFrameworkCore; -using System.Linq; namespace CDR.Register.Repository.Infrastructure { diff --git a/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs b/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs index a0d68a8..32e8f0c 100644 --- a/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs +++ b/Source/CDR.Register.Repository/Infrastructure/RepositoryMapper.cs @@ -13,47 +13,47 @@ public RepositoryMapper() { cfg.AddProfile(); }); - _mapper = configuration.CreateMapper(); + this._mapper = configuration.CreateMapper(); } public SoftwareProductInfosec Map(Entities.SoftwareProduct softwareProduct) { - return _mapper.Map(softwareProduct); + return this._mapper.Map(softwareProduct); } public DataRecipientBrand Map(Entities.Brand brand) { - return _mapper.Map(brand); + return this._mapper.Map(brand); } public DataRecipientLegalEntity Map(Entities.LegalEntity legalEntity) { - return _mapper.Map(legalEntity); + return this._mapper.Map(legalEntity); } public Entities.LegalEntity Map(Domain.Entities.DataRecipientLegalEntity legalEntity) { - return _mapper.Map(legalEntity); + return this._mapper.Map(legalEntity); } public Entities.Brand Map(DataRecipientBrand brand) { - return _mapper.Map(brand); + return this._mapper.Map(brand); } public Entities.SoftwareProduct Map(Domain.Entities.SoftwareProduct softwareProduct) { - return _mapper.Map(softwareProduct); + return this._mapper.Map(softwareProduct); } public Entities.SoftwareProductCertificate Map(SoftwareProductCertificateInfosec softwareProductCertificate) { - return _mapper.Map(softwareProductCertificate); + return this._mapper.Map(softwareProductCertificate); } public Domain.Entities.SoftwareProduct MapSoftwareProduct(Entities.SoftwareProduct softwareProduct) { - return _mapper.Map(softwareProduct); + return this._mapper.Map(softwareProduct); } } } diff --git a/Source/CDR.Register.Repository/Migrations/20211005022250_Init.cs b/Source/CDR.Register.Repository/Migrations/20211005022250_Init.cs index e1fc046..1e56999 100644 --- a/Source/CDR.Register.Repository/Migrations/20211005022250_Init.cs +++ b/Source/CDR.Register.Repository/Migrations/20211005022250_Init.cs @@ -14,7 +14,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { BrandStatusId = table.Column(type: "int", nullable: false), - BrandStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false) + BrandStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false), }, constraints: table => { @@ -26,7 +26,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { IndustryTypeId = table.Column(type: "int", nullable: false), - IndustryTypeCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false) + IndustryTypeCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false), }, constraints: table => { @@ -38,7 +38,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { OrganisationTypeId = table.Column(type: "int", nullable: false), - OrganisationTypeCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + OrganisationTypeCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), }, constraints: table => { @@ -50,7 +50,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { ParticipationStatusId = table.Column(type: "int", nullable: false), - ParticipationStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false) + ParticipationStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false), }, constraints: table => { @@ -62,7 +62,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { ParticipationTypeId = table.Column(type: "int", nullable: false), - ParticipationTypeCode = table.Column(type: "nvarchar(2)", maxLength: 2, nullable: false) + ParticipationTypeCode = table.Column(type: "nvarchar(2)", maxLength: 2, nullable: false), }, constraints: table => { @@ -74,7 +74,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { RegisterUTypeId = table.Column(type: "int", nullable: false), - RegisterUTypeCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false) + RegisterUTypeCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false), }, constraints: table => { @@ -86,7 +86,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { SoftwareProductStatusId = table.Column(type: "int", nullable: false), - SoftwareProductStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false) + SoftwareProductStatusCode = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: false), }, constraints: table => { @@ -108,7 +108,7 @@ protected override void Up(MigrationBuilder migrationBuilder) Arbn = table.Column(type: "nvarchar(9)", maxLength: 9, nullable: true), IndustryCode = table.Column(type: "nvarchar(4)", maxLength: 4, nullable: true), OrganisationTypeId = table.Column(type: "int", nullable: true), - AccreditationNumber = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: true) + AccreditationNumber = table.Column(type: "nvarchar(25)", maxLength: 25, nullable: true), }, constraints: table => { @@ -129,7 +129,7 @@ protected override void Up(MigrationBuilder migrationBuilder) LegalEntityId = table.Column(type: "uniqueidentifier", nullable: false), ParticipationTypeId = table.Column(type: "int", nullable: false), IndustryId = table.Column(type: "int", nullable: false), - StatusId = table.Column(type: "int", nullable: false) + StatusId = table.Column(type: "int", nullable: false), }, constraints: table => { @@ -169,7 +169,7 @@ protected override void Up(MigrationBuilder migrationBuilder) LogoUri = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), BrandStatusId = table.Column(type: "int", nullable: false), ParticipationId = table.Column(type: "uniqueidentifier", nullable: false), - LastUpdated = table.Column(type: "datetime2", nullable: false) + LastUpdated = table.Column(type: "datetime2", nullable: false), }, constraints: table => { @@ -194,7 +194,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { BrandId = table.Column(type: "uniqueidentifier", nullable: false), RegisterUTypeId = table.Column(type: "int", nullable: false), - JwksEndpoint = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false) + JwksEndpoint = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), }, constraints: table => { @@ -223,7 +223,7 @@ protected override void Up(MigrationBuilder migrationBuilder) ResourceBaseUri = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), InfosecBaseUri = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), ExtensionBaseUri = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), - WebsiteUri = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false) + WebsiteUri = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), }, constraints: table => { @@ -254,7 +254,7 @@ protected override void Up(MigrationBuilder migrationBuilder) JwksUri = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), Scope = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), StatusId = table.Column(type: "int", nullable: false), - BrandId = table.Column(type: "uniqueidentifier", nullable: false) + BrandId = table.Column(type: "uniqueidentifier", nullable: false), }, constraints: table => { @@ -280,7 +280,7 @@ protected override void Up(MigrationBuilder migrationBuilder) SoftwareProductCertificateId = table.Column(type: "uniqueidentifier", nullable: false), SoftwareProductId = table.Column(type: "uniqueidentifier", nullable: false), CommonName = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - Thumbprint = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) + Thumbprint = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), }, constraints: table => { @@ -300,7 +300,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { { 1, "ACTIVE" }, { 2, "INACTIVE" }, - { 3, "REMOVED" } + { 3, "REMOVED" }, }); migrationBuilder.InsertData( @@ -323,7 +323,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { 3, "PARTNERSHIP" }, { 4, "TRUST" }, { 5, "GOVERNMENT_ENTITY" }, - { 6, "OTHER" } + { 6, "OTHER" }, }); migrationBuilder.InsertData( @@ -336,7 +336,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { 4, "REVOKED" }, { 1, "ACTIVE" }, { 2, "REMOVED" }, - { 3, "SUSPENDED" } + { 3, "SUSPENDED" }, }); migrationBuilder.InsertData( @@ -345,7 +345,7 @@ protected override void Up(MigrationBuilder migrationBuilder) values: new object[,] { { 1, "DH" }, - { 2, "DR" } + { 2, "DR" }, }); migrationBuilder.InsertData( @@ -360,7 +360,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { { 2, "INACTIVE" }, { 1, "ACTIVE" }, - { 3, "REMOVED" } + { 3, "REMOVED" }, }); migrationBuilder.CreateIndex( diff --git a/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs b/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs index 47aedc7..3854220 100644 --- a/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs +++ b/Source/CDR.Register.Repository/Migrations/20211223050501_V2.cs @@ -48,7 +48,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { AccreditationLevelId = table.Column(type: "int", nullable: false), - AccreditationLevelCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + AccreditationLevelCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), }, constraints: table => { @@ -60,7 +60,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { LegalEntityStatusId = table.Column(type: "int", nullable: false), - LegalEntityStatusCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + LegalEntityStatusCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), }, constraints: table => { @@ -73,7 +73,7 @@ protected override void Up(MigrationBuilder migrationBuilder) values: new object[,] { { 1, "Active" }, - { 2, "Removed" } + { 2, "Removed" }, }); migrationBuilder.InsertData( @@ -82,7 +82,7 @@ protected override void Up(MigrationBuilder migrationBuilder) values: new object[,] { { 0, "Sponsored" }, - { 1, "Unrestricted" } + { 1, "Unrestricted" }, }); // Updating exsiting data diff --git a/Source/CDR.Register.Repository/Migrations/20220915001454_V4.cs b/Source/CDR.Register.Repository/Migrations/20220915001454_V4.cs index d1657b5..970f5bc 100644 --- a/Source/CDR.Register.Repository/Migrations/20220915001454_V4.cs +++ b/Source/CDR.Register.Repository/Migrations/20220915001454_V4.cs @@ -79,7 +79,7 @@ protected override void Down(MigrationBuilder migrationBuilder) columns: table => new { LegalEntityStatusId = table.Column(type: "int", nullable: false), - LegalEntityStatusCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + LegalEntityStatusCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), }, constraints: table => { diff --git a/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs b/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs index 090b60b..6c2be35 100644 --- a/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs +++ b/Source/CDR.Register.Repository/Migrations/20221005215852_V5.cs @@ -1,4 +1,4 @@ -using CDR.Register.Repository.Entities; +using CDR.Register.Repository.Enums; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/Source/CDR.Register.Repository/RegisterAdminRepository.cs b/Source/CDR.Register.Repository/RegisterAdminRepository.cs index 06f5473..0e3de30 100644 --- a/Source/CDR.Register.Repository/RegisterAdminRepository.cs +++ b/Source/CDR.Register.Repository/RegisterAdminRepository.cs @@ -1,13 +1,14 @@ -using AutoMapper; +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; using CDR.Register.Domain.Entities; using CDR.Register.Domain.ValueObjects; using CDR.Register.Repository.Entities; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using System; -using System.Linq; -using System.Threading.Tasks; namespace CDR.Register.Repository { @@ -20,53 +21,15 @@ public class RegisterAdminRepository : IRegisterAdminRepository public RegisterAdminRepository(RegisterDatabaseContext registerDatabaseContext, IMapper mapper, IRepositoryMapper repositoryMapper, ILogger logger) { - _registerDatabaseContext = registerDatabaseContext; - _mapper = mapper; - _reporsitoryMapper = repositoryMapper; - _logger = logger; - } - - private async Task<(LegalEntity, Participation)> SaveDataHolderLegalEntity(DataHolder dataHolder) - { - var participationToSave = _mapper.Map(dataHolder); - - // Check if the entity exists. If so, update the existing values - var existingLegalEntity = await _registerDatabaseContext.LegalEntities - .Include(le => le.Participations) - .Where(le => le.LegalEntityId == dataHolder.LegalEntity.LegalEntityId) - .FirstOrDefaultAsync(); - var legalEntityToSave = _mapper.Map(dataHolder.LegalEntity); - if (existingLegalEntity == null) - { - participationToSave.LegalEntity = legalEntityToSave; - _registerDatabaseContext.LegalEntities.Add(legalEntityToSave); - _registerDatabaseContext.Participations.Add(participationToSave); - - return (legalEntityToSave, participationToSave); - } - - // Only update the DH Brand related information. - _mapper.Map(dataHolder.LegalEntity, existingLegalEntity); - - // Update participation details - var existingDhParticipation = existingLegalEntity.Participations.FirstOrDefault(p => - p.ParticipationTypeId == ParticipationTypes.Dh && p.IndustryId == participationToSave.IndustryId); - if (existingDhParticipation == null) - { - participationToSave.LegalEntity = existingLegalEntity; - _registerDatabaseContext.Participations.Add(participationToSave); - } - else - { - participationToSave = _mapper.Map(dataHolder, existingDhParticipation); - } - - return (existingLegalEntity, participationToSave); + this._registerDatabaseContext = registerDatabaseContext; + this._mapper = mapper; + this._reporsitoryMapper = repositoryMapper; + this._logger = logger; } public async Task GetDataHolderBrandAsync(Guid brandId) { - var dataHolderBrandEntity = await _registerDatabaseContext.Brands.AsNoTracking() + var dataHolderBrandEntity = await this._registerDatabaseContext.Brands.AsNoTracking() .Include(b => b.Participation.LegalEntity) .Where(b => b.BrandId == brandId).FirstOrDefaultAsync(); if (dataHolderBrandEntity == null) @@ -79,7 +42,7 @@ public async Task GetDataHolderBrandAsync(Guid brandId) LogoUri = dataHolderBrandEntity.LogoUri, BrandId = dataHolderBrandEntity.BrandId, BrandName = dataHolderBrandEntity.BrandName, - BrandStatus = dataHolderBrandEntity.BrandStatusId.ToString() + BrandStatus = dataHolderBrandEntity.BrandStatusId.ToString(), }; if (dataHolderBrandEntity.Participation.ParticipationTypeId == ParticipationTypes.Dh) { @@ -88,7 +51,7 @@ public async Task GetDataHolderBrandAsync(Guid brandId) DataHolderId = dataHolderBrandEntity.ParticipationId, Industry = dataHolderBrandEntity.Participation?.IndustryId.ToString(), Status = dataHolderBrandEntity.Participation.StatusId.ToString(), - LegalEntity = _mapper.Map(dataHolderBrandEntity.Participation.LegalEntity) + LegalEntity = this._mapper.Map(dataHolderBrandEntity.Participation.LegalEntity), }; } @@ -97,7 +60,7 @@ public async Task GetDataHolderBrandAsync(Guid brandId) public async Task GetDataRecipientIdAsync(Guid legalEntityId) { - return await _registerDatabaseContext.Participations.AsNoTracking() + return await this._registerDatabaseContext.Participations.AsNoTracking() .Include(p => p.Status) .Include(p => p.Industry) .Include(p => p.LegalEntity) @@ -112,29 +75,29 @@ public async Task GetDataRecipientIdAsync(Guid legalEntityId) public async Task AddOrUpdateDataRecipient(DataRecipient dataRecipient) { - var existingDataRecipient = await GetDataRecipientIdAsync(dataRecipient.LegalEntity.LegalEntityId); + var existingDataRecipient = await this.GetDataRecipientIdAsync(dataRecipient.LegalEntity.LegalEntityId); - using var transaction = await _registerDatabaseContext.Database.BeginTransactionAsync(); + using var transaction = await this._registerDatabaseContext.Database.BeginTransactionAsync(); - LegalEntity legalEntity = _reporsitoryMapper.Map(dataRecipient.LegalEntity); + LegalEntity legalEntity = this._reporsitoryMapper.Map(dataRecipient.LegalEntity); - var existingLegalEntity = await _registerDatabaseContext.LegalEntities.AsNoTracking().SingleOrDefaultAsync(x => x.LegalEntityId == dataRecipient.LegalEntity.LegalEntityId); + var existingLegalEntity = await this._registerDatabaseContext.LegalEntities.AsNoTracking().SingleOrDefaultAsync(x => x.LegalEntityId == dataRecipient.LegalEntity.LegalEntityId); if (existingDataRecipient != null && existingLegalEntity != null) { - _registerDatabaseContext.LegalEntities.Update(legalEntity); + this._registerDatabaseContext.LegalEntities.Update(legalEntity); } if (existingDataRecipient == null && existingLegalEntity == null) { - await _registerDatabaseContext.LegalEntities.AddAsync(legalEntity); - _logger.LogInformation("New LegalEntity of id:{LegalEntityId} name:{LegalEntityName} getting added to the repository.", legalEntity.LegalEntityId, legalEntity.LegalEntityName); + await this._registerDatabaseContext.LegalEntities.AddAsync(legalEntity); + this._logger.LogInformation("New LegalEntity of id:{LegalEntityId} name:{LegalEntityName} getting added to the repository.", legalEntity.LegalEntityId, legalEntity.LegalEntityName); } - var error = await dataRecipient.AddOrUpdateDataRecipientLegalEntity(legalEntity, _registerDatabaseContext, _reporsitoryMapper, _logger); + var error = await dataRecipient.AddOrUpdateDataRecipientLegalEntity(legalEntity, this._registerDatabaseContext, this._reporsitoryMapper, this._logger); if (error == null) { - await _registerDatabaseContext.SaveChangesAsync(); + await this._registerDatabaseContext.SaveChangesAsync(); await transaction.CommitAsync(); } @@ -148,11 +111,11 @@ public async Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand return false; } - (_, var savedParticipation) = await SaveDataHolderLegalEntity(dataHolderBrand.DataHolder); + (_, var savedParticipation) = await this.SaveDataHolderLegalEntity(dataHolderBrand.DataHolder); // Save DH Brand - var dhBrandToSave = _mapper.Map(dataHolderBrand); - var existingBrand = await _registerDatabaseContext.Brands + var dhBrandToSave = this._mapper.Map(dataHolderBrand); + var existingBrand = await this._registerDatabaseContext.Brands .Include(b => b.Participation) .Include(b => b.AuthDetails) .Include(b => b.Endpoint) @@ -164,7 +127,7 @@ public async Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand } else { - dhBrandToSave = _mapper.Map(dataHolderBrand, existingBrand); + dhBrandToSave = this._mapper.Map(dataHolderBrand, existingBrand); dhBrandToSave.LastUpdated = DateTime.UtcNow; } @@ -174,13 +137,13 @@ public async Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand var existingEndpoint = existingBrand?.Endpoint; if (existingEndpoint == null) { - var endpointToSave = _mapper.Map(dataHolderBrand.DataHolderBrandServiceEndpoint); + var endpointToSave = this._mapper.Map(dataHolderBrand.DataHolderBrandServiceEndpoint); endpointToSave.Brand = dhBrandToSave; - _registerDatabaseContext.Endpoints.Add(endpointToSave); + this._registerDatabaseContext.Endpoints.Add(endpointToSave); } else { - _mapper.Map(dataHolderBrand.DataHolderBrandServiceEndpoint, existingEndpoint); + this._mapper.Map(dataHolderBrand.DataHolderBrandServiceEndpoint, existingEndpoint); } } @@ -190,18 +153,56 @@ public async Task SaveDataHolderBrand(Guid legalEntityId, DataHolderBrand var existingAuthDetail = existingBrand?.AuthDetails?.FirstOrDefault(); if (existingAuthDetail == null) { - var authDetailToSave = _mapper.Map(dataHolderBrand.DataHolderAuthentications[0]); + var authDetailToSave = this._mapper.Map(dataHolderBrand.DataHolderAuthentications[0]); authDetailToSave.Brand = dhBrandToSave; - _registerDatabaseContext.AuthDetails.Add(authDetailToSave); + this._registerDatabaseContext.AuthDetails.Add(authDetailToSave); } else { - _mapper.Map(dataHolderBrand.DataHolderAuthentications[0], existingAuthDetail); + this._mapper.Map(dataHolderBrand.DataHolderAuthentications[0], existingAuthDetail); } } - await _registerDatabaseContext.SaveChangesAsync(); + await this._registerDatabaseContext.SaveChangesAsync(); return true; } + + private async Task<(LegalEntity LegalEntity, Participation Participation)> SaveDataHolderLegalEntity(DataHolder dataHolder) + { + var participationToSave = this._mapper.Map(dataHolder); + + // Check if the entity exists. If so, update the existing values + var existingLegalEntity = await this._registerDatabaseContext.LegalEntities + .Include(le => le.Participations) + .Where(le => le.LegalEntityId == dataHolder.LegalEntity.LegalEntityId) + .FirstOrDefaultAsync(); + var legalEntityToSave = this._mapper.Map(dataHolder.LegalEntity); + if (existingLegalEntity == null) + { + participationToSave.LegalEntity = legalEntityToSave; + this._registerDatabaseContext.LegalEntities.Add(legalEntityToSave); + this._registerDatabaseContext.Participations.Add(participationToSave); + + return (legalEntityToSave, participationToSave); + } + + // Only update the DH Brand related information. + this._mapper.Map(dataHolder.LegalEntity, existingLegalEntity); + + // Update participation details + var existingDhParticipation = existingLegalEntity.Participations.FirstOrDefault(p => + p.ParticipationTypeId == ParticipationTypes.Dh && p.IndustryId == participationToSave.IndustryId); + if (existingDhParticipation == null) + { + participationToSave.LegalEntity = existingLegalEntity; + this._registerDatabaseContext.Participations.Add(participationToSave); + } + else + { + participationToSave = this._mapper.Map(dataHolder, existingDhParticipation); + } + + return (existingLegalEntity, participationToSave); + } } } diff --git a/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs b/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs index e25d6ee..122d0e9 100644 --- a/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs +++ b/Source/CDR.Register.Repository/RegisterDiscoveryRepository.cs @@ -1,15 +1,15 @@ -using AutoMapper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; using CDR.Register.Domain.Entities; using CDR.Register.Domain.ValueObjects; using CDR.Register.Repository.Entities; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using CDR.Register.Repository.Interfaces; using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace CDR.Register.Repository { @@ -26,18 +26,52 @@ public RegisterDiscoveryRepository(RegisterDatabaseContext registerDatabaseConte public async Task> GetDataHolderBrandsAsync(Infrastructure.Industry industry, DateTime? updatedSince, int page, int pageSize) { - (List allBrands, int totalRecords) = await ProcessGetDataHolderBrands(industry, updatedSince, page, pageSize); + (List allBrands, int totalRecords) = await this.ProcessGetDataHolderBrands(industry, updatedSince, page, pageSize); return new Page() { - Data = _mapper.Map(allBrands), + Data = this._mapper.Map(allBrands), CurrentPage = page, PageSize = pageSize, - TotalRecords = totalRecords + TotalRecords = totalRecords, }; } - protected async Task<(List, int)> ProcessGetDataHolderBrands(Infrastructure.Industry industry, DateTime? updatedSince, int page, int pageSize) + public async Task GetDataRecipientsAsync(Infrastructure.Industry industry) + { + List allParticipants = await this.ProcessGetDataRecipients(industry); + + // Additionally sort participants.brands, participants.brands.softwareproducts by id + allParticipants.ForEach(p => + { + p.Brands = p.Brands.OrderBy(b => b.BrandId).ToList(); + p.Brands.ToList().ForEach(b => + { + b.SoftwareProducts = b.SoftwareProducts.OrderBy(sp => sp.SoftwareProductId).ToList(); + }); + }); + + return this._mapper.Map(allParticipants); + } + + public async Task GetSoftwareProductIdAsync(Guid softwareProductId) + { + var softwareProduct = await this._registerDatabaseContext.SoftwareProducts.AsNoTracking() + .Include(softwareProduct => softwareProduct.Status) + .Include(softwareProduct => softwareProduct.Brand.BrandStatus) + .Include(softwareProduct => softwareProduct.Brand.Participation.Status) + .Where(softwareProduct => + softwareProduct.SoftwareProductId == softwareProductId + && softwareProduct.Brand.Participation.ParticipationTypeId == ParticipationTypes.Dr + && softwareProduct.StatusId == SoftwareProductStatusType.Active + && softwareProduct.Brand.BrandStatusId == BrandStatusType.Active + && softwareProduct.Brand.Participation.StatusId == ParticipationStatusType.Active) + .FirstOrDefaultAsync(); + + return this._mapper.Map(softwareProduct); + } + + protected async Task<(List Brands, int Count)> ProcessGetDataHolderBrands(Infrastructure.Industry industry, DateTime? updatedSince, int page, int pageSize) { var allBrandsQuery = this._registerDatabaseContext.Brands.AsNoTracking() .Include(brand => brand.Endpoint) @@ -72,26 +106,10 @@ public async Task> GetDataHolderBrandsAsync(Infrastructu return (allBrands, totalRecords); } - public async Task GetDataRecipientsAsync(Infrastructure.Industry industry) - { - List allParticipants = await ProcessGetDataRecipients(industry); - - // Additionally sort participants.brands, participants.brands.softwareproducts by id - allParticipants.ForEach(p => - { - p.Brands = p.Brands.OrderBy(b => b.BrandId).ToList(); - p.Brands.ToList().ForEach(b => - { - b.SoftwareProducts = b.SoftwareProducts.OrderBy(sp => sp.SoftwareProductId).ToList(); - }); - }); - - return _mapper.Map(allParticipants); - } - /// /// The industry parameter is passed but currently not used. /// + /// representing the asynchronous operation. protected async Task> ProcessGetDataRecipients(Infrastructure.Industry industry) { return await this._registerDatabaseContext.Participations.AsNoTracking() @@ -107,22 +125,5 @@ protected async Task> ProcessGetDataRecipients(Infrastructur .OrderBy(p => p.LegalEntityId) .ToListAsync(); } - - public async Task GetSoftwareProductIdAsync(Guid softwareProductId) - { - var softwareProduct = await _registerDatabaseContext.SoftwareProducts.AsNoTracking() - .Include(softwareProduct => softwareProduct.Status) - .Include(softwareProduct => softwareProduct.Brand.BrandStatus) - .Include(softwareProduct => softwareProduct.Brand.Participation.Status) - .Where(softwareProduct => - softwareProduct.SoftwareProductId == softwareProductId - && softwareProduct.Brand.Participation.ParticipationTypeId == ParticipationTypes.Dr - && softwareProduct.StatusId == SoftwareProductStatusType.Active - && softwareProduct.Brand.BrandStatusId == BrandStatusType.Active - && softwareProduct.Brand.Participation.StatusId == ParticipationStatusType.Active) - .FirstOrDefaultAsync(); - - return _mapper.Map(softwareProduct); - } } } diff --git a/Source/CDR.Register.Repository/RegisterInfosecRepository.cs b/Source/CDR.Register.Repository/RegisterInfosecRepository.cs index 4cefc9a..f431823 100644 --- a/Source/CDR.Register.Repository/RegisterInfosecRepository.cs +++ b/Source/CDR.Register.Repository/RegisterInfosecRepository.cs @@ -26,7 +26,7 @@ public async Task GetSoftwareProductAsync(Guid id) .Include(x => x.Certificates) .FirstOrDefaultAsync(x => x.SoftwareProductId == id); - return _mapper.Map(softwareProduct); + return this._mapper.Map(softwareProduct); } } } diff --git a/Source/CDR.Register.Repository/RegisterStatusRepository.cs b/Source/CDR.Register.Repository/RegisterStatusRepository.cs index 99a61b2..91601e6 100644 --- a/Source/CDR.Register.Repository/RegisterStatusRepository.cs +++ b/Source/CDR.Register.Repository/RegisterStatusRepository.cs @@ -1,10 +1,10 @@ -using CDR.Register.Domain.Entities; -using CDR.Register.Repository.Entities; +using System.Linq; +using System.Threading.Tasks; +using CDR.Register.Domain.Entities; +using CDR.Register.Repository.Enums; using CDR.Register.Repository.Infrastructure; using CDR.Register.Repository.Interfaces; using Microsoft.EntityFrameworkCore; -using System.Linq; -using System.Threading.Tasks; namespace CDR.Register.Repository { @@ -55,7 +55,7 @@ public async Task GetDataRecipientStatusesAsync(Infrastru new Domain.Entities.SoftwareProductStatus { SoftwareProductId = sp.SoftwareProductId, - Status = sp.Status.SoftwareProductStatusCode + 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 6a7c7f0..68208e7 100644 --- a/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs +++ b/Source/CDR.Register.Repository/SoftwareStatementAssertionRepository.cs @@ -15,21 +15,21 @@ public class SoftwareStatementAssertionRepository : ISoftwareStatementAssertionR public SoftwareStatementAssertionRepository(RegisterDatabaseContext registerDatabaseContext, IRepositoryMapper mapper) { - _registerDatabaseContext = registerDatabaseContext; - _mapper = mapper; + this._registerDatabaseContext = registerDatabaseContext; + this._mapper = mapper; } public async Task GetSoftwareStatementAssertionAsync(Guid dataRecipientBrandId, Guid softwareProductId) { - return await _registerDatabaseContext.SoftwareProducts + return await this._registerDatabaseContext.SoftwareProducts .Include(x => x.Certificates) .Include(x => x.Brand).ThenInclude(x => x.Participation).ThenInclude(x => x.LegalEntity) .Where(x => x.Brand.BrandId == dataRecipientBrandId && x.SoftwareProductId == softwareProductId) .Select(x => new SoftwareStatementAssertion { - DataRecipientBrand = _mapper.Map(x.Brand), - SoftwareProduct = _mapper.MapSoftwareProduct(x), - LegalEntity = _mapper.Map(x.Brand.Participation.LegalEntity) + DataRecipientBrand = this._mapper.Map(x.Brand), + SoftwareProduct = this._mapper.MapSoftwareProduct(x), + LegalEntity = this._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 0594b09..29e2914 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 @@ -56,7 +56,7 @@ 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 9c15801..1e57162 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/HttpClientHandlerExtensionsTests.cs @@ -30,10 +30,10 @@ public HttpClientHandlerExtensionsTests() { "EnableServerCertificateValidation", "True" }, }) .Build(); - _configuration = configuration; + this._configuration = configuration; - _handler = new HttpClientHandler(); - _handler.SetServerCertificateValidation(_configuration); + this._handler = new HttpClientHandler(); + this._handler.SetServerCertificateValidation(this._configuration); } [Theory] @@ -51,7 +51,7 @@ public async Task ServerCertificates_ValidationEnabled_ShouldValidateSslConnecti certPassword)) { mockEndpoint.Start(); - var client = new HttpClient(_handler); + var client = new HttpClient(this._handler); if (expected) { var result = await client.GetAsync("https://localhost:9990"); @@ -68,60 +68,59 @@ public async Task ServerCertificates_ValidationEnabled_ShouldValidateSslConnecti public partial class MockEndpoint : IAsyncDisposable { + private IWebHost? _host; + private bool _disposed; + public MockEndpoint(string url, string certificatePath, string certificatePassword) { - Url = url; - CertificatePath = certificatePath; - CertificatePassword = certificatePassword; + this.Url = url; + this.CertificatePath = certificatePath; + this.CertificatePassword = certificatePassword; } 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; + private int UrlPort => new Uri(this.Url).Port; public void Start() { - Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(Start), nameof(MockEndpoint)); + Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(this.Start), nameof(MockEndpoint)); - _host = new WebHostBuilder() + this._host = new WebHostBuilder() .UseKestrel(opts => { opts.ListenAnyIP( - UrlPort, - opts => opts.UseHttps(new X509Certificate2(CertificatePath, CertificatePassword, X509KeyStorageFlags.Exportable))); + this.UrlPort, + opts => opts.UseHttps(new X509Certificate2(this.CertificatePath, this.CertificatePassword, X509KeyStorageFlags.Exportable))); }) .UseStartup(typeof(MockEndpointStartup)) .Build(); - _host.RunAsync(); + this._host.RunAsync(); } public async Task Stop() { - Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(Stop), nameof(MockEndpoint)); + Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(this.Stop), nameof(MockEndpoint)); - if (_host != null) + if (this._host != null) { - await _host.StopAsync(); + await this._host.StopAsync(); } } - private bool _disposed; - public async ValueTask DisposeAsync() { - Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(DisposeAsync), nameof(MockEndpoint)); + Log.Information("Calling {FUNCTION} in {ClassName}.", nameof(this.DisposeAsync), nameof(MockEndpoint)); - if (!_disposed) + if (!this._disposed) { - await Stop(); - _disposed = true; + await this.Stop(); + this._disposed = true; } GC.SuppressFinalize(this); diff --git a/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs b/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs index e7fb764..34a0569 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/JwksTests.cs @@ -29,15 +29,15 @@ public void GenerateJwks_ValidCertificate_ShouldGenerateJwks() // Assert. Assert.NotNull(jwks); - Assert.Single(jwks.keys); + Assert.Single(jwks.Keys); - var jwk = jwks.keys[0]; - Assert.Equal("PS256", jwk.alg); - Assert.Equal("203A41DC1743F97212BFBF01B77895CD9F445BFB", jwk.kid); - Assert.Equal("RSA", jwk.kty); - Assert.Equal("sCKajSK266KlSW0sSOa3Jbfrq1PCa2EHkuXoNwC0VMhrq-u_J1qMxvM50LCT5OF45GWPl4LUFhoJlej-XQtHztBpB6NWX5eJXW45M2OHmerRqN9IP5oQ1yscTzyiQyFoTbLpjFyRASrQZy1XMGrMMa7tqLpyHDxzJX-SBsr_hq8Olj0LFLeWi3giLirj_4CRqqmTtvLCaMwGajpEGQz3Xc96FNZXUOIR-wX_WjbCzVn2-X7PHjgIbT_oURtnovxQ6ZXZRtqxBhIKwJ-zCOOZAAqDJcy-7QxtsWpU_IyRRPziAyQ254iLcjV125DgQnd5TsUQQX6nfBozgbYdSLfLzQ", jwk.n); - Assert.Equal("AQAB", jwk.e); - Assert.Equal(2, jwk.key_ops.Length); + var jwk = jwks.Keys[0]; + Assert.Equal("PS256", jwk.Alg); + Assert.Equal("203A41DC1743F97212BFBF01B77895CD9F445BFB", jwk.Kid); + Assert.Equal("RSA", jwk.Kty); + Assert.Equal("sCKajSK266KlSW0sSOa3Jbfrq1PCa2EHkuXoNwC0VMhrq-u_J1qMxvM50LCT5OF45GWPl4LUFhoJlej-XQtHztBpB6NWX5eJXW45M2OHmerRqN9IP5oQ1yscTzyiQyFoTbLpjFyRASrQZy1XMGrMMa7tqLpyHDxzJX-SBsr_hq8Olj0LFLeWi3giLirj_4CRqqmTtvLCaMwGajpEGQz3Xc96FNZXUOIR-wX_WjbCzVn2-X7PHjgIbT_oURtnovxQ6ZXZRtqxBhIKwJ-zCOOZAAqDJcy-7QxtsWpU_IyRRPziAyQ254iLcjV125DgQnd5TsUQQX6nfBozgbYdSLfLzQ", jwk.N); + 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 7b0d1b6..1b1cffe 100644 --- a/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs +++ b/Source/CDR.Register.SSA.API.UnitTests/TokenizerServiceTests.cs @@ -26,44 +26,44 @@ public TokenizerServiceTests() .AddJsonFile("appsettings.json") .Build(); - _configuration = configuration; + this._configuration = configuration; var services = new ServiceCollection(); - services.AddSingleton(x => new CertificateService(_configuration)); + services.AddSingleton(x => new CertificateService(this._configuration)); services.AddSingleton(); - _serviceProvider = services.BuildServiceProvider(); + this._serviceProvider = services.BuildServiceProvider(); } [Fact] public async Task GenerateJwtTokenAsync_Success() { // Arrange - var tokenizerService = _serviceProvider.GetRequiredService(); + var tokenizerService = this._serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel { - client_description = "Application to allow you to track your expenses", - client_name = "Track Xpense", - client_uri = "https://fintechx.io/products/trackxpense", - exp = 1619233821, - iat = 1619233221, - iss = "cdr-register", - jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", - jwks_uri = "https://fintechx.io/products/trackxpense/jwks", - logo_uri = "https://fintechx.io/products/trackxpense/logo.png", - org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", - org_name = "Finance X", - recipient_base_uri = "https://fintechx.io", - redirect_uris = new List() + Client_description = "Application to allow you to track your expenses", + Client_name = "Track Xpense", + Client_uri = "https://fintechx.io/products/trackxpense", + Exp = 1619233821, + Iat = 1619233221, + Iss = "cdr-register", + Jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", + Jwks_uri = "https://fintechx.io/products/trackxpense/jwks", + Logo_uri = "https://fintechx.io/products/trackxpense/logo.png", + Org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", + Org_name = "Finance X", + Recipient_base_uri = "https://fintechx.io", + Redirect_uris = new List() { - "https://fintechx.io/products/trackxpense/cb" + "https://fintechx.io/products/trackxpense/cb", }, - revocation_uri = "https://fintechx.io/products/trackxpense/revoke", - scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", - software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", - software_roles = "data-recipient-software-product", + Revocation_uri = "https://fintechx.io/products/trackxpense/revoke", + Scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", + Software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", + Software_roles = "data-recipient-software-product", }; // Generate the SSA JWT token @@ -72,7 +72,7 @@ public async Task GenerateJwtTokenAsync_Success() var tokenHandler = new JwtSecurityTokenHandler(); // Create the certificate which has only public key - var cert = new X509Certificate2(_configuration["SigningCertificatePublic:Path"]); + var cert = new X509Certificate2(this._configuration["SigningCertificatePublic:Path"]); // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); @@ -85,7 +85,7 @@ public async Task GenerateJwtTokenAsync_Success() ValidIssuer = "cdr-register", ValidateIssuer = true, ValidateLifetime = false, - ValidateAudience = false + ValidateAudience = false, }; SecurityToken validatedToken; @@ -103,30 +103,30 @@ public async Task GenerateJwtTokenAsync_Success() public async Task GenerateJwtTokenAsync_InvalidToken_Failure() { // Arrange - var tokenizerService = _serviceProvider.GetRequiredService(); + var tokenizerService = this._serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel { - client_description = "Application to allow you to track your expenses", - client_name = "Track Xpense", - client_uri = "https://fintechx.io/products/trackxpense", - exp = 1619233821, - iat = 1619233221, - iss = "cdr-register", - jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", - jwks_uri = "https://fintechx.io/products/trackxpense/jwks", - logo_uri = "https://fintechx.io/products/trackxpense/logo.png", - org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", - org_name = "Finance X", - recipient_base_uri = "https://fintechx.io", - redirect_uris = new List() + Client_description = "Application to allow you to track your expenses", + Client_name = "Track Xpense", + Client_uri = "https://fintechx.io/products/trackxpense", + Exp = 1619233821, + Iat = 1619233221, + Iss = "cdr-register", + Jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", + Jwks_uri = "https://fintechx.io/products/trackxpense/jwks", + Logo_uri = "https://fintechx.io/products/trackxpense/logo.png", + Org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", + Org_name = "Finance X", + Recipient_base_uri = "https://fintechx.io", + Redirect_uris = new List() { - "https://fintechx.io/products/trackxpense/cb" + "https://fintechx.io/products/trackxpense/cb", }, - revocation_uri = "https://fintechx.io/products/trackxpense/revoke", - scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", - software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", - software_roles = "data-recipient-software-product", + Revocation_uri = "https://fintechx.io/products/trackxpense/revoke", + Scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", + Software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", + Software_roles = "data-recipient-software-product", }; // Generate the SSA JWT token @@ -138,7 +138,7 @@ public async Task GenerateJwtTokenAsync_InvalidToken_Failure() var tokenHandler = new JwtSecurityTokenHandler(); // Create the certificate which has only public key - var cert = new X509Certificate2(_configuration["SigningCertificatePublic:Path"]); + var cert = new X509Certificate2(this._configuration["SigningCertificatePublic:Path"]); // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); @@ -151,7 +151,7 @@ public async Task GenerateJwtTokenAsync_InvalidToken_Failure() ValidIssuer = "cdr-register", ValidateIssuer = true, ValidateLifetime = false, - ValidateAudience = false + ValidateAudience = false, }; try @@ -174,30 +174,30 @@ public async Task GenerateJwtTokenAsync_InvalidToken_Failure() public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() { // Arrange - var tokenizerService = _serviceProvider.GetRequiredService(); + var tokenizerService = this._serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel { - client_description = "Application to allow you to track your expenses", - client_name = "Track Xpense", - client_uri = "https://fintechx.io/products/trackxpense", - exp = 1619233821, - iat = 1619233221, - iss = "cdr-register", - jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", - jwks_uri = "https://fintechx.io/products/trackxpense/jwks", - logo_uri = "https://fintechx.io/products/trackxpense/logo.png", - org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", - org_name = "Finance X", - recipient_base_uri = "https://fintechx.io", - redirect_uris = new List() + Client_description = "Application to allow you to track your expenses", + Client_name = "Track Xpense", + Client_uri = "https://fintechx.io/products/trackxpense", + Exp = 1619233821, + Iat = 1619233221, + Iss = "cdr-register", + Jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", + Jwks_uri = "https://fintechx.io/products/trackxpense/jwks", + Logo_uri = "https://fintechx.io/products/trackxpense/logo.png", + Org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", + Org_name = "Finance X", + Recipient_base_uri = "https://fintechx.io", + Redirect_uris = new List() { - "https://fintechx.io/products/trackxpense/cb" + "https://fintechx.io/products/trackxpense/cb", }, - revocation_uri = "https://fintechx.io/products/trackxpense/revoke", - scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", - software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", - software_roles = "data-recipient-software-product", + Revocation_uri = "https://fintechx.io/products/trackxpense/revoke", + Scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", + Software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", + Software_roles = "data-recipient-software-product", }; // Generate the SSA JWT token @@ -206,7 +206,7 @@ public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() var tokenHandler = new JwtSecurityTokenHandler(); // Create the certificate which has only public key - var cert = new X509Certificate2(_configuration["InvalidSigningCertificatePublic:Path"]); + var cert = new X509Certificate2(this._configuration["InvalidSigningCertificatePublic:Path"]); // Get credentials from certificate var certificateSecurityKey = new X509SecurityKey(cert); @@ -219,7 +219,7 @@ public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() ValidIssuer = "cdr-register", ValidateIssuer = true, ValidateLifetime = false, - ValidateAudience = false + ValidateAudience = false, }; try @@ -242,29 +242,29 @@ public async Task GenerateJwtTokenAsync_InvalidCertificate_Failure() public async Task GenerateJwtTokenAsync_ValidateJwks_Success() { // Arrange. - var tokenizerService = _serviceProvider.GetRequiredService(); + var tokenizerService = this._serviceProvider.GetRequiredService(); var ssa = new SoftwareStatementAssertionModel { - client_description = "Application to allow you to track your expenses", - client_name = "Track Xpense", - client_uri = "https://fintechx.io/products/trackxpense", - exp = 1619233821, - iat = 1619233221, - iss = "cdr-register", - jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", - jwks_uri = "https://fintechx.io/products/trackxpense/jwks", - logo_uri = "https://fintechx.io/products/trackxpense/logo.png", - org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", - org_name = "Finance X", - recipient_base_uri = "https://fintechx.io", - redirect_uris = new List() + Client_description = "Application to allow you to track your expenses", + Client_name = "Track Xpense", + Client_uri = "https://fintechx.io/products/trackxpense", + Exp = 1619233821, + Iat = 1619233221, + Iss = "cdr-register", + Jti = "cfa1cd02d7914be6b4d9c4d77c080ad7", + Jwks_uri = "https://fintechx.io/products/trackxpense/jwks", + Logo_uri = "https://fintechx.io/products/trackxpense/logo.png", + Org_id = "20c0864b-ceef-4de0-8944-eb0962f825eb", + Org_name = "Finance X", + Recipient_base_uri = "https://fintechx.io", + Redirect_uris = new List() { - "https://fintechx.io/products/trackxpense/cb" + "https://fintechx.io/products/trackxpense/cb", }, - revocation_uri = "https://fintechx.io/products/trackxpense/revoke", - scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", - software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", - software_roles = "data-recipient-software-product", + Revocation_uri = "https://fintechx.io/products/trackxpense/revoke", + Scope = "openid profile common:customer.basic:read common:customer.detail:read bank:accounts.basic:read bank:accounts.detail:read bank:transactions:read bank:regular_payments:read bank:payees:read energy:accounts.basic:read energy:accounts.detail:read energy:accounts.concessions:read energy:accounts.paymentschedule:read energy:billing:read energy:electricity.servicepoints.basic:read energy:electricity.servicepoints.detail:read energy:electricity.der:read energy:electricity.usage:read cdr:registration", + Software_id = "9381dad2-6b68-4879-b496-c1319d7dfbc9", + Software_roles = "data-recipient-software-product", }; // Generate the SSA JWT token @@ -289,12 +289,12 @@ public async Task GenerateJwtTokenAsync_ValidateJwks_Success() // Set token validation parameters var validationParameters = new TokenValidationParameters() { - IssuerSigningKey = new JsonWebKey(jwks.keys[0].ToJson()), + IssuerSigningKey = new JsonWebKey(jwks.Keys[0].ToJson()), ValidateIssuerSigningKey = true, ValidIssuer = "cdr-register", ValidateIssuer = true, ValidateLifetime = false, - ValidateAudience = false + ValidateAudience = false, }; SecurityToken validatedToken; diff --git a/Source/CDR.Register.SSA.API/Business/CertificateService.cs b/Source/CDR.Register.SSA.API/Business/CertificateService.cs index 81d04b0..fd0be72 100644 --- a/Source/CDR.Register.SSA.API/Business/CertificateService.cs +++ b/Source/CDR.Register.SSA.API/Business/CertificateService.cs @@ -9,36 +9,36 @@ namespace CDR.Register.SSA.API.Business /// public class CertificateService : ICertificateService { - public string Kid { get; private set; } - - public SignatureProvider SignatureProvider { get; private set; } - - public Register.API.Infrastructure.Models.JsonWebKeySet JsonWebKeySet { get; private set; } - - private X509SecurityKey SecurityKey { get; set; } - - private X509SigningCredentials SigningCredentials { get; set; } - public CertificateService(IConfiguration config) { // Create the certificate var cert = new X509Certificate2(config["SigningCertificate:Path"], config["SigningCertificate:Password"], X509KeyStorageFlags.Exportable); // Get credentials from certificate - SecurityKey = new X509SecurityKey(cert); - SigningCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); + this.SecurityKey = new X509SecurityKey(cert); + this.SigningCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSsaPssSha256); // Get certificate kid - this.Kid = SigningCredentials.Kid; + this.Kid = this.SigningCredentials.Kid; - SigningCredentials.CryptoProviderFactory = new CryptoProviderFactory(); + this.SigningCredentials.CryptoProviderFactory = new CryptoProviderFactory(); // Get signature provider - this.SignatureProvider = SigningCredentials.CryptoProviderFactory.CreateForSigning(SecurityKey, "PS256"); + this.SignatureProvider = this.SigningCredentials.CryptoProviderFactory.CreateForSigning(this.SecurityKey, "PS256"); - this.JsonWebKeySet = GenerateJwks(); + this.JsonWebKeySet = this.GenerateJwks(); } + public string Kid { get; private set; } + + public SignatureProvider SignatureProvider { get; private set; } + + public Register.API.Infrastructure.Models.JsonWebKeySet JsonWebKeySet { get; private set; } + + private X509SecurityKey SecurityKey { get; set; } + + private X509SigningCredentials SigningCredentials { get; set; } + private CDR.Register.API.Infrastructure.Models.JsonWebKeySet GenerateJwks() { var rsaParams = this.SigningCredentials.Certificate.GetRSAPublicKey().ExportParameters(false); @@ -47,17 +47,17 @@ private CDR.Register.API.Infrastructure.Models.JsonWebKeySet GenerateJwks() var jwk = new CDR.Register.API.Infrastructure.Models.JsonWebKey() { - alg = this.SigningCredentials.Algorithm, - kid = this.SigningCredentials.Kid, - kty = this.SecurityKey.PublicKey.KeyExchangeAlgorithm, - n = n, - e = e, - key_ops = ["sign", "verify"] + Alg = this.SigningCredentials.Algorithm, + Kid = this.SigningCredentials.Kid, + Kty = this.SecurityKey.PublicKey.KeyExchangeAlgorithm, + N = n, + E = e, + Key_ops = ["sign", "verify"], }; return new CDR.Register.API.Infrastructure.Models.JsonWebKeySet() { - keys = [jwk] + Keys = [jwk], }; } } diff --git a/Source/CDR.Register.SSA.API/Business/Mapper.cs b/Source/CDR.Register.SSA.API/Business/Mapper.cs index 0dfa38b..9c222e9 100644 --- a/Source/CDR.Register.SSA.API/Business/Mapper.cs +++ b/Source/CDR.Register.SSA.API/Business/Mapper.cs @@ -13,37 +13,37 @@ public class Mapper : IMapper public Mapper(IConfiguration config) { - _config = config; + this._config = config; var configuration = new MapperConfiguration(cfg => { // Base mapping. cfg.CreateMap() - .ForMember(d => d.client_name, s => s.MapFrom(source => source.SoftwareProduct.SoftwareProductName)) - .ForMember(d => d.client_description, s => s.MapFrom(source => source.SoftwareProduct.SoftwareProductDescription)) - .ForMember(d => d.client_uri, s => s.MapFrom(source => source.SoftwareProduct.ClientUri)) - .ForMember(d => d.jwks_uri, s => s.MapFrom(source => source.SoftwareProduct.JwksUri)) - .ForMember(d => d.logo_uri, s => s.MapFrom(source => source.SoftwareProduct.LogoUri)) - .ForMember(d => d.org_id, s => s.MapFrom(source => source.DataRecipientBrand.BrandId)) - .ForMember(d => d.org_name, s => s.MapFrom(source => source.DataRecipientBrand.BrandName)) - .ForMember(d => d.policy_uri, s => s.MapFrom(source => source.SoftwareProduct.PolicyUri)) - .ForMember(d => d.redirect_uris, s => s.MapFrom(source => source.SoftwareProduct.RedirectUris)) - .ForMember(d => d.revocation_uri, s => s.MapFrom(source => source.SoftwareProduct.RevocationUri)) - .ForMember(d => d.recipient_base_uri, s => s.MapFrom(source => source.SoftwareProduct.RecipientBaseUri)) - .ForMember(d => d.scope, s => s.MapFrom(source => source.SoftwareProduct.Scope)) - .ForMember(d => d.software_roles, s => s.MapFrom(source => "data-recipient-software-product")) - .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 - 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 - 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)); + .ForMember(d => d.Client_name, s => s.MapFrom(source => source.SoftwareProduct.SoftwareProductName)) + .ForMember(d => d.Client_description, s => s.MapFrom(source => source.SoftwareProduct.SoftwareProductDescription)) + .ForMember(d => d.Client_uri, s => s.MapFrom(source => source.SoftwareProduct.ClientUri)) + .ForMember(d => d.Jwks_uri, s => s.MapFrom(source => source.SoftwareProduct.JwksUri)) + .ForMember(d => d.Logo_uri, s => s.MapFrom(source => source.SoftwareProduct.LogoUri)) + .ForMember(d => d.Org_id, s => s.MapFrom(source => source.DataRecipientBrand.BrandId)) + .ForMember(d => d.Org_name, s => s.MapFrom(source => source.DataRecipientBrand.BrandName)) + .ForMember(d => d.Policy_uri, s => s.MapFrom(source => source.SoftwareProduct.PolicyUri)) + .ForMember(d => d.Redirect_uris, s => s.MapFrom(source => source.SoftwareProduct.RedirectUris)) + .ForMember(d => d.Revocation_uri, s => s.MapFrom(source => source.SoftwareProduct.RevocationUri)) + .ForMember(d => d.Recipient_base_uri, s => s.MapFrom(source => source.SoftwareProduct.RecipientBaseUri)) + .ForMember(d => d.Scope, s => s.MapFrom(source => source.SoftwareProduct.Scope)) + .ForMember(d => d.Software_roles, s => s.MapFrom(source => "data-recipient-software-product")) + .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 => this._config["SSA:Issuer"])) + .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 - DateTime.UnixEpoch).TotalSeconds + long.Parse(this._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)); }); - _mapper = configuration.CreateMapper(); + this._mapper = configuration.CreateMapper(); } public SoftwareStatementAssertionModel MapV3(SoftwareStatementAssertion softwareStatementAssertion) @@ -53,7 +53,7 @@ public SoftwareStatementAssertionModel MapV3(SoftwareStatementAssertion software return null; } - return _mapper.Map(softwareStatementAssertion); + return this._mapper.Map(softwareStatementAssertion); } } } diff --git a/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs b/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs index 5e19b87..6a384f0 100644 --- a/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs +++ b/Source/CDR.Register.SSA.API/Business/Models/SoftwareStatementAssertionModel.cs @@ -1,135 +1,158 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace CDR.Register.SSA.API.Business.Models { public class SoftwareStatementAssertionModel { /// - /// iss (issuer) claim denoting the party attesting to the claims in the software statement. + /// Gets or sets iss (issuer) claim denoting the party attesting to the claims in the software statement. /// [Required(AllowEmptyStrings = false)] - public string iss { get; set; } + [JsonPropertyName("iss")] + public string Iss { get; set; } /// - /// iat (issued at) claim. + /// Gets or sets iat (issued at) claim. /// [Required] - public long iat { get; set; } + [JsonPropertyName("iat")] + public long Iat { get; set; } /// - /// exp (expiration time) claim + /// Gets or sets exp (expiration time) claim /// MUST NOT be accepted for processing. /// [Required] - public long exp { get; set; } + [JsonPropertyName("exp")] + public long Exp { get; set; } /// - /// jti (JWT ID) claim. + /// Gets or sets jti (JWT ID) claim. /// [Required(AllowEmptyStrings = false)] - public string jti { get; set; } + [JsonPropertyName("jti")] + public string Jti { get; set; } /// - /// A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). + /// Gets or sets unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). /// [Required(AllowEmptyStrings = false)] - public string org_id { get; set; } + [JsonPropertyName("org_id")] + public string Org_id { get; set; } /// - /// Human-readable string name of the Accredited Data Recipient to be presented to the end user during authorization. + /// Gets or sets human-readable string name of the Accredited Data Recipient to be presented to the end user during authorization. /// [Required(AllowEmptyStrings = false)] - public string org_name { get; set; } + [JsonPropertyName("org_name")] + public string Org_name { get; set; } /// - /// Human-readable string name of the software product to be presented to the end-user during authorization. + /// Gets or sets 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; } + [JsonPropertyName("client_name")] + public string Client_name { get; set; } /// - /// Human-readable string name of the software product description to be presented to the end user during authorization. + /// Gets or sets 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; } + [JsonPropertyName("client_description")] + public string Client_description { get; set; } /// - /// URL string of a web page providing information about the ADR Software Product. + /// Gets or sets URL string of a web page providing information about the ADR Software Product. /// [Required(AllowEmptyStrings = false)] - public string client_uri { get; set; } + [JsonPropertyName("client_uri")] + public string Client_uri { get; set; } /// - /// Array of redirection URI strings for use in redirect-based flows. + /// Gets or sets array of redirection URI strings for use in redirect-based flows. /// [Required] - public IEnumerable redirect_uris { get; set; } + [JsonPropertyName("redirect_uris")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("logo_uri")] + public string Logo_uri { get; set; } /// - /// URL string that points to a humanreadable terms of service document for the Software Product. + /// Gets or sets URL string that points to a humanreadable terms of service document for the Software Product. /// - public string tos_uri { get; set; } + [JsonPropertyName("tos_uri")] + public string Tos_uri { get; set; } /// - /// URL string that points to a humanreadable policy document for the Software Product. + /// Gets or sets URL string that points to a humanreadable policy document for the Software Product. /// - public string policy_uri { get; set; } + [JsonPropertyName("policy_uri")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("jwks_uri")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("revocation_uri")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("recipient_base_uri")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("software_id")] + 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". + /// Gets or sets 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; } + [JsonPropertyName("software_roles")] + public string Software_roles { get; set; } /// - /// String containing a space-separated list of scope values that the client can use when requesting access tokens. + /// Gets or sets 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; } + [JsonPropertyName("scope")] + 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. + /// Gets or sets 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; } + [JsonPropertyName("sector_identifier_uri")] + public string Sector_identifier_uri { get; set; } /// - /// Human-readable string legal entity id of the Accredited Data Recipient to be presented to the end user during authorization. + /// Gets or sets human-readable string legal entity id of the Accredited Data Recipient to be presented to the end user during authorization. /// - public string legal_entity_id { get; set; } + [JsonPropertyName("legal_entity_id")] + public string Legal_entity_id { get; set; } /// - /// Human-readable string legal entity name of the Accredited Data Recipient to be presented to the end user during authorization. + /// Gets or sets human-readable string legal entity name of the Accredited Data Recipient to be presented to the end user during authorization. /// - public string legal_entity_name { get; set; } + [JsonPropertyName("legal_entity_name")] + public string Legal_entity_name { get; set; } } } diff --git a/Source/CDR.Register.SSA.API/Business/SSAService.cs b/Source/CDR.Register.SSA.API/Business/SSAService.cs index 8dbee87..f65616e 100644 --- a/Source/CDR.Register.SSA.API/Business/SSAService.cs +++ b/Source/CDR.Register.SSA.API/Business/SSAService.cs @@ -1,13 +1,13 @@ -using CDR.Register.Domain.Entities; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using CDR.Register.Domain.Entities; using CDR.Register.Domain.Repositories; using CDR.Register.SSA.API.Business.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog.Context; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; namespace CDR.Register.SSA.API.Business { @@ -25,23 +25,23 @@ public SsaService( ISoftwareStatementAssertionRepository repository, ITokenizerService tokenizer) { - _logger = logger; - _mapper = mapper; - _repository = repository; - _tokenizer = tokenizer; + this._logger = logger; + this._mapper = mapper; + this._repository = repository; + this._tokenizer = tokenizer; } public async Task GetSoftwareStatementAssertionJWTAsync(Repository.Infrastructure.Industry industry, string dataRecipientBrandId, string softwareProductId) { // Get the SSA to be put in a JWT - var ssa = await GetSoftwareStatementAssertionAsync(industry, dataRecipientBrandId, softwareProductId); - return await _tokenizer.GenerateJwtTokenAsync(ssa); + var ssa = await this.GetSoftwareStatementAssertionAsync(industry, dataRecipientBrandId, softwareProductId); + return await this._tokenizer.GenerateJwtTokenAsync(ssa); } public async Task GetSoftwareStatementAssertionAsync(Repository.Infrastructure.Industry industry, string dataRecipientBrandId, string softwareProductId) { - var softwareProductEntity = await GetSoftwareStatementAssertionAsync(dataRecipientBrandId, softwareProductId); - var ssa = _mapper.MapV3(softwareProductEntity); + var softwareProductEntity = await this.GetSoftwareStatementAssertionAsync(dataRecipientBrandId, softwareProductId); + var ssa = this._mapper.MapV3(softwareProductEntity); if (ssa == null) { return null; @@ -49,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()); + this._logger.LogDebug("SSA for dataRecipientBrandId: {DataRecipientBrandId} / softwareProductId: {SoftwareProductId} \r\n{Ssa}", dataRecipientBrandId, softwareProductId, ssa.ToJson()); } // Validate the SSA @@ -58,13 +58,13 @@ public async Task GetSoftwareStatementAssertion return ssa; } - private void Validate(SoftwareStatementAssertionModel ssa) + private static void Validate(SoftwareStatementAssertionModel ssa) { var validationContext = new ValidationContext(ssa); var validationResults = new List(); 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()}"; + var errorMessage = $"Validation errors in SSA for dataRecipientBrandId: {ssa.Org_id} / softwareProductId: {ssa.Software_id} \r\n{validationResults.ToJson()}"; throw new SsaValidationException(errorMessage); } } @@ -78,7 +78,7 @@ private async Task GetSoftwareStatementAssertionAsyn var dataRecipientBrandGuid = Guid.Parse(dataRecipientBrandId); var softwareProductGuid = Guid.Parse(softwareProductId); - return await _repository.GetSoftwareStatementAssertionAsync(dataRecipientBrandGuid, softwareProductGuid); + return await this._repository.GetSoftwareStatementAssertionAsync(dataRecipientBrandGuid, softwareProductGuid); } } } diff --git a/Source/CDR.Register.SSA.API/Business/TokenizerService.cs b/Source/CDR.Register.SSA.API/Business/TokenizerService.cs index eb8018c..7a37949 100644 --- a/Source/CDR.Register.SSA.API/Business/TokenizerService.cs +++ b/Source/CDR.Register.SSA.API/Business/TokenizerService.cs @@ -17,7 +17,7 @@ public class TokenizerService : ITokenizerService public TokenizerService(ICertificateService certificateService) { - _certificateService = certificateService; + this._certificateService = certificateService; } /// @@ -34,14 +34,14 @@ public async Task GenerateJwtTokenAsync(T ssa) return null; } - var signingKid = _certificateService.Kid; + var signingKid = this._certificateService.Kid; // Create the JWT header var jwtHeader = JsonConvert.SerializeObject(new Dictionary() { { JwtHeaderParameterNames.Alg, "PS256" }, { JwtHeaderParameterNames.Kid, signingKid }, - { JwtHeaderParameterNames.Typ, "JWT" } + { JwtHeaderParameterNames.Typ, "JWT" }, }); var jwtEncodedHeader = Base64UrlEncoder.Encode(jwtHeader); @@ -51,7 +51,7 @@ public async Task GenerateJwtTokenAsync(T ssa) var byteData = Encoding.UTF8.GetBytes(jwtEncodedHeader + "." + jwtEncodedPayload); - var jwtSignature = _certificateService.SignatureProvider.Sign(byteData); + var jwtSignature = this._certificateService.SignatureProvider.Sign(byteData); var jwtEncodedSignature = Base64UrlEncoder.Encode(jwtSignature); 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 70a9772..6bf190e 100644 --- a/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj +++ b/Source/CDR.Register.SSA.API/CDR.Register.SSA.API.csproj @@ -28,7 +28,7 @@ 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 a9384c6..b16079c 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 @@ -9,115 +9,115 @@ The class extracts information from the certificate. - + - iss (issuer) claim denoting the party attesting to the claims in the software statement. + Gets or sets iss (issuer) claim denoting the party attesting to the claims in the software statement. - + - iat (issued at) claim. + Gets or sets iat (issued at) claim. - + - exp (expiration time) claim + Gets or sets exp (expiration time) claim MUST NOT be accepted for processing. - + - jti (JWT ID) claim. + Gets or sets jti (JWT ID) claim. - + - A unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). + Gets or sets unique identifier string assigned by the CDR Register that identifies CDR Participant (e.g. ADR). - + - Human-readable string name of the Accredited Data Recipient to be presented to the end user during authorization. + Gets or sets human-readable string name of the Accredited Data Recipient 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets URL string of a web page providing information about the ADR Software Product. - + - Array of redirection URI strings for use in redirect-based flows. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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. + Gets or sets 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". + Gets or sets 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. + Gets or sets 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. + Gets or sets URL string that references a sector uri for the client. If present, the server SHOULD display this image to the end-user during approval. - + - Human-readable string legal entity id of the Accredited Data Recipient to be presented to the end user during authorization. + Gets or sets human-readable string legal entity id of the Accredited Data Recipient to be presented to the end user during authorization. - + - Human-readable string legal entity name of the Accredited Data Recipient to be presented to the end user during authorization. + Gets or sets human-readable string legal entity name of the Accredited Data Recipient to be presented to the end user during authorization. diff --git a/Source/CDR.Register.SSA.API/Controllers/SSAController.cs b/Source/CDR.Register.SSA.API/Controllers/SSAController.cs index ab3f106..4482aae 100644 --- a/Source/CDR.Register.SSA.API/Controllers/SSAController.cs +++ b/Source/CDR.Register.SSA.API/Controllers/SSAController.cs @@ -1,4 +1,7 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.Net; +using System.Threading.Tasks; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Authorization; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.API.Infrastructure.Services; @@ -7,9 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System; -using System.Net; -using System.Threading.Tasks; namespace CDR.Register.SSA.API.Controllers { @@ -28,10 +28,10 @@ public SsaController( IDataRecipientStatusCheckService statusCheckService, IConfiguration configuration) { - _ssaService = ssaService; - _certificateService = certificateService; - _statusCheckService = statusCheckService; - _configuration = configuration; + this._ssaService = ssaService; + this._certificateService = certificateService; + this._statusCheckService = statusCheckService; + this._configuration = configuration; } [PolicyAuthorize(RegisterAuthorisationPolicy.GetSSAMultiIndustry)] @@ -44,24 +44,24 @@ public SsaController( public async Task GetSoftwareStatementAssertionXV3(string industry, string dataRecipientBrandId, string softwareProductId) { // CTS conformance ID validations - var basePathExpression = _configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); + var basePathExpression = this._configuration.GetValue(Constants.ConfigurationKeys.BasePathExpression); if (!string.IsNullOrEmpty(basePathExpression)) { - var validIssuer = HttpContext.ValidateIssuer(); + var validIssuer = this.HttpContext.ValidateIssuer(); if (!validIssuer) { - return Unauthorized(new ResponseErrorList(StatusCodes.Status401Unauthorized.ToString(), HttpStatusCode.Unauthorized.ToString(), "invalid_token")); + return this.Unauthorized(new ResponseErrorList(StatusCodes.Status401Unauthorized.ToString(), HttpStatusCode.Unauthorized.ToString(), "invalid_token")); } } - var result = await CheckSoftwareProduct(softwareProductId); + var result = await this.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); + var ssa = await this._ssaService.GetSoftwareStatementAssertionJWTAsync(industry.ToIndustry(), dataRecipientBrandId, softwareProductId); + return string.IsNullOrEmpty(ssa) ? this.NotFound(new ResponseErrorList(ResponseErrorList.NotFound())) : this.Ok(ssa); } [HttpGet] @@ -70,12 +70,12 @@ public async Task GetSoftwareStatementAssertionXV3(string industr [ServiceFilter(typeof(LogActionEntryAttribute))] public IActionResult GetJwks() { - return new OkObjectResult(_certificateService.JsonWebKeySet); + return new OkObjectResult(this._certificateService.JsonWebKeySet); } private Guid? GetSoftwareProductIdFromAccessToken() { - string clientId = User.FindFirst("client_id")?.Value; + string clientId = this.User.FindFirst("client_id")?.Value; if (Guid.TryParse(clientId, out Guid softwareProductId)) { return softwareProductId; @@ -92,7 +92,7 @@ public IActionResult GetJwks() private async Task CheckSoftwareProduct(string softwareProductId) { // Get the software product id based on the access token. - var softwareProductIdAsGuid = GetSoftwareProductIdFromAccessToken(); + var softwareProductIdAsGuid = this.GetSoftwareProductIdFromAccessToken(); if (softwareProductIdAsGuid == null) { return new BadRequestObjectResult(new ResponseErrorList().AddUnexpectedError()); @@ -105,7 +105,7 @@ private async Task CheckSoftwareProduct(string softwareProductId) } // Check the status of the data recipient making the SSA request. - var statusErrors = await CheckStatus(softwareProductIdAsGuid.Value); + var statusErrors = await this.CheckStatus(softwareProductIdAsGuid.Value); if (statusErrors.HasErrors()) { return new RegisterForbidResult(statusErrors); @@ -116,7 +116,7 @@ private async Task CheckSoftwareProduct(string softwareProductId) private async Task CheckStatus(Guid softwareProductId) { - return await _statusCheckService.ValidateSoftwareProductStatus(softwareProductId); + return await this._statusCheckService.ValidateSoftwareProductStatus(softwareProductId); } } } diff --git a/Source/CDR.Register.SSA.API/Program.cs b/Source/CDR.Register.SSA.API/Program.cs index 21b8aa4..92573fe 100644 --- a/Source/CDR.Register.SSA.API/Program.cs +++ b/Source/CDR.Register.SSA.API/Program.cs @@ -1,10 +1,10 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.IO; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using System; -using System.IO; namespace CDR.Register.SSA.API { diff --git a/Source/CDR.Register.SSA.API/Startup.cs b/Source/CDR.Register.SSA.API/Startup.cs index 73d9633..2d629bc 100644 --- a/Source/CDR.Register.SSA.API/Startup.cs +++ b/Source/CDR.Register.SSA.API/Startup.cs @@ -21,7 +21,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -31,7 +31,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); - services.AddRegisterSSA(Configuration); + services.AddRegisterSSA(this.Configuration); services.AddControllers(); @@ -41,7 +41,7 @@ public void ConfigureServices(IServiceCollection services) options.ErrorResponses = new ApiVersionErrorResponse(); }); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { services.AddCdrSwaggerGen(opt => @@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); - if (Configuration.GetSection("SerilogRequestResponseLogger") != null) + if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) { Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); @@ -74,7 +74,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); }); - app.UseBasePathOrExpression(Configuration); + app.UseBasePathOrExpression(this.Configuration); app.UseSerilogRequestLogging(); @@ -86,7 +86,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthorization(); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { app.UseCdrSwagger(); diff --git a/Source/CDR.Register.Status.API/Business/IStatusService.cs b/Source/CDR.Register.Status.API/Business/IStatusService.cs index 5e7a0f6..cd1d895 100644 --- a/Source/CDR.Register.Status.API/Business/IStatusService.cs +++ b/Source/CDR.Register.Status.API/Business/IStatusService.cs @@ -1,6 +1,6 @@ -using CDR.Register.Repository.Infrastructure; +using System.Threading.Tasks; +using CDR.Register.Repository.Infrastructure; using CDR.Register.Status.API.Business.Responses; -using System.Threading.Tasks; namespace CDR.Register.Status.API.Business { diff --git a/Source/CDR.Register.Status.API/Business/MappingProfile.cs b/Source/CDR.Register.Status.API/Business/MappingProfile.cs index 4bcbc11..84791b1 100644 --- a/Source/CDR.Register.Status.API/Business/MappingProfile.cs +++ b/Source/CDR.Register.Status.API/Business/MappingProfile.cs @@ -10,27 +10,27 @@ public class MappingProfile : Profile public MappingProfile() { // DataRecipientStatus - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.DataRecipientId)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Data, source => source.MapFrom(source => source)); // SoftwareProductStatus - CreateMap() + this.CreateMap() .ForMember(dest => dest.SoftwareProductId, source => source.MapFrom(source => source.SoftwareProductId)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Data, source => source.MapFrom(source => source)); // DataHolderStatus - CreateMap() + this.CreateMap() .ForMember(dest => dest.LegalEntityId, source => source.MapFrom(source => source.LegalEntityId)) .ForMember(dest => dest.Status, source => source.MapFrom(source => source.Status)); - CreateMap() + this.CreateMap() .ForMember(dest => dest.Data, source => source.MapFrom(source => source)); } } diff --git a/Source/CDR.Register.Status.API/Business/StatusService.cs b/Source/CDR.Register.Status.API/Business/StatusService.cs index 845eca0..368883c 100644 --- a/Source/CDR.Register.Status.API/Business/StatusService.cs +++ b/Source/CDR.Register.Status.API/Business/StatusService.cs @@ -1,8 +1,8 @@ +using System.Threading.Tasks; using AutoMapper; using CDR.Register.Repository.Infrastructure; using CDR.Register.Repository.Interfaces; using CDR.Register.Status.API.Business.Responses; -using System.Threading.Tasks; namespace CDR.Register.Status.API.Business { @@ -15,26 +15,26 @@ public StatusService( IRegisterStatusRepository registerStatusRepository, IMapper mapper) { - _registerStatusRepository = registerStatusRepository; - _mapper = mapper; + this._registerStatusRepository = registerStatusRepository; + this._mapper = mapper; } public async Task GetDataRecipientStatusesAsync(Industry industry) { - var entity = await _registerStatusRepository.GetDataRecipientStatusesAsync(industry); - return _mapper.Map(entity); + var entity = await this._registerStatusRepository.GetDataRecipientStatusesAsync(industry); + return this._mapper.Map(entity); } public async Task GetSoftwareProductStatusesAsync(Industry industry) { - var entity = await _registerStatusRepository.GetSoftwareProductStatusesAsync(industry); - return _mapper.Map(entity); + var entity = await this._registerStatusRepository.GetSoftwareProductStatusesAsync(industry); + return this._mapper.Map(entity); } public async Task GetDataHolderStatusesAsyncXV1(Industry industry) { - var entity = await _registerStatusRepository.GetDataHolderStatusesAsync(industry); - return _mapper.Map(entity); + var entity = await this._registerStatusRepository.GetDataHolderStatusesAsync(industry); + return this._mapper.Map(entity); } } -} \ 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 0ca27f0..05dee27 100644 --- a/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj +++ b/Source/CDR.Register.Status.API/CDR.Register.Status.API.csproj @@ -30,7 +30,7 @@ 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 b344c10..c2caece 100644 --- a/Source/CDR.Register.Status.API/Controllers/StatusController.cs +++ b/Source/CDR.Register.Status.API/Controllers/StatusController.cs @@ -1,10 +1,10 @@ -using CDR.Register.API.Infrastructure; +using System.Threading.Tasks; +using CDR.Register.API.Infrastructure; using CDR.Register.API.Infrastructure.Filters; using CDR.Register.Status.API.Business; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; -using System.Threading.Tasks; namespace CDR.Register.Status.API.Controllers { @@ -19,8 +19,8 @@ public StatusController( IStatusService statusService, IConfiguration configuration) { - _statusService = statusService; - _configuration = configuration; + this._statusService = statusService; + this._configuration = configuration; } [HttpGet] @@ -32,9 +32,9 @@ public StatusController( [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetDataRecipientsStatusXV2(string industry) { - var response = await _statusService.GetDataRecipientStatusesAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); - return Ok(response); + var response = await this._statusService.GetDataRecipientStatusesAsync(industry.ToIndustry()); + response.Links = this.GetSelf(this._configuration, this.HttpContext, string.Empty); + return this.Ok(response); } [HttpGet] @@ -46,9 +46,9 @@ public async Task GetDataRecipientsStatusXV2(string ind [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetSoftwareProductStatusXV2(string industry) { - var response = await _statusService.GetSoftwareProductStatusesAsync(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); - return Ok(response); + var response = await this._statusService.GetSoftwareProductStatusesAsync(industry.ToIndustry()); + response.Links = this.GetSelf(this._configuration, this.HttpContext, string.Empty); + return this.Ok(response); } [HttpGet] @@ -60,9 +60,9 @@ public async Task GetSoftwareProductStatusXV2(string in [ServiceFilter(typeof(LogActionEntryAttribute))] public async Task GetDataHolderStatusXV1(string industry) { - var response = await _statusService.GetDataHolderStatusesAsyncXV1(industry.ToIndustry()); - response.Links = this.GetSelf(_configuration, HttpContext, string.Empty); - return Ok(response); + var response = await this._statusService.GetDataHolderStatusesAsyncXV1(industry.ToIndustry()); + response.Links = this.GetSelf(this._configuration, this.HttpContext, string.Empty); + return this.Ok(response); } } } diff --git a/Source/CDR.Register.Status.API/Program.cs b/Source/CDR.Register.Status.API/Program.cs index debefd0..a916aee 100644 --- a/Source/CDR.Register.Status.API/Program.cs +++ b/Source/CDR.Register.Status.API/Program.cs @@ -1,10 +1,10 @@ -using CDR.Register.API.Infrastructure; +using System; +using System.IO; +using CDR.Register.API.Infrastructure; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; -using System; -using System.IO; namespace CDR.Register.Status.API { diff --git a/Source/CDR.Register.Status.API/Startup.cs b/Source/CDR.Register.Status.API/Startup.cs index fce54c4..d829951 100644 --- a/Source/CDR.Register.Status.API/Startup.cs +++ b/Source/CDR.Register.Status.API/Startup.cs @@ -22,7 +22,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -31,7 +31,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); - services.AddRegisterStatus(Configuration); + services.AddRegisterStatus(this.Configuration); services.AddControllers(); @@ -41,7 +41,7 @@ public void ConfigureServices(IServiceCollection services) options.ErrorResponses = new ApiVersionErrorResponse(); }); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { services.AddCdrSwaggerGen(opt => @@ -55,13 +55,13 @@ public void ConfigureServices(IServiceCollection services) // This is to manage the EF database context through the web API DI. // If this is to be done inside the repository project itself, we need to manage the context life-cycle explicitly. - services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("Register_DB"))); + services.AddDbContext(options => options.UseSqlServer(this.Configuration.GetConnectionString("Register_DB"))); services.AddAutoMapper(typeof(Startup), typeof(RegisterDatabaseContext)); services.AddScoped(); - if (Configuration.GetSection("SerilogRequestResponseLogger") != null) + if (this.Configuration.GetSection("SerilogRequestResponseLogger") != null) { Log.Logger.Information("Adding request response logging middleware"); services.AddRequestResponseLogging(); @@ -78,7 +78,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) exceptionHandlerApp.Run(async context => await ApiExceptionHandler.Handle(context)); }); - app.UseBasePathOrExpression(Configuration); + app.UseBasePathOrExpression(this.Configuration); app.UseSerilogRequestLogging(); @@ -89,7 +89,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthentication(); app.UseAuthorization(); - var enableSwagger = Configuration.GetValue(ConfigurationKeys.EnableSwagger); + var enableSwagger = this.Configuration.GetValue(ConfigurationKeys.EnableSwagger); if (enableSwagger) { app.UseCdrSwagger(); diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index 46db1b5..e75e49f 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -1,10 +1,11 @@ net8.0 - 2.2.0 + 2.2.1 true true true + NU1901;NU1902 diff --git a/Source/docker-compose.Ecosystem.yml b/Source/docker-compose.Ecosystem.yml index 7d87aca..e0dd7fa 100644 --- a/Source/docker-compose.Ecosystem.yml +++ b/Source/docker-compose.Ecosystem.yml @@ -97,7 +97,7 @@ services: - ACCEPT_EULA=${ACCEPT_MSSQL_EULA:?This docker compose file utilises the Microsoft SQL Server Image from Docker Hub. The Microsoft EULA for the Microsoft SQL Server Image must be accepted to continue. Replace this unset ACCEPT_MSSQL_EULA variable with a Y if you accept the EULA. eg ACCEPT_EULA=Y. See the Microsoft SQL Server Image on Docker Hub for more information.} - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 10s diff --git a/Source/docker-compose.IntegrationTests.yml b/Source/docker-compose.IntegrationTests.yml index b779a09..dbeac30 100644 --- a/Source/docker-compose.IntegrationTests.yml +++ b/Source/docker-compose.IntegrationTests.yml @@ -67,7 +67,7 @@ services: - "1433:1433" environment: - ACCEPT_EULA=Y - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 5s diff --git a/Source/docker-compose.UnitTests.yml b/Source/docker-compose.UnitTests.yml index 871ed3e..8723721 100644 --- a/Source/docker-compose.UnitTests.yml +++ b/Source/docker-compose.UnitTests.yml @@ -22,7 +22,7 @@ services: - "1433:1433" environment: - ACCEPT_EULA=Y - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 5s diff --git a/Source/docker-compose.yml b/Source/docker-compose.yml index f4bbe6b..c46f589 100644 --- a/Source/docker-compose.yml +++ b/Source/docker-compose.yml @@ -36,7 +36,7 @@ services: - ACCEPT_EULA=${ACCEPT_MSSQL_EULA:?This docker compose file utilises the Microsoft SQL Server Image from Docker Hub. The Microsoft EULA for the Microsoft SQL Server Image must be accepted to continue. Replace this unset ACCEPT_MSSQL_EULA variable with a Y if you accept the EULA. eg ACCEPT_EULA=Y. See the Microsoft SQL Server Image on Docker Hub for more information.} - - SA_PASSWORD=Pa{}w0rd2019 + - MSSQL_SA_PASSWORD=Pa{}w0rd2019 healthcheck: test: /opt/mssql-tools18/bin/sqlcmd -S . -U sa -P "Pa{}w0rd2019" -Q "SELECT 1" -No || exit 1 timeout: 10s