diff --git a/.azuredevops/pipelines/build-dcr-func.yml b/.azuredevops/pipelines/build-dcr-func.yml
index 3ba6896..28cd2f0 100644
--- a/.azuredevops/pipelines/build-dcr-func.yml
+++ b/.azuredevops/pipelines/build-dcr-func.yml
@@ -1,3 +1,11 @@
+schedules:
+- cron: '0 5 * * 0'
+ displayName: 'Run at 5:00 AM every Sunday (UTC)'
+ always: true
+ branches:
+ include:
+ - develop
+
trigger:
- develop
- main
@@ -8,10 +16,10 @@ pool:
steps:
- task: UseDotNet@2
- displayName: 'Install .NET 6 SDK'
+ displayName: 'Install .NET 8 SDK'
inputs:
packageType: 'sdk'
- version: '6.0.x'
+ version: '8.0.x'
performMultiLevelLookup: true
- script: |
diff --git a/.azuredevops/pipelines/build-dh-func.yml b/.azuredevops/pipelines/build-dh-func.yml
index 50c7fe2..8927d6c 100644
--- a/.azuredevops/pipelines/build-dh-func.yml
+++ b/.azuredevops/pipelines/build-dh-func.yml
@@ -1,3 +1,11 @@
+schedules:
+- cron: '0 5 * * 0'
+ displayName: 'Run at 5:00 AM every Sunday (UTC)'
+ always: true
+ branches:
+ include:
+ - develop
+
trigger:
- develop
- main
@@ -8,10 +16,10 @@ pool:
steps:
- task: UseDotNet@2
- displayName: 'Install .NET 6 SDK'
+ displayName: 'Install .NET 8 SDK'
inputs:
packageType: 'sdk'
- version: '6.0.x'
+ version: '8.0.x'
performMultiLevelLookup: true
- script: |
diff --git a/.azuredevops/pipelines/build-no-tests.yml b/.azuredevops/pipelines/build-no-tests.yml
index 0a5cf0d..9f9f715 100644
--- a/.azuredevops/pipelines/build-no-tests.yml
+++ b/.azuredevops/pipelines/build-no-tests.yml
@@ -59,7 +59,7 @@ steps:
condition: always()
inputs:
packageType: sdk
- version: '6.0.x'
+ version: '8.0.x'
performMultiLevelLookup: true
- task: CmdLine@2
diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml
index b129292..fca897e 100644
--- a/.azuredevops/pipelines/build-v2.yml
+++ b/.azuredevops/pipelines/build-v2.yml
@@ -1,4 +1,11 @@
-# Build pipeline v2 (Containerised)
+schedules:
+- cron: '0 5 * * 0'
+ displayName: 'Run at 5:00 AM every Sunday (UTC)'
+ always: true
+ branches:
+ include:
+ - develop
+
variables:
@@ -194,18 +201,18 @@ jobs:
artifact: Mock-Data-Recipient - E2E tests
- task: UseDotNet@2
- displayName: 'Use .NET 6 sdk'
+ displayName: 'Use .NET 8 sdk'
condition: always()
inputs:
packageType: sdk
- version: '6.0.x'
+ version: '8.0.x'
performMultiLevelLookup: true
- task: CmdLine@2
displayName: 'Install dotnet-ef'
condition: always()
inputs:
- script: 'dotnet tool install --version 7.0.13 --global dotnet-ef'
+ script: 'dotnet tool install --version 8.0.4 --global dotnet-ef'
- task: CmdLine@2
displayName: 'Check dotnet-ef version'
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 4cce631..6c83d51 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -12,8 +12,7 @@ on:
- '.github/ISSUE_TEMPLATE/**'
- '.github/pull_request_template.md'
- '.github/stale.yml'
- - 'LICENSE'
- - 'Postman/**'
+ - 'LICENSE'
pull_request:
branches: [main, develop]
types: [opened, synchronize, reopened]
@@ -24,8 +23,7 @@ on:
- '.github/ISSUE_TEMPLATE/**'
- '.github/pull_request_template.md'
- '.github/stale.yml'
- - 'LICENSE'
- - 'Postman/**'
+ - 'LICENSE'
env:
DOCKER_IMAGE: consumerdataright/mock-data-recipient
@@ -36,11 +34,11 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup Docker Metadata
id: meta
- uses: docker/metadata-action@v3
+ uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
@@ -54,21 +52,21 @@ jobs:
type=semver,pattern={{major}}
- name: Setup Docker QEMU
- uses: docker/setup-qemu-action@v1
+ uses: docker/setup-qemu-action@v3
- name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v1
+ uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: ${{ github.repository_owner == 'ConsumerDataRight' && github.event_name != 'pull_request' }}
- uses: docker/login-action@v1
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
id: docker_build
- uses: docker/build-push-action@v2
+ uses: docker/build-push-action@v6
with:
context: ./Source
file: ./Source/Dockerfile
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 43666af..abb644a 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -10,8 +10,7 @@ on:
- '.github/ISSUE_TEMPLATE/**'
- '.github/pull_request_template.md'
- '.github/stale.yml'
- - 'LICENSE'
- - 'Postman/**'
+ - 'LICENSE'
pull_request:
branches: [ main, develop ]
types: [opened, synchronize, reopened]
@@ -22,8 +21,7 @@ on:
- '.github/ISSUE_TEMPLATE/**'
- '.github/pull_request_template.md'
- '.github/stale.yml'
- - 'LICENSE'
- - 'Postman/**'
+ - 'LICENSE'
env:
buildConfiguration: 'Release'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a1cdb7..c00372d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [2.1.0] - 2024-08-16
+### Changed
+- Updated nuget package versions
+
+## [2.0.0] - 2024-06-12
+### Changed
+- Migrated from .NET 6 to .NET 8
+- Migrated docker compose from v1 to v2
+
+## [1.3.0] - 2024-03-13
+### Changed
+- Removed Bank Participant Data scope (i.e. cdr-register:bank:read) references.
+- Updated NuGet packages to avoid vulnerabilities.
+- DCR and PAR screen defaults to Authorisation Code Flow (ACF).
+
+### Fixed
+- Fixed Consumer Data Sharing Common swagger proxy UI failure due to 'IndustryName' validation - [Mock Data Recipient Issue 68](https://github.com/ConsumerDataRight/mock-data-recipient/issues/68)
+
## [1.2.5] - 2023-11-29
### Fixed
- Refactored code and minor bug fixes
diff --git a/Help/azurefunctions/HELP.md b/Help/azurefunctions/HELP.md
index b2c3431..9f5ee83 100644
--- a/Help/azurefunctions/HELP.md
+++ b/Help/azurefunctions/HELP.md
@@ -45,7 +45,7 @@ azurite --silent --location c:\azurite --debug c:\azurite\debug.log
```
navigate to .\mock-data-holder\Source\CDR.GetDataRecipients
-func start --verbose
+func host start --verbose
```
diff --git a/Help/container/HELP.md b/Help/container/HELP.md
index b233ddc..bdfa1aa 100644
--- a/Help/container/HELP.md
+++ b/Help/container/HELP.md
@@ -49,7 +49,7 @@ Example of accepting the `ACCEPT_EULA` environment variable of the SQL Server co
```
mssql:
container_name: sql1
- image: 'mcr.microsoft.com/mssql/server:2019-latest'
+ image: 'mcr.microsoft.com/mssql/server:2022-latest'
ports:
- '1433:1433'
environment:
@@ -109,7 +109,7 @@ docker build -f Dockerfile -t mock-data-recipient .
```
Run the SQL Server image.
```
-docker run -d -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Pa{}w0rd2019" -p 1433:1433 --name sql1 -h sql1 -d mcr.microsoft.com/mssql/server:2019-latest
+docker run -d -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Pa{}w0rd2019" -p 1433:1433 --name sql1 -h sql1 -d mcr.microsoft.com/mssql/server:2022-latest
```
Run the new docker image.
```
diff --git a/README.md b/README.md
index 10abca6..6a4c525 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
![Consumer Data Right Logo](./cdr-logo.png?raw=true)
-[![Consumer Data Standards v1.27.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.27.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction)
+[![Consumer Data Standards v1.31.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.31.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction)
[![made-with-dotnet](https://img.shields.io/badge/Made%20with-.NET-1f425Ff.svg)](https://dotnet.microsoft.com/)
[![made-with-csharp](https://img.shields.io/badge/Made%20with-C%23-1f425Ff.svg)](https://docs.microsoft.com/en-us/dotnet/csharp/)
[![MIT License](https://img.shields.io/github/license/ConsumerDataRight/mock-data-recipient)](./LICENSE)
@@ -13,7 +13,7 @@ This repository contains a mock implementation of a Data Recipient and is offere
## Mock Data Recipient - Alignment
The Mock Data Recipient in this release:
-* aligns to [v1.27.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction);
+* aligns to [v1.31.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.31.0/#introduction);
* can connect to and complete authentication against both [FAPI 1.0 Migration Phase 2 and Phase 3](https://consumerdatastandardsaustralia.github.io/standards/#authentication-flows) compliant data holders.
## Getting Started
@@ -89,7 +89,7 @@ The Mock Data Recipient contains the following components:
- Used internally within the Mock Data Recipient to simplify interactions with the Register and Data Holders.
- Azure Functions
- Azure Functions that can automate the continuous Get Data Holders discovery and Dynamic Client Registration process.
- - For each Data Holder retrieved from the Register, a message will be added to the DynamicClietnRegistration queue. A function listening to the queue, will pick up the message and attempt to register the Data Recipient with the Data Holder.
+ - For each Data Holder retrieved from the Register, a message will be added to the DynamicClientRegistration queue. A function listening to the queue, will pick up the message and attempt to register the Data Recipient with the Data Holder.
- To get help on the Azure Functions, see the [help guide](./Help/azurefunctions/HELP.md).
- Repository
- A SQL repository is included that contains local data used within the Mock Data Recipient.
@@ -100,7 +100,7 @@ The Mock Data Recipient contains the following components:
## Technology Stack
The following technologies have been used to build the Mock Data Recipient:
-- The source code has been written in `C#` using the `.Net 6` framework.
+- The source code has been written in `C#` using the `.Net 8` framework.
- The Repository utilises a `SQL` instance.
# Testing
@@ -125,4 +125,4 @@ See our [security policy](./SECURITY.md) for information on security controls, r
[MIT License](./LICENSE)
# Notes
-The Mock Data Recipient is provided as a development tool only. It conforms to the Consumer Data Standards.
+The Mock Data Recipient is provided as a development tool only. It conforms to the Consumer Data Standards.
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
index 084cf77..ef53b05 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -11,9 +11,8 @@ Visit our [Responsible disclosure of security vulnerabilities policy](https://ww
| Version | Supported |
| ------- | ------------------ |
-| 1.2.x | :white_check_mark: |
-| 1.1.x | :x: |
-| 1.0.x | :x: |
+| 2.1.x | :white_check_mark: |
+| 1.x.x | :x: |
## Reporting a Vulnerability
diff --git a/Source/CDR.DCR/CDR.DCR.csproj b/Source/CDR.DCR/CDR.DCR.csproj
index 7d979dd..89b420d 100644
--- a/Source/CDR.DCR/CDR.DCR.csproj
+++ b/Source/CDR.DCR/CDR.DCR.csproj
@@ -1,23 +1,30 @@
- net6.0
v4
<_FunctionsSkipCleanOutput>true
- 1.2.5
- 1.2.5
- 1.2.5
+ $(TargetFrameworkVersion)
+ $(Version)
+ $(Version)
+ $(Version)
+ Exe
+ enabled
-
-
-
-
+
+
+
+
+
+
+
+ PreserveNewest
+
Always
@@ -29,4 +36,7 @@
Never
-
+
+
+
+
\ No newline at end of file
diff --git a/Source/CDR.DCR/DCR.cs b/Source/CDR.DCR/DCR.cs
index 4b9e50a..cdeb070 100644
--- a/Source/CDR.DCR/DCR.cs
+++ b/Source/CDR.DCR/DCR.cs
@@ -5,160 +5,130 @@
using CDR.DataRecipient.SDK.Enum;
using CDR.DataRecipient.SDK.Extensions;
using CDR.DataRecipient.SDK.Models;
-using Microsoft.Azure.WebJobs;
+using CDR.DCR.Extensions;
+using CDR.DCR.Models;
+using Microsoft.Azure.Functions.Worker;
using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
-using Microsoft.Azure.Storage.Queue;
using Newtonsoft.Json;
using System;
-using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
-using System.Linq;
-using CDR.DCR.Models;
namespace CDR.DCR
{
- public static class DynamicClientRegistrationFunction
+ public class DynamicClientRegistrationFunction
{
- // Error - Unable to perform DCR as there are no mutually supported values in the mandatory claim [CLAIM_NAME]
- private const string ErrorMessage = "Unable to perform DCR as there are no mutually supported values in the mandatory claim";
+ private readonly ILogger _logger;
+ private readonly DcrOptions _options;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly X509Certificate2 _signingCertificate;
+
+ public DynamicClientRegistrationFunction(ILogger
logger, IOptions options, IHttpClientFactory httpClientFactory)
+ {
+ _options = options.Value;
+ _logger = logger;
+ _httpClientFactory = httpClientFactory;
+
+ //get the certs
+ _logger.LogInformation("Loading the client certificate...");
+ byte[] clientCertBytes = Convert.FromBase64String(_options.Client_Certificate);
+ X509Certificate2 cclientCertificate = new(clientCertBytes, _options.Client_Certificate_Password, X509KeyStorageFlags.MachineKeySet);
+ _logger.LogInformation("Client certificate loaded: {thumbprint}", cclientCertificate.Thumbprint);
+
+
+ _logger.LogInformation("Loading the signing certificate...");
+ byte[] signCertBytes = Convert.FromBase64String(_options.Signing_Certificate);
+ _signingCertificate = new(signCertBytes, _options.Signing_Certificate_Password, X509KeyStorageFlags.MachineKeySet);
+ _logger.LogInformation("Signing certificate loaded: {thumbprint}", _signingCertificate.Thumbprint);
+ }
///
/// Dynamic Client Registration Function
///
/// Registers the Data Holders in the messaging queue and updates the local repository
- [FunctionName("FunctionDCR")]
- public static async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "StorageConnectionString")] CloudQueueMessage myQueueItem, ILogger log, ExecutionContext context)
+ [Function("FunctionDCR")]
+ public async Task DCR([QueueTrigger("dynamicclientregistration", Connection = "StorageConnectionString")] DcrQueueMessage myQueueItem, FunctionContext context)
{
string msg = string.Empty;
string dataHolderBrandName = string.Empty;
string infosecBaseUri = string.Empty;
string regEndpoint = string.Empty;
- DcrQueueMessage myQMsg = JsonConvert.DeserializeObject(myQueueItem.AsString);
-
try
{
- var isLocalDev = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT").Equals("Development");
- var configBuilder = new ConfigurationBuilder().SetBasePath(context.FunctionAppDirectory);
-
- if (isLocalDev)
- {
- configBuilder = configBuilder.AddJsonFile("local.settings.json", optional: false, reloadOnChange: true);
- }
-
- // Get environment variables.
- string qConnString = Environment.GetEnvironmentVariable("StorageConnectionString");
- string dbConnString = Environment.GetEnvironmentVariable("DataRecipient_DB_ConnectionString");
- string dbLoggingConnString = Environment.GetEnvironmentVariable("DataRecipient_Logging_DB_ConnectionString");
- string tokenEndpoint = Environment.GetEnvironmentVariable("Register_Token_Endpoint");
- string ssaEndpoint = Environment.GetEnvironmentVariable("Register_Get_SSA_Endpoint");
- string xv = Environment.GetEnvironmentVariable("Register_Get_SSA_XV");
- string brandId = Environment.GetEnvironmentVariable("Brand_Id");
- string softwareProductId = Environment.GetEnvironmentVariable("Software_Product_Id");
- string redirectUris = Environment.GetEnvironmentVariable("Redirect_Uris");
- string clientCert = Environment.GetEnvironmentVariable("Client_Certificate");
- string clientCertPwd = Environment.GetEnvironmentVariable("Client_Certificate_Password");
- string signCert = Environment.GetEnvironmentVariable("Signing_Certificate");
- string signCertPwd = Environment.GetEnvironmentVariable("Signing_Certificate_Password");
- var retries = Convert.ToInt16(Environment.GetEnvironmentVariable("Retries"));
- bool ignoreServerCertificateErrors = Environment.GetEnvironmentVariable("Ignore_Server_Certificate_Errors").Equals("true", StringComparison.OrdinalIgnoreCase);
-
- // DCR queue.
- log.LogInformation("Retrieving count for dynamicclientregistration queue...");
+ _logger.LogInformation("Retrieving count for dynamicclientregistration queue...");
string qName = "dynamicclientregistration";
- int qCount = await GetQueueCountAsync(qConnString, qName);
- log.LogInformation($"qCount = {qCount}");
+ int qCount = await GetQueueCountAsync(_options.StorageConnectionString, qName);
+ _logger.LogInformation("qCount = {count}", qCount);
- if (string.IsNullOrEmpty(myQMsg.DataHolderBrandId))
+ if (string.IsNullOrEmpty(myQueueItem.DataHolderBrandId))
{
// Add messsage to deadletter queue
- await AddDeadLetterQueMsgAsync(log, dbConnString, qConnString, myQMsg.DataHolderBrandId, myQueueItem, "deadletter");
+ await AddDeadLetterQueMsgAsync(myQueueItem, _options.DeadLetterQueueName, context);
}
else
{
- msg = $"DHBrandId - {myQMsg.DataHolderBrandId}";
-
- log.LogInformation("Loading the client certificate...");
- byte[] clientCertBytes = Convert.FromBase64String(clientCert);
- X509Certificate2 clientCertificate = new(clientCertBytes, clientCertPwd, X509KeyStorageFlags.MachineKeySet);
- log.LogInformation("Client certificate loaded: {thumbprint}", clientCertificate.Thumbprint);
+ msg = $"DHBrandId - {myQueueItem.DataHolderBrandId}";
- log.LogInformation("Loading the signing certificate...");
- byte[] signCertBytes = Convert.FromBase64String(signCert);
- X509Certificate2 signCertificate = new(signCertBytes, signCertPwd, X509KeyStorageFlags.MachineKeySet);
- log.LogInformation("Signing certificate loaded: {thumbprint}", signCertificate.Thumbprint);
-
- Response tokenResponse = await GetAccessToken(tokenEndpoint, softwareProductId, clientCertificate, signCertificate, log, ignoreServerCertificateErrors);
+ Response tokenResponse = await GetAccessToken();
if (tokenResponse.IsSuccessful)
- {
- var softwareStatementAssertion = new SoftwareStatementAssertion()
- {
- SsaEndpoint = ssaEndpoint,
- Version = xv,
- AccessToken = tokenResponse.Data.AccessToken,
- ClientCertificate = clientCertificate,
- BrandId = brandId,
- SoftwareProductId = softwareProductId,
- Log = log,
- IgnoreServerCertificateErrors = ignoreServerCertificateErrors
- };
- var ssa = await GetSoftwareStatementAssertion(softwareStatementAssertion);
+ {
+ var ssa = await GetSoftwareStatementAssertion(tokenResponse.Data.AccessToken);
if (ssa.IsSuccessful)
- {
+ {
//DOES the Data Holder Brand EXIST in the REPO?
- DataHolderBrand dh = await new SqlDataAccess(dbConnString).GetDataHolderBrand(myQMsg.DataHolderBrandId);
+ DataHolderBrand dh = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).GetDataHolderBrand(myQueueItem.DataHolderBrandId);
if (dh == null)
{
// NO - DOES the DcrMessage exist?
- (string dcrMsgId, string dcrMsgState) = await new SqlDataAccess(dbConnString).CheckDcrMessageExistByDHBrandId(myQMsg.DataHolderBrandId);
- if (!string.IsNullOrEmpty(dcrMsgId))
+ var result = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).CheckDcrMessageExistByDHBrandId(myQueueItem.DataHolderBrandId);
+ if (!string.IsNullOrEmpty(result.msgId))
{
// YES - UPDATE EXISTING DcrMessage (with ADDED Queue MessageId, Failed STATE and ERROR)
DcrMessage dcrMsg = new()
{
DataHolderBrandId = Guid.Empty,
- MessageId = dcrMsgId,
+ MessageId = result.msgId,
MessageState = Message.DCRFailed.ToString(),
MessageError = $"{msg} - does not exist in the repo"
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgReplaceMessageIdWithoutBrand(dcrMsg, myQueueItem.Id);
+
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgReplaceMessageIdWithoutBrand(dcrMsg, context.BindingContext.BindingData["Id"].ToString());
}
- await InsertLog(log, dbConnString, $"{msg} - does not exist in the repo", "Error", "DCR");
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg} - does not exist in the repo", "Error", "DCR");
}
else
{
- dataHolderBrandName = dh.BrandName;
+ dataHolderBrandName = dh.BrandName;
// YES - DOES a Registration already exist for the DataHolderBrandId in the local repo?
- string clientId = await new SqlDataAccess(dbConnString).GetRegByDHBrandId(dh.DataHolderBrandId);
- if (clientId == string.Empty)
+ string clientId = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).GetRegByDHBrandId(dh.DataHolderBrandId);
+ if (string.IsNullOrEmpty(clientId))
{
// NO - register this Data Holder Brand
infosecBaseUri = dh.EndpointDetail.InfoSecBaseUri;
- var oidcDiscovery = (await GetOidcDiscovery(infosecBaseUri, ignoreServerCertificateErrors: ignoreServerCertificateErrors)).Data;
+ var oidcDiscovery = (await GetOidcDiscovery(infosecBaseUri)).Data;
if (oidcDiscovery != null)
{
regEndpoint = oidcDiscovery.RegistrationEndpoint;
- var dcrRequest = new DcrRequest()
+ var dcrRequest = new DcrRequest()
{
- SoftwareProductId = softwareProductId,
- RedirectUris = redirectUris,
- Ssa = ssa.Data,
+ SoftwareProductId = _options.Software_Product_Id,
+ RedirectUris = _options.Redirect_Uris,
+ Ssa = ssa.Data,
Audience = oidcDiscovery.Issuer,
ResponseTypesSupported = oidcDiscovery.ResponseTypesSupported,
AuthorizationSigningResponseAlgValuesSupported = oidcDiscovery.AuthorizationSigningResponseAlgValuesSupported,
AuthorizationEncryptionResponseEncValuesSupported = oidcDiscovery.AuthorizationEncryptionResponseEncValuesSupported,
AuthorizationEncryptionResponseAlgValuesSupported = oidcDiscovery.AuthorizationEncryptionResponseAlgValuesSupported,
- SignCertificate = signCertificate
+ SignCertificate = _signingCertificate
};
(string errorMessage, string dcrRequestJwt) = PopulateDCRRequestJwt(dcrRequest);
@@ -169,13 +139,13 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
string regMessage = "";
string regClientId = "";
string jsonDoc = "";
-
+
// DO NOT register if FAPI claims are invalid
if (string.IsNullOrEmpty(errorMessage))
{
do
{
- var dcrResponse = await Register(regEndpoint, clientCertificate, dcrRequestJwt, ignoreServerCertificateErrors: ignoreServerCertificateErrors);
+ var dcrResponse = await Register(regEndpoint, dcrRequestJwt);
if (dcrResponse.IsSuccessful)
{
regSuccess = true;
@@ -187,9 +157,15 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
{
regStatusCode = dcrResponse.StatusCode.ToString();
regMessage = dcrResponse.Message;
- retries--;
+
+ //no need to retry if the error is duplicate registration.
+ if (dcrResponse.Message.Contains("ERR-DCR-001"))
+ {
+ break;
+ }
+ _options.Retries--;
}
- } while (!regSuccess && retries > 0);
+ } while (!regSuccess && _options.Retries > 0);
}
// Successful -> Update DcrMessage and Insert into Data Holder Registration repo
// DO NOT register if FAPI claims are invalid
@@ -203,19 +179,19 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
InfosecBaseUri = infosecBaseUri,
MessageState = Message.DCRComplete.ToString()
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByDHBrandId(dcrMsg);
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByDHBrandId(dcrMsg);
- var dcrInserted = await new SqlDataAccess(dbConnString).InsertDcrRegistration(regClientId, dh.DataHolderBrandId, jsonDoc);
+ var dcrInserted = await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).InsertDcrRegistration(regClientId, dh.DataHolderBrandId, jsonDoc);
if (dcrInserted)
- await InsertLog(log, dbConnString, $"{msg}, REGISTERED as ClientId - {regClientId}, {qCount - 1} items remain in queue, ", "Information", "DCR");
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, REGISTERED as ClientId - {regClientId}, {qCount - 1} items remain in queue, ", "Information", "DCR");
else
- await InsertLog(log, dbConnString, $"{msg}, REGISTERED as ClientId - {regClientId}, {qCount - 1} items remain in queue - FAILED to add to MDR REPO", "Error", "DCR");
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, REGISTERED as ClientId - {regClientId}, {qCount - 1} items remain in queue - FAILED to add to MDR REPO", "Error", "DCR");
}
// FAILED -> Update DcrMessage as DCRFailed
// FAPI 1.0 validation errors should also be logged
else if (!string.IsNullOrEmpty(errorMessage))
- {
+ {
DcrMessage dcrMsg = new()
{
DataHolderBrandId = new Guid(dh.DataHolderBrandId),
@@ -224,8 +200,8 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
MessageState = Message.DCRFailed.ToString(),
MessageError = $"{errorMessage}"
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByDHBrandId(dcrMsg);
- await InsertLog(log, dbConnString, $"{msg}, REGISTRATION CLAIMS VALIDATIONS FAILED, {errorMessage}", "Error", "DCR");
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByDHBrandId(dcrMsg);
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, REGISTRATION CLAIMS VALIDATIONS FAILED, {errorMessage}", "Error", "DCR");
}
else if (!regSuccess)
{
@@ -238,8 +214,8 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
MessageState = Message.DCRFailed.ToString(),
MessageError = $"StatusCode: {regStatusCode}, {regMessage}"
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByDHBrandId(dcrMsg);
- await InsertLog(log, dbConnString, $"{msg}, REGISTRATION FAILED, StatusCode: {regStatusCode}, {regMessage}", "Error", "DCR");
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByDHBrandId(dcrMsg);
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"{msg}, REGISTRATION FAILED, StatusCode: {regStatusCode}, {regMessage}", "Error", "DCR");
}
}
else
@@ -247,53 +223,50 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
// Oidc Discovery failed
DcrMessage dcrMsg = new()
{
- DataHolderBrandId = new Guid(myQMsg.DataHolderBrandId),
+ DataHolderBrandId = new Guid(myQueueItem.DataHolderBrandId),
BrandName = dh.BrandName,
InfosecBaseUri = infosecBaseUri,
MessageState = Message.DCRFailed.ToString(),
MessageError = "OidcDiscovery failed InfosecBaseUri: " + infosecBaseUri
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByDHBrandId(dcrMsg);
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByDHBrandId(dcrMsg);
string extraMsg = "";
if (!string.IsNullOrEmpty(infosecBaseUri))
extraMsg = " - InfosecBaseUri: " + infosecBaseUri;
- await InsertLog(log, dbLoggingConnString, $"{msg}, REGISTRATION FAILED{extraMsg}", "Exception", "DCR");
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, REGISTRATION FAILED{extraMsg}", "Exception", "DCR");
}
}
else
{
// YES - log this result
- await InsertLog(log, dbConnString, $"{msg} - is trying to be REGISTERED, but is already REGISTERED to ClientId - {clientId}", "Error", "DCR");
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg} - is trying to be REGISTERED, but is already REGISTERED to ClientId - {clientId}", "Error", "DCR");
}
}
}
else
{
- await InsertLog(log, dbConnString, $"{msg}, Unable to get the SSA from: {ssaEndpoint}, Ver: {xv}, BrandId: {brandId}, SoftwareProductId - {softwareProductId}", "Error", "DCR");
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, Unable to get the SSA from: {_options.Register_Get_SSA_Endpoint}, Ver: {_options.Register_Get_SSA_XV}, BrandId: {_options.Brand_Id}, SoftwareProductId - {_options.Software_Product_Id}", "Error", "DCR");
}
}
else
{
- await InsertLog(log, dbConnString, $"{msg}, Unable to get the Access Token for SoftwareProductId - {softwareProductId} - at the endpoint - {tokenEndpoint}", "Error", "DCR");
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, Unable to get the Access Token for SoftwareProductId - {_options.Software_Product_Id} - at the endpoint - {_options.Register_Token_Endpoint}", "Error", "DCR");
}
}
}
catch (Exception ex)
{
- string dbConnString = Environment.GetEnvironmentVariable("DataRecipient_DB_ConnectionString");
- string dbLoggingConnString = Environment.GetEnvironmentVariable("DataRecipient_Logging_DB_ConnectionString");
-
DcrMessage dcrMsg = new()
{
- DataHolderBrandId = new Guid(myQMsg.DataHolderBrandId),
+ DataHolderBrandId = new Guid(myQueueItem.DataHolderBrandId),
BrandName = dataHolderBrandName,
InfosecBaseUri = infosecBaseUri,
MessageState = Message.DCRFailed.ToString(),
MessageError = ex.Message
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByDHBrandId(dcrMsg);
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByDHBrandId(dcrMsg);
string extraMsg = "";
if (!string.IsNullOrEmpty(infosecBaseUri))
@@ -304,10 +277,10 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
if (ex is JsonReaderException)
{
- await InsertLog(log, dbLoggingConnString, $"{msg}, REGISTRATION FAILED: OidcDiscovery can't be deserialized {extraMsg}", "Exception", "DCR", ex);
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, REGISTRATION FAILED: OidcDiscovery can't be deserialized {extraMsg}", "Exception", "DCR", ex);
}
- else
- await InsertLog(log, dbLoggingConnString, $"{msg}, REGISTRATION FAILED{extraMsg}", "Exception", "DCR", ex);
+ else
+ await InsertLog(_options.DataRecipient_Logging_DB_ConnectionString, $"{msg}, REGISTRATION FAILED{extraMsg}", "Exception", "DCR", ex);
}
}
@@ -315,24 +288,18 @@ public static async Task DCR([QueueTrigger("dynamicclientregistration", Connecti
/// Get Access Token
///
/// JWT
- private static async Task> GetAccessToken(
- string tokenEndpoint,
- string clientId,
- X509Certificate2 clientCertificate,
- X509Certificate2 signingCertificate,
- ILogger log,
- bool ignoreServerCertificateErrors = false)
+ private async Task> GetAccessToken()
{
// Setup the http client.
- var client = GetHttpClient(clientCertificate, ignoreServerCertificateErrors: ignoreServerCertificateErrors);
+ var client = GetHttpClient();
// Make the request to the token endpoint.
- log.LogInformation("Retrieving access_token from the Register: {tokenEndpoint}", tokenEndpoint);
+ _logger.LogInformation("Retrieving access_token from the Register: {tokenEndpoint}", _options.Register_Token_Endpoint);
var response = await client.SendPrivateKeyJwtRequest(
- tokenEndpoint,
- signingCertificate,
- clientId,
- clientId,
+ _options.Register_Token_Endpoint,
+ _signingCertificate,
+ _options.Software_Product_Id,
+ _options.Software_Product_Id,
scope: Constants.Scopes.CDR_REGISTER,
grantType: Constants.GrantTypes.CLIENT_CREDENTIALS);
@@ -342,7 +309,7 @@ private static async Task> GetAccessToken(
StatusCode = response.StatusCode
};
- log.LogInformation("Register response: {statusCode} - {body}", tokenResponse.StatusCode, body);
+ _logger.LogInformation("Register response: {statusCode} - {body}", tokenResponse.StatusCode, body);
if (response.IsSuccessStatusCode)
tokenResponse.Data = JsonConvert.DeserializeObject(body);
@@ -355,15 +322,15 @@ private static async Task> GetAccessToken(
///
/// Generate the SSA
///
- private static async Task> GetSoftwareStatementAssertion(SoftwareStatementAssertion softwareStatementAssertion)
+ private async Task> GetSoftwareStatementAssertion(string accessToken)
{
// Setup the request to the get ssa endpoint.
- var endpoint = $"{softwareStatementAssertion.SsaEndpoint}{softwareStatementAssertion.BrandId}/software-products/{softwareStatementAssertion.SoftwareProductId}/ssa";
+ var endpoint = $"{_options.Register_Get_SSA_Endpoint}{_options.Brand_Id}/software-products/{_options.Software_Product_Id}/ssa";
// Setup the http client.
- var client = GetHttpClient(softwareStatementAssertion.ClientCertificate, softwareStatementAssertion.AccessToken, softwareStatementAssertion.Version, ignoreServerCertificateErrors: softwareStatementAssertion.IgnoreServerCertificateErrors);
+ var client = GetHttpClient( accessToken, _options.Register_Get_SSA_XV);
- softwareStatementAssertion.Log.LogInformation("Retrieving SSA from the Register: {ssaEndpoint}", endpoint);
+ _logger.LogInformation("Retrieving SSA from the Register: {ssaEndpoint}", endpoint);
// Make the request to the get data holder brands endpoint.
var response = await client.GetAsync(endpoint);
@@ -373,7 +340,7 @@ private static async Task> GetSoftwareStatementAssertion(Softwa
StatusCode = response.StatusCode
};
- softwareStatementAssertion.Log.LogInformation("SSA response: {statusCode} - {body}", ssaResponse.StatusCode, body);
+ _logger.LogInformation("SSA response: {statusCode} - {body}", ssaResponse.StatusCode, body);
if (response.IsSuccessStatusCode)
{
@@ -390,18 +357,11 @@ private static async Task> GetSoftwareStatementAssertion(Softwa
///
/// Get the OpenID Discovery
///
- private static async Task> GetOidcDiscovery(string infosecBaseUri, bool ignoreServerCertificateErrors = false)
+ private async Task> GetOidcDiscovery(string infosecBaseUri)
{
var oidcResponse = new Response();
- var clientHandler = new HttpClientHandler();
-
- if (ignoreServerCertificateErrors)
- {
- clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
- }
-
- var client = new HttpClient(clientHandler);
+ var client = GetHttpClient();
var configUrl = string.Concat(infosecBaseUri.TrimEnd('/'), "/.well-known/openid-configuration");
var configResponse = await client.GetAsync(configUrl);
@@ -422,101 +382,8 @@ private static async Task> GetOidcDiscovery(string infos
///
private static (string, string) PopulateDCRRequestJwt(DcrRequest dcrRequest)
{
- string errorMessage = string.Empty;
- var claims = new List
- {
- new Claim("jti", Guid.NewGuid().ToString()),
- new Claim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer),
- new Claim("token_endpoint_auth_signing_alg", "PS256"),
- new Claim("token_endpoint_auth_method", "private_key_jwt"),
- new Claim("application_type", "web"),
- new Claim("id_token_signed_response_alg", "PS256"),
- new Claim("id_token_encrypted_response_alg", "RSA-OAEP"),
- new Claim("id_token_encrypted_response_enc", "A256GCM"),
- new Claim("request_object_signing_alg", "PS256"),
- new Claim("software_statement", dcrRequest.Ssa ?? ""),
- new Claim("grant_types", "client_credentials"),
- new Claim("grant_types", "authorization_code"),
- new Claim("grant_types", "refresh_token")
- };
-
- // response_types updated below "code, code id_token" both types are returned and added below
- // A response type is mandatory
- if (!dcrRequest.ResponseTypesSupported.Contains("code") && !dcrRequest.ResponseTypesSupported.Contains("code id_token"))
- {
- // Return the error
- errorMessage = ErrorMessage + " response_types";
- return (errorMessage, "");
- }
-
- var responseTypesList = dcrRequest.ResponseTypesSupported.Where(x => x.ToLower().Equals("code") || x.ToLower().Equals("code id_token")).ToList();
- claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypesList), JsonClaimValueTypes.JsonArray));
-
+ var (claims, errorMessage) = dcrRequest.CreateClaimsForDCRRequest();
- var isCodeFlow = dcrRequest.ResponseTypesSupported.Contains("code");
- if (isCodeFlow && !dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Any())
- {
- // Log error message to the mandatory claim missing
- errorMessage = ErrorMessage + " authorization_signed_response_alg";
- return (errorMessage, "");
- }
-
- // Mandatory for code flow
- if (isCodeFlow)
- {
- if (!dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256"))
- {
- // Return the error
- errorMessage = ErrorMessage + " authorization_signed_response_alg";
- return (errorMessage, "");
- }
-
- if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256"))
- {
- claims.Add(new Claim("authorization_signed_response_alg", "PS256"));
- }
- else if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256"))
- {
- claims.Add(new Claim("authorization_signed_response_alg", "ES256"));
- }
- }
-
- // Check if the enc is empty but a alg is specified.
- if ((dcrRequest.AuthorizationEncryptionResponseEncValuesSupported == null || !dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Any()) // No enc specified
- && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256")
- && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) // but alg specified.
- {
- errorMessage = ErrorMessage + " authorization_encrypted_response_enc";
- return (errorMessage, "");
- }
-
-
- if (dcrRequest.AuthorizationEncryptionResponseEncValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Contains("A128CBC-HS256"))
- {
- claims.Add(new Claim("authorization_encrypted_response_enc", "A128CBC-HS256"));
- }
- else if (dcrRequest.AuthorizationEncryptionResponseEncValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Contains("A256GCM"))
- {
- claims.Add(new Claim("authorization_encrypted_response_enc", "A256GCM"));
- }
-
- // Conditional: Optional for response_type "code" if authorization_encryption_enc_values_supported is present
- if (isCodeFlow && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Any())
- {
- if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256"))
- {
- claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP-256"));
- }
- else if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP"))
- {
- claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP"));
- }
- }
-
- char[] delimiters = { ',', ' '};
- var redirectUrisList = dcrRequest.RedirectUris?.Split(delimiters).ToList();
- claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray));
-
var jwt = new JwtSecurityToken(
issuer: dcrRequest.SoftwareProductId,
audience: dcrRequest.Audience,
@@ -525,20 +392,18 @@ private static (string, string) PopulateDCRRequestJwt(DcrRequest dcrRequest)
signingCredentials: new X509SigningCredentials(dcrRequest.SignCertificate, SecurityAlgorithms.RsaSsaPssSha256));
var tokenHandler = new JwtSecurityTokenHandler();
- return (errorMessage, tokenHandler.WriteToken(jwt));
+ return (errorMessage, tokenHandler.WriteToken(jwt));
}
///
/// DCR
///
- private static async Task Register(
- string dcrEndpoint,
- X509Certificate2 clientCertificate,
- string payload,
- bool ignoreServerCertificateErrors = false)
+ private async Task Register(
+ string dcrEndpoint,
+ string payload)
{
// Setup the http client.
- var client = GetHttpClient(clientCertificate, ignoreServerCertificateErrors: ignoreServerCertificateErrors);
+ var client = GetHttpClient();
// Create the post content.
var content = new StringContent(payload);
@@ -557,70 +422,53 @@ private static async Task Register(
};
}
- private static HttpClient GetHttpClient(
- X509Certificate2 clientCertificate = null,
- string accessToken = null,
- string version = null,
- bool ignoreServerCertificateErrors = false)
+ private HttpClient GetHttpClient(string accessToken = null, string version = null)
{
- var clientHandler = new HttpClientHandler();
-
- // Set the client certificate for the connection if supplied.
- if (clientCertificate != null)
- {
- clientHandler.ClientCertificates.Add(clientCertificate);
- }
-
- if (ignoreServerCertificateErrors)
- {
- clientHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
- }
-
- var client = new HttpClient(clientHandler);
-
+ var httpClient = _httpClientFactory.CreateClient(DcrConstants.DcrHttpClientName);
+
// If an access token has been provided then add to the Authorization header of the client.
if (!string.IsNullOrEmpty(accessToken))
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Add the x-v header to the request if provided.
if (!string.IsNullOrEmpty(version))
- client.DefaultRequestHeaders.Add("x-v", version);
+ httpClient.DefaultRequestHeaders.Add("x-v", version);
- return client;
+ return httpClient;
}
///
/// Insert the Message into the Queue
///
- private static async Task AddDeadLetterQueMsgAsync(ILogger log, string dbConnString, string qConnString, string dhBrandId, CloudQueueMessage myQueueItem, string qName)
+ private async Task AddDeadLetterQueMsgAsync( DcrQueueMessage myQueueItem, string qName, FunctionContext context)
{
QueueClientOptions options = new()
{
MessageEncoding = QueueMessageEncoding.Base64
};
- QueueClient qClient = new(qConnString, qName, options);
+ QueueClient qClient = new(_options.StorageConnectionString, qName, options);
await qClient.CreateIfNotExistsAsync();
DeadLetterQueueMessage qMsg = new()
{
MessageVersion = "1.0",
- MessageSource = "dynamicclientregistration",
- SourceMessageId = myQueueItem.Id,
- SourceMessageInsertionTime = myQueueItem.InsertionTime.ToString(),
- DataHolderBrandId = dhBrandId
+ MessageSource = _options.QueueName,
+ SourceMessageId = context.BindingContext.BindingData["Id"].ToString(),
+ SourceMessageInsertionTime = context.BindingContext.BindingData["InsertionTime"].ToString(),
+ DataHolderBrandId = myQueueItem.DataHolderBrandId
};
string qMessage = JsonConvert.SerializeObject(qMsg);
await qClient.SendMessageAsync(qMessage);
- int qCount = await GetQueueCountAsync(qConnString, qName);
+ int qCount = await GetQueueCountAsync(_options.StorageConnectionString, qName);
DcrMessage dcrMsg = new()
{
- MessageId = myQueueItem.Id,
+ MessageId = context.BindingContext.BindingData["Id"].ToString(),
MessageState = Message.Abandoned.ToString(),
MessageError = $"DCR - {qCount} items queued, this DataHolderBrandId is malformed"
};
- await new SqlDataAccess(dbConnString).UpdateDcrMsgByMessageId(dcrMsg);
- await InsertLog(log, dbConnString, $"DCR - {qCount} items in {qName} queue", "Error", "DCR");
+ await new SqlDataAccess(_options.DataRecipient_DB_ConnectionString).UpdateDcrMsgByMessageId(dcrMsg);
+ await InsertLog(_options.DataRecipient_DB_ConnectionString, $"DCR - {qCount} items in {qName} queue", "Error", "DCR");
}
///
@@ -640,11 +488,11 @@ private static async Task GetQueueCountAsync(string qConnString, string qNa
///
/// Update the Log table
///
- private static async Task InsertLog(ILogger log, string dbConnString, string msg, string lvl, string methodName, Exception exMsg = null)
+ private async Task InsertLog( string DataRecipient_DB_ConnectionString, string msg, string lvl, string methodName, Exception exMsg = null)
{
- log.LogInformation($"{methodName} - {msg}");
+ _logger.LogInformation("{methodName} - {message}", methodName, msg);
- string exMessage = "";
+ string exMessage = string.Empty;
if (exMsg != null)
{
@@ -654,7 +502,7 @@ private static async Task InsertLog(ILogger log, string dbConnString, string msg
do
{
- // skip the first inner exeception message as it is the same as the exception message
+ // skip the first inner exception message as it is the same as the exception message
if (ctr > 0)
{
innerMsg.Append(string.IsNullOrEmpty(innerException.Message) ? string.Empty : innerException.Message);
@@ -680,26 +528,24 @@ private static async Task InsertLog(ILogger log, string dbConnString, string msg
exMessage = exMessage.Replace("'", "");
}
- using (SqlConnection db = new(dbConnString))
- {
- db.Open();
- var cmdText = "";
+ using SqlConnection db = new(DataRecipient_DB_ConnectionString);
+ db.Open();
+ var cmdText = string.Empty;
- if (string.IsNullOrEmpty(exMessage))
- cmdText = $"INSERT INTO [LogEventsDcrService] ([Message], [Level], [TimeStamp], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(),@procName,@methodName,@srcContext)";
- else
- cmdText = $"INSERT INTO [LogEventsDcrService] ([Message], [Level], [TimeStamp], [Exception], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(), @exMessage,@procName,@methodName,@srcContext)";
-
- using var cmd = new SqlCommand(cmdText, db);
- cmd.Parameters.AddWithValue("@msg", msg);
- cmd.Parameters.AddWithValue("@lvl", lvl);
- cmd.Parameters.AddWithValue("@exMessage", exMessage);
- cmd.Parameters.AddWithValue("@procName", "Azure Function");
- cmd.Parameters.AddWithValue("@methodName", methodName);
- cmd.Parameters.AddWithValue("@srcContext", "CDR.DCR");
- await cmd.ExecuteNonQueryAsync();
- db.Close();
- }
+ if (string.IsNullOrEmpty(exMessage))
+ cmdText = $"INSERT INTO [LogEventsDcrService] ([Message], [Level], [TimeStamp], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(),@procName,@methodName,@srcContext)";
+ else
+ cmdText = $"INSERT INTO [LogEventsDcrService] ([Message], [Level], [TimeStamp], [Exception], [ProcessName], [MethodName], [SourceContext]) VALUES (@msg,@lvl,GETUTCDATE(), @exMessage,@procName,@methodName,@srcContext)";
+
+ using var cmd = new SqlCommand(cmdText, db);
+ cmd.Parameters.AddWithValue("@msg", msg);
+ cmd.Parameters.AddWithValue("@lvl", lvl);
+ cmd.Parameters.AddWithValue("@exMessage", exMessage);
+ cmd.Parameters.AddWithValue("@procName", "Azure Function");
+ cmd.Parameters.AddWithValue("@methodName", methodName);
+ cmd.Parameters.AddWithValue("@srcContext", "CDR.DCR");
+ await cmd.ExecuteNonQueryAsync();
+ db.Close();
}
}
}
\ No newline at end of file
diff --git a/Source/CDR.DCR/DCRConstants.cs b/Source/CDR.DCR/DCRConstants.cs
new file mode 100644
index 0000000..768e348
--- /dev/null
+++ b/Source/CDR.DCR/DCRConstants.cs
@@ -0,0 +1,7 @@
+namespace CDR.DCR
+{
+ public static class DcrConstants
+ {
+ public const string DcrHttpClientName = "DCRHttpClient";
+ }
+}
diff --git a/Source/CDR.DCR/DCRHttpClientHandler.cs b/Source/CDR.DCR/DCRHttpClientHandler.cs
new file mode 100644
index 0000000..39118a3
--- /dev/null
+++ b/Source/CDR.DCR/DCRHttpClientHandler.cs
@@ -0,0 +1,31 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+
+namespace CDR.DCR
+{
+ public class DcrHttpClientHandler : HttpClientHandler
+ {
+ public DcrHttpClientHandler(
+ IOptions options,
+ ILogger logger)
+ {
+ var dcrOptions = options.Value;
+ logger.LogInformation("Loading the client certificate...");
+
+ byte[] clientCertBytes = Convert.FromBase64String(dcrOptions.Client_Certificate);
+ X509Certificate2 clientCertificate = new(clientCertBytes, dcrOptions.Client_Certificate_Password, X509KeyStorageFlags.MachineKeySet);
+ logger.LogInformation("Client certificate loaded: {thumbprint}", clientCertificate.Thumbprint);
+
+
+ ClientCertificates.Add(clientCertificate);
+
+ if (dcrOptions.Ignore_Server_Certificate_Errors)
+ {
+ ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/CDR.DCR/DCROptions.cs b/Source/CDR.DCR/DCROptions.cs
new file mode 100644
index 0000000..c72e480
--- /dev/null
+++ b/Source/CDR.DCR/DCROptions.cs
@@ -0,0 +1,26 @@
+namespace CDR.DCR
+{
+ public class DcrOptions
+ {
+ public string AzureWebJobsStorage { get; set; }
+ public string StorageConnectionString { get; set; }
+ public string FUNCTIONS_WORKER_RUNTIME { get; set; }
+ public string DataRecipient_DB_ConnectionString { get; set; }
+ public string DataRecipient_Logging_DB_ConnectionString { get; set; }
+ public string Register_Token_Endpoint { get; set; }
+ public string Register_Get_SSA_Endpoint { get; set; }
+ public string Register_Get_SSA_XV { get; set; }
+ public string Brand_Id { get; set; }
+ public string Software_Product_Id { get; set; }
+ public string Redirect_Uris { get; set; }
+ public string Client_Certificate { get; set; }
+ public string Client_Certificate_Password { get; set; }
+ public string Signing_Certificate { get; set; }
+ public string Signing_Certificate_Password { get; set; }
+ public int Retries { get; set; }
+ public bool Ignore_Server_Certificate_Errors { get; set; }
+ public string QueueName { get; set; }
+ public string DeadLetterQueueName { get; set; }
+
+ }
+}
diff --git a/Source/CDR.DCR/DCRQueueMessage.cs b/Source/CDR.DCR/DCRQueueMessage.cs
index d3f3963..e29f7da 100644
--- a/Source/CDR.DCR/DCRQueueMessage.cs
+++ b/Source/CDR.DCR/DCRQueueMessage.cs
@@ -1,4 +1,6 @@
-using Newtonsoft.Json;
+using Azure.Storage.Queues.Models;
+using Newtonsoft.Json;
+using System;
namespace CDR.DCR
{
diff --git a/Source/CDR.DCR/Extensions/RequestExtensions.cs b/Source/CDR.DCR/Extensions/RequestExtensions.cs
new file mode 100644
index 0000000..135b58c
--- /dev/null
+++ b/Source/CDR.DCR/Extensions/RequestExtensions.cs
@@ -0,0 +1,117 @@
+using CDR.DCR.Models;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+
+namespace CDR.DCR.Extensions
+{
+ public static class RequestExtensions
+ {
+ public static (List, string) CreateClaimsForDCRRequest(this DcrRequest dcrRequest)
+ {
+ string errorMessage = string.Empty;
+
+ // Error - Unable to perform DCR as there are no mutually supported values in the mandatory claim [CLAIM_NAME]
+ const string ErrorMessage = "Unable to perform DCR as there are no mutually supported values in the mandatory claim";
+
+ var claims = new List
+ {
+ new("jti", Guid.NewGuid().ToString()),
+ new("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer),
+ new("token_endpoint_auth_signing_alg", "PS256"),
+ new("token_endpoint_auth_method", "private_key_jwt"),
+ new("application_type", "web"),
+ new("id_token_signed_response_alg", "PS256"),
+ new("id_token_encrypted_response_alg", "RSA-OAEP"),
+ new("id_token_encrypted_response_enc", "A256GCM"),
+ new("request_object_signing_alg", "PS256"),
+ new("software_statement", dcrRequest.Ssa ?? ""),
+ new("grant_types", "client_credentials"),
+ new("grant_types", "authorization_code"),
+ new("grant_types", "refresh_token")
+ };
+
+ // response_types updated below "code, code id_token" both types are returned and added below
+ // A response type is mandatory
+ if (!dcrRequest.ResponseTypesSupported.Contains("code") && !dcrRequest.ResponseTypesSupported.Contains("code id_token"))
+ {
+ // Return the error
+ errorMessage = ErrorMessage + " response_types";
+ return (null, errorMessage);
+ }
+
+ var responseTypesList = dcrRequest.ResponseTypesSupported.Where(x => x.ToLower().Equals("code") || x.ToLower().Equals("code id_token")).ToList();
+ claims.Add(new Claim("response_types", JsonConvert.SerializeObject(responseTypesList), JsonClaimValueTypes.JsonArray));
+
+
+ var isCodeFlow = dcrRequest.ResponseTypesSupported.Contains("code");
+ if (isCodeFlow && dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Length==0)
+ {
+ // Log error message to the mandatory claim missing
+ errorMessage = ErrorMessage + " authorization_signed_response_alg";
+ return (null, errorMessage);
+ }
+
+ // Mandatory for code flow
+ if (isCodeFlow)
+ {
+ if (!dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256") && !dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256"))
+ {
+ // Return the error
+ errorMessage = ErrorMessage + " authorization_signed_response_alg";
+ return (null, errorMessage);
+ }
+
+ if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("PS256"))
+ {
+ claims.Add(new Claim("authorization_signed_response_alg", "PS256"));
+ }
+ else if (dcrRequest.AuthorizationSigningResponseAlgValuesSupported.Contains("ES256"))
+ {
+ claims.Add(new Claim("authorization_signed_response_alg", "ES256"));
+ }
+ }
+
+ // Check if the enc is empty but a alg is specified.
+ if ((dcrRequest.AuthorizationEncryptionResponseEncValuesSupported == null || dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Length==0) // No enc specified
+ && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256")
+ && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP")) // but alg specified.
+ {
+ errorMessage = ErrorMessage + " authorization_encrypted_response_enc";
+ return (null, errorMessage);
+ }
+
+
+ if (dcrRequest.AuthorizationEncryptionResponseEncValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Contains("A128CBC-HS256"))
+ {
+ claims.Add(new Claim("authorization_encrypted_response_enc", "A128CBC-HS256"));
+ }
+ else if (dcrRequest.AuthorizationEncryptionResponseEncValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseEncValuesSupported.Contains("A256GCM"))
+ {
+ claims.Add(new Claim("authorization_encrypted_response_enc", "A256GCM"));
+ }
+
+ // Conditional: Optional for response_type "code" if authorization_encryption_enc_values_supported is present
+ if (isCodeFlow && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported != null && dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Length!=0)
+ {
+ if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP-256"))
+ {
+ claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP-256"));
+ }
+ else if (dcrRequest.AuthorizationEncryptionResponseAlgValuesSupported.Contains("RSA-OAEP"))
+ {
+ claims.Add(new Claim("authorization_encrypted_response_alg", "RSA-OAEP"));
+ }
+ }
+
+ char[] delimiters = [',', ' '];
+ var redirectUrisList = dcrRequest.RedirectUris?.Split(delimiters).ToList();
+ claims.Add(new Claim("redirect_uris", JsonConvert.SerializeObject(redirectUrisList), JsonClaimValueTypes.JsonArray));
+
+ return (claims, errorMessage);
+ }
+ }
+}
diff --git a/Source/CDR.DCR/Models/SoftwareStatementAssertion.cs b/Source/CDR.DCR/Models/SoftwareStatementAssertion.cs
deleted file mode 100644
index 992073c..0000000
--- a/Source/CDR.DCR/Models/SoftwareStatementAssertion.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System.Security.Cryptography.X509Certificates;
-
-namespace CDR.DCR.Models
-{
- public class SoftwareStatementAssertion
- {
- public string SsaEndpoint { get; set; }
- public string Version { get; set; }
- public string AccessToken { get; set; }
- public X509Certificate2 ClientCertificate { get; set; }
- public string BrandId { get; set; }
- public string SoftwareProductId { get; set; }
- public ILogger Log { get; set; }
- public bool IgnoreServerCertificateErrors { get; set; } = false;
- }
-}
diff --git a/Source/CDR.DCR/Program.cs b/Source/CDR.DCR/Program.cs
new file mode 100644
index 0000000..5048ec6
--- /dev/null
+++ b/Source/CDR.DCR/Program.cs
@@ -0,0 +1,45 @@
+using CDR.DCR;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.IO;
+using System.Reflection;
+
+var host = new HostBuilder()
+ .ConfigureFunctionsWebApplication()
+ .ConfigureAppConfiguration((context, builder) =>
+ {
+ builder
+ .SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
+ //while running on local machine via vs studio these settings used
+ .AddJsonFile("local.settings.json", true, true)
+ //while running docker these are config values used
+ .AddJsonFile("appsettings.docker.json", true, true)
+ .AddEnvironmentVariables()
+ .AddCommandLine(Environment.GetCommandLineArgs())
+ .Build();
+
+ if (context.HostingEnvironment.IsDevelopment() && !string.IsNullOrEmpty(context.HostingEnvironment.ApplicationName))
+ {
+ Console.WriteLine("Development environment");
+ builder.AddUserSecrets(Assembly.GetExecutingAssembly(), true);
+ }
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddOptions()
+ .Configure((settings, configuration) =>
+ {
+ configuration.Bind(settings);
+ });
+
+ services.AddTransient();
+ services.AddHttpClient(DcrConstants.DcrHttpClientName, (provider, client) =>
+ {
+
+ }).ConfigurePrimaryHttpMessageHandler();
+ })
+ .Build();
+
+host.Run();
diff --git a/Source/CDR.DCR/appsettings.docker.json b/Source/CDR.DCR/appsettings.docker.json
new file mode 100644
index 0000000..97dc880
--- /dev/null
+++ b/Source/CDR.DCR/appsettings.docker.json
@@ -0,0 +1,20 @@
+{
+ "AzureWebJobsStorage": "UseDevelopmentStorage=true",
+ "StorageConnectionString": "UseDevelopmentStorage=true",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
+ "DataRecipient_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdr;Integrated Security=true",
+ "DataRecipient_Logging_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdr;Integrated Security=true",
+ "Register_Token_Endpoint": "https://localhost:7001/idp/connect/token",
+ "Register_Get_SSA_Endpoint": "https://localhost:7001/cdr-register/v1/all/data-recipients/brands/",
+ "Register_Get_SSA_XV": "3",
+ "Brand_Id": "ffb1c8ba-279e-44d8-96f0-1bc34a6b436f",
+ "Software_Product_Id": "c6327f87-687a-4369-99a4-eaacd3bb8210",
+ "Redirect_Uris": "https://localhost:9001/consent/callback",
+ "Client_Certificate": "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",
+ "Client_Certificate_Password": "#M0ckDataRecipient#",
+ "Signing_Certificate": "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",
+ "Signing_Certificate_Password": "#M0ckDataRecipient#",
+ "Retries": 3,
+ "Ignore_Server_Certificate_Errors": "true",
+ "QueueName": "dynamicclientregistration"
+}
diff --git a/Source/CDR.DCR/azure.settings.json b/Source/CDR.DCR/azure.settings.json
index be487e0..1bd9e85 100644
--- a/Source/CDR.DCR/azure.settings.json
+++ b/Source/CDR.DCR/azure.settings.json
@@ -1,6 +1,6 @@
{
"Values": {
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"Schedule": "*/5 * * * *",
"Brand_Id": "ffb1c8ba-279e-44d8-96f0-1bc34a6b436f",
"Software_Product_Id": "c6327f87-687a-4369-99a4-eaacd3bb8210",
diff --git a/Source/CDR.DCR/local.settings.json b/Source/CDR.DCR/local.settings.json
index 6bf7cd8..fa822b2 100644
--- a/Source/CDR.DCR/local.settings.json
+++ b/Source/CDR.DCR/local.settings.json
@@ -3,7 +3,7 @@
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"StorageConnectionString": "UseDevelopmentStorage=true",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"DataRecipient_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdr;Integrated Security=true",
"DataRecipient_Logging_DB_ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=cdr-mdr;Integrated Security=true",
"Schedule": "0-59 * * * *",
@@ -18,7 +18,9 @@
"Signing_Certificate": "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",
"Signing_Certificate_Password": "#M0ckDataRecipient#",
"Retries": 3,
- "Ignore_Server_Certificate_Errors": "false"
+ "Ignore_Server_Certificate_Errors": "true",
+ "QueueName": "dynamicclientregistration",
+ "DeadLetterQueueName": "deadletter"
},
"Host": {
"LocalHttpPort": 7072,
diff --git a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
index 2d62a1a..fc693ab 100644
--- a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
+++ b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
@@ -1,20 +1,18 @@
-
- net6.0
enable
enable
- 1.2.5
- 1.2.5
- 1.2.5
+ $(TargetFrameworkVersion)
+ $(Version)
+ $(Version)
+ $(Version)
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs
index d980c8e..6a9e8e2 100644
--- a/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs
+++ b/Source/CDR.DataRecipient.API.Logger/RequestResponseLoggingMiddleware.cs
@@ -38,7 +38,6 @@ public class RequestResponseLoggingMiddleware
private string? _fapiInteractionId;
private string? _dataHolderBrandId;
-
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
readonly RequestDelegate _next;
private readonly ILogger _requestResponseLogger;
@@ -95,7 +94,6 @@ private void LogWithContext()
}
}
-
private async Task ExtractRequestProperties(HttpContext context)
{
try
@@ -159,7 +157,7 @@ private void ExtractIdFromRequest(HttpRequest request)
_dataHolderBrandId = String.Empty;
SetIdFromJwt(parameter, ClaimIdentifiers.Iss, ref _dataHolderBrandId);
}
- }
+ }
}
catch (Exception ex)
{
@@ -234,7 +232,7 @@ private async Task ExtractResponseProperties(HttpContext httpContext)
await responseBody.CopyToAsync(originalBodyStream);
}
}
- private string GetHost(HttpRequest request)
+ 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
@@ -260,7 +258,7 @@ private string GetHost(HttpRequest request)
// The Client IP address may contain a comma separated list of ip addresses based on the network devices
// the traffic traverses through. We get the first (and potentially only) ip address from the list as the client IP.
// We also remove any port numbers that may be included on the client IP.
- return keys[0]
+ return keys[0]?
.Split(',')[0] // Get the first IP address in the list, in case there are multiple.
.Split(':')[0]; // Strip off the port number, in case it is attached to the IP address.
}
diff --git a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
index 8b5fb34..e3de8fd 100644
--- a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
+++ b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
@@ -8,6 +8,7 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
+using System.Threading;
using System.Threading.Tasks;
using CDR.DataRecipient.E2ETests.Pages;
using FluentAssertions;
@@ -15,7 +16,6 @@
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
-using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
@@ -42,11 +42,13 @@ public class BaseTest
// Data Holder
public const string DH_BRANDID = "804fc2fb-18a7-4235-9a49-2af393d18bc7";
- public const string DH_BRANDID_ENERGY = "cfcaf0df-401b-47f2-98af-94787289eca8"; // Mock Data Holder (Energy)
+ public const string DH_BRANDID_ENERGY = "cfcaf0df-401b-47f2-98af-94787289eca8"; // Mock Data Holder (Energy)
+ public const string DH_BRANDID_DUMMY_DH = "e748eadf-4aa4-4e2f-b3da-fb4a9d511994"; // Use "Bank Brand 2" Dummy Data Holder for negative testing
// Data Recipient
public const string DR_BRANDID = "ffb1c8ba-279e-44d8-96f0-1bc34a6b436f";
public const string DR_SOFTWAREPRODUCTID = "c6327f87-687a-4369-99a4-eaacd3bb8210";
+ public const string DR_DEFAULT_SCOPES = "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";
// URLs
static public string REGISTER_MTLS_BaseURL => Configuration["MTLS_BaseURL"]
@@ -160,7 +162,7 @@ static void DeleteFile(string filename)
// Setup browser
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
- SlowMo = 250,
+ SlowMo = 0,
#if TEST_DEBUG_MODE
Headless = false,
Timeout = 5000 // DEBUG - 5 seconds
@@ -239,7 +241,7 @@ protected async Task CleanupAsync(CleanupDelegate cleanup)
// Setup browser
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
- SlowMo = 250,
+ SlowMo = 0,
#if TEST_DEBUG_MODE
Headless = false,
Timeout = 5000 // DEBUG - 5 seconds
@@ -261,9 +263,7 @@ protected async Task CleanupAsync(CleanupDelegate cleanup)
try
{
var page = await context.NewPageAsync();
- page.Close += async (_, page) =>
- {
- };
+ page.Close += (_, page) => { };
using (new AssertionScope())
{
@@ -310,7 +310,7 @@ static void DeleteFile(string filename)
// Setup browser
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
- SlowMo = 250,
+ SlowMo = 0,
#if TEST_DEBUG_MODE
Headless = false,
Timeout = 5000 // DEBUG - 5 seconds
@@ -564,7 +564,7 @@ static protected async Task DataHolders_Discover(IPage page, string industry = "
// Arrange - Set industry
if (String.IsNullOrEmpty(industry)) // Clear industry
{
- await page.Locator("select[name=\"Industry\"]").SelectOptionAsync(new SelectOptionValue[] { });
+ await page.Locator("select[name=\"Industry\"]").SelectOptionAsync(Array.Empty());
}
else
{
@@ -582,6 +582,9 @@ static protected async Task DataHolders_Discover(IPage page, string industry = "
// Arrange - Set version
await page.Locator("input[name=\"Version\"]").FillAsync(version);
+ // Workaround issue where Refresh is clicked before form has loaded.
+ Thread.Sleep(300);
+
// Act - Click Refresh button
await page.Locator(@"h5:has-text(""Refresh Data Holders"") ~ div.card-body >> input:has-text(""Refresh"")").ClickAsync();
@@ -636,52 +639,51 @@ static protected async Task SSA_Get(IPage page, string industry, string version,
// Create Client Registration returning DH client ID of client that was registered
static protected async Task ClientRegistration_Create(IPage page,
- string dhBrandId,
- string drBrandId,
- string drSoftwareProductId,
+ string dhBrandId,
string? jarmSigningAlgo = null,
- string responseTypes = "code id_token",
+ string responseTypes = "code,code id_token",
string? jarmEncrypAlg = null,
string? jarmEncryptEnc = null)
{
- // Arrange - Goto home page, click menu button, check page loaded
- await page.GotoAsync(WEB_URL);
- await page.Locator("a >> text=Dynamic Client Registration").ClickAsync();
- await page.Locator("h2 >> text=Dynamic Client Registration").TextContentAsync();
- // Set data holder brand id
- await page.Locator("select[name=\"DataHolderBrandId\"]").SelectOptionAsync(new[] { dhBrandId });
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
- // Assert - Check software product id
- (await page.Locator("input[name=\"SoftwareProductId\"]").InputValueAsync()).Should().Be(drSoftwareProductId);
+ await dcrPage.GotoDynamicClientRegistrationPage();
- await page.Locator("input[name=\"ResponseTypes\"]").FillAsync(responseTypes);
+ await dcrPage.SelectDataHolderBrandId(dhBrandId);
+ await dcrPage.EnterResponseTypes(responseTypes);
if (jarmSigningAlgo != null)
{
- await page.Locator("input[name=\"AuthorizationSignedResponseAlg\"]").FillAsync(jarmSigningAlgo);
+ await dcrPage.EnterAuthorisedSignedResponsegAlgo(jarmSigningAlgo);
}
if (jarmEncrypAlg != null)
{
- await page.Locator("input[name=\"AuthorizationEncryptedResponseAlg\"]").FillAsync(jarmEncrypAlg);
+ await dcrPage.EnterAuthorisedEncryptedResponseAlgo(jarmEncrypAlg);
}
if (jarmEncryptEnc != null)
{
- await page.Locator("input[name=\"AuthorizationEncryptedResponseEnc\"]").FillAsync(jarmEncryptEnc);
+ await dcrPage.EnterAuthorisedEncryptedResponseEnc(jarmEncryptEnc);
+ }
+
+ if (responseTypes.Contains("id_token"))
+ {
+ await dcrPage.EnterIdTokenEncryptedResponseAlgo("RSA-OAEP");
+ await dcrPage.EnterIdTokenEncryptedResponseEnc("A128CBC-HS256");
}
- // Act - Click create button
- await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-body >> input:has-text(""Register"")").ClickAsync();
+ await dcrPage.ClickRegister();
- // Assert - Check client was registered
- await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-footer:has-text(""Created - Registered"")").TextContentAsync();
+ var registrationResponseJson = await dcrPage.GetRegistrationResponse();
+ return GetClientIdFromRegistrationResponse(registrationResponseJson);
- // Assert - Get json result
- var json = await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-footer >> pre").InnerTextAsync();
+ }
+ static protected string GetClientIdFromRegistrationResponse(string registrationResponseJson)
+ {
// Deserialise response and return DH client id
- DCRResponse dcrResponse = JsonConvert.DeserializeObject(json) ?? throw new NullReferenceException(nameof(json));
- return dcrResponse.ClientId ?? throw new NullReferenceException(nameof(dcrResponse.ClientId));
+ DCRResponse dcrResponse = JsonConvert.DeserializeObject(registrationResponseJson) ?? throw new NullReferenceException(nameof(registrationResponseJson));
+ return dcrResponse.ClientId ?? throw new NullReferenceException($"{nameof(dcrResponse.ClientId)} could not be found in {nameof(registrationResponseJson)} - {registrationResponseJson}");
}
static protected async Task ConsentAndAuthorisation2(IPage page, string customerId = CUSTOMERID_BANKING, string customerAccounts = CUSTOMERACCOUNTS_BANKING)
diff --git a/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj b/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj
index 87bef6c..1327b1b 100644
--- a/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj
+++ b/Source/CDR.DataRecipient.E2ETests/CDR.DataRecipient.E2ETests.csproj
@@ -1,17 +1,14 @@
-
-
+
- net6.0
false
- 1.2.5
- 1.2.5
- 1.2.5
+ $(TargetFrameworkVersion)
+ $(Version)
+ $(Version)
+ $(Version)
-
-
Always
@@ -22,32 +19,30 @@
Always
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
+
-
-
- Always
-
-
-
+
+ Always
+
+
+
\ No newline at end of file
diff --git a/Source/CDR.DataRecipient.E2ETests/Fixtures/TestFixture.cs b/Source/CDR.DataRecipient.E2ETests/Fixtures/TestFixture.cs
index d289b17..ca87e98 100644
--- a/Source/CDR.DataRecipient.E2ETests/Fixtures/TestFixture.cs
+++ b/Source/CDR.DataRecipient.E2ETests/Fixtures/TestFixture.cs
@@ -1,5 +1,3 @@
-using Microsoft.Data.SqlClient;
-using System;
using System.Threading.Tasks;
using Xunit;
@@ -9,14 +7,17 @@ namespace CDR.DataRecipient.E2ETests
{
public class TestFixture : IAsyncLifetime
{
+ private static readonly string[] installArguments = ["install"];
+ private static readonly string[] installDepsArguments = ["install-deeps"];
+
public Task InitializeAsync()
{
// Only install Playwright if not running in container, since Dockerfile.e2e-tests already installed Playwright
if (!BaseTest.RUNNING_IN_CONTAINER)
{
// Ensure that Playwright has been fully installed.
- Microsoft.Playwright.Program.Main(new string[] { "install" });
- Microsoft.Playwright.Program.Main(new string[] { "install-deps" });
+ Microsoft.Playwright.Program.Main(installArguments);
+ Microsoft.Playwright.Program.Main(installDepsArguments);
}
return Task.CompletedTask;
diff --git a/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs b/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs
index 72efdb8..efae57a 100644
--- a/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs
+++ b/Source/CDR.DataRecipient.E2ETests/Infrastructure/AccessToken.cs
@@ -16,7 +16,7 @@ public class AccessToken
{
private static readonly string IDENTITYSERVER_URL = BaseTest.REGISTER_IDENTITYSERVER_URL;
private static readonly string AUDIENCE = IDENTITYSERVER_URL;
- private const string SCOPE = "cdr-register:bank:read";
+ private const string SCOPE = "cdr-register:read";
private const string GRANT_TYPE = "client_credentials";
private const string CLIENT_ID = "c6327f87-687a-4369-99a4-eaacd3bb8210";
private const string CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs b/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs
index d0ca394..c6745bb 100644
--- a/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs
+++ b/Source/CDR.DataRecipient.E2ETests/Pages/ConsentAndAuthorisationPages.cs
@@ -19,9 +19,9 @@ internal class ConsentAndAuthorisationPages
public ConsentAndAuthorisationPages(IPage page)
{
_page = page;
- _txtCustomerId = _page.Locator("id=mui-1");
+ _txtCustomerId = _page.Locator("id=customerId");
_btnContinue = _page.Locator("button:has-text(\"Continue\")");
- _txtOneTimePassword = _page.Locator("id=mui-2");
+ _txtOneTimePassword = _page.Locator("id=otp");
_btnAuthorise = _page.Locator("text=Authorise");
_btnCancel = _page.Locator("text=Cancel");
diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs b/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs
new file mode 100644
index 0000000..ea9b03e
--- /dev/null
+++ b/Source/CDR.DataRecipient.E2ETests/Pages/DynamicClientRegistrationPage.cs
@@ -0,0 +1,285 @@
+using Microsoft.Playwright;
+using System.Threading.Tasks;
+
+namespace CDR.DataRecipient.E2ETests.Pages
+{
+
+ internal class DynamicClientRegistrationPage
+ {
+ private IPage _page;
+ private readonly string _dataRecipientBaseUrl;
+ private readonly ILocator _lnkDcrMenuItem;
+ private readonly ILocator _hedPageHeading;
+
+ private readonly ILocator _selSelectBrand;
+ private readonly ILocator _txtClientId;
+ private readonly ILocator _txtSsaVersion;
+ private readonly ILocator _selIndustry;
+ private readonly ILocator _selIndustrySelected;
+ private readonly ILocator _txtSoftwareProductId;
+ private readonly ILocator _txtRedirectUris;
+ private readonly ILocator _txtScope;
+ private readonly ILocator _txtTokenSigningAlgo;
+ private readonly ILocator _txtTokenAuthMethod;
+ private readonly ILocator _txtGrantTypes;
+ private readonly ILocator _txtResponseTypes;
+ private readonly ILocator _txtApplicationType;
+
+ private readonly ILocator _txtIdTokenSignedResponseAlgo;
+ private readonly ILocator _txtIdTokenEncryptedResponseAlgo;
+ private readonly ILocator _txtIdTokenEncryptedResponseEnc;
+
+ private readonly ILocator _txtRequestSigningAlgo;
+ private readonly ILocator _txtAuthorisedSignedResponsegAlgo;
+ private readonly ILocator _txtAuthorisedEncryptedResponseAlgo;
+ private readonly ILocator _txtAuthorisedEncryptedResponseEnc;
+
+ private readonly ILocator _btnRegister;
+ private readonly ILocator _btnUpdate;
+
+ private readonly ILocator _divRegistrationResponseWithHeading;
+ private readonly ILocator _divRegistrationResponseJson;
+ private readonly ILocator _divViewRegistrationResponse;
+ private ILocator _divDiscoveryDocumentDetails;
+
+
+ public DynamicClientRegistrationPage(IPage page, string dataRecipientBaseUrl)
+ {
+ _page = page;
+ _dataRecipientBaseUrl = dataRecipientBaseUrl;
+
+ _lnkDcrMenuItem = _page.Locator("a >> text=Dynamic Client Registration");
+ _hedPageHeading = _page.Locator("h2 >> text=Dynamic Client Registration");
+
+ _selSelectBrand = _page.Locator("id=DataHolderBrandId");
+ _txtClientId = _page.Locator("id=ClientId");
+ _txtSsaVersion = _page.Locator("id=SsaVersion");
+ _selIndustry = _page.Locator("id=Industry");
+ _selIndustrySelected = _page.Locator("//select[@id='Industry']/option[@selected='selected']");
+ _txtSoftwareProductId = _page.Locator("id=SoftwareProductId");
+ _txtRedirectUris = _page.Locator("id=RedirectUris");
+ _txtScope = _page.Locator("id=Scope");
+
+ _txtTokenSigningAlgo = _page.Locator("id=TokenEndpointAuthSigningAlg");
+ _txtTokenAuthMethod = _page.Locator("id=TokenEndpointAuthMethod");
+ _txtGrantTypes = _page.Locator("id=GrantTypes");
+ _txtResponseTypes = _page.Locator("id=ResponseTypes");
+ _txtApplicationType = _page.Locator("id=ApplicationType");
+
+ _txtIdTokenSignedResponseAlgo = _page.Locator("id=IdTokenSignedResponseAlg");
+ _txtIdTokenEncryptedResponseAlgo = _page.Locator("id=IdTokenEncryptedResponseAlg");
+ _txtIdTokenEncryptedResponseEnc = _page.Locator("id=IdTokenEncryptedResponseEnc");
+
+ _txtRequestSigningAlgo = _page.Locator("id=RequestObjectSigningAlg");
+ _txtAuthorisedSignedResponsegAlgo = _page.Locator("id=AuthorizationSignedResponseAlg");
+ _txtAuthorisedEncryptedResponseAlgo = _page.Locator("id=AuthorizationEncryptedResponseAlg");
+ _txtAuthorisedEncryptedResponseEnc = _page.Locator("id=AuthorizationEncryptedResponseEnc");
+
+ _btnRegister = _page.Locator("//input[@name='register' and @value='Register']");
+ _btnUpdate = _page.Locator("//input[@name='register' and @value='Update']");
+
+ _divRegistrationResponseWithHeading = _page.Locator("//div[@id='registration']");
+ _divRegistrationResponseJson = _page.Locator(@"h5:has-text(""Client Registration"") ~ div.card-footer >> pre");
+ _divViewRegistrationResponse = _page.Locator("//div[@id='modal-registration' and @class='modal show']//div[@class='modal-body']");
+
+ }
+
+ public async Task GotoDynamicClientRegistrationPage()
+ {
+ await _page.GotoAsync(_dataRecipientBaseUrl);
+ await _lnkDcrMenuItem.ClickAsync();
+ await _hedPageHeading.TextContentAsync();
+ }
+
+ public async Task SelectDataHolderBrandId(string dataholderBrandId)
+ {
+ await _selSelectBrand.SelectOptionAsync(new[] { dataholderBrandId });
+ }
+
+ public async Task EnterClientId(string clientId)
+ {
+ await _txtClientId.FillAsync(clientId);
+ }
+ public async Task EnterSsaVersion(string ssaVersion)
+ {
+ await _txtSsaVersion.FillAsync(ssaVersion);
+ }
+ public async Task SelectIndustry(string industry)
+ {
+ await _selIndustry.SelectOptionAsync(new[] { industry });
+ }
+ public async Task EnterRedirectUris(string redirectUris)
+ {
+ await _txtRedirectUris.FillAsync(redirectUris);
+ }
+ public async Task EnterScope(string scope)
+ {
+ await _txtScope.FillAsync(scope);
+ }
+ public async Task EnterTokenAuthSigningAlgo(string tokenAuthSigningAlgo)
+ {
+ await _txtTokenSigningAlgo.FillAsync(tokenAuthSigningAlgo);
+ }
+ public async Task EnterTokenAuthSigningMethod(string tokenAuthSigningMethod)
+ {
+ await _txtTokenAuthMethod.FillAsync(tokenAuthSigningMethod);
+ }
+ public async Task EnterGrantTypes(string grantTypes)
+ {
+ await _txtGrantTypes.FillAsync(grantTypes);
+ }
+ public async Task EnterResponseTypes(string responseTypes)
+ {
+ await _txtResponseTypes.FillAsync(responseTypes);
+ }
+ public async Task EnterIdTokenIdTokenSignedResponseAlgo(string idTokenIdTokenSignedResponseAlgo)
+ {
+ await _txtIdTokenSignedResponseAlgo.FillAsync(idTokenIdTokenSignedResponseAlgo);
+ }
+ public async Task EnterIdTokenEncryptedResponseAlgo(string idTokenEncryptedResponseAlgo)
+ {
+ await _txtIdTokenEncryptedResponseAlgo.FillAsync(idTokenEncryptedResponseAlgo);
+ }
+ public async Task EnterIdTokenEncryptedResponseEnc(string idTokenEncryptedResponseEnc)
+ {
+ await _txtIdTokenEncryptedResponseEnc.FillAsync(idTokenEncryptedResponseEnc);
+ }
+ public async Task EnterRequestSigningAlgo(string requestSigningAlgo)
+ {
+ await _txtRequestSigningAlgo.FillAsync(requestSigningAlgo);
+ }
+ public async Task EnterAuthorisedSignedResponsegAlgo(string authorisedSignedResponsegAlgo)
+ {
+ await _txtAuthorisedSignedResponsegAlgo.FillAsync(authorisedSignedResponsegAlgo);
+ }
+ public async Task EnterAuthorisedEncryptedResponseAlgo(string authorisedEncryptedResponseAlgo)
+ {
+ await _txtAuthorisedEncryptedResponseAlgo.FillAsync(authorisedEncryptedResponseAlgo);
+ }
+ public async Task EnterAuthorisedEncryptedResponseEnc(string authorisedEncryptedResponseEnc)
+ {
+ await _txtAuthorisedEncryptedResponseEnc.FillAsync(authorisedEncryptedResponseEnc);
+ }
+
+ public async Task ClickRegister()
+ {
+ await _btnRegister.ClickAsync();
+ }
+ public async Task ClickUpdate()
+ {
+ await _btnUpdate.ClickAsync();
+ }
+ public async Task ClickViewRegistration(string clientId)
+ {
+ await _page.Locator($"//a[@data-id='{clientId}' and text()='View']").ClickAsync();
+ }
+ public async Task ClickEditRegistration(string clientId)
+ {
+ await _page.Locator($"//tr[@id='{clientId}']//a[text()='Edit']").ClickAsync();
+ }
+ public async Task GetClientId()
+ {
+ return await _txtClientId.InputValueAsync();
+ }
+ public async Task GetSsaVersion()
+ {
+ return await _txtSsaVersion.InputValueAsync();
+ }
+ public async Task GetIndustry()
+ {
+ return await _selIndustrySelected.TextContentAsync();
+ }
+ public async Task GetSoftwareProductId()
+ {
+ return await _txtSoftwareProductId.InputValueAsync();
+ }
+ public async Task GetRedirectUris()
+ {
+ return await _txtRedirectUris.InputValueAsync();
+ }
+ public async Task GetScope()
+ {
+ return await _txtScope.InputValueAsync();
+ }
+ public async Task GetTokenSigningAlgo()
+ {
+ return await _txtTokenSigningAlgo.InputValueAsync();
+ }
+ public async Task GetTokenAuthMethod()
+ {
+ return await _txtTokenAuthMethod.InputValueAsync();
+ }
+ public async Task GetGrantTypes()
+ {
+ return await _txtGrantTypes.InputValueAsync();
+ }
+ public async Task GetResponseTypes()
+ {
+ return await _txtResponseTypes.InputValueAsync();
+ }
+ public async Task GetApplicationType()
+ {
+ return await _txtApplicationType.InputValueAsync();
+ }
+ public async Task GetIdTokenSignedResponseAlgo()
+ {
+ return await _txtIdTokenSignedResponseAlgo.InputValueAsync();
+ }
+ public async Task GetIdTokenEncryptedResponseAlgo()
+ {
+ return await _txtIdTokenEncryptedResponseAlgo.InputValueAsync();
+ }
+ public async Task GetIdTokenEncryptedResponseEnc()
+ {
+ return await _txtIdTokenEncryptedResponseEnc.InputValueAsync();
+ }
+ public async Task GetRequestSigningAlgo()
+ {
+ return await _txtRequestSigningAlgo.InputValueAsync();
+ }
+ public async Task GetAuthorisedSignedResponsegAlgo()
+ {
+ return await _txtAuthorisedSignedResponsegAlgo.InputValueAsync();
+ }
+ public async Task GetAuthorisedEncryptedResponseAlgo()
+ {
+ return await _txtAuthorisedEncryptedResponseAlgo.InputValueAsync();
+ }
+ public async Task GetAuthorisedEncryptedResponseEnc()
+ {
+ return await _txtAuthorisedEncryptedResponseEnc.InputValueAsync();
+ }
+ public async Task GetRegistrationResponse(bool includeHeading = false)
+ {
+ if(includeHeading)
+ {
+ return await _divRegistrationResponseWithHeading.TextContentAsync();
+ }
+ else
+ {
+ return await _divRegistrationResponseJson.TextContentAsync();
+ }
+
+ }
+ public async Task GetViewRegistrationResponse()
+ {
+ return await _divViewRegistrationResponse.TextContentAsync();
+ }
+ public async Task GetDiscoveryDocumentDetails(string textToSynchroniseWith = null)
+ {
+ // Workaround to wait for text to synchonise with.
+ // Without synchronisation, current text content is returned instead of waiting for text (page to reload)
+ if (textToSynchroniseWith == null)
+ {
+ _divDiscoveryDocumentDetails = _page.Locator("#discovery-document");
+ }
+ else
+ {
+ _divDiscoveryDocumentDetails = _page.Locator($"//div[@id='discovery-document']/div[contains(text(), '{textToSynchroniseWith}')]/..");
+ }
+
+ return await _divDiscoveryDocumentDetails.TextContentAsync();
+ }
+
+ }
+}
diff --git a/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs b/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs
index e5bcb22..b091940 100644
--- a/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs
+++ b/Source/CDR.DataRecipient.E2ETests/Pages/ParPage.cs
@@ -1,12 +1,6 @@
-using Microsoft.IdentityModel.Tokens;
-using Microsoft.Playwright;
+using Microsoft.Playwright;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
-using System.Web;
-using static System.Formats.Asn1.AsnWriter;
namespace CDR.DataRecipient.E2ETests.Pages
{
@@ -23,10 +17,13 @@ internal class ParPage
private readonly ILocator _txtResponseType;
private readonly ILocator _txtResponseMode;
private readonly ILocator _btnInitiatePar;
+ private readonly ILocator _btnViewRegistration;
+ private readonly ILocator _divViewRegistrationError;
private readonly ILocator _lnkRequestUri;
private readonly ILocator _chkUsePkce;
private readonly ILocator _divErrorMessage;
private readonly ILocator _lblRequestUri;
+ private readonly ILocator _divRegistrationModal;
public ParPage(IPage page)
{
@@ -41,10 +38,12 @@ public ParPage(IPage page)
_txtResponseType = _page.Locator("input[name=\"ResponseType\"]");
_txtResponseMode = _page.Locator("input[name=\"ResponseMode\"]");
_btnInitiatePar = _page.Locator("div.form >> text=Initiate PAR");
+ _btnViewRegistration = _page.Locator("#ViewRegistration");
+ _divViewRegistrationError = _page.Locator("#registrationid-validation-message");
_lnkRequestUri = _page.Locator("p.results > a");
_divErrorMessage = _page.Locator(".card-footer");
_lblRequestUri = _page.Locator("dd:has-text(\"urn:\")");
-
+ _divRegistrationModal = _page.Locator("//div[@id='modal-registration' and @class='modal show']//div[@class='modal-body']");
}
public async Task GotoPar()
@@ -53,31 +52,18 @@ public async Task GotoPar()
await _hedPageHeading.TextContentAsync();
}
- public async Task CompleteParFormOld(string dhClientId, string dhBrandId, string cdrArrangement, string sharingDuration)
- {
- await _selSelectRegistration.SelectOptionAsync(new[] { $"{dhClientId}|||{dhBrandId}" });
- await _selSelectRegistration.ClickAsync(); // there is JS that runs on the click event, so simulate click here
- await Task.Delay(2000);
- await _selSelectArrangementId.SelectOptionAsync(new[] { cdrArrangement });
- await _txtSharingDuration.FillAsync(sharingDuration);
- await _btnInitiatePar.ClickAsync();
- await _lnkRequestUri.ClickAsync();
-
- }
-
public async Task CompleteParForm(
string dhClientId,
string dhBrandId,
string scope = null,
string cdrArrangement = null,
- string responseType = "code id_token",
- string responseMode = "fragment",
+ string responseType = "code",
+ string responseMode = "jwt",
string sharingDuration = "",
bool usePkce = true)
{
- await _selSelectRegistration.SelectOptionAsync(new[] { $"{dhClientId}|||{dhBrandId}" });
- await _selSelectRegistration.ClickAsync(); // there is JS that runs on the click event, so simulate click here
- await Task.Delay(2000);
+ await SelectRegistration(dhBrandId, dhClientId);
+
if (cdrArrangement != null)
{
await _selSelectArrangementId.SelectOptionAsync(new[] { cdrArrangement });
@@ -103,11 +89,24 @@ public async Task CompleteParForm(
}
+ public async Task SelectRegistration(string dhBrandId, string dhClientId)
+ {
+ await _selSelectRegistration.SelectOptionAsync(new[] { $"{dhClientId}|||{dhBrandId}" });
+ await _selSelectRegistration.ClickAsync(); // there is JS that runs on the click event, so simulate click here
+ await Task.Delay(2000);
+ }
public async Task ClickInitiatePar()
{
await _btnInitiatePar.ClickAsync();
}
-
+ public async Task ClickViewRegistration()
+ {
+ await _btnViewRegistration.ClickAsync();
+ }
+ public async Task GetViewRegistrationError()
+ {
+ return await _divViewRegistrationError.InnerTextAsync();
+ }
public async Task ClickAuthorizeUrl()
{
await _lnkRequestUri.ClickAsync();
@@ -124,16 +123,18 @@ public async Task GetErrorMessage()
{
return await _divErrorMessage.InnerTextAsync();
}
-
public async Task GetResponseType()
{
return await _txtResponseType.InputValueAsync();
}
-
public async Task GetResponseMode()
{
return await _txtResponseMode.InputValueAsync();
}
+ public async Task GetViewRegistrationResponse()
+ {
+ return await _divRegistrationModal.InnerTextAsync();
+ }
public async Task ErrorExists(string errorToCheckFor)
{
try
diff --git a/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs b/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs
index 1549359..e96c2c6 100644
--- a/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs
+++ b/Source/CDR.DataRecipient.E2ETests/US23863_MDR_E2ETests.cs
@@ -1,5 +1,6 @@
using CDR.DataRecipient.E2ETests.Pages;
using FluentAssertions;
+using FluentAssertions.Execution;
using Microsoft.Data.SqlClient;
using Microsoft.Playwright;
using System;
@@ -51,7 +52,7 @@ static string GetClientId()
return clientId;
}
- public static async Task TestToken(IPage page, string menuText, string? expectedToken)
+ static async Task TestToken(IPage page, string menuText, string? expectedToken)
{
// Arrange - Goto home page, click menu button, check page loaded
await page.GotoAsync(WEB_URL);
@@ -194,7 +195,218 @@ await ArrangeAsync(testName, async (page) =>
await TestAsync(testName, async (page) =>
{
- await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId);
+
+ // Create a default Banking Dataholder Registration using defaults
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.SelectDataHolderBrandId(DH_BRANDID);
+ await dcrPage.ClickRegister();
+
+ // Assert Software Product Registered
+ var registrationResponse = await dcrPage.GetRegistrationResponse(includeHeading: true);
+ registrationResponse.Should().Contain("Created - Registered");
+
+ });
+ }
+ finally
+ {
+ await CleanupAsync(async (page) =>
+ {
+ try { await ClientRegistration_Delete(page); } catch { };
+ });
+ }
+ }
+
+ [Theory]
+ [InlineData(DH_BRANDID, DR_BRANDID, DR_SOFTWAREPRODUCTID)]
+ [InlineData(DH_BRANDID_ENERGY, DR_BRANDID, DR_SOFTWAREPRODUCTID)] // Also test for Energy DH
+ public async Task AC04_DynamicClientRegistration_Defaults(string dhBrandId = DH_BRANDID, string drBrandId = DR_BRANDID, string drSoftwareProductId = DR_SOFTWAREPRODUCTID)
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC04_DynamicClientRegistration_Defaults)} - DH_BrandId={dhBrandId} - DR_BrandId={drBrandId} - DR_SoftwareProductId={drSoftwareProductId}";
+
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+
+ // Select Dataholder
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.SelectDataHolderBrandId(dhBrandId);
+
+ // Assert all default values
+ using (new AssertionScope())
+ {
+ dcrPage.GetClientId().Result.Should().Be(String.Empty);
+ dcrPage.GetSsaVersion().Result.Should().Be("3");
+ dcrPage.GetIndustry().Result.Should().Be("ALL");
+ dcrPage.GetSoftwareProductId().Result.Should().Be(drSoftwareProductId);
+ dcrPage.GetRedirectUris().Result.Should().Be($"https://{BaseTest.HOSTNAME_DATARECIPIENT}:9001/consent/callback");
+ dcrPage.GetScope().Result.Should().Be(DR_DEFAULT_SCOPES);
+ dcrPage.GetTokenSigningAlgo().Result.Should().Be("PS256");
+ dcrPage.GetGrantTypes().Result.Should().Be("client_credentials,authorization_code,refresh_token");
+ dcrPage.GetResponseTypes().Result.Should().Be("code");
+ dcrPage.GetApplicationType().Result.Should().Be("web");
+ dcrPage.GetIdTokenSignedResponseAlgo().Result.Should().Be("PS256");
+ dcrPage.GetIdTokenEncryptedResponseAlgo().Result.Should().Be(String.Empty);
+ dcrPage.GetIdTokenEncryptedResponseEnc().Result.Should().Be(String.Empty);
+ dcrPage.GetRequestSigningAlgo().Result.Should().Be("PS256");
+ dcrPage.GetAuthorisedSignedResponsegAlgo().Result.Should().Be("PS256");
+ dcrPage.GetAuthorisedEncryptedResponseAlgo().Result.Should().Be(String.Empty);
+ dcrPage.GetAuthorisedEncryptedResponseEnc().Result.Should().Be(String.Empty);
+ }
+
+ });
+
+ }
+
+ [Fact]
+ public async Task AC04_DynamicClientRegistrationViewRegistration()
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC04_DynamicClientRegistrationViewRegistration)}";
+ try
+ {
+ string? dhClientId = null;
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ dhClientId = await ClientRegistration_Create(page, DH_BRANDID) ?? throw new NullReferenceException(nameof(dhClientId));
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+ // View newly created registration
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.ClickViewRegistration(dhClientId);
+
+ // Assert
+ using (new AssertionScope())
+ {
+ string viewRegistrationResponse = await dcrPage.GetViewRegistrationResponse();
+ viewRegistrationResponse.Should().Contain("Registration retrieved successfully.");
+ viewRegistrationResponse.Should().Contain($"\"client_id\": \"{dhClientId}\"");
+ }
+
+ });
+ }
+ finally
+ {
+ await CleanupAsync(async (page) =>
+ {
+ try { await ClientRegistration_Delete(page); } catch { };
+ });
+ }
+ }
+
+ [Fact]
+ public async Task AC04_DynamicClientRegistrationViewDiscoveryDocument()
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC04_DynamicClientRegistrationViewDiscoveryDocument)}";
+
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+ // View discovery document
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.SelectDataHolderBrandId(DH_BRANDID);
+
+ // Assert
+ using (new AssertionScope())
+ {
+ string discoveryDocumentDetails = await dcrPage.GetDiscoveryDocumentDetails("Discovery Document details loaded");
+ discoveryDocumentDetails.Should().Contain($"Discovery Document details loaded from https://{HOSTNAME_DATAHOLDER}:8001/.well-known/openid-configuration");
+ discoveryDocumentDetails.Should().Contain($"\"issuer\": \"https://{HOSTNAME_DATAHOLDER}:8001\"");
+ }
+
+ });
+
+ }
+
+ [Fact]
+ public async Task AC04_DynamicClientRegistrationViewDiscoveryDocument_Invalid()
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC04_DynamicClientRegistrationViewDiscoveryDocument_Invalid)}";
+
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+ // View discovery document
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.SelectDataHolderBrandId(DH_BRANDID_DUMMY_DH);
+
+ // Assert
+ using (new AssertionScope())
+ {
+ string discoveryDocumentDetails = await dcrPage.GetDiscoveryDocumentDetails("Discovery Document");
+ discoveryDocumentDetails.Should().Contain("Unable to load Discovery Document from https://idp.bank2/.well-known/openid-configuration");
+ }
+
+ });
+ }
+
+ [Theory]
+ [InlineData(DH_BRANDID)]
+ [InlineData(DH_BRANDID_ENERGY)] // Also test for Energy DH
+ public async Task AC04_DynamicClientRegistrationUpdate(string dhBrandId)
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC04_DynamicClientRegistrationUpdate)}";
+ try
+ {
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+ DynamicClientRegistrationPage dcrPage = new DynamicClientRegistrationPage(page, WEB_URL);
+
+ // Create a default Banking Dataholder Registration using defaults
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.SelectDataHolderBrandId(dhBrandId);
+ await dcrPage.ClickRegister();
+
+ var registrationResponseJson = await dcrPage.GetRegistrationResponse();
+
+ // Deserialise response and return DH client id
+ string clientId = GetClientIdFromRegistrationResponse(registrationResponseJson);
+
+ // Navigate to fresh DCR page
+ await dcrPage.GotoDynamicClientRegistrationPage();
+ await dcrPage.ClickEditRegistration(clientId);
+
+ // Assert Client Correctly Loaded
+ dcrPage.GetClientId().Result.Should().Be(clientId);
+
+ // Modify to valid hybrid mode values
+ await dcrPage.EnterResponseTypes("code,code id_token");
+ await dcrPage.EnterIdTokenEncryptedResponseAlgo("RSA-OAEP");
+ await dcrPage.EnterIdTokenEncryptedResponseEnc("A128CBC-HS256");
+
+ await dcrPage.ClickUpdate();
+
+ // Assert Software Product Registration Updated
+ var registrationResponse = await dcrPage.GetRegistrationResponse(includeHeading: true);
+ registrationResponse.Should().Contain("Registration update successful.");
+ registrationResponse.Should().Contain("code id_token");
+ registrationResponse.Should().Contain("\"id_token_encrypted_response_alg\": \"RSA-OAEP\"");
+ registrationResponse.Should().Contain("\"id_token_encrypted_response_enc\": \"A128CBC-HS256\"");
+
});
}
finally
@@ -207,7 +419,7 @@ await CleanupAsync(async (page) =>
}
public delegate Task ConsentsDelegate(IPage page, ConsentAndAuthorisationResponse response);
- public async Task Test_Consents(string dhBrandId, string drBrandId, string drSoftwareProductId, string customerId, string customerAccounts, string testName, ConsentsDelegate test)
+ async Task Test_Consents(string dhBrandId, string drBrandId, string drSoftwareProductId, string customerId, string customerAccounts, string testName, ConsentsDelegate test)
{
try
{
@@ -217,7 +429,7 @@ await ArrangeAsync($"{testName} - DH_BrandId={dhBrandId}", async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -245,6 +457,7 @@ public async Task AC06_Consents_ViewIDToken(string dhBrandId, string drBrandId,
{
await Test_Consents(dhBrandId, drBrandId, drSoftwareProductId, customerId, customerAccounts, $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC06_Consents_ViewIDToken)}", async (page, response) =>
{
+ // CT: This needs to be a different exception type
await TestToken(page, "View ID Token", response?.IDToken ?? throw new ArgumentNullException(nameof(response.IDToken)));
});
}
@@ -256,6 +469,7 @@ public async Task AC06_Consents_ViewAccessToken(string dhBrandId, string drBrand
{
await Test_Consents(dhBrandId, drBrandId, drSoftwareProductId, customerId, customerAccounts, $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC06_Consents_ViewAccessToken)}", async (page, response) =>
{
+ // CT: This needs to be a different exception type
await TestToken(page, "View Access Token", response?.AccessToken ?? throw new ArgumentNullException(nameof(response.AccessToken)));
});
}
@@ -267,6 +481,7 @@ public async Task AC06_Consents_ViewRefreshToken(string dhBrandId, string drBran
{
await Test_Consents(dhBrandId, drBrandId, drSoftwareProductId, customerId, customerAccounts, $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC06_Consents_ViewRefreshToken)}", async (page, response) =>
{
+ // CT: This needs to be a different exception type
await TestToken(page, "View Refresh Token", response?.RefreshToken ?? throw new ArgumentNullException(nameof(response.RefreshToken)));
});
}
@@ -302,6 +517,7 @@ public async Task AC06_Consents_Introspect(string dhBrandId, string drBrandId, s
{
await Test_Consents(dhBrandId, drBrandId, drSoftwareProductId, customerId, customerAccounts, $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC06_Consents_Introspect)}", async (page, response) =>
{
+ // CT: This needs to be a different exception type
var expected = new (string, string?)[]
{
("cdr_arrangement_id", response?.CDRArrangementID ?? throw new ArgumentNullException(nameof(response.CDRArrangementID))),
@@ -399,7 +615,7 @@ await ArrangeAsync(testName, async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -441,7 +657,7 @@ await ArrangeAsync(testName, async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -478,7 +694,7 @@ await ArrangeAsync(testName, async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -513,7 +729,7 @@ await iFrame.SelectOptionAsync(
await iFrame.ClickAsync("text=Try it out");
// Arrange - Set x-v
- await iFrame.FillAsync("[placeholder=\"x-v\"]", "1");
+ await iFrame.FillAsync("[placeholder=\"x-v\"]", "2");
// Act - Click Execute
await iFrame.ClickAsync("text=Execute");
@@ -545,7 +761,7 @@ await ArrangeAsync(testName, async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -582,7 +798,7 @@ await ArrangeAsync(testName, async (page) =>
{
await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
- dhClientId = await ClientRegistration_Create(page, dhBrandId, drBrandId, drSoftwareProductId)
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
?? throw new NullReferenceException(nameof(dhClientId));
cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
?? throw new NullReferenceException(nameof(cdrArrangement));
@@ -636,6 +852,73 @@ await CleanupAsync(async (page) =>
}
}
+ [Theory]
+ [InlineData(DH_BRANDID, DR_BRANDID, DR_SOFTWAREPRODUCTID, CUSTOMERID_BANKING, CUSTOMERACCOUNTS_BANKING)]
+ public async Task AC08_ConsumerDataSharing_Common_StatusGet(string dhBrandId, string drBrandId, string drSoftwareProductId, string customerId, string customerAccounts)
+ {
+ try
+ {
+ string testName = $"{nameof(US23863_MDR_E2ETests)} - {nameof(AC08_ConsumerDataSharing_Common_StatusGet)}";
+ string? dhClientId = null;
+ ConsentAndAuthorisationResponse? cdrArrangement = null;
+ await ArrangeAsync(testName, async (page) =>
+ {
+ await DataHolders_Discover(page, "ALL", "2", -1); // get all dh brands
+ await SSA_Get(page, "ALL", "3", drBrandId, drSoftwareProductId, "OK - SSA Generated");
+ dhClientId = await ClientRegistration_Create(page, dhBrandId)
+ ?? throw new NullReferenceException(nameof(dhClientId));
+ cdrArrangement = await NewConsentAndAuthorisationWithPAR(page, dhClientId, customerId, customerAccounts, dhBrandId)
+ ?? throw new NullReferenceException(nameof(cdrArrangement));
+ });
+
+ await TestAsync(testName, async (page) =>
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("span:text(\"Consumer Data Sharing\") + ul >> a:text(\"Common\")").ClickAsync();
+ await page.Locator("h2 >> text=Data Sharing - Common").TextContentAsync();
+
+ // Arrange - Get Swagger iframe
+ var iFrame = page.FrameByUrl($"{WEB_URL}/{SWAGGER_COMMON_IFRAME}") ?? throw new Exception($"IFrame not found - {SWAGGER_BANKING_IFRAME}");
+
+ // Wait for the CDR arrangement