Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(clearingHouse): Integrate the Tagus Release V2 Api #1220

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions docs/api/administration-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6030,18 +6030,18 @@ components:
ClearinghouseResponseData:
type: object
properties:
bpn:
type: string
status:
$ref: '#/components/schemas/ClearinghouseResponseStatus'
message:
validationMode:
type: string
nullable: true
validationUnits:
type: array
items:
$ref: '#/components/schemas/ValidationUnits'
additionalProperties: false
ClearinghouseResponseStatus:
enum:
- CONFIRM
- DECLINE
- VALID
- INVALID
- INCONCLUSIVE
type: string
CompanyAddressDetailData:
type: object
Expand Down Expand Up @@ -7990,6 +7990,17 @@ components:
items:
$ref: '#/components/schemas/ErrorDetails'
additionalProperties: false
ValidationUnits:
type: object
properties:
result:
$ref: '#/components/schemas/ClearinghouseResponseStatus'
type:
type: string
message:
type: string
nullable: true
additionalProperties: false
securitySchemes:
Bearer:
type: apiKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ public interface IRegistrationBusinessLogic
/// Processes the clearinghouse response
/// </summary>
/// <param name="data">the response data</param>
/// <param name="bpn">BusinessPartnerNumber of the responded data</param>
/// <param name="cancellationToken">cancellation token</param>
Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, CancellationToken cancellationToken);
Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, string bpn, CancellationToken cancellationToken);

/// <summary>
/// Processes the dim response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,18 +298,18 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn)
private ProcessStepTypeId CreateWalletStep() => _settings.UseDimWallet ? ProcessStepTypeId.CREATE_DIM_WALLET : ProcessStepTypeId.CREATE_IDENTITY_WALLET;

