From 8b4d375adc79e7a334ebb239d486505da943c500 Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Thu, 5 Oct 2023 12:39:17 +0200
Subject: [PATCH 01/26] fix(sonar): adjust sonarcloud workflow (#287)
adjust filter for dotnet tests to exclude endtoend tests
-----------
Reviewed-By: Evelyn Gurschler
---
.github/workflows/sonarcloud.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 87cb569978..bd35b608c6 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -70,6 +70,6 @@ jobs:
./.sonar/scanner/dotnet-sonarscanner begin /k:"${{ vars.SONAR_PROJECT_KEY }}" /o:"${{ vars.SONAR_ORGANIZATION }}" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=src/coverage.xml
dotnet build src
cd src
- dotnet-coverage collect 'dotnet test --filter FullyQualifiedName\!~Org.Eclipse.TractusX.Portal.Backend.EndToEnd.Tests --no-restore --verbosity normal' -s 'settings-coverage.xml' -f xml -o 'coverage.xml'
+ dotnet-coverage collect 'dotnet test --filter Category!=PortalHC&Category!=InterfaceHC&Category!=Portal&Category!=Registration --no-restore --verbosity normal' -s 'settings-coverage.xml' -f xml -o 'coverage.xml'
cd ..
./.sonar/scanner/dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
From f93caaa5c12e167dec3f6cbbe509a6dd50121a2b Mon Sep 17 00:00:00 2001
From: VPrasannaK94 <117351802+VPrasannaK94@users.noreply.github.com>
Date: Mon, 9 Oct 2023 13:58:15 +0530
Subject: [PATCH 02/26] feat(apps): added endpoint to fetch app active
documents (#266)
* feat(apps): added endpoint to fetch app active documents
---------
Ref: CPLP-3087
Co-authored-by: Norbert Truchsess
Reviewed-by: Norbert Truchsess
---
.../BusinessLogic/AppChangeBusinessLogic.cs | 33 +++++++++++-
.../BusinessLogic/AppsSettings.cs | 8 +++
.../BusinessLogic/IAppChangeBusinessLogic.cs | 6 +++
.../Controllers/AppChangeController.cs | 16 ++++++
.../Apps.Service/ViewModels/AppData.cs | 7 +++
.../Repositories/IOfferRepository.cs | 9 ++++
.../Repositories/OfferRepository.cs | 16 ++++++
.../AppChangeBusinessLogicTest.cs | 52 ++++++++++++++++++-
.../Controllers/AppChangeControllerTest.cs | 27 +++++++---
.../appsettings.IntegrationTests.json | 6 +++
.../OfferRepositoryTests.cs | 42 +++++++++++++++
.../Seeder/Data/documents.test.json | 24 ++++++++-
.../Data/offer_assigned_documents.test.json | 18 ++++++-
13 files changed, 253 insertions(+), 11 deletions(-)
diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs
index 0196787f57..0edf3588d4 100644
--- a/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs
+++ b/src/marketplace/Apps.Service/BusinessLogic/AppChangeBusinessLogic.cs
@@ -32,6 +32,7 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library;
using System.Text.Json;
@@ -48,6 +49,7 @@ public class AppChangeBusinessLogic : IAppChangeBusinessLogic
private readonly INotificationService _notificationService;
private readonly IProvisioningManager _provisioningManager;
private readonly IOfferService _offerService;
+ private readonly IIdentityService _identityService;
///
/// Constructor.
@@ -56,14 +58,22 @@ public class AppChangeBusinessLogic : IAppChangeBusinessLogic
/// the notification service
/// The provisioning manager
/// Settings for the app change bl
+ /// Identity
/// Offer Servicel
- public AppChangeBusinessLogic(IPortalRepositories portalRepositories, INotificationService notificationService, IProvisioningManager provisioningManager, IOfferService offerService, IOptions settings)
+ public AppChangeBusinessLogic(
+ IPortalRepositories portalRepositories,
+ INotificationService notificationService,
+ IProvisioningManager provisioningManager,
+ IOfferService offerService,
+ IOptions settings,
+ IIdentityService identityService)
{
_portalRepositories = portalRepositories;
_notificationService = notificationService;
_provisioningManager = provisioningManager;
_settings = settings.Value;
_offerService = offerService;
+ _identityService = identityService;
}
///
@@ -283,4 +293,25 @@ private async Task UpdateTenantUrlAsyncInternal(Guid offerId, Guid subscriptionI
await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
+
+ ///
+ public async Task GetActiveAppDocumentTypeDataAsync(Guid appId)
+ {
+ var appDocTypeData = await _portalRepositories.GetInstance()
+ .GetActiveOfferDocumentTypeDataOrderedAsync(appId, _identityService.IdentityData.CompanyId, OfferTypeId.APP, _settings.ActiveAppDocumentTypeIds)
+ .PreSortedGroupBy(result => result.DocumentTypeId)
+ .ToDictionaryAsync(
+ group => group.Key,
+ group => group.Select(result =>
+ new DocumentData(
+ result.DocumentId,
+ result.DocumentName)))
+ .ConfigureAwait(false);
+ return new ActiveAppDocumentData(
+ _settings.ActiveAppDocumentTypeIds.ToDictionary(
+ documentTypeId => documentTypeId,
+ documentTypeId => appDocTypeData.TryGetValue(documentTypeId, out var data)
+ ? data
+ : Enumerable.Empty()));
+ }
}
diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppsSettings.cs b/src/marketplace/Apps.Service/BusinessLogic/AppsSettings.cs
index 70cd4107f8..491b980ba4 100644
--- a/src/marketplace/Apps.Service/BusinessLogic/AppsSettings.cs
+++ b/src/marketplace/Apps.Service/BusinessLogic/AppsSettings.cs
@@ -196,6 +196,14 @@ public class AppsSettings
///
[Required(AllowEmptyStrings = false)]
public string ActivationPortalAddress { get; init; } = null!;
+
+ ///
+ /// Active Document Types
+ ///
+ [Required]
+ [EnumEnumeration]
+ [DistinctValues]
+ public IEnumerable ActiveAppDocumentTypeIds { get; set; } = null!;
}
///
diff --git a/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs
index 547bbfd418..dc703d49be 100644
--- a/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs
+++ b/src/marketplace/Apps.Service/BusinessLogic/IAppChangeBusinessLogic.cs
@@ -75,4 +75,10 @@ public interface IAppChangeBusinessLogic
/// the data to update the url
///
Task UpdateTenantUrlAsync(Guid offerId, Guid subscriptionId, UpdateTenantData data, Guid companyId);
+
+ ///
+ /// Gets the Active App Documents
+ ///
+ /// Id of the offer
+ Task GetActiveAppDocumentTypeDataAsync(Guid appId);
}
diff --git a/src/marketplace/Apps.Service/Controllers/AppChangeController.cs b/src/marketplace/Apps.Service/Controllers/AppChangeController.cs
index ec611382b4..872100ec8a 100644
--- a/src/marketplace/Apps.Service/Controllers/AppChangeController.cs
+++ b/src/marketplace/Apps.Service/Controllers/AppChangeController.cs
@@ -186,4 +186,20 @@ public async Task UpdateTenantUrl([FromRoute] Guid appId, [From
await this.WithCompanyId(companyId => _businessLogic.UpdateTenantUrlAsync(appId, subscriptionId, data, companyId)).ConfigureAwait(false);
return NoContent();
}
+
+ ///
+ /// Returns the Active App Documents
+ ///
+ /// Id of the app.
+ /// Example: GET /apps/appchange/{appId}/documents
+ /// Gets the Active Apps documents
+ /// If App does not exists
+ [HttpGet]
+ [Route("{appId}/documents")]
+ [Authorize(Roles = "edit_apps")]
+ [Authorize(Policy = PolicyTypes.ValidCompany)]
+ [ProducesResponseType(typeof(ActiveAppDocumentData), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
+ public async Task GetActiveAppDocuments([FromRoute] Guid appId) =>
+ await _businessLogic.GetActiveAppDocumentTypeDataAsync(appId).ConfigureAwait(false);
}
diff --git a/src/marketplace/Apps.Service/ViewModels/AppData.cs b/src/marketplace/Apps.Service/ViewModels/AppData.cs
index 7f03d03e9f..7ced554ff0 100644
--- a/src/marketplace/Apps.Service/ViewModels/AppData.cs
+++ b/src/marketplace/Apps.Service/ViewModels/AppData.cs
@@ -18,6 +18,7 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;
@@ -43,3 +44,9 @@ public record AppData(
string Price,
Guid LeadPictureId,
IEnumerable UseCases);
+
+///
+/// View model of an Active App Documents
+///
+/// Id of the App.
+public record ActiveAppDocumentData(IDictionary> Documents);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs
index 0ebf3efd4f..581f984dbb 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs
@@ -487,4 +487,13 @@ public interface IOfferRepository
///
///
Task<(bool IsSingleInstance, IEnumerable> ServiceAccountProfiles, string? OfferName)> GetServiceAccountProfileDataForSubscription(Guid subscriptionId);
+
+ ///
+ /// Gets the Active Offer DocumentType Data
+ ///
+ ///
+ ///
+ ///
+ ///
+ IAsyncEnumerable GetActiveOfferDocumentTypeDataOrderedAsync(Guid offerId, Guid userCompanyId, OfferTypeId offerTypeId, IEnumerable documentTypeIds);
}
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs
index 011207dd3c..8f60d6f1d9 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs
@@ -822,4 +822,20 @@ public void AttachAndModifyAppInstanceSetup(Guid appInstanceSetupId, Guid offerI
o.Offer.Name
))
.SingleOrDefaultAsync();
+
+ ///
+ public IAsyncEnumerable GetActiveOfferDocumentTypeDataOrderedAsync(Guid offerId, Guid userCompanyId, OfferTypeId offerTypeId, IEnumerable documentTypeIds) =>
+ _context.OfferAssignedDocuments
+ .Where(oad => oad.OfferId == offerId &&
+ oad.Offer!.OfferStatusId == OfferStatusId.ACTIVE &&
+ oad.Offer.OfferTypeId == offerTypeId &&
+ oad.Offer.ProviderCompanyId == userCompanyId &&
+ oad.Document!.DocumentStatusId != DocumentStatusId.INACTIVE &&
+ documentTypeIds.Contains(oad.Document!.DocumentTypeId))
+ .OrderBy(oad => oad.Document!.DocumentTypeId)
+ .Select(oad => new DocumentTypeData(
+ oad.Document!.DocumentTypeId,
+ oad.Document.Id,
+ oad.Document.DocumentName))
+ .ToAsyncEnumerable();
}
diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs
index d52fc9011b..e09d7ff831 100644
--- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs
+++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppChangeBusinessLogicTest.cs
@@ -22,7 +22,10 @@
using AutoFixture.AutoFakeItEasy;
using FakeItEasy;
using FluentAssertions;
+using Flurl.Util;
using Microsoft.Extensions.Options;
+using MimeKit.Encodings;
+using Org.BouncyCastle.Utilities.Collections;
using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
@@ -60,6 +63,7 @@ public class AppChangeBusinessLogicTest
private readonly IDocumentRepository _documentRepository;
private readonly INotificationService _notificationService;
private readonly IOfferService _offerService;
+ private readonly IIdentityService _identityService;
private readonly AppChangeBusinessLogic _sut;
public AppChangeBusinessLogicTest()
@@ -78,6 +82,7 @@ public AppChangeBusinessLogicTest()
_documentRepository = A.Fake();
_notificationService = A.Fake();
_offerService = A.Fake();
+ _identityService = A.Fake();
var settings = new AppsSettings
{
@@ -92,6 +97,13 @@ public AppChangeBusinessLogicTest()
CompanyAdminRoles = new[]
{
new UserRoleConfig(ClientId, new [] { "Company Admin" })
+ },
+ ActiveAppDocumentTypeIds = new[]
+ {
+ DocumentTypeId.APP_IMAGE,
+ DocumentTypeId.APP_TECHNICAL_INFORMATION,
+ DocumentTypeId.APP_CONTRACT,
+ DocumentTypeId.ADDITIONAL_DETAILS
}
};
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_notificationRepository);
@@ -99,7 +111,7 @@ public AppChangeBusinessLogicTest()
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_offerSubscriptionsRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRolesRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_documentRepository);
- _sut = new AppChangeBusinessLogic(_portalRepositories, _notificationService, _provisioningManager, _offerService, Options.Create(settings));
+ _sut = new AppChangeBusinessLogic(_portalRepositories, _notificationService, _provisioningManager, _offerService, Options.Create(settings), _identityService);
}
#region AddActiveAppUserRole
@@ -941,4 +953,42 @@ public async Task UpdateTenantUrlAsync_WithoutSubscriptionDetails_ThrowsConflict
}
#endregion
+
+ #region GetActiveAppDocumentTypeDataAsync
+
+ [Fact]
+ public async Task GetActiveAppDocumentTypeDataAsync_ReturnsExpected()
+ {
+ // Arrange
+ var appId = _fixture.Create();
+ var documentId1 = _fixture.Create();
+ var documentId2 = _fixture.Create();
+ var documentId3 = _fixture.Create();
+ var documentId4 = _fixture.Create();
+ var documentId5 = _fixture.Create();
+ var documentData = new[] {
+ new DocumentTypeData(DocumentTypeId.ADDITIONAL_DETAILS, documentId1, "TestDoc1"),
+ new DocumentTypeData(DocumentTypeId.ADDITIONAL_DETAILS, documentId2, "TestDoc2"),
+ new DocumentTypeData(DocumentTypeId.APP_IMAGE, documentId3, "TestDoc3"),
+ new DocumentTypeData(DocumentTypeId.APP_IMAGE, documentId4, "TestDoc4"),
+ new DocumentTypeData(DocumentTypeId.APP_TECHNICAL_INFORMATION, documentId5, "TestDoc5"),
+ }.ToAsyncEnumerable();
+
+ A.CallTo(() => _offerRepository.GetActiveOfferDocumentTypeDataOrderedAsync(A._, A._, OfferTypeId.APP, A>._))
+ .Returns(documentData);
+
+ // Act
+ var result = await _sut.GetActiveAppDocumentTypeDataAsync(appId).ConfigureAwait(false);
+
+ // Assert
+ A.CallTo(() => _offerRepository.GetActiveOfferDocumentTypeDataOrderedAsync(A._, A._, OfferTypeId.APP, A>._)).MustHaveHappened();
+ result.Documents.Should().NotBeNull().And.HaveCount(4).And.Satisfy(
+ x => x.Key == DocumentTypeId.APP_IMAGE && x.Value.SequenceEqual(new DocumentData[] { new(documentId3, "TestDoc3"), new(documentId4, "TestDoc4") }),
+ x => x.Key == DocumentTypeId.APP_TECHNICAL_INFORMATION && x.Value.SequenceEqual(new DocumentData[] { new(documentId5, "TestDoc5") }),
+ x => x.Key == DocumentTypeId.APP_CONTRACT && !x.Value.Any(),
+ x => x.Key == DocumentTypeId.ADDITIONAL_DETAILS && x.Value.SequenceEqual(new DocumentData[] { new(documentId1, "TestDoc1"), new(documentId2, "TestDoc2") })
+ );
+ }
+
+ #endregion
}
diff --git a/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs b/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs
index b79514e4ec..edee8c1288 100644
--- a/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs
+++ b/tests/marketplace/Apps.Service.Tests/Controllers/AppChangeControllerTest.cs
@@ -46,7 +46,7 @@ public AppChangeControllerTest()
{
_fixture = new Fixture();
_logic = A.Fake();
- this._controller = new AppChangeController(_logic);
+ _controller = new AppChangeController(_logic);
_controller.AddControllerContextWithClaim(IamUserId, _identity);
}
@@ -60,7 +60,7 @@ public async Task AddActiveAppUserRole_ReturnsExpectedCount()
.Returns(appRoleData);
//Act
- var result = await this._controller.AddActiveAppUserRole(appId, appUserRoles).ConfigureAwait(false);
+ var result = await _controller.AddActiveAppUserRole(appId, appUserRoles).ConfigureAwait(false);
foreach (var item in result)
{
//Assert
@@ -81,7 +81,7 @@ public async Task GetAppUpdateDescriptionsAsync_ReturnsExpected()
.Returns(offerDescriptionData);
//Act
- var result = await this._controller.GetAppUpdateDescriptionsAsync(appId).ConfigureAwait(false);
+ var result = await _controller.GetAppUpdateDescriptionsAsync(appId).ConfigureAwait(false);
//Assert
A.CallTo(() => _logic.GetAppUpdateDescriptionByIdAsync(A._, A._)).MustHaveHappened();
@@ -95,7 +95,7 @@ public async Task CreateOrUpdateAppDescriptionsAsync_ReturnsExpected()
var offerDescriptionData = _fixture.CreateMany(3);
//Act
- var result = await this._controller.CreateOrUpdateAppDescriptionsByIdAsync(appId, offerDescriptionData).ConfigureAwait(false);
+ var result = await _controller.CreateOrUpdateAppDescriptionsByIdAsync(appId, offerDescriptionData).ConfigureAwait(false);
//Assert
A.CallTo(() => _logic.CreateOrUpdateAppDescriptionByIdAsync(A._, A._, A>._)).MustHaveHappened();
@@ -112,7 +112,7 @@ public async Task UploadOfferAssignedAppLeadImageDocumentByIdAsync_ReturnsExpect
.ReturnsLazily(() => Task.CompletedTask);
// Act
- var result = await this._controller.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, file, CancellationToken.None).ConfigureAwait(false);
+ var result = await _controller.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, file, CancellationToken.None).ConfigureAwait(false);
// Assert
A.CallTo(() => _logic.UploadOfferAssignedAppLeadImageDocumentByIdAsync(appId, A>.That.Matches(x => x.Item1 == _identity.UserId && x.Item2 == _identity.CompanyId), file, CancellationToken.None)).MustHaveHappenedOnceExactly();
@@ -126,7 +126,7 @@ public async Task DeactivateApp_ReturnsNoContent()
var appId = _fixture.Create();
//Act
- var result = await this._controller.DeactivateApp(appId).ConfigureAwait(false);
+ var result = await _controller.DeactivateApp(appId).ConfigureAwait(false);
//Assert
A.CallTo(() => _logic.DeactivateOfferByAppIdAsync(appId)).MustHaveHappenedOnceExactly();
@@ -141,10 +141,23 @@ public async Task UpdateTenantUrl_ReturnsExpected()
var data = new UpdateTenantData("http://test.com");
//Act
- var result = await this._controller.UpdateTenantUrl(appId, subscriptionId, data).ConfigureAwait(false);
+ var result = await _controller.UpdateTenantUrl(appId, subscriptionId, data).ConfigureAwait(false);
//Assert
A.CallTo(() => _logic.UpdateTenantUrlAsync(appId, subscriptionId, data, _identity.CompanyId)).MustHaveHappened();
result.Should().BeOfType();
}
+
+ [Fact]
+ public async Task GetActiveAppDocuments_ReturnsExpected()
+ {
+ //Arrange
+ var appId = _fixture.Create();
+
+ //Act
+ await _controller.GetActiveAppDocuments(appId).ConfigureAwait(false);
+
+ //Assert
+ A.CallTo(() => _logic.GetActiveAppDocumentTypeDataAsync(appId)).MustHaveHappened();
+ }
}
diff --git a/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json b/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json
index e53d209bf6..0b7c41cb08 100644
--- a/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json
+++ b/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json
@@ -121,6 +121,12 @@
"APP_TECHNICAL_INFORMATION",
"CONFORMITY_APPROVAL_BUSINESS_APPS"
],
+ "ActiveAppDocumentTypeIds": [
+ "APP_IMAGE",
+ "APP_TECHNICAL_INFORMATION",
+ "APP_CONTRACT",
+ "ADDITIONAL_DETAILS"
+ ],
"ITAdminRoles": [
{
"ClientId": "Cl2-CX-Portal",
diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs
index 869dbb200d..b9e050cb81 100644
--- a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs
+++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs
@@ -1441,6 +1441,48 @@ public async Task GetServiceAccountProfileDataForSubscription_WithServiceAccount
#endregion
+ #region GetActiveOfferDocumentTypeData
+
+ [Fact]
+ public async Task GetActiveOfferDocumentTypeDataAsync_ReturnsExpectedResult()
+ {
+ // Arrange
+ var activeDocumentTypes = new[]{
+ DocumentTypeId.APP_IMAGE,
+ DocumentTypeId.APP_TECHNICAL_INFORMATION,
+ DocumentTypeId.APP_CONTRACT,
+ DocumentTypeId.ADDITIONAL_DETAILS
+ };
+ var sut = await CreateSut().ConfigureAwait(false);
+
+ // Act
+ var result = await sut.GetActiveOfferDocumentTypeDataOrderedAsync(
+ new("ac1cf001-7fbc-1f2f-817f-bce0572c0007"),
+ new("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"),
+ OfferTypeId.APP, activeDocumentTypes).ToListAsync().ConfigureAwait(false);
+
+ // Assert
+ result.Should().NotBeNull()
+ .And.BeInAscendingOrder(x => x.DocumentTypeId)
+ .And.HaveCount(4)
+ .And.Satisfy(
+ x => x.DocumentId == new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b2") &&
+ x.DocumentName == "Default_App_Image.png" &&
+ x.DocumentTypeId == DocumentTypeId.APP_IMAGE,
+ x => x.DocumentId == new Guid("0d68c68c-d689-474c-a3be-8493f99feab2") &&
+ x.DocumentName == "AdditionalServiceDetails.pdf" &&
+ x.DocumentTypeId == DocumentTypeId.ADDITIONAL_DETAILS,
+ x => x.DocumentId == new Guid("aaf53459-c36b-408e-a805-0b406ce9751e") &&
+ x.DocumentName == "AdditionalServiceDetails2.pdf" &&
+ x.DocumentTypeId == DocumentTypeId.ADDITIONAL_DETAILS,
+ x => x.DocumentId == new Guid("d9926bd9-bce0-4605-a083-7066ffe5147c") &&
+ x.DocumentName == "AdditionalTechnicalInfo.pdf" &&
+ x.DocumentTypeId == DocumentTypeId.APP_TECHNICAL_INFORMATION
+ );
+ }
+
+ #endregion
+
#region Setup
private async Task CreateSut()
diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/documents.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/documents.test.json
index 46b308638c..ff1c00bfdb 100644
--- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/documents.test.json
+++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/documents.test.json
@@ -10,6 +10,17 @@
"document_content": "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",
"document_status_id": 2
},
+ {
+ "id": "d9926bd9-bce0-4605-a083-7066ffe5147c",
+ "date_created": "2023-10-08T08:15:00.000000+00:00",
+ "document_name": "AdditionalTechnicalInfo.pdf",
+ "media_type_id": 6,
+ "document_type_id": 9,
+ "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993",
+ "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==",
+ "document_content": "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",
+ "document_status_id": 2
+ },
{
"id": "88793f9f-c5a4-4621-847b-3d47cd839283",
"date_created": "2023-01-04T16:05:14.294575+00:00",
@@ -97,5 +108,16 @@
"document_hash": "",
"document_content": "",
"document_status_id": 3
- }
+ },
+ {
+ "id": "aaf53459-c36b-408e-a805-0b406ce9751e",
+ "date_created": "2023-10-08T08:00:00.000000+00:00",
+ "document_name": "AdditionalServiceDetails2.pdf",
+ "media_type_id": 6,
+ "document_type_id": 5,
+ "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993",
+ "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==",
+ "document_content": "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",
+ "document_status_id": 2
+ }
]
\ No newline at end of file
diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_assigned_documents.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_assigned_documents.test.json
index 3d5b391b2d..816c23c287 100644
--- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_assigned_documents.test.json
+++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_assigned_documents.test.json
@@ -5,7 +5,7 @@
},
{
"offer_id": "ac1cf001-7fbc-1f2f-817f-bce0572c0007",
- "document_id": "e020787d-1e04-4c0b-9c06-bd1cd44724b2"
+ "document_id": "d9926bd9-bce0-4605-a083-7066ffe5147c"
},
{
"offer_id": "ac1cf001-7fbc-1f2f-817f-bce0000c0001",
@@ -14,5 +14,21 @@
{
"offer_id": "ac1cf001-7fbc-1f2f-817f-bce0000c0001",
"document_id": "3291cae8-3c7b-4862-8cec-93ea0dc8c61e"
+ },
+ {
+ "offer_id": "ac1cf001-7fbc-1f2f-817f-bce0572c0007",
+ "document_id": "0d68c68c-d689-474c-a3be-8493f99feab2"
+ },
+ {
+ "offer_id": "ac1cf001-7fbc-1f2f-817f-bce0572c0007",
+ "document_id": "e020787d-1e04-4c0b-9c06-bd1cd44724b2"
+ },
+ {
+ "offer_id": "ac1cf001-7fbc-1f2f-817f-bce0572c0007",
+ "document_id": "3291cae8-3c7b-4862-8cec-93ea0dc8c61e"
+ },
+ {
+ "offer_id": "ac1cf001-7fbc-1f2f-817f-bce0572c0007",
+ "document_id": "aaf53459-c36b-408e-a805-0b406ce9751e"
}
]
\ No newline at end of file
From 502bbaeeff9c2ad79edff95097c59c9e2292f850 Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Mon, 9 Oct 2023 12:11:10 +0200
Subject: [PATCH 03/26] fix (keycloak-library): set correct useAuthTrail (#291)
Reviewed-by: Norbert Truchsess
---
src/keycloak/Keycloak.Factory/IKeycloakFactory.cs | 2 +-
src/keycloak/Keycloak.Factory/KeycloakFactory.cs | 4 ++--
src/provisioning/Provisioning.Library/ProvisioningManager.cs | 4 ++--
src/provisioning/Provisioning.Library/ProvisioningSettings.cs | 2 --
.../Extensions/ClientManagerTests.cs | 2 +-
.../Provisioning.Library.Tests/ProvisioningManagerTests.cs | 2 +-
.../Provisioning.Library.Tests/UserManagerTests.cs | 2 +-
7 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/src/keycloak/Keycloak.Factory/IKeycloakFactory.cs b/src/keycloak/Keycloak.Factory/IKeycloakFactory.cs
index 30a9df8e9f..8dab46a100 100644
--- a/src/keycloak/Keycloak.Factory/IKeycloakFactory.cs
+++ b/src/keycloak/Keycloak.Factory/IKeycloakFactory.cs
@@ -26,5 +26,5 @@ public interface IKeycloakFactory
{
KeycloakClient CreateKeycloakClient(string instance);
- KeycloakClient CreateKeycloakClient(string instance, string clientId, string secret, bool useAuthTrail);
+ KeycloakClient CreateKeycloakClient(string instance, string clientId, string secret);
}
diff --git a/src/keycloak/Keycloak.Factory/KeycloakFactory.cs b/src/keycloak/Keycloak.Factory/KeycloakFactory.cs
index 3b35a33bff..71a2b98062 100644
--- a/src/keycloak/Keycloak.Factory/KeycloakFactory.cs
+++ b/src/keycloak/Keycloak.Factory/KeycloakFactory.cs
@@ -46,7 +46,7 @@ public KeycloakClient CreateKeycloakClient(string instance)
: KeycloakClient.CreateWithClientId(settings.ConnectionString, settings.ClientId, settings.ClientSecret, settings.UseAuthTrail, settings.AuthRealm);
}
- public KeycloakClient CreateKeycloakClient(string instance, string clientId, string secret, bool useAuthTrail)
+ public KeycloakClient CreateKeycloakClient(string instance, string clientId, string secret)
{
if (!_settings.Keys.Contains(instance, StringComparer.InvariantCultureIgnoreCase))
{
@@ -54,6 +54,6 @@ public KeycloakClient CreateKeycloakClient(string instance, string clientId, str
}
var settings = _settings.Single(x => x.Key.Equals(instance, StringComparison.InvariantCultureIgnoreCase)).Value;
- return KeycloakClient.CreateWithClientId(settings.ConnectionString, clientId, secret, useAuthTrail, settings.AuthRealm);
+ return KeycloakClient.CreateWithClientId(settings.ConnectionString, clientId, secret, settings.UseAuthTrail, settings.AuthRealm);
}
}
diff --git a/src/provisioning/Provisioning.Library/ProvisioningManager.cs b/src/provisioning/Provisioning.Library/ProvisioningManager.cs
index de9212fe4f..7731efc384 100644
--- a/src/provisioning/Provisioning.Library/ProvisioningManager.cs
+++ b/src/provisioning/Provisioning.Library/ProvisioningManager.cs
@@ -54,7 +54,7 @@ public async Task SetupSharedIdpAsync(string idpName, string organisationName, s
await CreateCentralIdentityProviderAsync(idpName, organisationName, _Settings.CentralIdentityProvider).ConfigureAwait(false);
var (clientId, secret) = await CreateSharedIdpServiceAccountAsync(idpName).ConfigureAwait(false);
- var sharedKeycloak = _Factory.CreateKeycloakClient("shared", clientId, secret, _Settings.UseAuthTrail);
+ var sharedKeycloak = _Factory.CreateKeycloakClient("shared", clientId, secret);
await CreateSharedRealmAsync(sharedKeycloak, idpName, organisationName, loginTheme).ConfigureAwait(false);
@@ -285,7 +285,7 @@ public async ValueTask UpdateCentralIdentityProviderDataSAMLAsync(IdentityProvid
private async Task GetSharedKeycloakClient(string realm)
{
var (clientId, secret) = await GetSharedIdpServiceAccountSecretAsync(realm).ConfigureAwait(false);
- return _Factory.CreateKeycloakClient("shared", clientId, secret, _Settings.UseAuthTrail);
+ return _Factory.CreateKeycloakClient("shared", clientId, secret);
}
private static T Clone(T cloneObject)
diff --git a/src/provisioning/Provisioning.Library/ProvisioningSettings.cs b/src/provisioning/Provisioning.Library/ProvisioningSettings.cs
index 94c1dd0b43..48cb445269 100644
--- a/src/provisioning/Provisioning.Library/ProvisioningSettings.cs
+++ b/src/provisioning/Provisioning.Library/ProvisioningSettings.cs
@@ -33,8 +33,6 @@ public partial class ProvisioningSettings
public string MappedBpnAttribute { get; set; }
public string UserNameMapperTemplate { get; set; }
-
- public bool UseAuthTrail { get; set; }
}
public static class ProvisioningSettingsExtension
diff --git a/tests/provisioning/Provisioning.Library.Tests/Extensions/ClientManagerTests.cs b/tests/provisioning/Provisioning.Library.Tests/Extensions/ClientManagerTests.cs
index dd75189ef7..2bd033d0e7 100644
--- a/tests/provisioning/Provisioning.Library.Tests/Extensions/ClientManagerTests.cs
+++ b/tests/provisioning/Provisioning.Library.Tests/Extensions/ClientManagerTests.cs
@@ -54,7 +54,7 @@ public ClientManagerTests()
.Returns(new KeycloakClient(CentralUrl, "test", "test", "test", false));
A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared"))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
- A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._, A._))
+ A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
var settings = new ProvisioningSettings
{
diff --git a/tests/provisioning/Provisioning.Library.Tests/ProvisioningManagerTests.cs b/tests/provisioning/Provisioning.Library.Tests/ProvisioningManagerTests.cs
index 709da2d292..161757eb78 100644
--- a/tests/provisioning/Provisioning.Library.Tests/ProvisioningManagerTests.cs
+++ b/tests/provisioning/Provisioning.Library.Tests/ProvisioningManagerTests.cs
@@ -59,7 +59,7 @@ public ProvisioningManagerTests()
.Returns(new KeycloakClient(CentralUrl, "test", "test", "test", false));
A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared"))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
- A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._, A._))
+ A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
var settings = new ProvisioningSettings
{
diff --git a/tests/provisioning/Provisioning.Library.Tests/UserManagerTests.cs b/tests/provisioning/Provisioning.Library.Tests/UserManagerTests.cs
index ec1cadf73b..22112280ac 100644
--- a/tests/provisioning/Provisioning.Library.Tests/UserManagerTests.cs
+++ b/tests/provisioning/Provisioning.Library.Tests/UserManagerTests.cs
@@ -58,7 +58,7 @@ public UserManagerTests()
.Returns(new KeycloakClient(CentralUrl, "test", "test", "test", false));
A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared"))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
- A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._, A._))
+ A.CallTo(() => keycloakFactory.CreateKeycloakClient("shared", A._, A._))
.Returns(new KeycloakClient(SharedUrl, "test", "test", "test", false));
var settings = new ProvisioningSettings
{
From 8caaca3e8c84f50c8d7a4ace230644ccf751034e Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 10:16:26 +0200
Subject: [PATCH 04/26] chore: adjust process worker workflow (#276)
* fix(n2n): create process steps on submit
* feat(n2n): move submit endpoint to registration service
* chore: adjust copyright header
---------
Refs: CPLP-3197
Co-authored-by: Norbert Truchsess
Reviewed-by: Norbert Truchsess
---
.../BusinessLogic/INetworkBusinessLogic.cs | 3 -
.../BusinessLogic/NetworkBusinessLogic.cs | 70 ----
.../Controllers/NetworkController.cs | 29 +-
.../Models/PartnerSubmitData.cs | 9 -
.../Repositories/INetworkRepository.cs | 2 +-
.../Repositories/NetworkRepository.cs | 6 +-
.../ApplicationChecklistExtensions.cs | 12 +-
.../BusinessLogic/INetworkBusinessLogic.cs | 27 ++
.../BusinessLogic/NetworkBusinessLogic.cs | 111 +++++
.../Controllers/NetworkController.cs | 68 ++++
.../Model/PartnerSubmitData.cs | 40 ++
.../Registration.Service.csproj | 145 +++----
.../NetworkBusinessLogicTests.cs | 246 +-----------
.../Controllers/NetworkControllerTests.cs | 16 -
.../NetworkRepositoryTests.cs | 17 +-
.../NetworkBusinessLogicTests.cs | 379 ++++++++++++++++++
.../Controller/NetworkControllerTests.cs | 57 +++
.../Registration.Service.Tests.csproj | 111 ++---
18 files changed, 829 insertions(+), 519 deletions(-)
delete mode 100644 src/administration/Administration.Service/Models/PartnerSubmitData.cs
create mode 100644 src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
create mode 100644 src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
create mode 100644 src/registration/Registration.Service/Controllers/NetworkController.cs
create mode 100644 src/registration/Registration.Service/Model/PartnerSubmitData.cs
create mode 100644 tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
create mode 100644 tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
diff --git a/src/administration/Administration.Service/BusinessLogic/INetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/INetworkBusinessLogic.cs
index 180e8df421..5446b619d4 100644
--- a/src/administration/Administration.Service/BusinessLogic/INetworkBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/INetworkBusinessLogic.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -26,7 +25,5 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLog
public interface INetworkBusinessLogic
{
Task HandlePartnerRegistration(PartnerRegistrationData data);
-
Task RetriggerProcessStep(Guid externalId, ProcessStepTypeId processStepTypeId);
- Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken);
}
diff --git a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
index 84e8f3c8cd..a181587251 100644
--- a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -33,7 +32,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Processes.NetworkRegistration.Library;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
-using System.Collections.Immutable;
using System.Text.RegularExpressions;
namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic;
@@ -307,72 +305,4 @@ private static void ValidateUsers(UserDetailData user)
throw new ControllerArgumentException("Lastname does not match expected format");
}
}
-
- public async Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken)
- {
- var companyId = _identityService.IdentityData.CompanyId;
- var userId = _identityService.IdentityData.UserId;
- var userRoleIds = await _portalRepositories.GetInstance()
- .GetUserRoleIdsUntrackedAsync(_settings.InitialRoles)
- .ToListAsync(cancellationToken)
- .ConfigureAwait(false);
- var data = await _portalRepositories.GetInstance()
- .GetSubmitData(companyId, userId, userRoleIds)
- .ConfigureAwait(false);
- if (!data.Exists)
- {
- throw new NotFoundException($"Company {companyId} not found");
- }
-
- if (!data.IsUserInRole)
- {
- throw new ForbiddenException($"User must be in role {string.Join(",", _settings.InitialRoles.SelectMany(x => x.UserRoleNames))}");
- }
-
- if (data.CompanyApplications.Count() != 1)
- {
- throw new ConflictException($"Company {companyId} has no or more than one application");
- }
-
- if (data.ProcessId == null)
- {
- throw new ConflictException("There must be an process");
- }
-
- var companyApplication = data.CompanyApplications.Single();
- if (companyApplication.CompanyApplicationStatusId != CompanyApplicationStatusId.CREATED)
- {
- throw new ConflictException($"Application {companyApplication.CompanyApplicationId} is not in state CREATED");
- }
-
- submitData.Agreements.Where(x => x.ConsentStatusId != ConsentStatusId.ACTIVE).IfAny(inactive =>
- throw new ControllerArgumentException($"All agreements must be agreed to. Agreements that are not active: {string.Join(",", inactive.Select(x => x.AgreementId))}", nameof(submitData.Agreements)));
-
- data.CompanyRoleAgreementIds
- .ExceptBy(submitData.CompanyRoles, x => x.CompanyRoleId)
- .IfAny(missing =>
- throw new ControllerArgumentException($"CompanyRoles {string.Join(",", missing.Select(x => x.CompanyRoleId))} are missing", nameof(submitData.CompanyRoles)));
-
- var requiredAgreementIds = data.CompanyRoleAgreementIds
- .SelectMany(x => x.AgreementIds)
- .Distinct().ToImmutableList();
-
- requiredAgreementIds.Except(submitData.Agreements.Where(x => x.ConsentStatusId == ConsentStatusId.ACTIVE).Select(x => x.AgreementId))
- .IfAny(missing =>
- throw new ControllerArgumentException($"All Agreements for the company roles must be agreed to, missing agreementIds: {string.Join(",", missing)}", nameof(submitData.Agreements)));
-
- _portalRepositories.GetInstance()
- .CreateConsents(requiredAgreementIds.Select(agreementId => (agreementId, companyId, userId, ConsentStatusId.ACTIVE)));
-
- var processId = _portalRepositories.GetInstance().CreateProcess(ProcessTypeId.APPLICATION_CHECKLIST).Id;
- _portalRepositories.GetInstance().AttachAndModifyCompanyApplication(companyApplication.CompanyApplicationId,
- ca =>
- {
- ca.ApplicationStatusId = CompanyApplicationStatusId.SUBMITTED;
- ca.ChecklistProcessId = processId;
- });
- _portalRepositories.GetInstance().CreateProcessStepRange(Enumerable.Repeat(new ValueTuple(ProcessStepTypeId.TRIGGER_CALLBACK_OSP_SUBMITTED, ProcessStepStatusId.TODO, data.ProcessId.Value), 1));
-
- await _portalRepositories.SaveAsync().ConfigureAwait(false);
- }
}
diff --git a/src/administration/Administration.Service/Controllers/NetworkController.cs b/src/administration/Administration.Service/Controllers/NetworkController.cs
index 0c9d0fd64d..c0421c6977 100644
--- a/src/administration/Administration.Service/Controllers/NetworkController.cs
+++ b/src/administration/Administration.Service/Controllers/NetworkController.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -70,7 +69,7 @@ public async Task PartnerRegister([FromBody] PartnerRegistrationData d
/// Empty response on success.
/// No registration found for the externalId.
[HttpPost]
- //[Authorize(Roles = "tbd")]
+ [Authorize(Roles = "approve_new_partner")]
[Authorize(Policy = PolicyTypes.CompanyUser)]
[Route("{externalId}/retrigger-synchronize-users")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -82,28 +81,6 @@ public async Task RetriggerSynchronizeUser([FromRoute] Guid ext
return NoContent();
}
- ///
- /// Submits the application
- ///
- /// The agreements for the companyRoles
- /// Cancellation Token
- /// NoContent
- /// Example: POST: api/administration/registration/network/partnerRegistration/submit
- /// Empty response on success.
- /// No registration found for the externalId.
- [HttpPost]
- [Authorize(Roles = "submit_registration")]
- [Authorize(Policy = PolicyTypes.CompanyUser)]
- [Route("partnerRegistration/submit")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
- [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
- public async Task Submit([FromBody] PartnerSubmitData data, CancellationToken cancellationToken)
- {
- await _logic.Submit(data, cancellationToken).ConfigureAwait(false);
- return NoContent();
- }
-
///
/// Retriggers the last failed step
///
@@ -113,7 +90,7 @@ public async Task Submit([FromBody] PartnerSubmitData data, Can
/// Empty response on success.
/// No registration found for the externalId.
[HttpPost]
- //[Authorize(Roles = "tbd")]
+ [Authorize(Roles = "approve_new_partner")]
[Authorize(Policy = PolicyTypes.CompanyUser)]
[Route("{externalId}/retrigger-callback-osp-approve")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -155,7 +132,7 @@ public async Task RetriggerCallbackOspDecline([FromRoute] Guid
/// Empty response on success.
/// No registration found for the externalId.
[HttpPost]
- //[Authorize(Roles = "tbd")]
+ [Authorize(Roles = "approve_new_partner")]
[Authorize(Policy = PolicyTypes.CompanyUser)]
[Route("{externalId}/retrigger-callback-osp-submitted")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
diff --git a/src/administration/Administration.Service/Models/PartnerSubmitData.cs b/src/administration/Administration.Service/Models/PartnerSubmitData.cs
deleted file mode 100644
index 6c91c94cb3..0000000000
--- a/src/administration/Administration.Service/Models/PartnerSubmitData.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
-
-namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
-
-public record PartnerSubmitData
-(
- IEnumerable CompanyRoles,
- IEnumerable Agreements
-);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
index 12c03b8306..90a6f94073 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
@@ -30,6 +30,6 @@ public interface INetworkRepository
Task CheckExternalIdExists(Guid externalId, Guid onboardingServiceProviderId);
Task GetNetworkRegistrationDataForProcessIdAsync(Guid processId);
Task<(bool RegistrationIdExists, VerifyProcessData processData)> IsValidRegistration(Guid externalId, IEnumerable processStepTypeIds);
- Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, bool IsUserInRole, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId, Guid userId, IEnumerable roleIds);
+ Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId);
Task<(OspDetails? OspDetails, Guid? ExternalId, string? Bpn, Guid ApplicationId, IEnumerable Comments)> GetCallbackData(Guid networkRegistrationId, ProcessStepTypeId processStepTypeId);
}
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
index 5ab7108cdd..45cf0f094e 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
@@ -66,11 +66,11 @@ public Task GetNetworkRegistrationDataForProcessIdAsync(Guid processId) =>
))
.SingleOrDefaultAsync();
- public Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, bool IsUserInRole, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId, Guid userId, IEnumerable roleIds) =>
+ public Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId) =>
_context.Companies
.AsSplitQuery()
.Where(x => x.Id == companyId)
- .Select(x => new ValueTuple, bool, IEnumerable<(CompanyRoleId, IEnumerable)>, Guid?>(
+ .Select(x => new ValueTuple, IEnumerable<(CompanyRoleId, IEnumerable)>, Guid?>(
true,
x.CompanyApplications
.Where(ca => ca.CompanyApplicationTypeId == CompanyApplicationTypeId.EXTERNAL)
@@ -78,8 +78,6 @@ public Task GetNetworkRegistrationDataForProcessIdAsync(Guid processId) =>
ca.Id,
ca.ApplicationStatusId,
ca.OnboardingServiceProvider!.OnboardingServiceProviderDetail!.CallbackUrl)),
- x.Identities
- .Any(i => i.Id == userId && i.IdentityAssignedRoles.Any(roles => roleIds.Any(r => r == roles.UserRoleId))),
x.CompanyAssignedRoles.Select(assigned => new ValueTuple>(
assigned.CompanyRoleId,
assigned.CompanyRole!.AgreementAssignedCompanyRoles.Select(a => a.AgreementId))),
diff --git a/src/processes/ApplicationChecklist.Config/ApplicationChecklistExtensions.cs b/src/processes/ApplicationChecklist.Config/ApplicationChecklistExtensions.cs
index 1b25a887cb..f0c60d92c9 100644
--- a/src/processes/ApplicationChecklist.Config/ApplicationChecklistExtensions.cs
+++ b/src/processes/ApplicationChecklist.Config/ApplicationChecklistExtensions.cs
@@ -31,20 +31,16 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Con
public static class ApplicationChecklistExtensions
{
- public static IServiceCollection AddApplicationChecklist(this IServiceCollection services, IConfigurationSection section)
- {
- return services
+ public static IServiceCollection AddApplicationChecklist(this IServiceCollection services, IConfigurationSection section) =>
+ services
.AddTransient()
.AddTransient()
.AddBpdmService(section.GetSection("Bpdm"))
.AddCustodianService(section.GetSection("Custodian"))
.AddClearinghouseService(section.GetSection("Clearinghouse"))
.AddSdFactoryService(section.GetSection("SdFactory"));
- }
- public static IServiceCollection AddApplicationChecklistCreation(this IServiceCollection services)
- {
- return services
+ public static IServiceCollection AddApplicationChecklistCreation(this IServiceCollection services) =>
+ services
.AddTransient();
- }
}
diff --git a/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
new file mode 100644
index 0000000000..b6092212c6
--- /dev/null
+++ b/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
@@ -0,0 +1,27 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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.Registration.Service.Model;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic;
+
+public interface INetworkBusinessLogic
+{
+ Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken);
+}
diff --git a/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
new file mode 100644
index 0000000000..24a7b61e70
--- /dev/null
+++ b/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
@@ -0,0 +1,111 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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.Framework.ErrorHandling;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
+using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Library;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Model;
+using System.Collections.Immutable;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic;
+
+public class NetworkBusinessLogic : INetworkBusinessLogic
+{
+ private readonly IPortalRepositories _portalRepositories;
+ private readonly IIdentityService _identityService;
+ private readonly IApplicationChecklistCreationService _checklistService;
+
+ public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentityService identityService, IApplicationChecklistCreationService checklistService)
+ {
+ _portalRepositories = portalRepositories;
+ _identityService = identityService;
+ _checklistService = checklistService;
+ }
+
+ public async Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken)
+ {
+ var companyId = _identityService.IdentityData.CompanyId;
+ var userId = _identityService.IdentityData.UserId;
+ var data = await _portalRepositories.GetInstance()
+ .GetSubmitData(companyId)
+ .ConfigureAwait(false);
+ if (!data.Exists)
+ {
+ throw new NotFoundException($"Company {companyId} not found");
+ }
+
+ if (data.CompanyApplications.Count() != 1)
+ {
+ throw new ConflictException($"Company {companyId} has no or more than one application");
+ }
+
+ if (data.ProcessId == null)
+ {
+ throw new ConflictException("There must be an process");
+ }
+
+ var companyApplication = data.CompanyApplications.Single();
+ if (companyApplication.CompanyApplicationStatusId != CompanyApplicationStatusId.CREATED)
+ {
+ throw new ConflictException($"Application {companyApplication.CompanyApplicationId} is not in state CREATED");
+ }
+
+ submitData.Agreements.Where(x => x.ConsentStatusId != ConsentStatusId.ACTIVE).IfAny(inactive =>
+ throw new ControllerArgumentException($"All agreements must be agreed to. Agreements that are not active: {string.Join(",", inactive.Select(x => x.AgreementId))}", nameof(submitData.Agreements)));
+
+ data.CompanyRoleAgreementIds
+ .ExceptBy(submitData.CompanyRoles, x => x.CompanyRoleId)
+ .IfAny(missing =>
+ throw new ControllerArgumentException($"CompanyRoles {string.Join(",", missing.Select(x => x.CompanyRoleId))} are missing", nameof(submitData.CompanyRoles)));
+
+ var requiredAgreementIds = data.CompanyRoleAgreementIds
+ .SelectMany(x => x.AgreementIds)
+ .Distinct().ToImmutableList();
+
+ requiredAgreementIds.Except(submitData.Agreements.Where(x => x.ConsentStatusId == ConsentStatusId.ACTIVE).Select(x => x.AgreementId))
+ .IfAny(missing =>
+ throw new ControllerArgumentException($"All Agreements for the company roles must be agreed to, missing agreementIds: {string.Join(",", missing)}", nameof(submitData.Agreements)));
+
+ _portalRepositories.GetInstance()
+ .CreateConsents(requiredAgreementIds.Select(agreementId => (agreementId, companyId, userId, ConsentStatusId.ACTIVE)));
+
+ var entries = await _checklistService.CreateInitialChecklistAsync(companyApplication.CompanyApplicationId);
+ var processId = _portalRepositories.GetInstance().CreateProcess(ProcessTypeId.APPLICATION_CHECKLIST).Id;
+ _portalRepositories.GetInstance()
+ .CreateProcessStepRange(
+ _checklistService
+ .GetInitialProcessStepTypeIds(entries)
+ .Select(processStepTypeId => (processStepTypeId, ProcessStepStatusId.TODO, processId))
+ // in addition to the initial steps of new process application_checklist also create next step for process network_registration
+ .Append((ProcessStepTypeId.TRIGGER_CALLBACK_OSP_SUBMITTED, ProcessStepStatusId.TODO, data.ProcessId.Value)));
+
+ _portalRepositories.GetInstance().AttachAndModifyCompanyApplication(companyApplication.CompanyApplicationId,
+ ca =>
+ {
+ ca.ApplicationStatusId = CompanyApplicationStatusId.SUBMITTED;
+ ca.ChecklistProcessId = processId;
+ });
+
+ await _portalRepositories.SaveAsync().ConfigureAwait(false);
+ }
+}
diff --git a/src/registration/Registration.Service/Controllers/NetworkController.cs b/src/registration/Registration.Service/Controllers/NetworkController.cs
new file mode 100644
index 0000000000..cffb43c33f
--- /dev/null
+++ b/src/registration/Registration.Service/Controllers/NetworkController.cs
@@ -0,0 +1,68 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 BMW Group AG
+ * Copyright (c) 2021, 2023 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 Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Model;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Controllers;
+
+[ApiController]
+[Route("api/registration/[controller]")]
+[Produces("application/json")]
+[Consumes("application/json")]
+public class NetworkController : ControllerBase
+{
+ private readonly INetworkBusinessLogic _logic;
+
+ ///
+ /// Creates a new instance of
+ ///
+ /// The business logic for the registration
+ public NetworkController(INetworkBusinessLogic logic)
+ {
+ _logic = logic;
+ }
+
+ ///
+ /// Submits the application
+ ///
+ /// The agreements for the companyRoles
+ /// Cancellation Token
+ /// NoContent
+ /// Example: POST: api/administration/registration/network/partnerRegistration/submit
+ /// Empty response on success.
+ /// No registration found for the externalId.
+ [HttpPost]
+ [Authorize(Roles = "submit_registration")]
+ [Authorize(Policy = PolicyTypes.CompanyUser)]
+ [Route("partnerRegistration/submit")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
+ public async Task Submit([FromBody] PartnerSubmitData data, CancellationToken cancellationToken)
+ {
+ await _logic.Submit(data, cancellationToken).ConfigureAwait(false);
+ return NoContent();
+ }
+}
diff --git a/src/registration/Registration.Service/Model/PartnerSubmitData.cs b/src/registration/Registration.Service/Model/PartnerSubmitData.cs
new file mode 100644
index 0000000000..b8d10b5d27
--- /dev/null
+++ b/src/registration/Registration.Service/Model/PartnerSubmitData.cs
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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;
+using System.Text.Json.Serialization;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Model;
+
+public record PartnerSubmitData
+(
+ IEnumerable CompanyRoles,
+ IEnumerable Agreements
+);
+
+///
+///
+///
+///
+///
+///
+public record AgreementConsentData(
+ Guid AgreementId,
+ [property: JsonPropertyName("consentStatus")] ConsentStatusId ConsentStatusId
+);
diff --git a/src/registration/Registration.Service/Registration.Service.csproj b/src/registration/Registration.Service/Registration.Service.csproj
index 6e83605fc3..8d1158a692 100644
--- a/src/registration/Registration.Service/Registration.Service.csproj
+++ b/src/registration/Registration.Service/Registration.Service.csproj
@@ -1,72 +1,73 @@
-
-
-
-
-
- Org.Eclipse.TractusX.Portal.Backend.Registration.Service
- net7.0
- enable
- enable
- 1557fa58-6743-480f-8f98-155d33f89c0a
- Linux
- ..\..\..
- True
- CS1591
- Org.Eclipse.TractusX.Portal.Backend.Registration.Service
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
-
-
-
- Program.cs
-
-
-
-
+
+
+
+
+
+ Org.Eclipse.TractusX.Portal.Backend.Registration.Service
+ Org.Eclipse.TractusX.Portal.Backend.Registration.Service
+ net7.0
+ enable
+ enable
+ 1557fa58-6743-480f-8f98-155d33f89c0a
+ Linux
+ ..\..\..
+ True
+ CS1591
+ Org.Eclipse.TractusX.Portal.Backend.Registration.Service
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+ Program.cs
+
+
+
+
diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
index 0dffdb26cb..d449c14eed 100644
--- a/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
+++ b/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -64,8 +63,6 @@ public class NetworkBusinessLogicTests
private readonly IIdentityProviderRepository _identityProviderRepository;
private readonly ICountryRepository _countryRepository;
private readonly NetworkBusinessLogic _sut;
- private readonly PartnerRegistrationSettings _settings;
- private readonly IConsentRepository _consentRepository;
public NetworkBusinessLogicTests()
{
@@ -87,19 +84,17 @@ public NetworkBusinessLogicTests()
_networkRepository = A.Fake();
_identityProviderRepository = A.Fake();
_countryRepository = A.Fake();
- _consentRepository = A.Fake();
- _settings = new PartnerRegistrationSettings
+ var settings = new PartnerRegistrationSettings
{
InitialRoles = new[] { new UserRoleConfig("cl1", new[] { "Company Admin" }) }
};
var options = A.Fake>();
- A.CallTo(() => options.Value).Returns(_settings);
+ A.CallTo(() => options.Value).Returns(settings);
A.CallTo(() => _identityService.IdentityData).Returns(_identity);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository);
- A.CallTo(() => _portalRepositories.GetInstance()).Returns(_consentRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRolesRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_processStepRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_applicationRepository);
@@ -382,9 +377,6 @@ public async Task HandlePartnerRegistration_WithUserCreationThrowsException_Thro
public async Task HandlePartnerRegistration_WithSingleIdpWithoutAlias_ThrowsServiceException()
{
// Arrange
- var newCompanyId = Guid.NewGuid();
- var processId = Guid.NewGuid();
-
var data = new PartnerRegistrationData(
Guid.NewGuid(),
"Test N2N",
@@ -700,240 +692,6 @@ public async Task RetriggerSynchronizeUser_CallsExpected()
#endregion
- #region Submit
-
- [Fact]
- public async Task Submit_WithNotExistingSubmitData_ThrowsNotFoundException()
- {
- // Arrange
- var data = _fixture.Create();
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns(new ValueTuple>, bool, IEnumerable>>, Guid?>());
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"Company {_identity.CompanyId} not found");
- }
-
- [Fact]
- public async Task Submit_WithUserNotInRole_ThrowsForbiddenException()
- {
- // Arrange
- var data = _fixture.Create();
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Empty>(), false, Enumerable.Empty>>(), null));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"User must be in role {string.Join(",", _settings.InitialRoles.SelectMany(x => x.UserRoleNames))}");
- }
-
- [Fact]
- public async Task Submit_WithoutCompanyApplications_ThrowsConflictException()
- {
- // Arrange
- var data = _fixture.Create();
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Empty>(), true, Enumerable.Empty>>(), null));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"Company {_identity.CompanyId} has no or more than one application");
- }
-
- [Fact]
- public async Task Submit_WithMultipleCompanyApplications_ThrowsConflictException()
- {
- // Arrange
- var data = _fixture.Create();
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, _fixture.CreateMany>(2), true, Enumerable.Empty>>(), null));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"Company {_identity.CompanyId} has no or more than one application");
- }
-
- [Fact]
- public async Task Submit_WithWrongApplicationStatus_ThrowsConflictException()
- {
- // Arrange
- var applicationId = Guid.NewGuid();
- var data = _fixture.Create();
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.VERIFY, null), 1), true, Enumerable.Empty>>(), Guid.NewGuid()));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"Application {applicationId} is not in state CREATED");
- }
-
- [Fact]
- public async Task Submit_WithOneMissingAgreement_ThrowsConflictException()
- {
- // Arrange
- var applicationId = Guid.NewGuid();
- var agreementId = Guid.NewGuid();
- var notExistingAgreementId = Guid.NewGuid();
- var data = new PartnerSubmitData(
- new[] { CompanyRoleId.APP_PROVIDER },
- new[] { new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE) });
- var companyRoleIds = new ValueTuple>[]
- {
- (CompanyRoleId.APP_PROVIDER, new [] {agreementId, notExistingAgreementId})
- };
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), true, companyRoleIds, Guid.NewGuid()));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"All Agreements for the company roles must be agreed to, missing agreementIds: {notExistingAgreementId} (Parameter 'Agreements')");
- }
-
- [Fact]
- public async Task Submit_WithOneInactiveAgreement_ThrowsConflictException()
- {
- // Arrange
- var applicationId = Guid.NewGuid();
- var agreementId = Guid.NewGuid();
- var inactiveAgreementId = Guid.NewGuid();
- var data = new PartnerSubmitData(
- new[] { CompanyRoleId.APP_PROVIDER },
- new[]
- {
- new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
- new AgreementConsentData(inactiveAgreementId, ConsentStatusId.INACTIVE),
- });
- var companyRoleIds = new ValueTuple>[]
- {
- (CompanyRoleId.APP_PROVIDER, new [] {agreementId, inactiveAgreementId})
- };
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), true, companyRoleIds, Guid.NewGuid()));
-
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be($"All agreements must be agreed to. Agreements that are not active: {inactiveAgreementId} (Parameter 'Agreements')");
- }
-
- [Fact]
- public async Task Submit_WithoutProcessId_ThrowsConflictException()
- {
- // Arrange
- var applicationId = Guid.NewGuid();
- var agreementId = Guid.NewGuid();
- var agreementId1 = Guid.NewGuid();
- var data = new PartnerSubmitData(
- new[] { CompanyRoleId.APP_PROVIDER },
- new[]
- {
- new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
- new AgreementConsentData(agreementId1, ConsentStatusId.ACTIVE),
- });
- var companyRoleIds = new ValueTuple>[]
- {
- (CompanyRoleId.APP_PROVIDER, new [] {agreementId, agreementId1})
- };
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, "https://callback.url"), 1), true, companyRoleIds, null));
- // Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- var ex = await Assert.ThrowsAsync(Act);
- ex.Message.Should().Be("There must be an process");
- }
-
- [Fact]
- public async Task Submit_WithValidData_CallsExpected()
- {
- // Arrange
- var applicationId = Guid.NewGuid();
- var agreementId = Guid.NewGuid();
- var agreementId1 = Guid.NewGuid();
- var processSteps = new List();
- var application = new CompanyApplication(applicationId, _identity.CompanyId, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.EXTERNAL, DateTimeOffset.UtcNow);
-
- var data = new PartnerSubmitData(
- new[] { CompanyRoleId.APP_PROVIDER },
- new[]
- {
- new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
- new AgreementConsentData(agreementId1, ConsentStatusId.ACTIVE),
- });
- var companyRoleIds = new ValueTuple>[]
- {
- (CompanyRoleId.APP_PROVIDER, new [] {agreementId, agreementId1})
- };
- A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId, _identity.UserId, A>._))
- .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, "https://callback.url"), 1), true, companyRoleIds, Guid.NewGuid()));
- A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._))
- .Invokes((Guid _, Action setOptionalFields) =>
- {
- setOptionalFields.Invoke(application);
- });
- A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>.That.Matches(x =>
- x.Count() == 1 &&
- x.Single().ProcessStepTypeId == ProcessStepTypeId.TRIGGER_CALLBACK_OSP_SUBMITTED)))
- .Invokes((IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> steps) =>
- {
- processSteps.AddRange(steps.Select(x => new ProcessStep(Guid.NewGuid(), x.ProcessStepTypeId, x.ProcessStepStatusId, x.ProcessId, DateTimeOffset.UtcNow)));
- });
- var consents = new List();
- var now = DateTimeOffset.UtcNow;
- A.CallTo(() => _consentRepository.CreateConsents(A>._))
- .Invokes((IEnumerable<(Guid AgreementId, Guid CompanyId, Guid CompanyUserId, ConsentStatusId ConsentStatusId)> agreementConsents) =>
- {
- foreach (var x in agreementConsents)
- {
- consents.Add(new Consent(Guid.NewGuid(), x.AgreementId, x.CompanyId, x.CompanyUserId, x.ConsentStatusId, now));
- }
- });
- // Act
- await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- application.ApplicationStatusId.Should().Be(CompanyApplicationStatusId.SUBMITTED);
- A.CallTo(() => _consentRepository.CreateConsents(A>._))
- .MustHaveHappenedOnceExactly();
- consents.Should().HaveCount(2)
- .And.AllSatisfy(x => x.Should().Match(x =>
- x.CompanyId == _identity.CompanyId &&
- x.CompanyUserId == _identity.UserId &&
- x.ConsentStatusId == ConsentStatusId.ACTIVE))
- .And.Satisfy(
- x => x.AgreementId == agreementId,
- x => x.AgreementId == agreementId1);
- A.CallTo(() => _portalRepositories.SaveAsync())
- .MustHaveHappenedOnceExactly();
- processSteps.Should().ContainSingle().Which.Should().Match(x =>
- x.ProcessStepTypeId == ProcessStepTypeId.TRIGGER_CALLBACK_OSP_SUBMITTED &&
- x.ProcessStepStatusId == ProcessStepStatusId.TODO);
- }
-
- #endregion
-
#region Setup
private void SetupRepos()
diff --git a/tests/administration/Administration.Service.Tests/Controllers/NetworkControllerTests.cs b/tests/administration/Administration.Service.Tests/Controllers/NetworkControllerTests.cs
index 1e5a216545..e3984f2914 100644
--- a/tests/administration/Administration.Service.Tests/Controllers/NetworkControllerTests.cs
+++ b/tests/administration/Administration.Service.Tests/Controllers/NetworkControllerTests.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -67,21 +66,6 @@ public async Task RetriggerSynchronizeUser_ReturnsExpected()
.MustHaveHappenedOnceExactly();
}
- [Fact]
- public async Task Submit_ReturnsExpected()
- {
- // Arrange
- var data = _fixture.Create();
-
- // Act
- var result = await this._controller.Submit(data, CancellationToken.None).ConfigureAwait(false);
-
- // Assert
- result.StatusCode.Should().Be(204);
- A.CallTo(() => _logic.Submit(A._, A._))
- .MustHaveHappenedOnceExactly();
- }
-
[Fact]
public async Task RetriggerCallbackOspApprove_ReturnsExpected()
{
diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/NetworkRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/NetworkRepositoryTests.cs
index 44d830d9ff..32da103345 100644
--- a/tests/portalbackend/PortalBackend.DBAccess.Tests/NetworkRepositoryTests.cs
+++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/NetworkRepositoryTests.cs
@@ -162,16 +162,12 @@ public async Task GetSubmitData_WithoutNetworkRegistration_ReturnsExpected()
var sut = await CreateSut().ConfigureAwait(false);
// Act
- var result = await sut.GetSubmitData(new Guid("729e0af2-6723-4a7f-85a1-833d84b39bdf"), new Guid("8b42e6de-7b59-4217-a63c-198e83d93776"), Enumerable.Repeat(new Guid("aabcdfeb-6669-4c74-89f0-19cda090873e"), 1)).ConfigureAwait(false);
+ var result = await sut.GetSubmitData(new Guid("3390c2d7-75c1-4169-aa27-6ce00e1f3cdd")).ConfigureAwait(false);
// Assert
- result.Should().NotBe(default);
- result.Exists.Should().Be(true);
- result.IsUserInRole.Should().Be(false);
- result.CompanyRoleAgreementIds.Should().HaveCount(2).And.Satisfy(
- x => x.CompanyRoleId == CompanyRoleId.APP_PROVIDER,
- x => x.CompanyRoleId == CompanyRoleId.SERVICE_PROVIDER);
- result.CompanyApplications.Should().HaveCount(1).And.Satisfy(x => x.CompanyApplicationStatusId == CompanyApplicationStatusId.CREATED);
+ result.Exists.Should().BeTrue();
+ result.CompanyApplications.Should().BeEmpty();
+ result.CompanyRoleAgreementIds.Should().Satisfy(x => x.CompanyRoleId == CompanyRoleId.SERVICE_PROVIDER && x.AgreementIds.Count() == 2);
result.ProcessId.Should().BeNull();
}
@@ -182,12 +178,11 @@ public async Task GetSubmitData_WithValid_ReturnsExpected()
var sut = await CreateSut().ConfigureAwait(false);
// Act
- var result = await sut.GetSubmitData(new Guid("ac861325-bc54-4583-bcdc-9e9f2a38ff84"), new Guid("8b42e6de-7b59-4217-a63c-198e83d93776"), Enumerable.Repeat(new Guid("aabcdfeb-6669-4c74-89f0-19cda090873e"), 1)).ConfigureAwait(false);
+ var result = await sut.GetSubmitData(new Guid("ac861325-bc54-4583-bcdc-9e9f2a38ff84")).ConfigureAwait(false);
// Assert
result.Should().NotBe(default);
- result.Exists.Should().Be(true);
- result.IsUserInRole.Should().Be(true);
+ result.Exists.Should().BeTrue();
result.CompanyRoleAgreementIds.Should().HaveCount(2).And.Satisfy(
x => x.CompanyRoleId == CompanyRoleId.ACTIVE_PARTICIPANT,
x => x.CompanyRoleId == CompanyRoleId.APP_PROVIDER);
diff --git a/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs b/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
new file mode 100644
index 0000000000..1743f86667
--- /dev/null
+++ b/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
@@ -0,0 +1,379 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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 AutoFixture;
+using AutoFixture.AutoFakeItEasy;
+using FakeItEasy;
+using FluentAssertions;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
+using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Library;
+using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Model;
+using Xunit;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests.BusinessLogic;
+
+public class NetworkBusinessLogicTests
+{
+ private const string Bpnl = "BPNL00000001TEST";
+ private static readonly Guid ExistingExternalId = Guid.NewGuid();
+ private static readonly Guid UserRoleId = Guid.NewGuid();
+ private static readonly Guid MultiIdpCompanyId = Guid.NewGuid();
+ private static readonly Guid NoIdpCompanyId = Guid.NewGuid();
+ private static readonly Guid IdpId = Guid.NewGuid();
+ private static readonly Guid NoAliasIdpCompanyId = Guid.NewGuid();
+
+ private readonly IFixture _fixture;
+
+ private readonly IdentityData _identity = new(Guid.NewGuid().ToString(), Guid.NewGuid(), IdentityTypeId.COMPANY_USER, Guid.NewGuid());
+ private readonly IUserProvisioningService _userProvisioningService;
+ private readonly IApplicationChecklistCreationService _checklistService;
+
+ private readonly IPortalRepositories _portalRepositories;
+ private readonly ICompanyRepository _companyRepository;
+ private readonly IProcessStepRepository _processStepRepository;
+ private readonly IApplicationRepository _applicationRepository;
+ private readonly INetworkRepository _networkRepository;
+ private readonly IIdentityProviderRepository _identityProviderRepository;
+ private readonly ICountryRepository _countryRepository;
+ private readonly NetworkBusinessLogic _sut;
+ private readonly IConsentRepository _consentRepository;
+
+ public NetworkBusinessLogicTests()
+ {
+ _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true });
+ _fixture.Behaviors.OfType().ToList()
+ .ForEach(b => _fixture.Behaviors.Remove(b));
+ _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
+
+ _userProvisioningService = A.Fake();
+ _portalRepositories = A.Fake();
+ _checklistService = A.Fake();
+
+ _companyRepository = A.Fake();
+ _processStepRepository = A.Fake();
+ _applicationRepository = A.Fake();
+ _networkRepository = A.Fake();
+ _identityProviderRepository = A.Fake();
+ _countryRepository = A.Fake();
+ _consentRepository = A.Fake();
+
+ var identityService = A.Fake();
+ A.CallTo(() => identityService.IdentityData).Returns(_identity);
+
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_consentRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_processStepRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_applicationRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_networkRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_identityProviderRepository);
+ A.CallTo(() => _portalRepositories.GetInstance()).Returns(_countryRepository);
+
+ _sut = new NetworkBusinessLogic(_portalRepositories, identityService, _checklistService);
+
+ SetupRepos();
+ }
+
+ #region Submit
+
+ [Fact]
+ public async Task Submit_WithNotExistingSubmitData_ThrowsNotFoundException()
+ {
+ // Arrange
+ var data = _fixture.Create();
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns(new ValueTuple>, IEnumerable>>, Guid?>());
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"Company {_identity.CompanyId} not found");
+ }
+
+ [Fact]
+ public async Task Submit_WithoutCompanyApplications_ThrowsConflictException()
+ {
+ // Arrange
+ var data = _fixture.Create();
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Empty>(), Enumerable.Empty>>(), null));
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"Company {_identity.CompanyId} has no or more than one application");
+ }
+
+ [Fact]
+ public async Task Submit_WithMultipleCompanyApplications_ThrowsConflictException()
+ {
+ // Arrange
+ var data = _fixture.Create();
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, _fixture.CreateMany>(2), Enumerable.Empty>>(), null));
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"Company {_identity.CompanyId} has no or more than one application");
+ }
+
+ [Fact]
+ public async Task Submit_WithWrongApplicationStatus_ThrowsConflictException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid();
+ var data = _fixture.Create();
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.VERIFY, null), 1), Enumerable.Empty>>(), Guid.NewGuid()));
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"Application {applicationId} is not in state CREATED");
+ }
+
+ [Fact]
+ public async Task Submit_WithOneMissingAgreement_ThrowsConflictException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid();
+ var agreementId = Guid.NewGuid();
+ var notExistingAgreementId = Guid.NewGuid();
+ var data = new PartnerSubmitData(
+ new[] { CompanyRoleId.APP_PROVIDER },
+ new[] { new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE) });
+ var companyRoleIds = new ValueTuple>[]
+ {
+ (CompanyRoleId.APP_PROVIDER, new [] {agreementId, notExistingAgreementId})
+ };
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), companyRoleIds, Guid.NewGuid()));
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"All Agreements for the company roles must be agreed to, missing agreementIds: {notExistingAgreementId} (Parameter 'Agreements')");
+ }
+
+ [Fact]
+ public async Task Submit_WithOneInactiveAgreement_ThrowsConflictException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid();
+ var agreementId = Guid.NewGuid();
+ var inactiveAgreementId = Guid.NewGuid();
+ var data = new PartnerSubmitData(
+ new[] { CompanyRoleId.APP_PROVIDER },
+ new[]
+ {
+ new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
+ new AgreementConsentData(inactiveAgreementId, ConsentStatusId.INACTIVE),
+ });
+ var companyRoleIds = new ValueTuple>[]
+ {
+ (CompanyRoleId.APP_PROVIDER, new [] {agreementId, inactiveAgreementId})
+ };
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), companyRoleIds, Guid.NewGuid()));
+
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"All agreements must be agreed to. Agreements that are not active: {inactiveAgreementId} (Parameter 'Agreements')");
+ }
+
+ [Fact]
+ public async Task Submit_WithoutProcessId_ThrowsConflictException()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid();
+ var agreementId = Guid.NewGuid();
+ var agreementId1 = Guid.NewGuid();
+ var data = new PartnerSubmitData(
+ new[] { CompanyRoleId.APP_PROVIDER },
+ new[]
+ {
+ new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
+ new AgreementConsentData(agreementId1, ConsentStatusId.ACTIVE),
+ });
+ var companyRoleIds = new ValueTuple>[]
+ {
+ (CompanyRoleId.APP_PROVIDER, new [] {agreementId, agreementId1})
+ };
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, "https://callback.url"), 1), companyRoleIds, null));
+ // Act
+ async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be("There must be an process");
+ }
+
+ [Fact]
+ public async Task Submit_WithValidData_CallsExpected()
+ {
+ // Arrange
+ var applicationId = Guid.NewGuid();
+ var agreementId = Guid.NewGuid();
+ var agreementId1 = Guid.NewGuid();
+ var processId = Guid.NewGuid();
+ var submitProcessId = Guid.NewGuid();
+ var processSteps = new List();
+ var application = new CompanyApplication(applicationId, _identity.CompanyId, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.EXTERNAL, DateTimeOffset.UtcNow);
+
+ var data = new PartnerSubmitData(
+ new[] { CompanyRoleId.APP_PROVIDER },
+ new[]
+ {
+ new AgreementConsentData(agreementId, ConsentStatusId.ACTIVE),
+ new AgreementConsentData(agreementId1, ConsentStatusId.ACTIVE),
+ });
+ var companyRoleIds = new ValueTuple>[]
+ {
+ (CompanyRoleId.APP_PROVIDER, new [] {agreementId, agreementId1})
+ };
+ A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
+ .Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, "https://callback.url"), 1), companyRoleIds, submitProcessId));
+ A.CallTo(() => _applicationRepository.AttachAndModifyCompanyApplication(applicationId, A>._))
+ .Invokes((Guid _, Action setOptionalFields) =>
+ {
+ setOptionalFields.Invoke(application);
+ });
+ A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>._))
+ .Invokes((IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> steps) =>
+ {
+ processSteps.AddRange(steps.Select(x => new ProcessStep(Guid.NewGuid(), x.ProcessStepTypeId, x.ProcessStepStatusId, x.ProcessId, DateTimeOffset.UtcNow)));
+ });
+ var consents = new List();
+ var now = DateTimeOffset.UtcNow;
+ A.CallTo(() => _consentRepository.CreateConsents(A>._))
+ .Invokes((IEnumerable<(Guid AgreementId, Guid CompanyId, Guid CompanyUserId, ConsentStatusId ConsentStatusId)> agreementConsents) =>
+ {
+ foreach (var x in agreementConsents)
+ {
+ consents.Add(new Consent(Guid.NewGuid(), x.AgreementId, x.CompanyId, x.CompanyUserId, x.ConsentStatusId, now));
+ }
+ });
+ A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.APPLICATION_CHECKLIST))
+ .ReturnsLazily((ProcessTypeId processTypeId) => new Process(processId, processTypeId, Guid.NewGuid()));
+ A.CallTo(() => _checklistService.CreateInitialChecklistAsync(applicationId))
+ .Returns(new[]
+ {
+ (ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.TO_DO),
+ (ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE),
+ (ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.TO_DO),
+ (ApplicationChecklistEntryTypeId.SELF_DESCRIPTION_LP, ApplicationChecklistEntryStatusId.TO_DO),
+ (ApplicationChecklistEntryTypeId.CLEARING_HOUSE, ApplicationChecklistEntryStatusId.TO_DO),
+ (ApplicationChecklistEntryTypeId.APPLICATION_ACTIVATION, ApplicationChecklistEntryStatusId.TO_DO),
+ });
+ A.CallTo(() => _checklistService.GetInitialProcessStepTypeIds(A>._))
+ .Returns(new[] { ProcessStepTypeId.VERIFY_REGISTRATION, ProcessStepTypeId.DECLINE_APPLICATION });
+
+ // Act
+ await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ application.ApplicationStatusId.Should().Be(CompanyApplicationStatusId.SUBMITTED);
+ A.CallTo(() => _consentRepository.CreateConsents(A>._))
+ .MustHaveHappenedOnceExactly();
+ consents.Should().HaveCount(2)
+ .And.AllSatisfy(x => x.Should().Match(x =>
+ x.CompanyId == _identity.CompanyId &&
+ x.CompanyUserId == _identity.UserId &&
+ x.ConsentStatusId == ConsentStatusId.ACTIVE))
+ .And.Satisfy(
+ x => x.AgreementId == agreementId,
+ x => x.AgreementId == agreementId1);
+ A.CallTo(() => _portalRepositories.SaveAsync())
+ .MustHaveHappenedOnceExactly();
+ A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>._))
+ .MustHaveHappenedOnceExactly();
+ processSteps.Should().Satisfy(
+ x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.VERIFY_REGISTRATION && x.ProcessStepStatusId == ProcessStepStatusId.TODO,
+ x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.DECLINE_APPLICATION && x.ProcessStepStatusId == ProcessStepStatusId.TODO,
+ x => x.ProcessId == submitProcessId && x.ProcessStepTypeId == ProcessStepTypeId.TRIGGER_CALLBACK_OSP_SUBMITTED && x.ProcessStepStatusId == ProcessStepStatusId.TODO);
+ }
+
+ #endregion
+
+ #region Setup
+
+ private void SetupRepos()
+ {
+ A.CallTo(() => _networkRepository.CheckExternalIdExists(ExistingExternalId, A.That.Matches(x => x == _identity.CompanyId || x == NoIdpCompanyId)))
+ .Returns(true);
+ A.CallTo(() => _networkRepository.CheckExternalIdExists(A.That.Not.Matches(x => x == ExistingExternalId), A.That.Matches(x => x == _identity.CompanyId || x == NoIdpCompanyId)))
+ .Returns(false);
+
+ A.CallTo(() => _companyRepository.CheckBpnExists(Bpnl)).Returns(false);
+ A.CallTo(() => _companyRepository.CheckBpnExists(A.That.Not.Matches(x => x == Bpnl))).Returns(true);
+
+ A.CallTo(() => _countryRepository.CheckCountryExistsByAlpha2CodeAsync("XX"))
+ .Returns(false);
+ A.CallTo(() => _countryRepository.CheckCountryExistsByAlpha2CodeAsync(A.That.Not.Matches(x => x == "XX")))
+ .Returns(true);
+
+ A.CallTo(() => _companyRepository.GetCompanyNameUntrackedAsync(A.That.Matches(x => x == _identity.CompanyId || x == NoIdpCompanyId)))
+ .Returns((true, "testCompany"));
+ A.CallTo(() => _companyRepository.GetCompanyNameUntrackedAsync(A.That.Not.Matches(x => x == _identity.CompanyId)))
+ .Returns((false, ""));
+
+ A.CallTo(() => _identityProviderRepository.GetSingleManagedIdentityProviderAliasDataUntracked(_identity.CompanyId))
+ .Returns((IdpId, (string?)"test-alias"));
+
+ A.CallTo(() => _identityProviderRepository.GetSingleManagedIdentityProviderAliasDataUntracked(NoAliasIdpCompanyId))
+ .Returns((IdpId, (string?)null));
+
+ A.CallTo(() => _identityProviderRepository.GetSingleManagedIdentityProviderAliasDataUntracked(NoIdpCompanyId))
+ .Returns(((Guid, string?))default);
+
+ A.CallTo(() => _identityProviderRepository.GetSingleManagedIdentityProviderAliasDataUntracked(MultiIdpCompanyId))
+ .Throws(new InvalidOperationException("Sequence contains more than one element."));
+
+ A.CallTo(() => _identityProviderRepository.GetManagedIdentityProviderAliasDataUntracked(A.That.Matches(x => x == _identity.CompanyId || x == NoIdpCompanyId), A>._))
+ .Returns(new[] { (IdpId, (string?)"test-alias") }.ToAsyncEnumerable());
+
+ A.CallTo(() => _userProvisioningService.GetRoleDatas(A>._))
+ .Returns(new[] { new UserRoleData(UserRoleId, "cl1", "Company Admin") }.ToAsyncEnumerable());
+ }
+
+ #endregion
+}
diff --git a/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs b/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
new file mode 100644
index 0000000000..d9258726f4
--- /dev/null
+++ b/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
@@ -0,0 +1,57 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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 AutoFixture;
+using FakeItEasy;
+using FluentAssertions;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Controllers;
+using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Model;
+using Xunit;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests.Controller;
+
+public class NetworkControllerTests
+{
+ private readonly INetworkBusinessLogic _logic;
+ private readonly NetworkController _controller;
+ private readonly Fixture _fixture;
+
+ public NetworkControllerTests()
+ {
+ _fixture = new Fixture();
+ _logic = A.Fake();
+ this._controller = new NetworkController(_logic);
+ }
+
+ [Fact]
+ public async Task Submit_ReturnsExpected()
+ {
+ // Arrange
+ var data = _fixture.Create();
+
+ // Act
+ var result = await this._controller.Submit(data, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ result.StatusCode.Should().Be(204);
+ A.CallTo(() => _logic.Submit(A._, A._))
+ .MustHaveHappenedOnceExactly();
+ }
+}
diff --git a/tests/registration/Registration.Service.Tests/Registration.Service.Tests.csproj b/tests/registration/Registration.Service.Tests/Registration.Service.Tests.csproj
index 131544a1bc..8ae32c1fe9 100644
--- a/tests/registration/Registration.Service.Tests/Registration.Service.Tests.csproj
+++ b/tests/registration/Registration.Service.Tests/Registration.Service.Tests.csproj
@@ -1,55 +1,56 @@
-ļ»æ
-
-
-
-
- Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests
- net7.0
- enable
- enable
- false
-
-
-
-
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
-
+ļ»æ
+
+
+
+
+ Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests
+ Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests
+ net7.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
From 8df783256be62e1be22fe9403991d1e523a10b93 Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 11:51:13 +0200
Subject: [PATCH 05/26] fix: add networkBl registration (#292)
* fix: add networkBl registration
* test: remove unneeded cancellationToken
-----------------------------------
Refs: CPLP-3197
Reviewed-by: Norbert Truchsess
---
.../BusinessLogic/INetworkBusinessLogic.cs | 2 +-
.../BusinessLogic/NetworkBusinessLogic.cs | 2 +-
.../Controllers/NetworkController.cs | 5 ++---
.../Registration.Service/Program.cs | 3 ++-
.../BusinessLogic/NetworkBusinessLogicTests.cs | 17 +++++++++--------
.../Controller/NetworkControllerTests.cs | 4 ++--
6 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
index b6092212c6..e2ccf31488 100644
--- a/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
+++ b/src/registration/Registration.Service/BusinessLogic/INetworkBusinessLogic.cs
@@ -23,5 +23,5 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic
public interface INetworkBusinessLogic
{
- Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken);
+ Task Submit(PartnerSubmitData submitData);
}
diff --git a/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
index 24a7b61e70..b4c535f7c0 100644
--- a/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
+++ b/src/registration/Registration.Service/BusinessLogic/NetworkBusinessLogic.cs
@@ -42,7 +42,7 @@ public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentitySer
_checklistService = checklistService;
}
- public async Task Submit(PartnerSubmitData submitData, CancellationToken cancellationToken)
+ public async Task Submit(PartnerSubmitData submitData)
{
var companyId = _identityService.IdentityData.CompanyId;
var userId = _identityService.IdentityData.UserId;
diff --git a/src/registration/Registration.Service/Controllers/NetworkController.cs b/src/registration/Registration.Service/Controllers/NetworkController.cs
index cffb43c33f..d21e30ee33 100644
--- a/src/registration/Registration.Service/Controllers/NetworkController.cs
+++ b/src/registration/Registration.Service/Controllers/NetworkController.cs
@@ -48,7 +48,6 @@ public NetworkController(INetworkBusinessLogic logic)
/// Submits the application
///
/// The agreements for the companyRoles
- /// Cancellation Token
/// NoContent
/// Example: POST: api/administration/registration/network/partnerRegistration/submit
/// Empty response on success.
@@ -60,9 +59,9 @@ public NetworkController(INetworkBusinessLogic logic)
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
- public async Task Submit([FromBody] PartnerSubmitData data, CancellationToken cancellationToken)
+ public async Task Submit([FromBody] PartnerSubmitData data)
{
- await _logic.Submit(data, cancellationToken).ConfigureAwait(false);
+ await _logic.Submit(data).ConfigureAwait(false);
return NoContent();
}
}
diff --git a/src/registration/Registration.Service/Program.cs b/src/registration/Registration.Service/Program.cs
index 23fae3c6c8..7469e19b6c 100644
--- a/src/registration/Registration.Service/Program.cs
+++ b/src/registration/Registration.Service/Program.cs
@@ -41,7 +41,8 @@
builder.Services.AddTransient();
builder.Services.AddTransient()
- .ConfigureRegistrationSettings(builder.Configuration.GetSection("Registration"));
+ .ConfigureRegistrationSettings(builder.Configuration.GetSection("Registration"))
+ .AddTransient();
builder.Services.AddApplicationChecklistCreation();
builder.Services.AddBpnAccess(builder.Configuration.GetValue("BPN_Address") ?? throw new ConfigurationException("BPN_Address is not configured"));
diff --git a/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs b/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
index 1743f86667..a0e7380de3 100644
--- a/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
+++ b/tests/registration/Registration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
@@ -109,7 +109,7 @@ public async Task Submit_WithNotExistingSubmitData_ThrowsNotFoundException()
.Returns(new ValueTuple>, IEnumerable>>, Guid?>());
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -125,7 +125,7 @@ public async Task Submit_WithoutCompanyApplications_ThrowsConflictException()
.Returns((true, Enumerable.Empty>(), Enumerable.Empty>>(), null));
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -141,7 +141,7 @@ public async Task Submit_WithMultipleCompanyApplications_ThrowsConflictException
.Returns((true, _fixture.CreateMany>(2), Enumerable.Empty>>(), null));
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -158,7 +158,7 @@ public async Task Submit_WithWrongApplicationStatus_ThrowsConflictException()
.Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.VERIFY, null), 1), Enumerable.Empty>>(), Guid.NewGuid()));
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -183,7 +183,7 @@ public async Task Submit_WithOneMissingAgreement_ThrowsConflictException()
.Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), companyRoleIds, Guid.NewGuid()));
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -212,7 +212,7 @@ public async Task Submit_WithOneInactiveAgreement_ThrowsConflictException()
.Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, null), 1), companyRoleIds, Guid.NewGuid()));
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -239,8 +239,9 @@ public async Task Submit_WithoutProcessId_ThrowsConflictException()
};
A.CallTo(() => _networkRepository.GetSubmitData(_identity.CompanyId))
.Returns((true, Enumerable.Repeat>((applicationId, CompanyApplicationStatusId.CREATED, "https://callback.url"), 1), companyRoleIds, null));
+
// Act
- async Task Act() => await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ async Task Act() => await _sut.Submit(data).ConfigureAwait(false);
// Assert
var ex = await Assert.ThrowsAsync(Act);
@@ -308,7 +309,7 @@ public async Task Submit_WithValidData_CallsExpected()
.Returns(new[] { ProcessStepTypeId.VERIFY_REGISTRATION, ProcessStepTypeId.DECLINE_APPLICATION });
// Act
- await _sut.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ await _sut.Submit(data).ConfigureAwait(false);
// Assert
application.ApplicationStatusId.Should().Be(CompanyApplicationStatusId.SUBMITTED);
diff --git a/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs b/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
index d9258726f4..a606c4aa70 100644
--- a/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
+++ b/tests/registration/Registration.Service.Tests/Controller/NetworkControllerTests.cs
@@ -47,11 +47,11 @@ public async Task Submit_ReturnsExpected()
var data = _fixture.Create();
// Act
- var result = await this._controller.Submit(data, CancellationToken.None).ConfigureAwait(false);
+ var result = await this._controller.Submit(data).ConfigureAwait(false);
// Assert
result.StatusCode.Should().Be(204);
- A.CallTo(() => _logic.Submit(A._, A._))
+ A.CallTo(() => _logic.Submit(A._))
.MustHaveHappenedOnceExactly();
}
}
From dfeca44873b36268d15bdb471f987a5ea0443708 Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 12:34:22 +0200
Subject: [PATCH 06/26] feat(n2n): move keycloak user creation to process
(#280)
* feat(n2n): move keycloak user creation to process
Refs: CPLP-3295
* feat(n2n): add idp to welcome mails
Refs: CPLP-2639
---------
Co-authored-by: Norbert Truchsess
Reviewed-by: Norbert Truchsess
---
.../BusinessLogic/NetworkBusinessLogic.cs | 46 +++++-------
.../BusinessLogic/UserBusinessLogic.cs | 1 +
.../EmailTemplates/osp_welcome_email.html | 2 +-
.../Repositories/INetworkRepository.cs | 1 +
.../Repositories/NetworkRepository.cs | 5 ++
.../NetworkRegistrationHandlerExtensions.cs | 5 +-
.../INetworkRegistrationHandler.cs | 1 -
.../INetworkRegistrationProcessHelper.cs | 1 -
.../Models/UserMailInformation.cs | 24 +++++++
.../NetworkRegistration.Library.csproj | 1 +
.../NetworkRegistrationHandler.cs | 62 ++++++++++++----
.../NetworkRegistrationProcessHelper.cs | 2 -
.../Service/IUserProvisioningService.cs | 5 +-
.../Service/UserProvisioningService.cs | 72 ++++++++++++-------
.../NetworkBusinessLogicTests.cs | 19 ++---
.../NetworkRegistrationHandlerTests.cs | 48 ++++++++++---
16 files changed, 200 insertions(+), 95 deletions(-)
create mode 100644 src/processes/NetworkRegistration.Library/Models/UserMailInformation.cs
diff --git a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
index a181587251..51522cb5b7 100644
--- a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
@@ -23,7 +23,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
-using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
@@ -46,16 +45,14 @@ public class NetworkBusinessLogic : INetworkBusinessLogic
private readonly IIdentityService _identityService;
private readonly IUserProvisioningService _userProvisioningService;
private readonly INetworkRegistrationProcessHelper _processHelper;
- private readonly IMailingService _mailingService;
private readonly PartnerRegistrationSettings _settings;
- public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentityService identityService, IUserProvisioningService userProvisioningService, INetworkRegistrationProcessHelper processHelper, IMailingService mailingService, IOptions options)
+ public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentityService identityService, IUserProvisioningService userProvisioningService, INetworkRegistrationProcessHelper processHelper, IOptions options)
{
_portalRepositories = portalRepositories;
_identityService = identityService;
_userProvisioningService = userProvisioningService;
_processHelper = processHelper;
- _mailingService = mailingService;
_settings = options.Value;
}
@@ -69,8 +66,6 @@ public async Task HandlePartnerRegistration(PartnerRegistrationData data)
var (roleData, identityProviderIdAliase, singleIdentityProviderIdAlias, allIdentityProviderIds) = await ValidatePartnerRegistrationData(data, networkRepository, identityProviderRepository, ownerCompanyId).ConfigureAwait(false);
- var (_, companyName) = await companyRepository.GetCompanyNameUntrackedAsync(ownerCompanyId).ConfigureAwait(false);
-
var companyId = CreatePartnerCompany(companyRepository, data);
var applicationId = _portalRepositories.GetInstance().CreateCompanyApplication(companyId, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.EXTERNAL,
@@ -96,13 +91,27 @@ string GetIdpAlias(Guid? identityProviderId) =>
? singleIdentityProviderIdAlias?.Alias ?? throw new UnexpectedConditionException("singleIdentityProviderIdAlias should never be null here")
: identityProviderIdAliase?[identityProviderId.Value] ?? throw new UnexpectedConditionException("identityProviderIdAliase should never be null here and should always contain an entry for identityProviderId");
- async IAsyncEnumerable<(Guid CompanyUserId, string UserName, string? Password, Exception? Error)> CreateUsers()
+ async IAsyncEnumerable<(Guid CompanyUserId, Exception? Error)> CreateUsers()
{
- foreach (var user in GetUserCreationData(companyId, GetIdpId, GetIdpAlias, data, roleData))
+ var userRepository = _portalRepositories.GetInstance();
+ await foreach (var (aliasData, creationInfos) in GetUserCreationData(companyId, GetIdpId, GetIdpAlias, data, roleData).ToAsyncEnumerable())
{
- await foreach (var result in _userProvisioningService.CreateOwnCompanyIdpUsersAsync(user.AliasData, user.CreationInfos.ToAsyncEnumerable()))
+ foreach (var creationInfo in creationInfos)
{
- yield return result;
+ var identityId = Guid.Empty;
+ Exception? error = null;
+ try
+ {
+ var (_, companyUserId) = await _userProvisioningService.GetOrCreateCompanyUser(userRepository, aliasData.IdpAlias,
+ creationInfo, companyId, aliasData.IdpId, data.Bpn).ConfigureAwait(false);
+ identityId = companyUserId;
+ }
+ catch (Exception ex)
+ {
+ error = ex;
+ }
+
+ yield return (identityId, error);
}
}
}
@@ -111,7 +120,6 @@ string GetIdpAlias(Guid? identityProviderId) =>
userCreationErrors.IfAny(errors => throw new ServiceException($"Errors occured while saving the users: ${string.Join("", errors.Select(x => x.Message))}", errors.First()));
await _portalRepositories.SaveAsync().ConfigureAwait(false);
- await SendMails(data.UserDetails.Select(x => new ValueTuple(x.Email, x.FirstName, x.LastName)), companyName).ConfigureAwait(false);
}
private Guid CreatePartnerCompany(ICompanyRepository companyRepository, PartnerRegistrationData data)
@@ -167,22 +175,6 @@ private Guid CreatePartnerCompany(ICompanyRepository companyRepository, PartnerR
return (AliasData: companyNameIdpAliasData, CreationInfos: userCreationInfos);
});
- private async Task SendMails(IEnumerable<(string Email, string? FirstName, string? LastName)> companyUserWithRoleIdForCompany, string ospName)
- {
- foreach (var (receiver, firstName, lastName) in companyUserWithRoleIdForCompany)
- {
- var userName = string.Join(" ", firstName, lastName);
- var mailParameters = new Dictionary
- {
- { "userName", !string.IsNullOrWhiteSpace(userName) ? userName : receiver },
- { "hostname", _settings.BasePortalAddress },
- { "osp", ospName },
- { "url", _settings.BasePortalAddress }
- };
- await _mailingService.SendMails(receiver, mailParameters, Enumerable.Repeat("OspWelcomeMail", 1)).ConfigureAwait(false);
- }
- }
-
public Task RetriggerProcessStep(Guid externalId, ProcessStepTypeId processStepTypeId) =>
_processHelper.TriggerProcessStep(externalId, processStepTypeId);
diff --git a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
index bf8ad2fa48..18b9d3fc79 100644
--- a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
@@ -154,6 +154,7 @@ private Task> GetOwnCompanyUserRoleData(IEnumerable());
}
+
return _userProvisioningService.GetOwnCompanyPortalRoleDatas(_settings.Portal.KeycloakClientID, roles, companyId);
}
diff --git a/src/mailing/Mailing.Template/EmailTemplates/osp_welcome_email.html b/src/mailing/Mailing.Template/EmailTemplates/osp_welcome_email.html
index b78482a1bc..f4eea5098a 100644
--- a/src/mailing/Mailing.Template/EmailTemplates/osp_welcome_email.html
+++ b/src/mailing/Mailing.Template/EmailTemplates/osp_welcome_email.html
@@ -92,7 +92,7 @@
style="Margin:0;padding-top:20px;padding-bottom:20px;padding-left:30px;padding-right:30px;text-align: left;">
- Dear {userName},
your registration at the Catena-X dataspace got based on your request successfully generated by {osp}.
We have created your registration request. Before the registration validation is taking place, a final check from your side confirming your registration data and confirming the terms & conditions is needed.
Please follow the link below to access your registration data and to confirm the company role related terms & conditions.
You may want to update the company roles by selecting additional roles.
+ Dear {userName},
your registration at the Catena-X dataspace got based on your request successfully generated by {osp} for idps {idpAliasse}.
We have created your registration request. Before the registration validation is taking place, a final check from your side confirming your registration data and confirming the terms & conditions is needed.
Please follow the link below to access your registration data and to confirm the company role related terms & conditions.
You may want to update the company roles by selecting additional roles.
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
index 90a6f94073..46e4991a9d 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/INetworkRepository.cs
@@ -32,4 +32,5 @@ public interface INetworkRepository
Task<(bool RegistrationIdExists, VerifyProcessData processData)> IsValidRegistration(Guid externalId, IEnumerable processStepTypeIds);
Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId);
Task<(OspDetails? OspDetails, Guid? ExternalId, string? Bpn, Guid ApplicationId, IEnumerable Comments)> GetCallbackData(Guid networkRegistrationId, ProcessStepTypeId processStepTypeId);
+ Task GetOspCompanyName(Guid networkRegistrationId);
}
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
index 45cf0f094e..7a7b149e59 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/NetworkRepository.cs
@@ -108,4 +108,9 @@ public Task GetNetworkRegistrationDataForProcessIdAsync(Guid processId) =>
.Select(step => step.Message!)
: new List()))
.SingleOrDefaultAsync();
+
+ public Task GetOspCompanyName(Guid networkRegistrationId) =>
+ _context.NetworkRegistrations.Where(x => x.Id == networkRegistrationId)
+ .Select(x => x.OnboardingServiceProvider!.Name)
+ .SingleOrDefaultAsync();
}
diff --git a/src/processes/NetworkRegistration.Library/DependencyInjection/NetworkRegistrationHandlerExtensions.cs b/src/processes/NetworkRegistration.Library/DependencyInjection/NetworkRegistrationHandlerExtensions.cs
index 5adc8ae9f1..fd1bdb32dd 100644
--- a/src/processes/NetworkRegistration.Library/DependencyInjection/NetworkRegistrationHandlerExtensions.cs
+++ b/src/processes/NetworkRegistration.Library/DependencyInjection/NetworkRegistrationHandlerExtensions.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -32,6 +31,9 @@ public class NetworkRegistrationProcessSettings
[Required]
[DistinctValues("x => x.ClientId")]
public IEnumerable InitialRoles { get; set; } = null!;
+
+ [Required(AllowEmptyStrings = false)]
+ public string BasePortalAddress { get; set; } = null!;
}
public static class NetworkRegistrationHandlerExtensions
@@ -41,6 +43,7 @@ public static IServiceCollection AddNetworkRegistrationHandler(this IServiceColl
var section = config.GetSection("NetworkRegistration");
services.AddOptions()
.Bind(section)
+ .ValidateDataAnnotations()
.ValidateDistinctValues(section)
.ValidateOnStart();
diff --git a/src/processes/NetworkRegistration.Library/INetworkRegistrationHandler.cs b/src/processes/NetworkRegistration.Library/INetworkRegistrationHandler.cs
index 4f4133fb06..f562464594 100644
--- a/src/processes/NetworkRegistration.Library/INetworkRegistrationHandler.cs
+++ b/src/processes/NetworkRegistration.Library/INetworkRegistrationHandler.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
diff --git a/src/processes/NetworkRegistration.Library/INetworkRegistrationProcessHelper.cs b/src/processes/NetworkRegistration.Library/INetworkRegistrationProcessHelper.cs
index 413d60a6ec..8c014ee160 100644
--- a/src/processes/NetworkRegistration.Library/INetworkRegistrationProcessHelper.cs
+++ b/src/processes/NetworkRegistration.Library/INetworkRegistrationProcessHelper.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
diff --git a/src/processes/NetworkRegistration.Library/Models/UserMailInformation.cs b/src/processes/NetworkRegistration.Library/Models/UserMailInformation.cs
new file mode 100644
index 0000000000..d07af71a83
--- /dev/null
+++ b/src/processes/NetworkRegistration.Library/Models/UserMailInformation.cs
@@ -0,0 +1,24 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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 System.Collections.Generic;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Processes.NetworkRegistration.Library.Models;
+
+public record UserMailInformation(string Email, string? FirstName, string? LastName, IEnumerable IdpAliasse);
diff --git a/src/processes/NetworkRegistration.Library/NetworkRegistration.Library.csproj b/src/processes/NetworkRegistration.Library/NetworkRegistration.Library.csproj
index 4db5590cf9..9babed5eed 100644
--- a/src/processes/NetworkRegistration.Library/NetworkRegistration.Library.csproj
+++ b/src/processes/NetworkRegistration.Library/NetworkRegistration.Library.csproj
@@ -29,6 +29,7 @@
+
diff --git a/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs b/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
index ce2df221d0..62e95ead15 100644
--- a/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
+++ b/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -20,10 +19,12 @@
using Microsoft.Extensions.Options;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
+using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.Processes.NetworkRegistration.Library.DependencyInjection;
+using Org.Eclipse.TractusX.Portal.Backend.Processes.NetworkRegistration.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
@@ -36,16 +37,19 @@ public class NetworkRegistrationHandler : INetworkRegistrationHandler
private readonly IUserProvisioningService _userProvisioningService;
private readonly IProvisioningManager _provisioningManager;
private readonly NetworkRegistrationProcessSettings _settings;
+ private readonly IMailingService _mailingService;
public NetworkRegistrationHandler(
IPortalRepositories portalRepositories,
IUserProvisioningService userProvisioningService,
IProvisioningManager provisioningManager,
+ IMailingService mailingService,
IOptions options)
{
_portalRepositories = portalRepositories;
_userProvisioningService = userProvisioningService;
_provisioningManager = provisioningManager;
+ _mailingService = mailingService;
_settings = options.Value;
}
@@ -53,6 +57,13 @@ public NetworkRegistrationHandler(
public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SynchronizeUser(Guid networkRegistrationId)
{
var userRepository = _portalRepositories.GetInstance();
+ var userRoleRepository = _portalRepositories.GetInstance();
+ var ospName = await _portalRepositories.GetInstance().GetOspCompanyName(networkRegistrationId).ConfigureAwait(false);
+ if (string.IsNullOrWhiteSpace(ospName))
+ {
+ throw new UnexpectedConditionException("Onboarding Service Provider name must be set");
+ }
+
var companyAssignedIdentityProviders = await userRepository
.GetUserAssignedIdentityProviderForNetworkRegistration(networkRegistrationId)
.ToListAsync()
@@ -75,19 +86,25 @@ public NetworkRegistrationHandler(
try
{
- var userId = await _provisioningManager.GetUserByUserName(cu.CompanyUserId.ToString()).ConfigureAwait(false) ??
- await _userProvisioningService.CreateCentralUserWithProviderLinks(cu.CompanyUserId, new UserCreationRoleDataIdpInfo(cu.FirstName!, cu.LastName!, cu.Email!, roleData, string.Empty, string.Empty, UserStatusId.ACTIVE, true), cu.CompanyName, cu.Bpn, cu.ProviderLinkData.Select(x => new IdentityProviderLink(x.Alias!, x.ProviderUserId, x.UserName)));
+ var userId = await _provisioningManager.GetUserByUserName(cu.CompanyUserId.ToString()).ConfigureAwait(false);
+ if (!string.IsNullOrWhiteSpace(userId))
+ {
+ userRepository.AttachAndModifyIdentity(cu.CompanyUserId, i =>
+ {
+ i.UserStatusId = UserStatusId.PENDING;
+ i.UserEntityId = null;
+ },
+ i =>
+ {
+ i.UserStatusId = UserStatusId.ACTIVE;
+ i.UserEntityId = userId;
+ });
- userRepository.AttachAndModifyIdentity(cu.CompanyUserId, i =>
- {
- i.UserStatusId = UserStatusId.PENDING;
- i.UserEntityId = null;
- },
- i =>
- {
- i.UserStatusId = UserStatusId.ACTIVE;
- i.UserEntityId = userId;
- });
+ await _userProvisioningService.AssignRolesToNewUserAsync(userRoleRepository, roleData, (userId, cu.CompanyUserId)).ConfigureAwait(false);
+ continue;
+ }
+
+ await _userProvisioningService.HandleCentralKeycloakCreation(new UserCreationRoleDataIdpInfo(cu.FirstName!, cu.LastName!, cu.Email!, roleData, string.Empty, string.Empty, UserStatusId.ACTIVE, true), cu.CompanyUserId, cu.CompanyName, cu.Bpn, null, cu.ProviderLinkData.Select(x => new IdentityProviderLink(x.Alias!, x.ProviderUserId, x.UserName)), userRepository, userRoleRepository).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -95,10 +112,29 @@ public NetworkRegistrationHandler(
}
}
+ await SendMails(companyAssignedIdentityProviders.Select(x => new UserMailInformation(x.Email!, x.FirstName, x.LastName, x.ProviderLinkData.Select(pld => pld.Alias!))), ospName).ConfigureAwait(false);
return new ValueTuple?, ProcessStepStatusId, bool, string?>(
null,
ProcessStepStatusId.DONE,
false,
null);
}
+
+ private async Task SendMails(IEnumerable companyUserWithRoleIdForCompany, string ospName)
+ {
+ var templates = Enumerable.Repeat("OspWelcomeMail", 1);
+ foreach (var (receiver, firstName, lastName, idpAliasse) in companyUserWithRoleIdForCompany)
+ {
+ var userName = string.Join(" ", firstName, lastName);
+ var mailParameters = new Dictionary
+ {
+ { "userName", !string.IsNullOrWhiteSpace(userName) ? userName : receiver },
+ { "hostname", _settings.BasePortalAddress },
+ { "osp", ospName },
+ { "url", _settings.BasePortalAddress },
+ { "idpAliasse", string.Join(",", idpAliasse) }
+ };
+ await _mailingService.SendMails(receiver, mailParameters, templates).ConfigureAwait(false);
+ }
+ }
}
diff --git a/src/processes/NetworkRegistration.Library/NetworkRegistrationProcessHelper.cs b/src/processes/NetworkRegistration.Library/NetworkRegistrationProcessHelper.cs
index afd1380dd2..c5c968c5aa 100644
--- a/src/processes/NetworkRegistration.Library/NetworkRegistrationProcessHelper.cs
+++ b/src/processes/NetworkRegistration.Library/NetworkRegistrationProcessHelper.cs
@@ -1,5 +1,4 @@
/********************************************************************************
- * Copyright (c) 2021, 2023 BMW Group AG
* Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
@@ -20,7 +19,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
-using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.Processes.Library;
diff --git a/src/provisioning/Provisioning.Library/Service/IUserProvisioningService.cs b/src/provisioning/Provisioning.Library/Service/IUserProvisioningService.cs
index f686052f71..3dc302b5db 100644
--- a/src/provisioning/Provisioning.Library/Service/IUserProvisioningService.cs
+++ b/src/provisioning/Provisioning.Library/Service/IUserProvisioningService.cs
@@ -22,7 +22,6 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
-using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Models;
namespace Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
@@ -30,10 +29,12 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
public interface IUserProvisioningService
{
IAsyncEnumerable<(Guid CompanyUserId, string UserName, string? Password, Exception? Error)> CreateOwnCompanyIdpUsersAsync(CompanyNameIdpAliasData companyNameIdpAliasData, IAsyncEnumerable userCreationInfos, CancellationToken cancellationToken = default);
+ Task HandleCentralKeycloakCreation(UserCreationRoleDataIdpInfo user, Guid companyUserId, string companyName, string? businessPartnerNumber, Identity? identity, IEnumerable identityProviderLinks, IUserRepository userRepository, IUserRolesRepository userRolesRepository);
Task<(CompanyNameIdpAliasData IdpAliasData, string NameCreatedBy)> GetCompanyNameIdpAliasData(Guid identityProviderId, Guid companyUserId);
Task<(CompanyNameIdpAliasData IdpAliasData, string NameCreatedBy)> GetCompanyNameSharedIdpAliasData(Guid companyUserId, Guid? applicationId = null);
Task GetIdentityProviderDisplayName(string idpAlias);
IAsyncEnumerable GetRoleDatas(IEnumerable clientRoles);
Task> GetOwnCompanyPortalRoleDatas(string clientId, IEnumerable roles, Guid companyId);
- Task CreateCentralUserWithProviderLinks(Guid companyUserId, UserCreationRoleDataIdpInfo user, string companyName, string? businessPartnerNumber, IEnumerable identityProviderLinks);
+ Task<(Identity? identity, Guid companyUserId)> GetOrCreateCompanyUser(IUserRepository userRepository, string alias, UserCreationRoleDataIdpInfo user, Guid companyId, Guid identityProviderId, string? businessPartnerNumber);
+ Task AssignRolesToNewUserAsync(IUserRolesRepository userRolesRepository, IEnumerable roleDatas, (string UserEntityId, Guid CompanyUserId) userdata);
}
diff --git a/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs b/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs
index eb94a9db03..8958418f68 100644
--- a/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs
+++ b/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs
@@ -36,6 +36,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
public class UserProvisioningService : IUserProvisioningService
{
+ private static readonly IEnumerable ValidCompanyUserStatusIds = new[] { UserStatusId.ACTIVE, UserStatusId.INACTIVE, UserStatusId.PENDING };
private readonly IProvisioningManager _provisioningManager;
private readonly IPortalRepositories _portalRepositories;
@@ -68,35 +69,26 @@ public UserProvisioningService(IProvisioningManager provisioningManager, IPortal
Exception? error = null;
var nextPassword = passwordProvider.NextOptionalPassword();
-
try
{
- var (identity, companyUserId) = await GetOrCreateCompanyUser(userRepository, alias, user, companyId, businessPartnerNumber);
-
- cancellationToken.ThrowIfCancellationRequested();
+ var (identity, companyUserId) = await GetOrCreateCompanyUser(userRepository, alias, user, companyId, identityProviderId, businessPartnerNumber);
- userRepository.AddCompanyUserAssignedIdentityProvider(companyUserId, identityProviderId, user.UserId, user.UserName);
- var providerUserId = await CreateSharedIdpUserOrReturnUserId(user, alias, nextPassword, isSharedIdp).ConfigureAwait(false);
- var centralUserId = await CreateCentralUserWithProviderLinks(companyUserId, user, companyName, businessPartnerNumber, Enumerable.Repeat(new IdentityProviderLink(alias, providerUserId, user.UserName), 1));
- userdata = new(centralUserId, companyUserId);
- if (identity == null)
+ userdata.CompanyUserId = companyUserId;
+ if (!string.IsNullOrWhiteSpace(identity?.UserEntityId))
{
- userRepository.AttachAndModifyIdentity(companyUserId, null, cu =>
- {
- cu.UserEntityId = centralUserId;
- });
- }
- else
- {
- identity.UserEntityId = centralUserId;
+ userdata.UserEntityId = identity.UserEntityId;
}
- await AssignRolesToNewUserAsync(userRolesRepository, user.RoleDatas, userdata).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var providerUserId = await CreateSharedIdpUserOrReturnUserId(user, alias, nextPassword, isSharedIdp).ConfigureAwait(false);
+ await HandleCentralKeycloakCreation(user, companyUserId, companyName, businessPartnerNumber, identity, Enumerable.Repeat(new IdentityProviderLink(alias, providerUserId, user.UserName), 1), userRepository, userRolesRepository).ConfigureAwait(false);
}
catch (Exception e) when (e is not OperationCanceledException)
{
error = e;
}
+
if (userdata == default && error == null)
{
error = new UnexpectedConditionException($"failed to create companyUser for provider userid {user.UserId}, username {user.UserName} while not throwing any error");
@@ -108,7 +100,27 @@ public UserProvisioningService(IProvisioningManager provisioningManager, IPortal
}
}
- public async Task CreateCentralUserWithProviderLinks(Guid companyUserId, UserCreationRoleDataIdpInfo user, string companyName, string? businessPartnerNumber, IEnumerable identityProviderLinks)
+ public async Task HandleCentralKeycloakCreation(UserCreationRoleDataIdpInfo user, Guid companyUserId, string companyName, string? businessPartnerNumber, Identity? identity, IEnumerable identityProviderLinks, IUserRepository userRepository, IUserRolesRepository userRolesRepository)
+ {
+ var centralUserId = await CreateCentralUserWithProviderLinks(companyUserId, user, companyName, businessPartnerNumber, identityProviderLinks).ConfigureAwait(false);
+ if (identity == null)
+ {
+ userRepository.AttachAndModifyIdentity(companyUserId, null, cu =>
+ {
+ cu.UserEntityId = centralUserId;
+ cu.UserStatusId = user.UserStatusId;
+ });
+ }
+ else
+ {
+ identity.UserEntityId = centralUserId;
+ identity.UserStatusId = user.UserStatusId;
+ }
+
+ await AssignRolesToNewUserAsync(userRolesRepository, user.RoleDatas, (centralUserId, companyUserId)).ConfigureAwait(false);
+ }
+
+ private async Task CreateCentralUserWithProviderLinks(Guid companyUserId, UserCreationRoleDataIdpInfo user, string companyName, string? businessPartnerNumber, IEnumerable identityProviderLinks)
{
var centralUserId = await _provisioningManager.CreateCentralUserAsync(
new UserProfile(
@@ -133,11 +145,12 @@ await _provisioningManager.AddProviderUserLinkToCentralUserAsync(centralUserId,
return centralUserId;
}
- private async Task<(Identity? identity, Guid companyUserId)> GetOrCreateCompanyUser(
+ public async Task<(Identity? identity, Guid companyUserId)> GetOrCreateCompanyUser(
IUserRepository userRepository,
string alias,
UserCreationRoleDataIdpInfo user,
Guid companyId,
+ Guid identityProviderId,
string? businessPartnerNumber)
{
var businessPartnerRepository = _portalRepositories.GetInstance();
@@ -156,6 +169,8 @@ await _provisioningManager.AddProviderUserLinkToCentralUserAsync(centralUserId,
businessPartnerRepository.CreateCompanyUserAssignedBusinessPartner(companyUserId, businessPartnerNumber);
}
+ userRepository.AddCompanyUserAssignedIdentityProvider(companyUserId, identityProviderId, user.UserId, user.UserName);
+
return (identity, companyUserId);
}
@@ -191,6 +206,7 @@ private Task CreateSharedIdpUserOrReturnUserId(UserCreationRoleDataIdpIn
{
throw new ControllerArgumentException($"user {companyUserId} does not exist");
}
+
var (company, companyUser, identityProvider) = result;
if (identityProvider.IdpAlias == null)
{
@@ -216,15 +232,18 @@ private Task CreateSharedIdpUserOrReturnUserId(UserCreationRoleDataIdpIn
? new ControllerArgumentException($"user {companyUserId} does not exist")
: new ControllerArgumentException($"user {companyUserId} is not associated with application {applicationId}");
}
+
var (company, companyUser, idpAliase) = result;
if (company.CompanyName == null)
{
throw new ConflictException($"assertion failed: companyName of company {company.CompanyId} should never be null here");
}
+
if (!idpAliase.Any())
{
throw new ConflictException($"user {companyUserId} is not associated with any shared idp");
}
+
if (idpAliase.Count() > 1)
{
throw new ConflictException($"user {companyUserId} is associated with more than one shared idp");
@@ -243,14 +262,17 @@ private static string CreateNameString(string? firstName, string? lastName, stri
{
sb.Append(firstName);
}
+
if (lastName != null)
{
sb.AppendFormat((firstName == null ? "{0}" : ", {0}"), lastName);
}
+
if (email != null)
{
sb.AppendFormat((firstName == null && lastName == null) ? "{0}" : " ({0})", email);
}
+
return firstName == null && lastName == null && email == null ? "Dear User" : sb.ToString();
}
@@ -261,9 +283,7 @@ private async Task ValidateDuplicateIdpUsersAsync(IUserRepository userRepo
{
var existingCompanyUserId = Guid.Empty;
- var validCompanyUserStatusIds = new[] { UserStatusId.ACTIVE, UserStatusId.INACTIVE };
-
- await foreach (var (userEntityId, companyUserId) in userRepository.GetMatchingCompanyIamUsersByNameEmail(user.FirstName, user.LastName, user.Email, companyId, validCompanyUserStatusIds).ConfigureAwait(false))
+ await foreach (var (userEntityId, companyUserId) in userRepository.GetMatchingCompanyIamUsersByNameEmail(user.FirstName, user.LastName, user.Email, companyId, ValidCompanyUserStatusIds).ConfigureAwait(false))
{
if (userEntityId == null)
{
@@ -271,8 +291,10 @@ private async Task ValidateDuplicateIdpUsersAsync(IUserRepository userRepo
{
existingCompanyUserId = companyUserId;
}
+
continue;
}
+
try
{
if (await _provisioningManager.GetProviderUserLinkDataForCentralUserIdAsync(userEntityId).AnyAsync(link =>
@@ -286,10 +308,11 @@ private async Task ValidateDuplicateIdpUsersAsync(IUserRepository userRepo
// when searching for duplicates this is not a validation-error
}
}
+
return existingCompanyUserId;
}
- private async Task AssignRolesToNewUserAsync(IUserRolesRepository userRolesRepository, IEnumerable roleDatas, (string UserEntityId, Guid CompanyUserId) userdata)
+ public async Task AssignRolesToNewUserAsync(IUserRolesRepository userRolesRepository, IEnumerable roleDatas, (string UserEntityId, Guid CompanyUserId) userdata)
{
if (roleDatas.Any())
{
@@ -304,6 +327,7 @@ private async Task AssignRolesToNewUserAsync(IUserRolesRepository userRolesRepos
var roleId = roleDatas.First(roleInfo => roleInfo.ClientClientId == assigned.Client && roleInfo.UserRoleText == role).UserRoleId;
userRolesRepository.CreateIdentityAssignedRole(userdata.CompanyUserId, roleId);
}
+
messages.AddRange(clientRoleNames[assigned.Client].Except(assigned.Roles).Select(roleName => $"clientId: {assigned.Client}, role: {roleName}"));
}
diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
index d449c14eed..5c32ed6216 100644
--- a/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
+++ b/tests/administration/Administration.Service.Tests/BusinessLogic/NetworkBusinessLogicTests.cs
@@ -23,7 +23,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
-using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
@@ -52,7 +51,6 @@ public class NetworkBusinessLogicTests
private readonly IIdentityService _identityService;
private readonly IUserProvisioningService _userProvisioningService;
private readonly INetworkRegistrationProcessHelper _networkRegistrationProcessHelper;
- private readonly IMailingService _mailingService;
private readonly IPortalRepositories _portalRepositories;
private readonly ICompanyRepository _companyRepository;
@@ -75,7 +73,6 @@ public NetworkBusinessLogicTests()
_portalRepositories = A.Fake();
_identityService = A.Fake();
_networkRegistrationProcessHelper = A.Fake();
- _mailingService = A.Fake();
_companyRepository = A.Fake();
_companyRolesRepository = A.Fake();
@@ -102,7 +99,7 @@ public NetworkBusinessLogicTests()
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_identityProviderRepository);
A.CallTo(() => _portalRepositories.GetInstance()).Returns(_countryRepository);
- _sut = new NetworkBusinessLogic(_portalRepositories, _identityService, _userProvisioningService, _networkRegistrationProcessHelper, _mailingService, options);
+ _sut = new NetworkBusinessLogic(_portalRepositories, _identityService, _userProvisioningService, _networkRegistrationProcessHelper, options);
SetupRepos();
}
@@ -362,8 +359,8 @@ public async Task HandlePartnerRegistration_WithUserCreationThrowsException_Thro
A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.PARTNER_REGISTRATION))
.Returns(new Process(processId, default, default));
- A.CallTo(() => _userProvisioningService.CreateOwnCompanyIdpUsersAsync(A._, A>._, A._))
- .Returns(new[] { (Guid.Empty, "", (string?)null, (Exception?)new UnexpectedConditionException("Test")) }.ToAsyncEnumerable());
+ A.CallTo(() => _userProvisioningService.GetOrCreateCompanyUser(A._, A._, A._, A._, A._, "BPNL00000001TEST"))
+ .Throws(new UnexpectedConditionException("Test message"));
// Act
async Task Act() => await _sut.HandlePartnerRegistration(data).ConfigureAwait(false);
@@ -528,13 +525,11 @@ public async Task HandlePartnerRegistration_WithIdpNotSetAndOnlyOneIdp_CallsExpe
x.ProcessId == newProcessId &&
x.ApplicationId == newApplicationId);
- A.CallTo(() => _userProvisioningService.CreateOwnCompanyIdpUsersAsync(A._, A>._, A._))
+ A.CallTo(() => _userProvisioningService.GetOrCreateCompanyUser(A._, "test-alias", A._, newCompanyId, IdpId, Bpnl))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _identityProviderRepository.CreateCompanyIdentityProviders(A>.That.IsSameSequenceAs(new[] { new ValueTuple(newCompanyId, IdpId) })))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
- A.CallTo(() => _mailingService.SendMails(A._, A>._, A>.That.IsSameSequenceAs(new[] { "OspWelcomeMail" })))
- .MustHaveHappenedOnceExactly();
}
[Fact]
@@ -628,8 +623,6 @@ public async Task HandlePartnerRegistration_WithValidData_CallsExpected()
{
networkRegistrations.Add(new NetworkRegistration(Guid.NewGuid(), externalId, companyId, pId, ospId, companyApplicationId, DateTimeOffset.UtcNow));
});
- A.CallTo(() => _userProvisioningService.CreateOwnCompanyIdpUsersAsync(A._, A>._, A._))
- .Returns(new[] { (Guid.NewGuid(), "ironman", (string?)"testpw", (Exception?)null) }.ToAsyncEnumerable());
// Act
await _sut.HandlePartnerRegistration(data).ConfigureAwait(false);
@@ -663,13 +656,11 @@ public async Task HandlePartnerRegistration_WithValidData_CallsExpected()
x.ProcessId == newProcessId &&
x.ApplicationId == newApplicationId);
- A.CallTo(() => _userProvisioningService.CreateOwnCompanyIdpUsersAsync(A._, A>._, A._))
+ A.CallTo(() => _userProvisioningService.GetOrCreateCompanyUser(A._, "test-alias", A._, newCompanyId, IdpId, Bpnl))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _identityProviderRepository.CreateCompanyIdentityProviders(A>.That.IsSameSequenceAs(new[] { new ValueTuple(newCompanyId, IdpId) })))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
- A.CallTo(() => _mailingService.SendMails(A._, A>._, A>.That.IsSameSequenceAs(new[] { "OspWelcomeMail" })))
- .MustHaveHappenedOnceExactly();
}
#endregion
diff --git a/tests/processes/NetworkRegistration.Library.Tests/NetworkRegistrationHandlerTests.cs b/tests/processes/NetworkRegistration.Library.Tests/NetworkRegistrationHandlerTests.cs
index 93fe9a3710..6d7b38c446 100644
--- a/tests/processes/NetworkRegistration.Library.Tests/NetworkRegistrationHandlerTests.cs
+++ b/tests/processes/NetworkRegistration.Library.Tests/NetworkRegistrationHandlerTests.cs
@@ -21,6 +21,7 @@
using Microsoft.Extensions.Options;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
+using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
@@ -42,28 +43,47 @@ public class NetworkRegistrationHandlerTests
private readonly IProvisioningManager _provisioningManger;
private readonly IUserRepository _userRepository;
+ private readonly INetworkRepository _networkRepository;
private readonly NetworkRegistrationHandler _sut;
- private readonly NetworkRegistrationProcessSettings _settings;
+ private readonly IMailingService _mailingService;
public NetworkRegistrationHandlerTests()
{
var portalRepositories = A.Fake();
_userRepository = A.Fake();
+ _networkRepository = A.Fake();
_userProvisioningService = A.Fake();
_provisioningManger = A.Fake();
+ _mailingService = A.Fake();
- _settings = new NetworkRegistrationProcessSettings
+ var settings = new NetworkRegistrationProcessSettings
{
InitialRoles = Enumerable.Repeat(new UserRoleConfig("cl1", Enumerable.Repeat("Company Admin", 1)), 1)
};
var options = A.Fake>();
- A.CallTo(() => options.Value).Returns(_settings);
+ A.CallTo(() => options.Value).Returns(settings);
A.CallTo(() => portalRepositories.GetInstance()).Returns(_userRepository);
+ A.CallTo(() => portalRepositories.GetInstance()).Returns(_networkRepository);
- _sut = new NetworkRegistrationHandler(portalRepositories, _userProvisioningService, _provisioningManger, options);
+ _sut = new NetworkRegistrationHandler(portalRepositories, _userProvisioningService, _provisioningManger, _mailingService, options);
+ }
+
+ [Fact]
+ public async Task SynchronizeUser_WithoutOspName_ThrowsUnexpectedConditionException()
+ {
+ // Arrange
+ A.CallTo(() => _networkRepository.GetOspCompanyName(NetworkRegistrationId))
+ .Returns((string?)null);
+
+ // Act
+ async Task Act() => await _sut.SynchronizeUser(NetworkRegistrationId).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be("Onboarding Service Provider name must be set");
}
[Theory]
@@ -78,6 +98,8 @@ public async Task SynchronizeUser_WithUserDataNull_ThrowsConflictException(strin
"123456789", "Test Company", "BPNL00000001TEST",
Enumerable.Repeat(new ProviderLinkData("ironman", "idp1", "id1234"), 1));
+ A.CallTo(() => _networkRepository.GetOspCompanyName(NetworkRegistrationId))
+ .Returns("Onboarding Service Provider");
A.CallTo(() => _userRepository.GetUserAssignedIdentityProviderForNetworkRegistration(NetworkRegistrationId))
.Returns(new[]
{
@@ -102,10 +124,12 @@ public async Task SynchronizeUser_WithAliasNull_ThrowsConflictException()
var user1 = new CompanyUserIdentityProviderProcessData(user1Id, "tony", "stark", "tony@stark.com", "123456789", "Test Company", "BPNL00000001TEST",
Enumerable.Repeat(new ProviderLinkData("ironman", null, "id1234"), 1));
+ A.CallTo(() => _networkRepository.GetOspCompanyName(NetworkRegistrationId))
+ .Returns("Onboarding Service Provider");
A.CallTo(() => _userRepository.GetUserAssignedIdentityProviderForNetworkRegistration(NetworkRegistrationId))
.Returns(new[]
{
- user1,
+ user1
}.ToAsyncEnumerable());
A.CallTo(() => _userProvisioningService.GetRoleDatas(A>._))
.Returns(Enumerable.Repeat(new UserRoleData(UserRoleIds, "cl1", "Company Admin"), 1).ToAsyncEnumerable());
@@ -123,7 +147,6 @@ public async Task SynchronizeUser_WithValidData_ReturnsExpected()
{
// Arrange
var user1Id = Guid.NewGuid().ToString();
- var user2Id = Guid.NewGuid().ToString();
var user1 = new CompanyUserIdentityProviderProcessData(Guid.NewGuid(), "tony", "stark", "tony@stark.com",
"123456789", "Test Company", "BPNL00000001TEST",
Enumerable.Repeat(new ProviderLinkData("ironman", "idp1", "id1234"), 1));
@@ -131,14 +154,14 @@ public async Task SynchronizeUser_WithValidData_ReturnsExpected()
"steven@strange.com", "987654321", "Test Company", "BPNL00000001TEST",
Enumerable.Repeat(new ProviderLinkData("drstrange", "idp1", "id9876"), 1));
+ A.CallTo(() => _networkRepository.GetOspCompanyName(NetworkRegistrationId))
+ .Returns("Onboarding Service Provider");
A.CallTo(() => _userRepository.GetUserAssignedIdentityProviderForNetworkRegistration(NetworkRegistrationId))
.Returns(new[]
{
user1,
user2
}.ToAsyncEnumerable());
- A.CallTo(() => _userProvisioningService.CreateCentralUserWithProviderLinks(user2.CompanyUserId, A._, A._, A._, A>._))
- .Returns(user2Id);
A.CallTo(() => _userProvisioningService.GetRoleDatas(A>._))
.Returns(Enumerable.Repeat(new UserRoleData(UserRoleIds, "cl1", "Company Admin"), 1).ToAsyncEnumerable());
A.CallTo(() => _provisioningManger.GetUserByUserName(user1.CompanyUserId.ToString())).Returns(user1Id);
@@ -148,12 +171,19 @@ public async Task SynchronizeUser_WithValidData_ReturnsExpected()
var result = await _sut.SynchronizeUser(NetworkRegistrationId).ConfigureAwait(false);
// Assert
- A.CallTo(() => _userProvisioningService.CreateCentralUserWithProviderLinks(user2.CompanyUserId, A._, A._, A._, A>._))
+ A.CallTo(() => _userProvisioningService.HandleCentralKeycloakCreation(A._, user1.CompanyUserId, A._, A._, null, A>._, A._, A._))
+ .MustNotHaveHappened();
+ A.CallTo(() => _userProvisioningService.HandleCentralKeycloakCreation(A._, user2.CompanyUserId, A._, A._, null, A>._, A._, A._))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _userRepository.AttachAndModifyIdentity(user1.CompanyUserId, A>._, A>._))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _userRepository.AttachAndModifyIdentity(user2.CompanyUserId, A>._, A>._))
+ .MustNotHaveHappened();
+ A.CallTo(() => _mailingService.SendMails("tony@stark.com", A>._, A>._))
+ .MustHaveHappenedOnceExactly();
+ A.CallTo(() => _mailingService.SendMails("steven@strange.com", A>._, A>._))
.MustHaveHappenedOnceExactly();
+
result.modified.Should().BeFalse();
result.processMessage.Should().BeNull();
result.stepStatusId.Should().Be(ProcessStepStatusId.DONE);
From cac5fd6d1d565c20e57ac54d7d4d26a32a92adf9 Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 20:24:30 +0200
Subject: [PATCH 07/26] feat(connector): add osp idp detail endpoint (#283)
add /api/administration/identityprovider/network/identityproviders/managed/{identityProviderId}
endpoint to get idp information with the connected companies
---------
Refs: CPLP-3194
Reviewed-by: Norbert Truchsess
Co-authored-by: Norbert Truchsess
---
.../IIdentityProviderBusinessLogic.cs | 1 +
.../IdentityProviderBusinessLogic.cs | 30 ++++
.../Controllers/IdentityProviderController.cs | 23 +++
...tyProviderDetailsWithConnectedCompanies.cs | 35 ++++
.../Models/ConnectedCompanyData.cs | 26 +++
.../IIdentityProviderRepository.cs | 1 +
.../IdentityProviderRepository.cs | 13 ++
.../IdentityProviderBusinessLogicTests.cs | 158 ++++++++++++++++++
.../IdentityProviderControllerTests.cs | 13 ++
.../IdentityProviderRepositoryTests.cs | 51 ++++++
10 files changed, 351 insertions(+)
create mode 100644 src/administration/Administration.Service/Models/IdentityProviderDetailsWithConnectedCompanies.cs
create mode 100644 src/portalbackend/PortalBackend.DBAccess/Models/ConnectedCompanyData.cs
diff --git a/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs
index 07fa0078d3..ad7198144a 100644
--- a/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs
@@ -40,4 +40,5 @@ public interface IIdentityProviderBusinessLogic
ValueTask CreateOrUpdateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, UserLinkData userLinkData, Guid companyId);
ValueTask GetOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, Guid companyId);
ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync(Guid companyUserId, Guid identityProviderId, Guid companyId);
+ ValueTask GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId);
}
diff --git a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
index af53fd38a1..6b6440652e 100644
--- a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
@@ -550,6 +550,36 @@ public async ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync(Guid compan
}
}
+ public async ValueTask GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId)
+ {
+ var companyId = _identityService.IdentityData.CompanyId;
+
+ var (alias, category, isOwnerCompany, typeId, connectedCompanies) = await _portalRepositories.GetInstance().GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, companyId).ConfigureAwait(false);
+ if (!isOwnerCompany)
+ {
+ throw new ConflictException($"identityProvider {identityProviderId} is not associated with company {companyId}");
+ }
+
+ if (alias == null)
+ {
+ throw new NotFoundException($"identityProvider {identityProviderId} does not exist");
+ }
+
+ if (category == IdentityProviderCategoryId.KEYCLOAK_SAML && typeId is IdentityProviderTypeId.SHARED)
+ {
+ throw new ConflictException("Shared Idps must not use SAML");
+ }
+
+ var details = category switch
+ {
+ IdentityProviderCategoryId.KEYCLOAK_OIDC => await GetIdentityProviderDetailsOidc(identityProviderId, alias, category, typeId).ConfigureAwait(false),
+ IdentityProviderCategoryId.KEYCLOAK_SAML => await GetIdentityProviderDetailsSaml(identityProviderId, alias, typeId).ConfigureAwait(false),
+ _ => throw new UnexpectedConditionException($"unexpected value for category '{category}' of identityProvider '{identityProviderId}'")
+ };
+
+ return new(details.identityProviderId, details.alias, details.identityProviderCategoryId, details.IdentityProviderTypeId, details.displayName, details.redirectUrl, details.enabled, connectedCompanies);
+ }
+
public async IAsyncEnumerable GetOwnCompanyUsersIdentityProviderDataAsync(IEnumerable identityProviderIds, Guid companyId, bool unlinkedUsersOnly)
{
var identityProviderAliasDatas = await GetOwnCompanyUsersIdentityProviderAliasDataInternalAsync(identityProviderIds, companyId).ConfigureAwait(false);
diff --git a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs
index 406418efda..de2208c728 100644
--- a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs
+++ b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs
@@ -92,6 +92,29 @@ public async ValueTask> CreateOwnCompanyId
return (ActionResult)CreatedAtRoute(nameof(GetOwnCompanyIdentityProvider), new { identityProviderId = details.identityProviderId }, details);
}
+ ///
+ /// Gets a specific identity provider with the connected Companies
+ ///
+ /// Id of the identity provider
+ /// Returns details of the identity provider
+ ///
+ /// Example: GET: api/administration/identityprovider/network/identityproviders/managed/{identityProviderId}
+ ///
+ /// Return the details of the identityProvider.
+ /// The user is not associated with the owner company.
+ /// Unexpected value of protocol.
+ /// Bad Gateway Service Error.
+ [HttpGet]
+ [Authorize(Roles = "view_managed_idp")]
+ [Authorize(Policy = PolicyTypes.ValidCompany)]
+ [Route("network/identityproviders/managed/{identityProviderId}")]
+ [ProducesResponseType(typeof(IdentityProviderDetailsWithConnectedCompanies), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
+ [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status502BadGateway)]
+ public ValueTask GetOwnIdentityProviderWithConnectedCompanies([FromRoute] Guid identityProviderId) =>
+ _businessLogic.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId);
+
///
/// Gets a specific identity provider
///
diff --git a/src/administration/Administration.Service/Models/IdentityProviderDetailsWithConnectedCompanies.cs b/src/administration/Administration.Service/Models/IdentityProviderDetailsWithConnectedCompanies.cs
new file mode 100644
index 0000000000..2bafac36de
--- /dev/null
+++ b/src/administration/Administration.Service/Models/IdentityProviderDetailsWithConnectedCompanies.cs
@@ -0,0 +1,35 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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.DBAccess.Models;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
+
+public record IdentityProviderDetailsWithConnectedCompanies
+(
+ Guid IdentityProviderId,
+ string? Alias,
+ IdentityProviderCategoryId IdentityProviderCategoryId,
+ IdentityProviderTypeId IdentityProviderTypeId,
+ string? DisplayName,
+ string? RedirectUrl,
+ bool? Enabled,
+ IEnumerable ConnectedCompanies
+);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/ConnectedCompanyData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/ConnectedCompanyData.cs
new file mode 100644
index 0000000000..2e34e3e387
--- /dev/null
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/ConnectedCompanyData.cs
@@ -0,0 +1,26 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2023 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.PortalBackend.DBAccess.Models;
+
+public record ConnectedCompanyData
+(
+ Guid CompanyId,
+ string CompanyName
+);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs
index 534db19d70..aa40fb0601 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs
@@ -35,6 +35,7 @@ public interface IIdentityProviderRepository
Task GetSharedIdentityProviderIamAliasDataUntrackedAsync(Guid companyId);
Task GetIdpCategoryIdByUserIdAsync(Guid companyUserId, Guid userCompanyId);
Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnOrOwnerCompany, IdentityProviderTypeId TypeId)> GetOwnCompanyIdentityProviderAliasUntrackedAsync(Guid identityProviderId, Guid companyId);
+ Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnerCompany, IdentityProviderTypeId TypeId, IEnumerable ConnectedCompanies)> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId, Guid companyId);
Task<(bool IsOwner, string? Alias, IdentityProviderCategoryId IdentityProviderCategory, IdentityProviderTypeId IdentityProviderTypeId, IEnumerable<(Guid CompanyId, IEnumerable Aliase)>? CompanyIdAliase)> GetOwnCompanyIdentityProviderUpdateDataUntrackedAsync(Guid identityProviderId, Guid companyId, bool queryAliase);
IAsyncEnumerable<(Guid IdentityProviderId, IdentityProviderCategoryId CategoryId, string? Alias, IdentityProviderTypeId TypeId)> GetCompanyIdentityProviderCategoryDataUntracked(Guid companyId);
IAsyncEnumerable<(Guid IdentityProviderId, string Alias)> GetOwnCompanyIdentityProviderAliasDataUntracked(Guid companyId, IEnumerable identityProviderIds);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs
index a2320ad015..f6ec7db2a8 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs
@@ -108,6 +108,19 @@ public IamIdentityProvider CreateIamIdentityProvider(Guid identityProviderId, st
identityProvider.IdentityProviderTypeId))
.SingleOrDefaultAsync();
+ public Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnerCompany, IdentityProviderTypeId TypeId, IEnumerable ConnectedCompanies)> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId, Guid companyId) =>
+ _context.IdentityProviders
+ .Where(identityProvider => identityProvider.Id == identityProviderId)
+ .Select(identityProvider =>
+ new ValueTuple>(
+ identityProvider.IamIdentityProvider!.IamIdpAlias,
+ identityProvider.IdentityProviderCategoryId,
+ identityProvider.OwnerId == companyId,
+ identityProvider.IdentityProviderTypeId,
+ identityProvider.Companies.Select(c => new ConnectedCompanyData(c.Id, c.Name))
+ ))
+ .SingleOrDefaultAsync();
+
public Task<(bool IsOwner, string? Alias, IdentityProviderCategoryId IdentityProviderCategory, IdentityProviderTypeId IdentityProviderTypeId, IEnumerable<(Guid CompanyId, IEnumerable Aliase)>? CompanyIdAliase)> GetOwnCompanyIdentityProviderUpdateDataUntrackedAsync(Guid identityProviderId, Guid companyId, bool queryAliase) =>
_context.IdentityProviders
.Where(identityProvider => identityProvider.Id == identityProviderId)
diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/IdentityProviderBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/IdentityProviderBusinessLogicTests.cs
index 4960d50ced..8def00ec89 100644
--- a/tests/administration/Administration.Service.Tests/BusinessLogic/IdentityProviderBusinessLogicTests.cs
+++ b/tests/administration/Administration.Service.Tests/BusinessLogic/IdentityProviderBusinessLogicTests.cs
@@ -28,6 +28,7 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.IO;
using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
+using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
@@ -2054,6 +2055,163 @@ public async Task GetOwnCompanyUsersIdentityProviderDataAsync_WithoutMatchingIdp
#endregion
+ #region GetOwnIdentityProviderWithConnectedCompanies
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithDifferentCompany_ThrowsConflictException()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns((string.Empty, IdentityProviderCategoryId.KEYCLOAK_OIDC, false, IdentityProviderTypeId.OWN, Enumerable.Empty()));
+
+ // Act
+ async Task Act() => await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"identityProvider {identityProviderId} is not associated with company {_companyId}");
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithAliasNull_ThrowsNotFoundException()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns((null, IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Empty()));
+
+ // Act
+ async Task Act() => await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ var ex = await Assert.ThrowsAsync(Act);
+ ex.Message.Should().Be($"identityProvider {identityProviderId} does not exist");
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithOidcWithoutExistingKeycloakClient_CallsExpected()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns(("cl1", IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Empty()));
+ A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataOIDCAsync("cl1"))
+ .Throws(new KeycloakEntityNotFoundException("cl1 not existing"));
+
+ // Act
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ result.DisplayName.Should().BeNull();
+ result.Enabled.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidOidc_CallsExpected()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var companyId = Guid.NewGuid();
+
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns(("cl1", IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
+ A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataOIDCAsync("cl1"))
+ .Returns(_fixture.Build().With(x => x.Enabled, true).With(x => x.DisplayName, "dis-oidc").Create());
+ A.CallTo(() => _provisioningManager.GetIdentityProviderMappers("cl1"))
+ .Returns(_fixture.CreateMany(3).ToAsyncEnumerable());
+
+ // Act
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ result.DisplayName.Should().Be("dis-oidc");
+ result.Enabled.Should().BeTrue();
+ result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithSamlWithoutExistingKeycloakClient_CallsExpected()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var companyId = Guid.NewGuid();
+
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns(("saml-alias", IdentityProviderCategoryId.KEYCLOAK_SAML, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
+ A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataSAMLAsync("saml-alias"))
+ .Throws(new KeycloakEntityNotFoundException("saml-alias"));
+
+ // Act
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ result.DisplayName.Should().BeNull();
+ result.Enabled.Should().BeNull();
+ result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidSaml_CallsExpected()
+ {
+ // Arrange
+ var identityProviderId = Guid.NewGuid();
+ var companyId = Guid.NewGuid();
+
+ var sut = new IdentityProviderBusinessLogic(
+ _portalRepositories,
+ _provisioningManager,
+ _identityService,
+ _options,
+ _logger);
+ A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
+ .Returns(("saml-alias", IdentityProviderCategoryId.KEYCLOAK_SAML, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
+ A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataSAMLAsync("saml-alias"))
+ .Returns(_fixture.Build().With(x => x.Enabled, true).With(x => x.DisplayName, "dis-saml").Create());
+ A.CallTo(() => _provisioningManager.GetIdentityProviderMappers("saml-alias"))
+ .Returns(_fixture.CreateMany(2).ToAsyncEnumerable());
+
+ // Act
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);
+
+ // Assert
+ result.DisplayName.Should().Be("dis-saml");
+ result.Enabled.Should().BeTrue();
+ result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
+ }
+
+ #endregion
+
#region Setup
private void SetupCreateOwnCompanyIdentityProvider(IamIdentityProviderProtocol protocol = IamIdentityProviderProtocol.OIDC, ICollection? idps = null, ICollection? companyIdps = null, ICollection? iamIdps = null)
diff --git a/tests/administration/Administration.Service.Tests/Controllers/IdentityProviderControllerTests.cs b/tests/administration/Administration.Service.Tests/Controllers/IdentityProviderControllerTests.cs
index b201d1b8f9..406e973d4a 100644
--- a/tests/administration/Administration.Service.Tests/Controllers/IdentityProviderControllerTests.cs
+++ b/tests/administration/Administration.Service.Tests/Controllers/IdentityProviderControllerTests.cs
@@ -66,4 +66,17 @@ public async Task DeleteOwnCompanyIdentityProvider_WithValidData_ReturnsOk()
//Assert
A.CallTo(() => _logic.DeleteCompanyIdentityProviderAsync(id)).MustHaveHappenedOnceExactly();
}
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidData_ReturnsOk()
+ {
+ //Arrange
+ var id = Guid.NewGuid();
+
+ //Act
+ await this._controller.GetOwnIdentityProviderWithConnectedCompanies(id).ConfigureAwait(false);
+
+ //Assert
+ A.CallTo(() => _logic.GetOwnIdentityProviderWithConnectedCompanies(id)).MustHaveHappenedOnceExactly();
+ }
}
diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/IdentityProviderRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/IdentityProviderRepositoryTests.cs
index aa8faa72b9..e576f6e30b 100644
--- a/tests/portalbackend/PortalBackend.DBAccess.Tests/IdentityProviderRepositoryTests.cs
+++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/IdentityProviderRepositoryTests.cs
@@ -340,6 +340,57 @@ public async Task GetCompanyNameIdpAliasUntrackedAsync_ReturnsExpectedResult()
#endregion
+ #region GetOwnIdentityProviderWithConnectedCompanies
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithNotOwned_ReturnsExpected()
+ {
+ var sut = await CreateSut().ConfigureAwait(false);
+
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(new Guid("38f56465-ce26-4f25-9745-1791620dc198"), new Guid("3390c2d7-75c1-4169-aa27-6ce00e1f3cdd")).ConfigureAwait(false);
+
+ // Assert
+ result.Alias.Should().Be("Idp-123");
+ result.IsOwnerCompany.Should().BeFalse();
+ result.TypeId.Should().Be(IdentityProviderTypeId.MANAGED);
+ result.ConnectedCompanies.Should().HaveCount(2).And.Satisfy(
+ x => x.CompanyId == new Guid("0dcd8209-85e2-4073-b130-ac094fb47106") && x.CompanyName == "SAP AG",
+ x => x.CompanyId == new Guid("3390c2d7-75c1-4169-aa27-6ce00e1f3cdd") && x.CompanyName == "Service Provider");
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValid_ReturnsExpected()
+ {
+ var sut = await CreateSut().ConfigureAwait(false);
+
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(new Guid("38f56465-ce26-4f25-9745-1791620dc199"), new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f88")).ConfigureAwait(false);
+
+ // Assert
+ result.Alias.Should().Be("Test-Alias");
+ result.IsOwnerCompany.Should().BeTrue();
+ result.TypeId.Should().Be(IdentityProviderTypeId.OWN);
+ result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x =>
+ x.CompanyId == new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f88") && x.CompanyName == "CX-Test-Access");
+ }
+
+ [Fact]
+ public async Task GetOwnIdentityProviderWithConnectedCompanies_WithMultipleValid_ReturnsExpected()
+ {
+ var sut = await CreateSut().ConfigureAwait(false);
+
+ var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(new Guid("38f56465-ce26-4f25-9745-1791620dc198"), new Guid("ac861325-bc54-4583-bcdc-9e9f2a38ff84")).ConfigureAwait(false);
+
+ // Assert
+ result.Alias.Should().Be("Idp-123");
+ result.IsOwnerCompany.Should().BeTrue();
+ result.TypeId.Should().Be(IdentityProviderTypeId.MANAGED);
+ result.ConnectedCompanies.Should().HaveCount(2).And.Satisfy(
+ x => x.CompanyId == new Guid("0dcd8209-85e2-4073-b130-ac094fb47106") && x.CompanyName == "SAP AG",
+ x => x.CompanyId == new Guid("3390c2d7-75c1-4169-aa27-6ce00e1f3cdd") && x.CompanyName == "Service Provider");
+ }
+
+ #endregion
+
#region Setup
private async Task<(IdentityProviderRepository, PortalDbContext)> CreateSutWithContext()
From 0ad522ea830ec4a66a7c01669324657827037f0a Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 22:24:03 +0200
Subject: [PATCH 08/26] fix(bpdm): handle nullable SharingProcessStarted (#289)
Refs: CPLP-3226
Reviewed-by: Norbert Truchsess
---
.../BusinessLogic/BpdmBusinessLogic.cs | 5 ++
.../Bpdm.Library/Models/BpdmSharingState.cs | 6 +--
.../Bpdm.Library/BpdmBusinessLogicTests.cs | 54 +++++++++++++++++--
3 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs
index a197ddf291..e0ce7487c1 100644
--- a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs
+++ b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs
@@ -108,6 +108,11 @@ public BpdmBusinessLogic(IPortalRepositories portalRepositories, IBpdmService bp
}
var sharingState = await _bpdmService.GetSharingState(context.ApplicationId, cancellationToken).ConfigureAwait(false);
+ if (sharingState.SharingProcessStarted == null)
+ {
+ return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult(ProcessStepStatusId.TODO, null, null, null, false, "SharingProcessStarted was not set");
+ }
+
return sharingState.SharingStateType switch
{
BpdmSharingStateType.Success =>
diff --git a/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs b/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs
index dc6086a8a8..0f2ecba27e 100644
--- a/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs
+++ b/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs
@@ -24,13 +24,13 @@ public record BpdmPaginationSharingStateOutput(
);
public record BpdmSharingState(
- BpdmSharingStateBusinessPartnerType BusinessPartnerType,
+ BpdmSharingStateBusinessPartnerType? BusinessPartnerType,
Guid ExternalId,
- BpdmSharingStateType SharingStateType,
+ BpdmSharingStateType? SharingStateType,
string? SharingErrorCode,
string? SharingErrorMessage,
string? Bpn,
- DateTimeOffset SharingProcessStarted
+ DateTimeOffset? SharingProcessStarted
);
public enum BpdmSharingStateType
diff --git a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs
index 5b81c02415..701f66a150 100644
--- a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs
+++ b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs
@@ -38,6 +38,7 @@ public class BpdmBusinessLogicTests
private static readonly Guid IdWithBpn = new("c244f79a-7faf-4c59-bb85-fbfdf72ce46f");
private static readonly Guid IdWithSharingPending = new("920AF606-9581-4EAD-A7FD-78480F42D3A1");
private static readonly Guid IdWithSharingError = new("9460ED6B-2DD3-4446-9B9D-9AE3640717F4");
+ private static readonly Guid IdWithoutSharingProcessStarted = new("f167835a-9859-4ae4-8f1d-b5d682e2562c");
private static readonly Guid IdWithStateCreated = new("bda6d1b5-042e-493a-894c-11f3a89c12b1");
private static readonly Guid IdWithoutZipCode = new("beaa6de5-d411-4da8-850e-06047d3170be");
private static readonly Guid ValidCompanyId = new("abf990f8-0c27-43dc-bbd0-b1bce964d8f4");
@@ -335,6 +336,38 @@ public async Task HandlePullLegalEntity_WithSharingStateError_ThrowsServiceExcep
ex.Message.Should().Be($"ErrorCode: Code 43, ErrorMessage: This is a test sharing state error");
}
+ [Fact]
+ public async Task HandlePullLegalEntity_WithSharingProcessStartedNotSet_ReturnsExpected()
+ {
+ // Arrange
+ var company = new Company(Guid.NewGuid(), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow)
+ {
+ BusinessPartnerNumber = "1"
+ };
+ var checklistEntry = _fixture.Build()
+ .With(x => x.ApplicationChecklistEntryStatusId, ApplicationChecklistEntryStatusId.TO_DO)
+ .Create();
+ var checklist = new Dictionary
+ {
+ {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE},
+ {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO},
+ }
+ .ToImmutableDictionary();
+ var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutSharingProcessStarted, default, checklist, Enumerable.Empty());
+ SetupForHandlePullLegalEntity(company);
+
+ // Act
+ var result = await _logic.HandlePullLegalEntity(context, CancellationToken.None).ConfigureAwait(false);
+
+ // Assert
+ result.ModifyChecklistEntry?.Invoke(checklistEntry);
+ checklistEntry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.TO_DO);
+ result.ScheduleStepTypeIds.Should().BeNull();
+ result.SkipStepTypeIds.Should().BeNull();
+ result.Modified.Should().BeFalse();
+ result.ProcessMessage.Should().Be("SharingProcessStarted was not set");
+ }
+
[Fact]
public async Task HandlePullLegalEntity_WithSharingTypePending_ReturnsExpected()
{
@@ -469,13 +502,13 @@ private void SetupForHandlePullLegalEntity(Company? company = null)
.With(x => x.BusinessPartnerNumber, (string?)null)
.Create();
- A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn || x == IdWithSharingError || x == IdWithSharingPending)))
+ A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn || x == IdWithSharingError || x == IdWithSharingPending || x == IdWithoutSharingProcessStarted)))
.Returns((ValidCompanyId, validData));
A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithStateCreated)))
.Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>())));
A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode)))
.Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>())));
- A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Not.Matches(x => x == IdWithStateCreated || x == IdWithBpn || x == IdWithoutZipCode || x == IdWithSharingError || x == IdWithSharingPending)))
+ A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Not.Matches(x => x == IdWithStateCreated || x == IdWithBpn || x == IdWithoutZipCode || x == IdWithSharingError || x == IdWithSharingPending || x == IdWithoutSharingProcessStarted)))
.Returns(new ValueTuple());
A.CallTo(() => _bpdmService.FetchInputLegalEntity(A.That.Matches(x => x == IdWithStateCreated.ToString()), A._))
@@ -484,15 +517,26 @@ private void SetupForHandlePullLegalEntity(Company? company = null)
.Returns(_fixture.Build().With(x => x.Bpn, (string?)null).Create());
A.CallTo(() => _bpdmService.FetchInputLegalEntity(A.That.Matches(x => x == IdWithBpn.ToString()), A._))
.Returns(_fixture.Build().With(x => x.Bpn, "CAXSDUMMYCATENAZZ").Create());
- A.CallTo(() => _bpdmService.GetSharingState(A.That.Matches(x => x == IdWithBpn || x == IdWithStateCreated || x == IdWithoutZipCode), A._))
- .Returns(_fixture.Build().With(x => x.SharingStateType, BpdmSharingStateType.Success).Create());
+ A.CallTo(() => _bpdmService.GetSharingState(A.That.Matches(x => x == IdWithBpn || x == IdWithStateCreated || x == IdWithoutZipCode || x == IdWithoutSharingProcessStarted), A._))
+ .Returns(_fixture.Build()
+ .With(x => x.SharingStateType, BpdmSharingStateType.Success)
+ .With(x => x.SharingProcessStarted, DateTimeOffset.UtcNow)
+ .Create());
A.CallTo(() => _bpdmService.GetSharingState(IdWithSharingPending, A._))
- .Returns(_fixture.Build().With(x => x.SharingStateType, BpdmSharingStateType.Pending).Create());
+ .Returns(_fixture.Build()
+ .With(x => x.SharingStateType, BpdmSharingStateType.Pending)
+ .With(x => x.SharingProcessStarted, DateTimeOffset.UtcNow)
+ .Create());
A.CallTo(() => _bpdmService.GetSharingState(IdWithSharingError, A._))
.Returns(_fixture.Build()
.With(x => x.SharingStateType, BpdmSharingStateType.Error)
.With(x => x.SharingErrorMessage, "This is a test sharing state error")
.With(x => x.SharingErrorCode, "Code 43")
+ .With(x => x.SharingProcessStarted, DateTimeOffset.UtcNow)
+ .Create());
+ A.CallTo(() => _bpdmService.GetSharingState(IdWithoutSharingProcessStarted, A._))
+ .Returns(_fixture.Build()
+ .With(x => x.SharingProcessStarted, (DateTimeOffset?)null)
.Create());
if (company != null)
From 79fd86f084fbf9393bb9ed4f1047262e7e5af3fe Mon Sep 17 00:00:00 2001
From: Phil Schneider
Date: Tue, 10 Oct 2023 22:51:13 +0200
Subject: [PATCH 09/26] fix(autoSetup): notification will be created directly
after subscribing (#273)
* fix(autoSetup): notification will be created directly after subscribing
instead of creating the notification for an app subscription after
triggering the provider, the notification will directly be created when
subscribing to an offer
Refs: CPLP-3288
---------
Reviewed-by: Norbert Truchsess
---
.../OfferProviderBusinessLogic.cs | 69 +--------------
.../BusinessLogic/AppsBusinessLogic.cs | 2 +-
.../Service/IOfferSubscriptionService.cs | 3 +-
.../Service/OfferSubscriptionService.cs | 70 +++++++++++++--
.../BusinessLogic/ServiceBusinessLogic.cs | 2 +-
.../Models/CompanyInformationData.cs | 3 +-
.../Models/TriggerProviderInformation.cs | 1 -
.../Repositories/CompanyRepository.cs | 13 +--
.../Repositories/ICompanyRepository.cs | 2 +-
.../OfferSubscriptionsRepository.cs | 4 +-
.../OfferProviderBusinessLogicTests.cs | 63 +++----------
.../BusinessLogic/AppBusinessLogicTests.cs | 3 +-
.../Service/OfferSubscriptionServiceTests.cs | 88 +++++++++++--------
.../ServiceBusinessLogicTests.cs | 3 +-
.../CompanyRepositoryTests.cs | 5 +-
15 files changed, 152 insertions(+), 179 deletions(-)
diff --git a/src/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogic.cs b/src/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogic.cs
index 8f8a365362..87d9f3ed28 100644
--- a/src/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogic.cs
+++ b/src/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogic.cs
@@ -18,15 +18,12 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
-using Microsoft.Extensions.Options;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
-using Org.Eclipse.TractusX.Portal.Backend.OfferProvider.Library.DependencyInjection;
using Org.Eclipse.TractusX.Portal.Backend.OfferProvider.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library;
-using System.Text.Json;
namespace Org.Eclipse.TractusX.Portal.Backend.OfferProvider.Library.BusinessLogic;
@@ -35,7 +32,6 @@ public class OfferProviderBusinessLogic : IOfferProviderBusinessLogic
private readonly IPortalRepositories _portalRepositories;
private readonly IOfferProviderService _offerProviderService;
private readonly IProvisioningManager _provisioningManager;
- private readonly OfferProviderSettings _settings;
///
/// Constructor.
@@ -47,13 +43,11 @@ public class OfferProviderBusinessLogic : IOfferProviderBusinessLogic
public OfferProviderBusinessLogic(
IPortalRepositories portalRepositories,
IOfferProviderService offerProviderService,
- IProvisioningManager provisioningManager,
- IOptions options)
+ IProvisioningManager provisioningManager)
{
_portalRepositories = portalRepositories;
_offerProviderService = offerProviderService;
_provisioningManager = provisioningManager;
- _settings = options.Value;
}
///
@@ -77,7 +71,7 @@ public OfferProviderBusinessLogic(
new OfferThirdPartyAutoSetupCustomerData(
data.CompanyInformationData.OrganizationName,
data.CompanyInformationData.Country,
- data.UserEmail),
+ data.CompanyInformationData.CompanyUserEmail),
new OfferThirdPartyAutoSetupPropertyData(
data.CompanyInformationData.BusinessPartnerNumber,
offerSubscriptionId,
@@ -88,15 +82,6 @@ await _offerProviderService
.ConfigureAwait(false);
}
- var content = JsonSerializer.Serialize(new
- {
- AppName = data.OfferName,
- data.OfferId,
- RequestorCompanyName = data.CompanyInformationData.OrganizationName,
- data.UserEmail,
- AutoSetupExecuted = triggerProvider
- });
- await SendNotifications(data.OfferId, data.OfferTypeId, data.SalesManagerId, data.CompanyUserId, content).ConfigureAwait(false);
return (
new[] {
data.IsSingleInstance ?
@@ -107,56 +92,6 @@ await _offerProviderService
null);
}
- private async Task SendNotifications(
- Guid offerId,
- OfferTypeId offerTypeId,
- Guid? salesManagerId,
- Guid companyUserId,
- string notificationContent)
- {
- var serviceManagerRoles = _settings.ServiceManagerRoles;
- var notificationRepository = _portalRepositories.GetInstance();
-
- var notificationTypeId = offerTypeId == OfferTypeId.SERVICE ? NotificationTypeId.SERVICE_REQUEST : NotificationTypeId.APP_SUBSCRIPTION_REQUEST;
- if (salesManagerId.HasValue)
- {
- notificationRepository.CreateNotification(salesManagerId.Value, notificationTypeId, false,
- notification =>
- {
- notification.CreatorUserId = companyUserId;
- notification.Content = notificationContent;
- });
- }
-
- var userRolesRepository = _portalRepositories.GetInstance();
- var roleData = await userRolesRepository
- .GetUserRoleIdsUntrackedAsync(serviceManagerRoles)
- .ToListAsync()
- .ConfigureAwait(false);
- if (roleData.Count < serviceManagerRoles.Sum(clientRoles => clientRoles.UserRoleNames.Count()))
- {
- throw new ConfigurationException($"invalid configuration, at least one of the configured roles does not exist in the database: {string.Join(", ", serviceManagerRoles.Select(clientRoles => $"client: {clientRoles.ClientId}, roles: [{string.Join(", ", clientRoles.UserRoleNames)}]"))}");
- }
-
- await foreach (var receiver in _portalRepositories.GetInstance().GetServiceProviderCompanyUserWithRoleIdAsync(offerId, roleData))
- {
- if (salesManagerId.HasValue && receiver == salesManagerId.Value)
- {
- continue;
- }
-
- notificationRepository.CreateNotification(
- receiver,
- notificationTypeId,
- false,
- notification =>
- {
- notification.CreatorUserId = companyUserId;
- notification.Content = notificationContent;
- });
- }
- }
-
///
public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerProviderCallback(Guid offerSubscriptionId, CancellationToken cancellationToken)
{
diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppsBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppsBusinessLogic.cs
index 3f18eb5213..413bc7f8f5 100644
--- a/src/marketplace/Apps.Service/BusinessLogic/AppsBusinessLogic.cs
+++ b/src/marketplace/Apps.Service/BusinessLogic/AppsBusinessLogic.cs
@@ -177,7 +177,7 @@ public async Task AddFavouriteAppForUserAsync(Guid appId)
///
public Task AddOwnCompanyAppSubscriptionAsync(Guid appId, IEnumerable offerAgreementConsentData) =>
- _offerSubscriptionService.AddOfferSubscriptionAsync(appId, offerAgreementConsentData, OfferTypeId.APP, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles);
+ _offerSubscriptionService.AddOfferSubscriptionAsync(appId, offerAgreementConsentData, OfferTypeId.APP, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles, _settings.ServiceManagerRoles);
///
public Task TriggerActivateOfferSubscription(Guid subscriptionId) =>
diff --git a/src/marketplace/Offers.Library/Service/IOfferSubscriptionService.cs b/src/marketplace/Offers.Library/Service/IOfferSubscriptionService.cs
index 9261f7214b..a9e6158f77 100644
--- a/src/marketplace/Offers.Library/Service/IOfferSubscriptionService.cs
+++ b/src/marketplace/Offers.Library/Service/IOfferSubscriptionService.cs
@@ -21,11 +21,10 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration;
using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
-using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
namespace Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service;
public interface IOfferSubscriptionService
{
- Task AddOfferSubscriptionAsync(Guid offerId, IEnumerable offerAgreementConsentData, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable notificationRecipients);
+ Task AddOfferSubscriptionAsync(Guid offerId, IEnumerable offerAgreementConsentData, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable notificationRecipients, IEnumerable serviceManagerRoles);
}
diff --git a/src/marketplace/Offers.Library/Service/OfferSubscriptionService.cs b/src/marketplace/Offers.Library/Service/OfferSubscriptionService.cs
index 402f35b540..47a02c7f49 100644
--- a/src/marketplace/Offers.Library/Service/OfferSubscriptionService.cs
+++ b/src/marketplace/Offers.Library/Service/OfferSubscriptionService.cs
@@ -29,6 +29,7 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
using System.Collections.Immutable;
+using System.Text.Json;
namespace Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service;
@@ -43,7 +44,7 @@ public class OfferSubscriptionService : IOfferSubscriptionService
///
/// Factory to access the repositories
/// Access to the identity of the user
- /// Mail service.
+ /// Mail service.
public OfferSubscriptionService(
IPortalRepositories portalRepositories,
IIdentityService identityService,
@@ -55,10 +56,10 @@ public OfferSubscriptionService(
}
///
- public async Task AddOfferSubscriptionAsync(Guid offerId, IEnumerable offerAgreementConsentData, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable notificationRecipients)
+ public async Task AddOfferSubscriptionAsync(Guid offerId, IEnumerable offerAgreementConsentData, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable notificationRecipients, IEnumerable serviceManagerRoles)
{
var identity = _identityService.IdentityData;
- var companyInformation = await ValidateCompanyInformationAsync(identity.CompanyId).ConfigureAwait(false);
+ var companyInformation = await ValidateCompanyInformationAsync(identity.CompanyId, identity.UserId).ConfigureAwait(false);
var offerProviderDetails = await ValidateOfferProviderDetailDataAsync(offerId, offerTypeId).ConfigureAwait(false);
if (offerProviderDetails.ProviderCompanyId == null)
@@ -77,6 +78,15 @@ public async Task AddOfferSubscriptionAsync(Guid offerId, IEnumerable serviceManagerRoles)
+ {
+ var notificationRepository = _portalRepositories.GetInstance();
+
+ var notificationTypeId = offerTypeId == OfferTypeId.SERVICE ? NotificationTypeId.SERVICE_REQUEST : NotificationTypeId.APP_SUBSCRIPTION_REQUEST;
+ if (salesManagerId.HasValue)
+ {
+ notificationRepository.CreateNotification(salesManagerId.Value, notificationTypeId, false,
+ notification =>
+ {
+ notification.CreatorUserId = companyUserId;
+ notification.Content = notificationContent;
+ });
+ }
+
+ var userRolesRepository = _portalRepositories.GetInstance();
+ var roleData = await userRolesRepository
+ .GetUserRoleIdsUntrackedAsync(serviceManagerRoles)
+ .ToListAsync()
+ .ConfigureAwait(false);
+ if (roleData.Count < serviceManagerRoles.Sum(clientRoles => clientRoles.UserRoleNames.Count()))
+ {
+ throw new ConfigurationException($"invalid configuration, at least one of the configured roles does not exist in the database: {string.Join(", ", serviceManagerRoles.Select(clientRoles => $"client: {clientRoles.ClientId}, roles: [{string.Join(", ", clientRoles.UserRoleNames)}]"))}");
+ }
+
+ await foreach (var receiver in _portalRepositories.GetInstance().GetServiceProviderCompanyUserWithRoleIdAsync(offerId, roleData))
+ {
+ if (salesManagerId.HasValue && receiver == salesManagerId.Value)
+ {
+ continue;
+ }
+
+ notificationRepository.CreateNotification(
+ receiver,
+ notificationTypeId,
+ false,
+ notification =>
+ {
+ notification.CreatorUserId = companyUserId;
+ notification.Content = notificationContent;
+ });
+ }
+ }
+
private async Task ValidateOfferProviderDetailDataAsync(Guid offerId, OfferTypeId offerTypeId)
{
var offerProviderDetails = await _portalRepositories.GetInstance()
@@ -133,10 +193,10 @@ private async Task ValidateConsent(IEnumerable offerA
}
}
- private async Task ValidateCompanyInformationAsync(Guid companyId)
+ private async Task ValidateCompanyInformationAsync(Guid companyId, Guid companyUserId)
{
var companyInformation = await _portalRepositories.GetInstance()
- .GetOwnCompanyInformationAsync(companyId).ConfigureAwait(false);
+ .GetOwnCompanyInformationAsync(companyId, companyUserId).ConfigureAwait(false);
if (companyInformation == null)
{
throw new ControllerArgumentException($"Company {companyId} does not exist", nameof(companyId));
diff --git a/src/marketplace/Services.Service/BusinessLogic/ServiceBusinessLogic.cs b/src/marketplace/Services.Service/BusinessLogic/ServiceBusinessLogic.cs
index f1fe6618a4..32b79a0cef 100644
--- a/src/marketplace/Services.Service/BusinessLogic/ServiceBusinessLogic.cs
+++ b/src/marketplace/Services.Service/BusinessLogic/ServiceBusinessLogic.cs
@@ -79,7 +79,7 @@ public ServiceBusinessLogic(
///
public Task AddServiceSubscription(Guid serviceId, IEnumerable offerAgreementConsentData) =>
- _offerSubscriptionService.AddOfferSubscriptionAsync(serviceId, offerAgreementConsentData, OfferTypeId.SERVICE, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles);
+ _offerSubscriptionService.AddOfferSubscriptionAsync(serviceId, offerAgreementConsentData, OfferTypeId.SERVICE, _settings.BasePortalAddress, _settings.SubscriptionManagerRoles, _settings.ServiceManagerRoles);
///
public async Task GetServiceDetailsAsync(Guid serviceId, string lang)
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyInformationData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyInformationData.cs
index 32dbca0a4a..3fcd394a13 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyInformationData.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyInformationData.cs
@@ -24,4 +24,5 @@ public record CompanyInformationData(
Guid CompanyId,
string OrganizationName,
string? Country,
- string? BusinessPartnerNumber);
+ string? BusinessPartnerNumber,
+ string? CompanyUserEmail);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/TriggerProviderInformation.cs b/src/portalbackend/PortalBackend.DBAccess/Models/TriggerProviderInformation.cs
index 1d46e0248c..96b1e35bc7 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Models/TriggerProviderInformation.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/TriggerProviderInformation.cs
@@ -27,7 +27,6 @@ public record TriggerProviderInformation(
string? OfferName,
string? AutoSetupUrl,
CompanyInformationData CompanyInformationData,
- string? UserEmail,
OfferTypeId OfferTypeId,
Guid? SalesManagerId,
Guid CompanyUserId,
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
index 76da595d14..32a02c26c6 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
@@ -281,15 +281,16 @@ public IAsyncEnumerable GetCompanyRoleAndConsentAgreemen
true
)).SingleOrDefaultAsync();
- public Task GetOwnCompanyInformationAsync(Guid companyId) =>
+ public Task GetOwnCompanyInformationAsync(Guid companyId, Guid companyUserId) =>
_context.Companies
.AsNoTracking()
.Where(c => c.Id == companyId)
- .Select(user => new CompanyInformationData(
- user.Id,
- user.Name,
- user.Address!.CountryAlpha2Code,
- user.BusinessPartnerNumber
+ .Select(company => new CompanyInformationData(
+ company.Id,
+ company.Name,
+ company.Address!.CountryAlpha2Code,
+ company.BusinessPartnerNumber,
+ company.Identities.Where(x => x.Id == companyUserId && x.IdentityTypeId == IdentityTypeId.COMPANY_USER).Select(x => x.CompanyUser!.Email).SingleOrDefault()
))
.SingleOrDefaultAsync();
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs
index 53609f9668..5b9f9a6739 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs
@@ -157,7 +157,7 @@ public interface ICompanyRepository
/// Returns the CompanyStatus Data
Task<(bool IsActive, bool IsValid)> GetCompanyStatusDataAsync(Guid companyId);
- Task GetOwnCompanyInformationAsync(Guid companyId);
+ Task GetOwnCompanyInformationAsync(Guid companyId, Guid companyUserId);
IAsyncEnumerable GetOwnCompanyRolesAsync(Guid companyId);
///
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferSubscriptionsRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferSubscriptionsRepository.cs
index d8701023b4..bdc16659b8 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferSubscriptionsRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferSubscriptionsRepository.cs
@@ -362,9 +362,9 @@ public Task GetOfferSubscriptionDataForProcessIdAsync(Guid processId) =>
x.RequesterCompany!.Id,
x.RequesterCompany.Name,
x.RequesterCompany.Address!.CountryAlpha2Code,
- x.RequesterCompany.BusinessPartnerNumber
+ x.RequesterCompany.BusinessPartnerNumber,
+ x.Email
),
- x.Email,
x.OfferTypeId,
x.SalesManagerId,
x.CompanyUserId,
diff --git a/tests/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogicTests.cs b/tests/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogicTests.cs
index db8cae93ef..54226de3ee 100644
--- a/tests/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogicTests.cs
+++ b/tests/externalsystems/OfferProvider.Library/BusinessLogic/OfferProviderBusinessLogicTests.cs
@@ -44,54 +44,38 @@ public class OfferProviderBusinessLogicTests
private readonly Guid _salesManagerId = Guid.NewGuid();
private readonly Guid _receiverId = Guid.NewGuid();
- private readonly IFixture _fixture;
- private readonly IPortalRepositories _portalRepositories;
- private readonly INotificationRepository _notificationRepository;
private readonly IOfferSubscriptionsRepository _offerSubscriptionRepository;
private readonly IUserRolesRepository _userRolesRepository;
private readonly IUserRepository _userRepository;
private readonly IOfferProviderService _offerProviderService;
private readonly IProvisioningManager _provisioningManager;
- private readonly OfferProviderSettings _settings;
private readonly OfferProviderBusinessLogic _sut;
public OfferProviderBusinessLogicTests()
{
- _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true });
- _fixture.Behaviors.OfType().ToList()
- .ForEach(b => _fixture.Behaviors.Remove(b));
- _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
- _portalRepositories = A.Fake();
- _notificationRepository = A.Fake();
+ var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true });
+ fixture.Behaviors.OfType().ToList()
+ .ForEach(b => fixture.Behaviors.Remove(b));
+ fixture.Behaviors.Add(new OmitOnRecursionBehavior());
+ var portalRepositories = A.Fake();
_offerSubscriptionRepository = A.Fake();
_userRolesRepository = A.Fake();
_userRepository = A.Fake();
- A.CallTo(() => _portalRepositories.GetInstance())
+ A.CallTo(() => portalRepositories.GetInstance())
.Returns(_offerSubscriptionRepository);
- A.CallTo(() => _portalRepositories.GetInstance())
- .Returns(_notificationRepository);
- A.CallTo(() => _portalRepositories.GetInstance())
+ A.CallTo(() => portalRepositories.GetInstance())
.Returns(_userRolesRepository);
- A.CallTo(() => _portalRepositories.GetInstance())
+ A.CallTo(() => portalRepositories.GetInstance())
.Returns(_userRepository);
_offerProviderService = A.Fake();
_provisioningManager = A.Fake();
- _settings = new OfferProviderSettings
- {
- Password = "test",
- ClientId = "123",
- ClientSecret = "test",
- ServiceManagerRoles = Enumerable.Repeat(new UserRoleConfig("Portal", new[] { "Manager" }), 1)
- };
-
_sut = new OfferProviderBusinessLogic(
- _portalRepositories,
+ portalRepositories,
_offerProviderService,
- _provisioningManager,
- Options.Create(_settings));
+ _provisioningManager);
}
#region TriggerProvider
@@ -130,16 +114,6 @@ public async Task TriggerProvider_ValidMultiInstanceApp_ReturnsExpected(OfferTyp
_offerProviderService.TriggerOfferProvider(A._, A._,
A._))
.MustHaveHappenedOnceExactly();
- A.CallTo(() => _notificationRepository.CreateNotification(_salesManagerId,
- offerTypeId == OfferTypeId.APP
- ? NotificationTypeId.APP_SUBSCRIPTION_REQUEST
- : NotificationTypeId.SERVICE_REQUEST, false, A>._))
- .MustHaveHappenedOnceExactly();
- A.CallTo(() => _notificationRepository.CreateNotification(_receiverId,
- offerTypeId == OfferTypeId.APP
- ? NotificationTypeId.APP_SUBSCRIPTION_REQUEST
- : NotificationTypeId.SERVICE_REQUEST, false, A>._))
- .MustHaveHappenedOnceExactly();
}
[Theory]
@@ -161,16 +135,6 @@ public async Task TriggerProvider_ValidSingleInstanceApp_ReturnsExpected(OfferTy
_offerProviderService.TriggerOfferProvider(A._, A._,
A._))
.MustNotHaveHappened();
- A.CallTo(() => _notificationRepository.CreateNotification(_salesManagerId,
- offerTypeId == OfferTypeId.APP
- ? NotificationTypeId.APP_SUBSCRIPTION_REQUEST
- : NotificationTypeId.SERVICE_REQUEST, false, A>._))
- .MustHaveHappenedOnceExactly();
- A.CallTo(() => _notificationRepository.CreateNotification(_receiverId,
- offerTypeId == OfferTypeId.APP
- ? NotificationTypeId.APP_SUBSCRIPTION_REQUEST
- : NotificationTypeId.SERVICE_REQUEST, false, A>._))
- .MustHaveHappenedOnceExactly();
}
#endregion
@@ -319,8 +283,7 @@ private void SetupTriggerProvider(OfferTypeId offerTypeId = OfferTypeId.APP)
_offerId,
"Test App",
"https://www.test.com",
- new CompanyInformationData(Guid.NewGuid(), "Stark", "DE", "BPNL0000123TEST"),
- "test@email.com",
+ new CompanyInformationData(Guid.NewGuid(), "Stark", "DE", "BPNL0000123TEST", "test@email.com"),
offerTypeId,
_salesManagerId,
_companyUserId,
@@ -332,8 +295,8 @@ private void SetupTriggerProvider(OfferTypeId offerTypeId = OfferTypeId.APP)
_offerId,
"Single Test App",
"https://www.test.com",
- new CompanyInformationData(Guid.NewGuid(), "Stark", "DE", "BPNL0000123TEST"),
- "test@email.com",
+ new CompanyInformationData(Guid.NewGuid(), "Stark", "DE", "BPNL0000123TEST",
+ "test@email.com"),
offerTypeId,
_salesManagerId,
_companyUserId,
diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppBusinessLogicTests.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppBusinessLogicTests.cs
index e62c9636fd..f61f60454c 100644
--- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppBusinessLogicTests.cs
+++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppBusinessLogicTests.cs
@@ -187,7 +187,7 @@ public async Task AddServiceSubscription_ReturnsCorrectId()
var offerSubscriptionId = Guid.NewGuid();
var offerSubscriptionService = A.Fake();
var consentData = _fixture.CreateMany(2);
- A.CallTo(() => offerSubscriptionService.AddOfferSubscriptionAsync(A._, A>._, A._, A._, A>._))
+ A.CallTo(() => offerSubscriptionService.AddOfferSubscriptionAsync(A