/// <inheritdoc />
public async Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, CancellationToken cancellationToken)
public async Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, string bpn, CancellationToken cancellationToken)
{
logger.LogInformation("Process SelfDescription called with the following data {Data}", data.ToString().Replace(Environment.NewLine, string.Empty));
var result = await portalRepositories.GetInstance<IApplicationRepository>().GetSubmittedApplicationIdsByBpn(data.BusinessPartnerNumber.ToUpper()).ToListAsync(cancellationToken).ConfigureAwait(false);
logger.LogInformation("Process SelfDescription called with the following data {Data} and bpn {Bpn}", data.ToString().Replace(Environment.NewLine, string.Empty), bpn.ToString().Replace(Environment.NewLine, string.Empty));
var result = await portalRepositories.GetInstance<IApplicationRepository>().GetSubmittedApplicationIdsByBpn(bpn.ToUpper()).ToListAsync(cancellationToken).ConfigureAwait(false);
if (!result.Any())
{
throw NotFoundException.Create(AdministrationRegistrationErrors.REGISTRATION_NOT_COMP_APP_BPN_STATUS_SUBMIT, new ErrorParameter[] { new("businessPartnerNumber", data.BusinessPartnerNumber) });
throw NotFoundException.Create(AdministrationRegistrationErrors.REGISTRATION_NOT_COMP_APP_BPN_STATUS_SUBMIT, new ErrorParameter[] { new("businessPartnerNumber", bpn) });
}

if (result.Count > 1)
{
throw ConflictException.Create(AdministrationRegistrationErrors.REGISTRATION_CONFLICT_APP_STATUS_STATUS_SUBMIT_FOUND_BPN, new ErrorParameter[] { new("businessPartnerNumber", data.BusinessPartnerNumber) });
throw ConflictException.Create(AdministrationRegistrationErrors.REGISTRATION_CONFLICT_APP_STATUS_STATUS_SUBMIT_FOUND_BPN, new ErrorParameter[] { new("businessPartnerNumber", bpn) });
}

await clearinghouseBusinessLogic.ProcessEndClearinghouse(result.Single(), data, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Web;
using Org.Eclipse.TractusX.Portal.Backend.IssuerComponent.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.Models;
Expand Down Expand Up @@ -183,7 +184,7 @@ public async Task<NoContentResult> DeclineApplication([FromRoute] Guid applicati
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
public async Task<NoContentResult> ProcessClearinghouseResponse([FromBody] ClearinghouseResponseData responseData, CancellationToken cancellationToken)
{
await logic.ProcessClearinghouseResponseAsync(responseData, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
await this.WithBpn(bpn => logic.ProcessClearinghouseResponseAsync(responseData, bpn, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None));
return NoContent();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.BusinessLogi
public class ClearinghouseBusinessLogic(
IPortalRepositories portalRepositories,
IClearinghouseService clearinghouseService,
ICustodianBusinessLogic custodianBusinessLogic,
IApplicationChecklistService checklistService,
IDateTimeProvider dateTimeProvider,
IOptions<ClearinghouseSettings> options)
Expand All @@ -43,37 +42,7 @@ public class ClearinghouseBusinessLogic(

public async Task<IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult> HandleClearinghouse(IApplicationChecklistService.WorkerChecklistProcessStepData context, CancellationToken cancellationToken)
{
var overwrite = context.ProcessStepTypeId switch
{
ProcessStepTypeId.START_OVERRIDE_CLEARING_HOUSE => true,
ProcessStepTypeId.START_CLEARING_HOUSE => false,
_ => throw new UnexpectedConditionException($"HandleClearingHouse called for unexpected processStepTypeId {context.ProcessStepTypeId}. Expected {ProcessStepTypeId.START_CLEARING_HOUSE} or {ProcessStepTypeId.START_OVERRIDE_CLEARING_HOUSE}")
};

string companyDid;
if (_settings.UseDimWallet)
{
var (exists, did) = await portalRepositories.GetInstance<IApplicationRepository>()
.GetDidForApplicationId(context.ApplicationId).ConfigureAwait(ConfigureAwaitOptions.None);
if (!exists || string.IsNullOrWhiteSpace(did))
{
throw new ConflictException($"Did must be set for Application {context.ApplicationId}");
}

companyDid = did;
}
else
{
var walletData = await custodianBusinessLogic.GetWalletByBpnAsync(context.ApplicationId, cancellationToken);
if (walletData == null || string.IsNullOrEmpty(walletData.Did))
{
throw new ConflictException($"Decentralized Identifier for application {context.ApplicationId} is not set");
}

companyDid = walletData.Did;
}

await TriggerCompanyDataPost(context.ApplicationId, companyDid, overwrite, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
await TriggerCompanyDataPost(context.ApplicationId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);

return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult(
ProcessStepStatusId.DONE,
Expand All @@ -84,7 +53,7 @@ public class ClearinghouseBusinessLogic(
null);
}

private async Task TriggerCompanyDataPost(Guid applicationId, string decentralizedIdentifier, bool overwrite, CancellationToken cancellationToken)
private async Task TriggerCompanyDataPost(Guid applicationId, CancellationToken cancellationToken)
{
var data = await portalRepositories.GetInstance<IApplicationRepository>()
.GetClearinghouseDataForApplicationId(applicationId).ConfigureAwait(ConfigureAwaitOptions.None);
Expand All @@ -98,16 +67,21 @@ private async Task TriggerCompanyDataPost(Guid applicationId, string decentraliz
throw new ConflictException($"CompanyApplication {applicationId} is not in status SUBMITTED");
}

if (string.IsNullOrWhiteSpace(data.ParticipantDetails.Bpn))
if (string.IsNullOrWhiteSpace(data.Bpn))
{
throw new ConflictException("BusinessPartnerNumber is null");
}

var headers = new List<KeyValuePair<string, string>>
{
new("Business-Partner-Number", data.Bpn)
}.AsEnumerable();

var transferData = new ClearinghouseTransferData(
data.ParticipantDetails,
new IdentityDetails(decentralizedIdentifier, data.UniqueIds),
_settings.CallbackUrl,
overwrite);
data.LegalEntity,
ValidationModes.LEGAL_NAME,
new CallBack(_settings.CallbackUrl, headers)
);

await clearinghouseService.TriggerCompanyDataPost(transferData, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}
Expand All @@ -123,19 +97,23 @@ public async Task ProcessEndClearinghouse(Guid applicationId, ClearinghouseRespo
processStepTypeIds: [ProcessStepTypeId.START_SELF_DESCRIPTION_LP])
.ConfigureAwait(ConfigureAwaitOptions.None);

var declined = data.Status == ClearinghouseResponseStatus.DECLINE;

var validData = data.ValidationUnits.FirstOrDefault(s => s.Status == ClearinghouseResponseStatus.VALID);
var isInvalid = validData == null;
checklistService.FinalizeChecklistEntryAndProcessSteps(
context,
null,
item =>
{
item.ApplicationChecklistEntryStatusId = declined
item.ApplicationChecklistEntryStatusId = isInvalid
? ApplicationChecklistEntryStatusId.FAILED
: ApplicationChecklistEntryStatusId.DONE;
item.Comment = data.Message;

// There is not "Message" param available in the response in case of VALID so, thats why saving ClearinghouseResponseStatus param into the Comments in case of VALID only.
item.Comment = isInvalid
? data.ValidationUnits.FirstOrDefault(s => s.Status != ClearinghouseResponseStatus.VALID)!.Message
: validData!.Status.ToString();
},
declined
isInvalid
? [ProcessStepTypeId.MANUAL_TRIGGER_OVERRIDE_CLEARING_HOUSE]
: [ProcessStepTypeId.START_SELF_DESCRIPTION_LP]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/********************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;

namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Extensions;

public static class UniqueIdentifiersExtension
{
public static string GetUniqueIdentifierValue(this UniqueIdentifierId uniqueIdentifierId) =>
uniqueIdentifierId switch
{
UniqueIdentifierId.COMMERCIAL_REG_NUMBER => "schema:taxID",
UniqueIdentifierId.VAT_ID => "schema:vatID",
UniqueIdentifierId.LEI_CODE => "schema:leiCode",
UniqueIdentifierId.VIES => "EUID",
UniqueIdentifierId.EORI => "gx:eori",
_ => throw new ArgumentOutOfRangeException(nameof(uniqueIdentifierId), uniqueIdentifierId, "Unique Identifier not found for SdFactory Conversion")
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Models;

public record ClearinghouseResponseData(
[property: JsonPropertyName("bpn")] string BusinessPartnerNumber,
[property: JsonPropertyName("status")] ClearinghouseResponseStatus Status,
[property: JsonPropertyName("message")] string? Message);
[property: JsonPropertyName("validationMode")] string ValidationMode,
[property: JsonPropertyName("validationUnits")] IEnumerable<ValidationUnits> ValidationUnits
);

public record ValidationUnits(
[property: JsonPropertyName("result")] ClearinghouseResponseStatus Status,
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("message")] string? Message
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,18 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Models;

public enum ClearinghouseResponseStatus
{
CONFIRM = 1,
/// <summary>
/// In case the identifier has been found in the trust sources of clearing house.
/// </summary>
VALID = 1,

DECLINE = 2
/// <summary>
/// In case the identifier format is not valid or the identifier was not found in the trust source of clearing house.
/// </summary>
INVALID = 2,

/// <summary>
/// In case the validation can't be performed, due to the unavailablity of the trust source of clearing house.
/// </summary>
INCONCLUSIVE = 3
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library.Models;

public record ClearinghouseTransferData(
[property: JsonPropertyName("participantDetails")] ParticipantDetails ParticipantDetails,
[property: JsonPropertyName("identityDetails")] IdentityDetails IdentityDetails,
[property: JsonPropertyName("callbackUrl")] string CallbackUrl,
[property: JsonPropertyName("exceptProfile")] bool ExceptProfile);
[property: JsonPropertyName("legalEntity")] LegalEntity LegalEntity,
[property: JsonPropertyName("validationMode")] string ValidationMode,
[property: JsonPropertyName("callback")] CallBack Callback
);

public record IdentityDetails(
[property: JsonPropertyName("did")] string Did,
[property: JsonPropertyName("uniqueIds")] IEnumerable<UniqueIdData> UniqueIds);
public record CallBack(
[property: JsonPropertyName("url")] string Url,
[property: JsonPropertyName("headers")] IEnumerable<KeyValuePair<string, string>> Headers
);
36 changes: 36 additions & 0 deletions src/externalsystems/Clearinghouse.Library/ValidationModes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/********************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

namespace Org.Eclipse.TractusX.Portal.Backend.Clearinghouse.Library;

public static class ValidationModes
{
/// <summary>
/// DEFAULT - validates whether the identifiers themselves exists, indepenedent of their relationship to the legal entity provided
/// </summary>
public const string IDENTIFIER = "IDENTIFIER";
/// <summary>
/// Validates whether the identifier is valid, and whether the name of the legal entity it is associated with matches the provided legal name
/// </summary>
public const string LEGAL_NAME = "LEGAL_NAME";
/// <summary>
/// Validates whether the identifier is valid, and whether the name of the legal entity, as well as the addresss it is associated with matches the provided ones.
/// </summary>
public const string LEGAL_NAME_AND_ADDRESS = "LEGAL_NAME_AND_ADDRESS";
}
22 changes: 22 additions & 0 deletions src/keycloak/Keycloak.Authentication/ControllerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
********************************************************************************/

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;

namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication;
Expand All @@ -30,6 +31,9 @@ public static class ControllerExtensions
public static T WithBearerToken<T>(this ControllerBase controller, Func<string, T> tokenConsumingFunction) =>
tokenConsumingFunction(controller.GetBearerToken());

public static T WithBpn<T>(this ControllerBase controller, Func<string, T> bpnConsumingFunction) =>
bpnConsumingFunction(controller.GetBpn());

private static string GetBearerToken(this ControllerBase controller)
{
var authorization = controller.Request.Headers.Authorization.FirstOrDefault();
Expand All @@ -47,4 +51,22 @@ private static string GetBearerToken(this ControllerBase controller)

return bearer;
}

private static string GetBpn(this ControllerBase controller)
{
var headers = controller.Request.Headers.FirstOrDefault(x => x.Key == "Business-Partner-Number");
if (headers.Equals(default(KeyValuePair<string, StringValues>)))
{
throw new ControllerArgumentException("Request does not contain Business-Partner-Number header",
nameof(headers.Key));
}

var bpn = headers.Value.FirstOrDefault();
if (string.IsNullOrWhiteSpace(bpn))
{
throw new ControllerArgumentException("Business-Partner-Number in header must not be empty", nameof(bpn));
}

return bpn;
}
}
Loading
Loading