diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 6c7aaf31ee..542f515b6a 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -14,7 +14,7 @@ Link to Github issue.
Please delete options that are not relevant.
-- [ ] I have followed the [contributing guidelines](https://github.com/eclipse-tractusx/portal-assets/blob/main/developer/Technical%20Documentation/Dev%20Process/How%20to%20contribute.md#commit-and-pr-guidelines)
+- [ ] I have followed the [contributing guidelines](https://github.com/eclipse-tractusx/portal-assets/blob/main/docs/developer/Technical%20Documentation/Dev%20Process/How%20to%20contribute.md#commit-and-pr-guidelines)
- [ ] I have performed [IP checks](https://eclipse-tractusx.github.io/docs/release/trg-7/trg-7-04#checking-libraries-using-the-eclipse-dash-license-tool) for added or updated 3rd party libraries
- [ ] I have created and linked IP issues or requested their creation by a committer
- [ ] I have performed a self-review of my own code
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 543288e1aa..b4691cbe09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,29 +2,69 @@
New features, fixed bugs, known defects and other noteworthy changes to each release of the Catena-X Portal Backend.
+## 1.8.0-RC4
+
+### Change
+* updated response body of the GET: /api/administration/user/owncompany/users endpoint by changing the "role" section to an array to include role client information ![Tag](https://img.shields.io/static/v1?label=&message=BreakingChange&color=yellow&style=flat)
+* pattern harmonization of 'company name' insert endpoints
+ * POST: api/administration/invitation
+ * POST: api/administration/registration/network/partnerRegistration
+ * POST: api/registration/application/{applicationId}/companyDetailsWithAddress
+ * PUT: /api/apps/appreleaseprocess/{appID}
+ * POST: /api/apps/appreleaseprocess/createapp
+* pattern harmonization of 'company name' search endpoints
+ * GET: api/administration/registration/applicationsWithStatus
+ * GET: api/administration/registration/applications
+* adjusted business logic of post/put BPN endpoints to allow the post/put of lowercase BPNs and ensure the transition to all uppercase by the backend logic (connector controller, registration controller, user controller)
+
+### Feature
+* Administration Service
+ * API endpoints for user account creation backend logic updated to set the providerID (unique username on the IdP which holds the user identity) is getting stored inside the portal db
+ * POST: /api/administration/identityprovider/owncompany/usersfile
+ * POST: /api/administration/registration/network/{externalId}/partnerRegistration
+ * POST: /api/administration/invitation
+ * POST: /api/administration/user/owncompany/users
+ * POST: /api/administration/user/owncompany/identityprovider/{identityProviderId}/users
+ * POST: /api/administration/user/owncompany/identityprovider/{identityProviderId}/usersfile
+ * POST: /api/administration/user/owncompany/usersfile
+ * POST: /api/registration/application/{applicationId}/inviteNewUser
+ * added additional user identity provider attributes (such as idpDisplayName and providerID) for all GET user account data
+ * GET: /api/administration/user/owncompany/users?page=0&size=5
+ * GET: /api/administration/user/owncompany/users/{userId}
+ * GET: /api/administration/user/ownUser
+
+### Technical Support
+* fixed sonar cloud finding to use correct pagination params
+
+### Bugfix
+* changed claimTypes static class of clientId claim to client_id
+
+### Known Knowns
+n/a
+
## 1.8.0-RC3
### Change
-- External Interface Details
- - BPDM interface refactored - bpdm push process was updated to support the new interface spec of the bpdm gate service
- - Clearinghouse interface updated - possible generated clearinghouse service error content is getting saved inside the application comment level
-- Email Template "cx_admin_invitation" enhanced by adding the section and link of the decline url (portal-frontend implementation)
+* External Interface Details
+ * BPDM interface refactored - bpdm push process was updated to support the new interface spec of the bpdm gate service
+ * Clearinghouse interface updated - possible generated clearinghouse service error content is getting saved inside the application comment level
+* Email Template "cx_admin_invitation" enhanced by adding the section and link of the decline url (portal-frontend implementation)
### Feature
-- Onboarding Service Provider Function
- - enabled deactivation of managed idps (administration service) via the existing idp status update endpoint
- - enabled deletion of managed idps (administration service) via the existing idp delete endpoint
- - added new endpoint to enable customer to decline their own company application which was created by an osp
+* Onboarding Service Provider Function
+ * enabled deactivation of managed idps (administration service) via the existing idp status update endpoint
+ * enabled deletion of managed idps (administration service) via the existing idp delete endpoint
+ * added new endpoint to enable customer to decline their own company application which was created by an osp
### Technical Support
-- Release workflow updated by adding additional image tag of type semver
-- Upgraded external packages with security vulnerabilities
+* Release workflow updated by adding additional image tag of type semver
+* Upgraded external packages with security vulnerabilities
### Bugfix
-- Endpoint authorization on valid companyId added for
- - POST: /api/apps/appreleaseprocess/consent/{appId}/agreementConsents
- - POST: /api/services/servicerelease/consent/{serviceId}/agreementConsents
-- Adjusted endpoint GET: api/administration/serviceaccount/owncompany/serviceaccounts to filter for active service accounts by default if no parameter is submitted
+* Endpoint authorization on valid companyId added for
+ * POST: /api/apps/appreleaseprocess/consent/{appId}/agreementConsents
+ * POST: /api/services/servicerelease/consent/{serviceId}/agreementConsents
+* Adjusted endpoint GET: api/administration/serviceaccount/owncompany/serviceaccounts to filter for active service accounts by default if no parameter is submitted
## 1.8.0-RC2
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 1a3e96b3ff..6aedd1f21c 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -20,6 +20,6 @@
1.8.0
- RC3
+ RC4
diff --git a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs
index 21bafbde04..236d7a3e6e 100644
--- a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs
@@ -343,7 +343,7 @@ public IAsyncEnumerable GetCompanyConnectorEndPointAsync(
}
return _portalRepositories.GetInstance()
- .GetConnectorEndPointDataAsync(bpns)
+ .GetConnectorEndPointDataAsync(bpns.Select(x => x.ToUpper()))
.PreSortedGroupBy(data => data.BusinessPartnerNumber)
.Select(group =>
new ConnectorEndPointData(
diff --git a/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs
index 4e264eaabe..17b8904bb9 100644
--- a/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs
@@ -33,7 +33,7 @@ public interface IUserBusinessLogic
Task> GetOwnCompanyUserDatasAsync(int page, int size, GetOwnCompanyUsersFilter filter);
[Obsolete("to be replaced by UserRolesBusinessLogic.GetAppRolesAsync. Remove as soon frontend is adjusted")]
IAsyncEnumerable GetClientRolesAsync(Guid appId, string? languageShortName = null);
- Task GetOwnCompanyUserDetailsAsync(Guid userId);
+ Task GetOwnCompanyUserDetailsAsync(Guid userId);
Task AddOwnCompanyUsersBusinessPartnerNumbersAsync(Guid userId, IEnumerable businessPartnerNumbers);
Task AddOwnCompanyUsersBusinessPartnerNumberAsync(Guid userId, string businessPartnerNumber);
Task GetOwnUserDetails();
diff --git a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
index 4c1d72a10a..f5f6d03592 100644
--- a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs
@@ -380,7 +380,6 @@ public async ValueTask DeleteCompanyIdentityProviderAsync(Guid identityProviderI
}
identityProviderRepository.DeleteIdentityProvider(identityProviderId);
-
await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
@@ -743,7 +742,7 @@ private async ValueTask UploadOwnCompanyUsersIdenti
{
var userRepository = _portalRepositories.GetInstance();
var companyId = _identityData.CompanyId;
- var (sharedIdpAlias, existingAliase) = await GetCompanyAliasDataAsync(companyId).ConfigureAwait(false);
+ var (sharedIdp, existingAliase) = await GetCompanyAliasDataAsync(companyId).ConfigureAwait(false);
using var stream = document.OpenReadStream();
@@ -755,8 +754,8 @@ private async ValueTask UploadOwnCompanyUsersIdenti
{
numIdps = ParseCSVFirstLineReturningNumIdps(line);
},
- line => ParseCSVLine(line, numIdps, existingAliase),
- lines => ProcessOwnCompanyUsersIdentityProviderLinkDataInternalAsync(lines, userRepository, companyId, sharedIdpAlias, cancellationToken),
+ line => ParseCSVLine(line, numIdps, existingAliase.Select(x => x.Alias)),
+ lines => ProcessOwnCompanyUsersIdentityProviderLinkDataInternalAsync(lines, userRepository, companyId, sharedIdp, existingAliase, cancellationToken),
cancellationToken
).ConfigureAwait(false);
@@ -782,7 +781,8 @@ private UserUpdateError CreateUserUpdateError(int line, Exception error) =>
IAsyncEnumerable<(Guid CompanyUserId, UserProfile UserProfile, IEnumerable IdentityProviderLinks)> userProfileLinkDatas,
IUserRepository userRepository,
Guid companyId,
- string? sharedIdpAlias,
+ (Guid IdentityProviderId, string Alias) sharedIdp,
+ IEnumerable<(Guid IdentityProviderId, string Alias)> existingIdps,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var (companyUserId, profile, identityProviderLinks) in userProfileLinkDatas)
@@ -799,12 +799,12 @@ private UserUpdateError CreateUserUpdateError(int line, Exception error) =>
foreach (var identityProviderLink in identityProviderLinks)
{
- updated |= await UpdateIdentityProviderLinksAsync(iamUserId, companyUserId, identityProviderLink, existingLinks, sharedIdpAlias).ConfigureAwait(false);
+ updated |= await UpdateIdentityProviderLinksAsync(iamUserId, companyUserId, identityProviderLink, existingLinks, sharedIdp, existingIdps).ConfigureAwait(false);
}
if (existingProfile != profile)
{
- await UpdateUserProfileAsync(userRepository, iamUserId, companyUserId, profile, existingLinks, sharedIdpAlias).ConfigureAwait(false);
+ await UpdateUserProfileAsync(userRepository, iamUserId, companyUserId, profile, existingLinks, sharedIdp).ConfigureAwait(false);
updated = true;
}
success = updated;
@@ -821,15 +821,15 @@ private UserUpdateError CreateUserUpdateError(int line, Exception error) =>
}
}
- private async ValueTask<(string? SharedIdpAlias, IEnumerable ValidAliase)> GetCompanyAliasDataAsync(Guid companyId)
+ private async ValueTask<((Guid IdentityProviderId, string Alias) SharedIdp, IEnumerable<(Guid IdentityProviderId, string Alias)> ValidAliase)> GetCompanyAliasDataAsync(Guid companyId)
{
var identityProviderCategoryData = await _portalRepositories.GetInstance()
.GetCompanyIdentityProviderCategoryDataUntracked(companyId)
.Where(data => data.Alias != null)
- .Select(data => (data.TypeId, Alias: data.Alias!))
+ .Select(data => (data.IdentityProviderId, data.TypeId, Alias: data.Alias!))
.ToListAsync().ConfigureAwait(false);
- var sharedIdpAlias = identityProviderCategoryData.SingleOrDefault(data => data.TypeId == IdentityProviderTypeId.SHARED).Alias;
- var validAliase = identityProviderCategoryData.Select(data => data.Alias).ToList();
+ var sharedIdpAlias = identityProviderCategoryData.Where(data => data.TypeId == IdentityProviderTypeId.SHARED).Select(data => (data.IdentityProviderId, data.Alias)).SingleOrDefault();
+ var validAliase = identityProviderCategoryData.Select(data => (data.IdentityProviderId, data.Alias)).ToList();
return (sharedIdpAlias, validAliase);
}
@@ -851,7 +851,13 @@ private UserUpdateError CreateUserUpdateError(int line, Exception error) =>
);
}
- private async ValueTask UpdateIdentityProviderLinksAsync(string iamUserId, Guid companyUserId, IdentityProviderLink identityProviderLink, IEnumerable existingLinks, string? sharedIdpAlias)
+ private async ValueTask UpdateIdentityProviderLinksAsync(
+ string iamUserId,
+ Guid companyUserId,
+ IdentityProviderLink identityProviderLink,
+ IEnumerable existingLinks,
+ (Guid IdentityProviderId, string Alias) sharedIdp,
+ IEnumerable<(Guid IdentityProviderId, string Alias)> existingIdps)
{
var (alias, userId, userName) = identityProviderLink;
@@ -863,7 +869,7 @@ private async ValueTask UpdateIdentityProviderLinksAsync(string iamUserId,
return false;
}
- if (alias == sharedIdpAlias)
+ if (alias == sharedIdp.Alias)
{
throw new ControllerArgumentException($"unexpected update of shared identityProviderLink, alias '{alias}', companyUser '{companyUserId}', providerUserId: '{userId}', providerUserName: '{userName}'");
}
@@ -873,23 +879,49 @@ private async ValueTask UpdateIdentityProviderLinksAsync(string iamUserId,
await _provisioningManager.DeleteProviderUserLinkToCentralUserAsync(iamUserId, alias).ConfigureAwait(false);
}
await _provisioningManager.AddProviderUserLinkToCentralUserAsync(iamUserId, identityProviderLink).ConfigureAwait(false);
+ await InsertUpdateCompanyUserAssignedIdentityProvider(companyUserId, existingIdps.Single(x => x.Alias == alias).IdentityProviderId, identityProviderLink).ConfigureAwait(false);
return true;
}
- private async ValueTask UpdateUserProfileAsync(IUserRepository userRepository, string iamUserId, Guid companyUserId, UserProfile profile, IEnumerable existingLinks, string? sharedIdpAlias)
+ private async Task InsertUpdateCompanyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId, IdentityProviderLink providerLink)
+ {
+ var userRepository = _portalRepositories.GetInstance();
+ var data = await userRepository.GetCompanyUserAssignedIdentityProvider(companyUserId, identityProviderId).ConfigureAwait(false);
+ if (data == default)
+ {
+ userRepository.AddCompanyUserAssignedIdentityProvider(companyUserId, identityProviderId, providerLink.UserId, providerLink.UserName);
+ }
+ else
+ {
+ userRepository.AttachAndModifyUserAssignedIdentityProvider(companyUserId, identityProviderId,
+ uaip =>
+ {
+ uaip.ProviderId = data.ProviderId;
+ uaip.UserName = data.Username;
+ },
+ uaip =>
+ {
+ uaip.ProviderId = providerLink.UserId;
+ uaip.UserName = providerLink.UserName;
+ });
+ }
+ }
+
+ private async ValueTask UpdateUserProfileAsync(IUserRepository userRepository, string iamUserId, Guid companyUserId, UserProfile profile, IEnumerable existingLinks, (Guid IdentityProviderId, string Alias) sharedIdp)
{
var (firstName, lastName, email) = (profile.FirstName ?? "", profile.LastName ?? "", profile.Email ?? "");
await _provisioningManager.UpdateCentralUserAsync(iamUserId, firstName, lastName, email).ConfigureAwait(false);
- if (sharedIdpAlias != null)
+ if (sharedIdp != default)
{
- var sharedIdpLink = existingLinks.FirstOrDefault(link => link.Alias == sharedIdpAlias);
+ var sharedIdpLink = existingLinks.FirstOrDefault(link => link.Alias == sharedIdp.Alias);
if (sharedIdpLink != default)
{
- await _provisioningManager.UpdateSharedRealmUserAsync(sharedIdpAlias, sharedIdpLink.UserId, firstName, lastName, email).ConfigureAwait(false);
+ await _provisioningManager.UpdateSharedRealmUserAsync(sharedIdp.Alias, sharedIdpLink.UserId, firstName, lastName, email).ConfigureAwait(false);
}
}
+
userRepository.AttachAndModifyCompanyUser(companyUserId, null, companyUser =>
{
companyUser.Firstname = profile.FirstName;
diff --git a/src/administration/Administration.Service/BusinessLogic/InvitationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/InvitationBusinessLogic.cs
index 1e4f2c7ccf..2db4192c97 100644
--- a/src/administration/Administration.Service/BusinessLogic/InvitationBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/InvitationBusinessLogic.cs
@@ -21,6 +21,7 @@
using Microsoft.Extensions.Options;
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;
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;
@@ -29,11 +30,13 @@
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;
+using System.Text.RegularExpressions;
namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic;
public class InvitationBusinessLogic : IInvitationBusinessLogic
{
+ private static readonly Regex Company = new(ValidationExpressions.Company, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly IProvisioningManager _provisioningManager;
private readonly IUserProvisioningService _userProvisioningService;
private readonly IPortalRepositories _portalRepositories;
@@ -72,6 +75,10 @@ public Task ExecuteInvitation(CompanyInvitationData invitationData)
{
throw new ControllerArgumentException("organisationName must not be empty", "organisationName");
}
+ if (!string.IsNullOrEmpty(invitationData.organisationName) && !Company.IsMatch(invitationData.organisationName))
+ {
+ throw new ControllerArgumentException("OrganisationName length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the Organisation name", "organisationName");
+ }
return ExecuteInvitationInternalAsync(invitationData);
}
diff --git a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
index 1f3bc23156..0d8b899652 100644
--- a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs
@@ -41,6 +41,7 @@ public class NetworkBusinessLogic : INetworkBusinessLogic
{
private static readonly Regex Name = new(ValidationExpressions.Name, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private static readonly Regex ExternalID = new("^[A-Za-z0-9\\-+_/,.]{6,36}$", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
+ private static readonly Regex Company = new(ValidationExpressions.Company, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly IPortalRepositories _portalRepositories;
private readonly IIdentityData _identityData;
@@ -59,6 +60,10 @@ public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentitySer
public async Task HandlePartnerRegistration(PartnerRegistrationData data)
{
+ if (!string.IsNullOrEmpty(data.Name) && !Company.IsMatch(data.Name))
+ {
+ throw new ControllerArgumentException("OrganisationName length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the Organisation name", "organisationName");
+ }
var ownerCompanyId = _identityData.CompanyId;
var networkRepository = _portalRepositories.GetInstance();
var companyRepository = _portalRepositories.GetInstance();
diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs
index 127a01af8d..f8ba83d385 100644
--- a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs
@@ -43,7 +43,9 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLog
public sealed class RegistrationBusinessLogic : IRegistrationBusinessLogic
{
- private static readonly Regex BpnRegex = new(@"(\w|\d){16}", RegexOptions.Compiled, TimeSpan.FromSeconds(1));
+ private static readonly Regex BpnRegex = new(ValidationExpressions.Bpn, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
+
+ private static readonly Regex Company = new(ValidationExpressions.Company, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly IPortalRepositories _portalRepositories;
private readonly RegistrationSettings _settings;
@@ -91,6 +93,10 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu
{
throw NotFoundException.Create(AdministrationRegistrationErrors.APPLICATION_NOT_FOUND, new ErrorParameter[] { new("applicationId", applicationId.ToString()) });
}
+ if (!string.IsNullOrEmpty(companyWithAddress.Name) && !Company.IsMatch(companyWithAddress.Name))
+ {
+ throw new ControllerArgumentException("OrganisationName length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the Organisation name", "organisationName");
+ }
return new CompanyWithAddressData(
companyWithAddress.CompanyId,
@@ -123,6 +129,10 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu
public Task> GetCompanyApplicationDetailsAsync(int page, int size, CompanyApplicationStatusFilter? companyApplicationStatusFilter = null, string? companyName = null)
{
+ if (!string.IsNullOrEmpty(companyName) && !Company.IsMatch(companyName))
+ {
+ throw new ControllerArgumentException("CompanyName length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the company name", "companyName");
+ }
var applications = _portalRepositories.GetInstance()
.GetCompanyApplicationsFilteredQuery(
companyName?.Length >= 3 ? companyName : null,
@@ -161,6 +171,10 @@ private async Task GetCompanyWithAddressAsyncInternal(Gu
public Task> GetAllCompanyApplicationsDetailsAsync(int page, int size, string? companyName = null)
{
+ if (!string.IsNullOrEmpty(companyName) && !Company.IsMatch(companyName))
+ {
+ throw new ControllerArgumentException("CompanyName length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the company name", "companyName");
+ }
var applications = _portalRepositories.GetInstance().GetAllCompanyApplicationsDetailsQuery(companyName);
return Pagination.CreateResponseAsync(
@@ -215,7 +229,7 @@ public Task UpdateCompanyBpn(Guid applicationId, string bpn)
private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn)
{
var result = await _portalRepositories.GetInstance()
- .GetBpnForIamUserUntrackedAsync(applicationId, bpn).ToListAsync().ConfigureAwait(false);
+ .GetBpnForIamUserUntrackedAsync(applicationId, bpn.ToUpper()).ToListAsync().ConfigureAwait(false);
if (!result.Exists(item => item.IsApplicationCompany))
{
throw new NotFoundException($"application {applicationId} not found");
@@ -262,7 +276,7 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn)
.ConfigureAwait(false);
_portalRepositories.GetInstance().AttachAndModifyCompany(applicationCompanyData.CompanyId, null,
- c => { c.BusinessPartnerNumber = bpn; });
+ c => { c.BusinessPartnerNumber = bpn.ToUpper(); });
var registrationValidationFailed = context.Checklist[ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION] == new ValueTuple(ApplicationChecklistEntryStatusId.FAILED, null);
@@ -290,7 +304,7 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn)
public async Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, CancellationToken cancellationToken)
{
_logger.LogInformation("Process SelfDescription called with the following data {Data}", data);
- var result = await _portalRepositories.GetInstance().GetSubmittedApplicationIdsByBpn(data.BusinessPartnerNumber).ToListAsync(cancellationToken).ConfigureAwait(false);
+ var result = await _portalRepositories.GetInstance().GetSubmittedApplicationIdsByBpn(data.BusinessPartnerNumber.ToUpper()).ToListAsync(cancellationToken).ConfigureAwait(false);
if (!result.Any())
{
throw new NotFoundException($"No companyApplication for BPN {data.BusinessPartnerNumber} is not in status SUBMITTED");
diff --git a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
index ca36ccdf4e..fd1a04f1d8 100644
--- a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
+++ b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs
@@ -17,9 +17,9 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.Async;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail;
@@ -32,6 +32,7 @@
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;
+using System.Text.RegularExpressions;
namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic;
@@ -40,6 +41,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLog
///
public class UserBusinessLogic : IUserBusinessLogic
{
+ private static readonly Regex BpnRegex = new(ValidationExpressions.Bpn, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly IProvisioningManager _provisioningManager;
private readonly IUserProvisioningService _userProvisioningService;
private readonly IProvisioningDBAccess _provisioningDbAccess;
@@ -196,7 +198,7 @@ public async Task CreateOwnCompanyIdpUserAsync(Guid identityProviderId, Us
throw result.Error;
}
- var mailParameters = new Dictionary()
+ var mailParameters = new Dictionary
{
{ "companyName", displayName },
{ "nameCreatedBy", nameCreatedBy },
@@ -220,40 +222,56 @@ public async Task CreateOwnCompanyIdpUserAsync(Guid identityProviderId, Us
{
_logger.LogError(e, "Error sending email to {Email} after creating user {UserName}", userCreationInfo.Email, userCreationInfo.UserName);
}
+
return result.CompanyUserId;
}
public Task> GetOwnCompanyUserDatasAsync(int page, int size, GetOwnCompanyUsersFilter filter)
{
- var companyUsers = _portalRepositories.GetInstance().GetOwnCompanyUserQuery(
- _identityData.CompanyId,
- filter.CompanyUserId,
- filter.FirstName,
- filter.LastName,
- filter.Email,
- _settings.CompanyUserStatusIds
- );
- return Pagination.CreateResponseAsync(
+ async Task?> GetCompanyUserData(int skip, int take)
+ {
+ var companyData = await _portalRepositories.GetInstance().GetOwnCompanyUserData(
+ _identityData.CompanyId,
+ filter.CompanyUserId,
+ filter.FirstName,
+ filter.LastName,
+ filter.Email,
+ _settings.CompanyUserStatusIds
+ )(skip, take).ConfigureAwait(false);
+
+ if (companyData == null)
+ return null;
+
+ var displayNames = await companyData.Data
+ .SelectMany(x => x.IdpUserIds)
+ .Select(x => x.Alias ?? throw new ConflictException("Alias must not be null"))
+ .Distinct()
+ .ToImmutableDictionaryAsync(GetDisplayName).ConfigureAwait(false);
+
+ return new Pagination.Source(
+ companyData.Count,
+ companyData.Data.Select(d => new CompanyUserData(
+ d.CompanyUserId,
+ d.UserStatusId,
+ d.FirstName,
+ d.LastName,
+ d.Email,
+ d.Roles,
+ d.IdpUserIds.Select(x =>
+ new IdpUserId(
+ displayNames[x.Alias!],
+ x.Alias!,
+ x.UserId)))));
+ }
+ return Pagination.CreateResponseAsync(
page,
size,
_settings.ApplicationsMaxPageSize,
- (int skip, int take) => new Pagination.AsyncSource(
- companyUsers.CountAsync(),
- companyUsers.OrderByDescending(companyUser => companyUser.Identity!.DateCreated)
- .Skip(skip)
- .Take(take)
- .Select(companyUser => new CompanyUserData(
- companyUser.Id,
- companyUser.Identity!.UserStatusId,
- companyUser.Identity!.IdentityAssignedRoles.Select(x => x.UserRole!).Select(userRole => userRole.UserRoleText))
- {
- FirstName = companyUser.Firstname,
- LastName = companyUser.Lastname,
- Email = companyUser.Email
- })
- .AsAsyncEnumerable()));
+ GetCompanyUserData);
}
+ private async Task GetDisplayName(string alias) => await _provisioningManager.GetIdentityProviderDisplayName(alias).ConfigureAwait(false) ?? throw new ConflictException($"Display Name should not be null for alias: {alias}");
+
[Obsolete("to be replaced by UserRolesBusinessLogic.GetAppRolesAsync. Remove as soon frontend is adjusted")]
public async IAsyncEnumerable GetClientRolesAsync(Guid appId, string? languageShortName = null)
{
@@ -274,7 +292,7 @@ public async IAsyncEnumerable GetClientRolesAsync(Guid appId, strin
}
}
- public async Task GetOwnCompanyUserDetailsAsync(Guid userId)
+ public async Task GetOwnCompanyUserDetailsAsync(Guid userId)
{
var companyId = _identityData.CompanyId;
var details = await _portalRepositories.GetInstance().GetOwnCompanyUserDetailsUntrackedAsync(userId, companyId).ConfigureAwait(false);
@@ -282,27 +300,41 @@ public async Task GetOwnCompanyUserDetailsAsync(Guid userId)
{
throw new NotFoundException($"no company-user data found for user {userId} in company {companyId}");
}
- return details;
+
+ return new CompanyUserDetailData(
+ details.CompanyUserId,
+ details.CreatedAt,
+ details.BusinessPartnerNumbers,
+ details.CompanyName,
+ details.UserStatusId,
+ details.AssignedRoles,
+ await Task.WhenAll(details.IdpUserIds.Select(async x =>
+ new IdpUserId(
+ await GetDisplayName(x.Alias ?? throw new ConflictException("Alias must not be null")).ConfigureAwait(false),
+ x.Alias,
+ x.UserId))).ConfigureAwait(false));
}
public async Task AddOwnCompanyUsersBusinessPartnerNumbersAsync(Guid userId, IEnumerable businessPartnerNumbers)
{
- if (businessPartnerNumbers.Any(businessPartnerNumber => businessPartnerNumber.Length > 20))
+ if (businessPartnerNumbers.Any(bpn => !BpnRegex.IsMatch(bpn)))
{
- throw new ControllerArgumentException("businessPartnerNumbers must not exceed 20 characters", nameof(businessPartnerNumbers));
+ throw new ControllerArgumentException("BPN must contain exactly 16 characters and must be prefixed with BPNL", nameof(businessPartnerNumbers));
}
+
var companyId = _identityData.CompanyId;
var (assignedBusinessPartnerNumbers, isValidUser) = await _portalRepositories.GetInstance().GetOwnCompanyUserWithAssignedBusinessPartnerNumbersUntrackedAsync(userId, companyId).ConfigureAwait(false);
if (!isValidUser)
{
throw new NotFoundException($"user {userId} not found in company {companyId}");
}
+
var iamUserId = await _provisioningManager.GetUserByUserName(userId.ToString()).ConfigureAwait(false) ?? throw new ConflictException("user {userId} not found in keycloak");
var businessPartnerRepository = _portalRepositories.GetInstance();
await _provisioningManager.AddBpnAttributetoUserAsync(iamUserId, businessPartnerNumbers).ConfigureAwait(false);
foreach (var businessPartnerToAdd in businessPartnerNumbers.Except(assignedBusinessPartnerNumbers))
{
- businessPartnerRepository.CreateCompanyUserAssignedBusinessPartner(userId, businessPartnerToAdd);
+ businessPartnerRepository.CreateCompanyUserAssignedBusinessPartner(userId, businessPartnerToAdd.ToUpper());
}
return await _portalRepositories.SaveAsync();
@@ -321,7 +353,20 @@ public async Task GetOwnUserDetails()
{
throw new NotFoundException($"no company-user data found for user {userId}");
}
- return details;
+
+ return new CompanyOwnUserDetails(
+ details.CompanyUserId,
+ details.CreatedAt,
+ details.BusinessPartnerNumbers,
+ details.CompanyName,
+ details.UserStatusId,
+ details.AssignedRoles,
+ details.AdminDetails,
+ await Task.WhenAll(details.IdpUserIds.Select(async x =>
+ new IdpUserId(
+ await GetDisplayName(x.Alias ?? throw new ConflictException("Alias must not be null")).ConfigureAwait(false),
+ x.Alias,
+ x.UserId))).ConfigureAwait(false));
}
public async Task UpdateOwnUserDetails(Guid companyUserId, OwnCompanyUserEditableDetails ownCompanyUserEditableDetails)
@@ -331,14 +376,15 @@ public async Task UpdateOwnUserDetails(Guid companyUserId, O
{
throw new ForbiddenException($"invalid userId {companyUserId} for user {userId}");
}
+
var userRepository = _portalRepositories.GetInstance();
var userData = await userRepository.GetUserWithCompanyIdpAsync(companyUserId).ConfigureAwait(false);
if (userData == null)
{
throw new ArgumentOutOfRangeException($"user {companyUserId} is not a shared idp user");
}
- var companyUser = userData.CompanyUser;
+ var companyUser = userData.CompanyUser;
var iamUserId = await _provisioningManager.GetUserByUserName(companyUserId.ToString()).ConfigureAwait(false) ?? throw new ConflictException($"user {companyUserId} not found in keycloak");
var iamIdpAlias = userData.IamIdpAlias;
var userIdShared = await _provisioningManager.GetProviderUserIdForCentralUserIdAsync(iamIdpAlias, iamUserId).ConfigureAwait(false);
@@ -346,6 +392,7 @@ public async Task UpdateOwnUserDetails(Guid companyUserId, O
{
throw new NotFoundException($"no shared realm userid found for {iamUserId} in realm {iamIdpAlias}");
}
+
await _provisioningManager.UpdateSharedRealmUserAsync(
iamIdpAlias,
userIdShared,
@@ -469,6 +516,7 @@ private async Task DeleteIamUserAsync(string? sharedIdpAlias, string iamUserId)
await _provisioningManager.DeleteSharedRealmUserAsync(sharedIdpAlias, userIdShared).ConfigureAwait(false);
}
}
+
await _provisioningManager.DeleteCentralRealmUserAsync(iamUserId).ConfigureAwait(false);
}
@@ -495,6 +543,7 @@ private async Task CanResetPassword(Guid userId)
await _provisioningDbAccess.SaveAsync().ConfigureAwait(false);
return true;
}
+
return false;
}
@@ -509,8 +558,10 @@ public async Task ExecuteOwnCompanyUserPasswordReset(Guid companyUserId)
await _provisioningManager.ResetSharedUserPasswordAsync(alias, iamUserId).ConfigureAwait(false);
return true;
}
+
throw new ArgumentException($"cannot reset password more often than {_settings.PasswordReset.MaxNoOfReset} in {_settings.PasswordReset.NoOfHours} hours");
}
+
throw new NotFoundException($"Cannot identify companyId or shared idp : userId {companyUserId} is not associated with admin users company {_identityData.CompanyId}");
}
@@ -530,7 +581,7 @@ public async Task DeleteOwnUserBusinessPartnerNumbersAsync(Guid userId, str
{
var userBusinessPartnerRepository = _portalRepositories.GetInstance();
- var (isValidUser, isAssignedBusinessPartner, isSameCompany) = await userBusinessPartnerRepository.GetOwnCompanyUserWithAssignedBusinessPartnerNumbersAsync(userId, _identityData.CompanyId, businessPartnerNumber).ConfigureAwait(false);
+ var (isValidUser, isAssignedBusinessPartner, isSameCompany) = await userBusinessPartnerRepository.GetOwnCompanyUserWithAssignedBusinessPartnerNumbersAsync(userId, _identityData.CompanyId, businessPartnerNumber.ToUpper()).ConfigureAwait(false);
if (!isValidUser)
{
@@ -549,9 +600,9 @@ public async Task DeleteOwnUserBusinessPartnerNumbersAsync(Guid userId, str
var iamUserId = await _provisioningManager.GetUserByUserName(userId.ToString()).ConfigureAwait(false) ?? throw new ConflictException($"user {userId} is not associated with a user in keycloak");
- userBusinessPartnerRepository.DeleteCompanyUserAssignedBusinessPartner(userId, businessPartnerNumber);
+ userBusinessPartnerRepository.DeleteCompanyUserAssignedBusinessPartner(userId, businessPartnerNumber.ToUpper());
- await _provisioningManager.DeleteCentralUserBusinessPartnerNumberAsync(iamUserId, businessPartnerNumber).ConfigureAwait(false);
+ await _provisioningManager.DeleteCentralUserBusinessPartnerNumberAsync(iamUserId, businessPartnerNumber.ToUpper()).ConfigureAwait(false);
return await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
diff --git a/src/administration/Administration.Service/Controllers/RegistrationController.cs b/src/administration/Administration.Service/Controllers/RegistrationController.cs
index 0a5ba9d0e2..c207cbc016 100644
--- a/src/administration/Administration.Service/Controllers/RegistrationController.cs
+++ b/src/administration/Administration.Service/Controllers/RegistrationController.cs
@@ -154,7 +154,7 @@ public async Task ApproveApplication([FromRoute] Guid applicati
/// Comment to explain why the application got declined
/// cancellation token
///
- /// Example: POST: api/administration/registration/application/4f0146c6-32aa-4bb1-b844-df7e8babdcb4/decline
+ /// Example: POST: api/administration/registration/application/{applicationId}/decline
///
/// Successfully declined the application
/// Either the CompanyApplication is not in status SUBMITTED, or there is no checklist entry of type Registration_Verification.
diff --git a/src/administration/Administration.Service/Controllers/UserController.cs b/src/administration/Administration.Service/Controllers/UserController.cs
index 3bfdbec02f..97fd04db41 100644
--- a/src/administration/Administration.Service/Controllers/UserController.cs
+++ b/src/administration/Administration.Service/Controllers/UserController.cs
@@ -196,9 +196,9 @@ public ValueTask UploadOwnCompanyUsersIdentityProviderFileAsy
[Authorize(Roles = "view_user_management")]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[Route("owncompany/users/{companyUserId}", Name = nameof(GetOwnCompanyUserDetails))]
- [ProducesResponseType(typeof(CompanyUserDetails), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(CompanyUserDetailData), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
- public Task GetOwnCompanyUserDetails([FromRoute] Guid companyUserId) =>
+ public Task GetOwnCompanyUserDetails([FromRoute] Guid companyUserId) =>
_logic.GetOwnCompanyUserDetailsAsync(companyUserId);
///
@@ -249,7 +249,7 @@ public Task> ModifyAppUserRolesAsync([FromRoute] Gui
/// Id of the user to add the business partner numbers to.
/// the business partner numbers that should be added.
///
- /// Example: POST: api/administration/user/owncompany/users/ac1cf001-7fbc-1f2f-817f-bce0575a0011/businessPartnerNumbers
+ /// Example: POST: api/administration/user/owncompany/users/{companyUserId}/businessPartnerNumbers
/// The business partner numbers have been added successfully.
/// Business Partner Numbers must not exceed 20 characters.
/// User not found.
@@ -269,7 +269,7 @@ public Task AddOwnCompanyUserBusinessPartnerNumbers(Guid companyUserId, IEn
/// Id of the user to add the business partner numbers to.
/// the business partner number that should be added.
///
- /// Example: PUT: api/administration/user/owncompany/users/ac1cf001-7fbc-1f2f-817f-bce0575a0011/businessPartnerNumbers/CAXSDUMMYCATENAZZ
+ /// Example: PUT: api/administration/user/owncompany/users/{companyUserId}/businessPartnerNumbers/{businessPartnerNumber}
/// The business partner number have been added successfully.
/// Business Partner Numbers must not exceed 20 characters.
/// User is not existing.
diff --git a/src/framework/Framework.Async/ToImmutableDictionaryAsyncExtension.cs b/src/framework/Framework.Async/ToImmutableDictionaryAsyncExtension.cs
new file mode 100644
index 0000000000..29918c8809
--- /dev/null
+++ b/src/framework/Framework.Async/ToImmutableDictionaryAsyncExtension.cs
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+using System.Collections.Immutable;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Async;
+
+public static class ToImmutableDictionaryAsyncExtension
+{
+ public static async Task> ToImmutableDictionaryAsync(this IEnumerable keys, Func> selector) where K : notnull
+ {
+ var builder = ImmutableDictionary.CreateBuilder();
+ builder.AddRange(
+ await Task.WhenAll(
+ keys.Select(async key => new KeyValuePair(
+ key,
+ await selector(key).ConfigureAwait(false)))).ConfigureAwait(false));
+ return builder.ToImmutableDictionary();
+ }
+}
diff --git a/src/framework/Framework.Models/PortalClaimTypes.cs b/src/framework/Framework.Models/PortalClaimTypes.cs
index a27cc9bcd0..4e59b296c1 100644
--- a/src/framework/Framework.Models/PortalClaimTypes.cs
+++ b/src/framework/Framework.Models/PortalClaimTypes.cs
@@ -22,7 +22,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
public static class PortalClaimTypes
{
public const string Sub = "sub";
- public const string ClientId = "clientId";
+ public const string ClientId = "client_id";
public const string PreferredUserName = "preferred_username";
public const string ResourceAccess = "resource_access";
}
diff --git a/src/framework/Framework.Models/ValidationExpressions.cs b/src/framework/Framework.Models/ValidationExpressions.cs
index 94d446ded3..3efc662680 100644
--- a/src/framework/Framework.Models/ValidationExpressions.cs
+++ b/src/framework/Framework.Models/ValidationExpressions.cs
@@ -24,5 +24,5 @@ public static class ValidationExpressions
{
public const string Name = @"^.+$"; // TODO: should be @"^(([A-Za-zÀ-ÿ]{1,40}?([-,.'\s]?[A-Za-zÀ-ÿ]{1,40}?)){1,8})$";
public const string Bpn = @"^(BPNL|bpnl)[\w|\d]{12}$";
- public const string Company = @"^\d*?[A-Za-zÀ-ÿ]\d?([A-Za-z0-9À-ÿ-_+=.,:;!?'\x22@()]\s?){3,80}$";
+ public const string Company = @"^\d*?[A-Za-zÀ-ÿ]\d?([A-Za-z0-9À-ÿ-_+=.,:;!?'\x22@()]\s?){2,40}$";
}
diff --git a/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs
index d09eb66fe9..75907ed8e1 100644
--- a/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs
+++ b/src/marketplace/Apps.Service/BusinessLogic/AppReleaseBusinessLogic.cs
@@ -32,6 +32,7 @@
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 System.Text.RegularExpressions;
namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.BusinessLogic;
@@ -40,6 +41,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.BusinessLogic;
///
public class AppReleaseBusinessLogic : IAppReleaseBusinessLogic
{
+ private static readonly Regex Company = new(ValidationExpressions.Company, RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly IPortalRepositories _portalRepositories;
private readonly AppsSettings _settings;
private readonly IOfferService _offerService;
@@ -188,6 +190,11 @@ public Task AddAppAsync(AppRequestModel appRequestModel)
throw new ControllerArgumentException("Use Case Ids must not be null or empty", nameof(appRequestModel.UseCaseIds));
}
+ if (!string.IsNullOrEmpty(appRequestModel.Provider) && !Company.IsMatch(appRequestModel.Provider))
+ {
+ throw new ControllerArgumentException("Provider length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the Organisation name", nameof(appRequestModel.Provider));
+ }
+
return this.CreateAppAsync(appRequestModel);
}
@@ -263,6 +270,11 @@ public async Task UpdateAppReleaseAsync(Guid appId, AppRequestModel appRequestMo
throw new ForbiddenException($"Company {companyId} is not the app provider.");
}
+ if (!string.IsNullOrEmpty(appRequestModel.Provider) && !Company.IsMatch(appRequestModel.Provider))
+ {
+ throw new ControllerArgumentException("Provider length must be 3-40 characters and *+=#%\\s not used as one of the first three characters in the Organisation name", nameof(appRequestModel.Provider));
+ }
+
if (appRequestModel.SalesManagerId.HasValue)
{
await _offerService.ValidateSalesManager(appRequestModel.SalesManagerId.Value, _settings.SalesManagerRoles).ConfigureAwait(false);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserData.cs
index d45b5e2241..683d65c9d3 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserData.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserData.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
@@ -23,30 +22,38 @@
namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
-public class CompanyUserData
-{
- public CompanyUserData(Guid companyUserId, UserStatusId userStatusId, IEnumerable roles)
- {
- CompanyUserId = companyUserId;
- UserStatusId = userStatusId;
- Roles = roles;
- }
-
- [JsonPropertyName("companyUserId")]
- public Guid CompanyUserId { get; set; }
-
- [JsonPropertyName("status")]
- public UserStatusId UserStatusId { get; set; }
-
- [JsonPropertyName("firstName")]
- public string? FirstName { get; set; }
-
- [JsonPropertyName("lastName")]
- public string? LastName { get; set; }
-
- [JsonPropertyName("email")]
- public string? Email { get; set; }
-
- [JsonPropertyName("roles")]
- public IEnumerable Roles { get; set; }
-}
+public record CompanyUserData(
+ [property: JsonPropertyName("companyUserId")]
+ Guid CompanyUserId,
+ [property: JsonPropertyName("status")]
+ UserStatusId UserStatusId,
+ [property: JsonPropertyName("firstName")]
+ string? FirstName,
+ [property: JsonPropertyName("lastName")]
+ string? LastName,
+ [property: JsonPropertyName("email")]
+ string? Email,
+ [property: JsonPropertyName("roles")]
+ IEnumerable Roles,
+ [property: JsonPropertyName("idpUserIds")]
+ IEnumerable IdpUserIds
+);
+
+public record CompanyUserTransferData(
+ [property: JsonPropertyName("companyUserId")]
+ Guid CompanyUserId,
+ [property: JsonPropertyName("status")]
+ UserStatusId UserStatusId,
+ [property: JsonPropertyName("dateCreated")]
+ DateTimeOffset DateCreated,
+ [property: JsonPropertyName("firstName")]
+ string? FirstName,
+ [property: JsonPropertyName("lastName")]
+ string? LastName,
+ [property: JsonPropertyName("email")]
+ string? Email,
+ [property: JsonPropertyName("roles")]
+ IEnumerable Roles,
+ [property: JsonPropertyName("idpUserIds")]
+ IEnumerable IdpUserIds
+);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserDetails.cs b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserDetails.cs
index cbd1cea74e..8266746cea 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserDetails.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserDetails.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
@@ -41,6 +40,25 @@ public record CompanyUserDetails(
public string? Email { get; set; }
}
+public record CompanyUserDetailData(
+ [property: JsonPropertyName("companyUserId")] Guid CompanyUserId,
+ [property: JsonPropertyName("created")] DateTimeOffset CreatedAt,
+ [property: JsonPropertyName("bpn")] IEnumerable BusinessPartnerNumbers,
+ [property: JsonPropertyName("company")] string CompanyName,
+ [property: JsonPropertyName("status")] UserStatusId UserStatusId,
+ [property: JsonPropertyName("assignedRoles")] IEnumerable AssignedRoles,
+ [property: JsonPropertyName("idpUserIds")] IEnumerable IdpUserIds)
+{
+ [JsonPropertyName("firstName")]
+ public string? FirstName { get; set; }
+
+ [JsonPropertyName("lastName")]
+ public string? LastName { get; set; }
+
+ [JsonPropertyName("email")]
+ public string? Email { get; set; }
+}
+
public record CompanyOwnUserDetails(
[property: JsonPropertyName("companyUserId")] Guid CompanyUserId,
[property: JsonPropertyName("created")] DateTimeOffset CreatedAt,
@@ -48,7 +66,8 @@ public record CompanyOwnUserDetails(
[property: JsonPropertyName("company")] string CompanyName,
[property: JsonPropertyName("status")] UserStatusId UserStatusId,
[property: JsonPropertyName("assignedRoles")] IEnumerable AssignedRoles,
- [property: JsonPropertyName("admin")] IEnumerable AdminDetails)
+ [property: JsonPropertyName("admin")] IEnumerable AdminDetails,
+ [property: JsonPropertyName("idpUserIds")] IEnumerable IdpUserIds)
{
[JsonPropertyName("firstName")]
public string? FirstName { get; set; }
@@ -67,3 +86,51 @@ public record CompanyUserAdminDetails(
public record CompanyUserAssignedRoleDetails(
[property: JsonPropertyName("appId")] Guid OfferId,
[property: JsonPropertyName("roles")] IEnumerable UserRoles);
+
+public record IdpUserId(
+ [property: JsonPropertyName("idpDisplayName")] string IdpDisplayName,
+ [property: JsonPropertyName("idpAlias")] string IdpAlias,
+ [property: JsonPropertyName("userId")] string UserId);
+
+public record CompanyOwnUserTransferDetails(
+ [property: JsonPropertyName("companyUserId")] Guid CompanyUserId,
+ [property: JsonPropertyName("created")] DateTimeOffset CreatedAt,
+ [property: JsonPropertyName("bpn")] IEnumerable BusinessPartnerNumbers,
+ [property: JsonPropertyName("company")] string CompanyName,
+ [property: JsonPropertyName("status")] UserStatusId UserStatusId,
+ [property: JsonPropertyName("assignedRoles")] IEnumerable AssignedRoles,
+ [property: JsonPropertyName("admin")] IEnumerable AdminDetails,
+ [property: JsonPropertyName("idpUserIds")] IEnumerable IdpUserIds)
+{
+ [JsonPropertyName("firstName")]
+ public string? FirstName { get; set; }
+
+ [JsonPropertyName("lastName")]
+ public string? LastName { get; set; }
+
+ [JsonPropertyName("email")]
+ public string? Email { get; set; }
+}
+
+public record CompanyUserDetailTransferData(
+ [property: JsonPropertyName("companyUserId")] Guid CompanyUserId,
+ [property: JsonPropertyName("created")] DateTimeOffset CreatedAt,
+ [property: JsonPropertyName("bpn")] IEnumerable BusinessPartnerNumbers,
+ [property: JsonPropertyName("company")] string CompanyName,
+ [property: JsonPropertyName("status")] UserStatusId UserStatusId,
+ [property: JsonPropertyName("assignedRoles")] IEnumerable AssignedRoles,
+ [property: JsonPropertyName("idpUserIds")] IEnumerable IdpUserIds)
+{
+ [JsonPropertyName("firstName")]
+ public string? FirstName { get; set; }
+
+ [JsonPropertyName("lastName")]
+ public string? LastName { get; set; }
+
+ [JsonPropertyName("email")]
+ public string? Email { get; set; }
+}
+
+public record IdpUserTransferId(
+ [property: JsonPropertyName("alias")] string? Alias,
+ [property: JsonPropertyName("userId")] string UserId);
diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserIdentityProviderProcessData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserIdentityProviderProcessData.cs
index 0ac8b20fdf..c9df539bfc 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserIdentityProviderProcessData.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Models/CompanyUserIdentityProviderProcessData.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,17 +19,17 @@
namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
-public record CompanyUserIdentityProviderProcessData(
+public record CompanyUserIdentityProviderProcessTransferData(
Guid CompanyUserId,
string? FirstName,
string? LastName,
string? Email,
string CompanyName,
string? Bpn,
- IEnumerable ProviderLinkData
+ IEnumerable ProviderLinkData
);
-public record ProviderLinkData(
+public record ProviderLinkTransferData(
string UserName,
string? Alias,
string ProviderUserId
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
index d6db105873..01c98504eb 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs
@@ -112,7 +112,7 @@ public void CreateUpdateDeleteIdentifiers(Guid companyId, IEnumerable<(UniqueIde
_context.Companies
.AsNoTracking()
.Where(company => company.CompanyStatusId == CompanyStatusId.ACTIVE &&
- (bpnIds == null || bpnIds.Contains(company.BusinessPartnerNumber) &&
+ (bpnIds == null || bpnIds.Select(x => x.ToUpper()).Contains(company.BusinessPartnerNumber) &&
company.BusinessPartnerNumber != null))
.Select(company => company.BusinessPartnerNumber)
.AsAsyncEnumerable();
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs
index 29190381e5..bfd69dded3 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs
@@ -34,12 +34,14 @@ public interface IUserRepository
Identity CreateIdentity(Guid companyId, UserStatusId userStatusId, IdentityTypeId identityTypeId, Action? setOptionalFields);
void AttachAndModifyCompanyUser(Guid companyUserId, Action? initialize, Action setOptionalParameters);
IQueryable GetOwnCompanyUserQuery(Guid companyId, Guid? companyUserId = null, string? firstName = null, string? lastName = null, string? email = null, IEnumerable? statusIds = null);
+ Func?>> GetOwnCompanyUserData(Guid companyId, Guid? companyUserId = null, string? firstName = null, string? lastName = null, string? email = null, IEnumerable? statusIds = null);
+
Task<(string? FirstName, string? LastName, string? Email)> GetUserEntityDataAsync(Guid companyUserId, Guid companyId);
IAsyncEnumerable<(Guid CompanyUserId, bool IsFullMatch)> GetMatchingCompanyIamUsersByNameEmail(string firstName, string lastName, string email, Guid companyId, IEnumerable companyUserStatusIds);
Task IsOwnCompanyUserWithEmailExisting(string email, Guid companyId);
- Task GetOwnCompanyUserDetailsUntrackedAsync(Guid companyUserId, Guid companyId);
+ Task GetOwnCompanyUserDetailsUntrackedAsync(Guid companyUserId, Guid companyId);
Task<(IEnumerable AssignedBusinessPartnerNumbers, bool IsValidUser)> GetOwnCompanyUserWithAssignedBusinessPartnerNumbersUntrackedAsync(Guid companyUserId, Guid companyId);
- Task GetUserDetailsUntrackedAsync(Guid companyUserId, IEnumerable userRoleIds);
+ Task GetUserDetailsUntrackedAsync(Guid companyUserId, IEnumerable userRoleIds);
Task GetUserWithCompanyIdpAsync(Guid companyUserId);
///
@@ -123,6 +125,8 @@ public interface IUserRepository
void AttachAndModifyIdentities(IEnumerable<(Guid IdentityId, Action? Initialize, Action Modify)> identities);
CompanyUserAssignedIdentityProvider AddCompanyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId, string providerId, string userName);
void RemoveCompanyUserAssignedIdentityProviders(IEnumerable<(Guid CompanyUserId, Guid IdentityProviderId)> companyUserIdentityProviderIds);
- IAsyncEnumerable GetUserAssignedIdentityProviderForNetworkRegistration(Guid networkRegistrationId);
+ IAsyncEnumerable GetUserAssignedIdentityProviderForNetworkRegistration(Guid networkRegistrationId);
IAsyncEnumerable GetNextIdentitiesForNetworkRegistration(Guid networkRegistrationId, IEnumerable validUserStates);
+ Task<(bool Exists, string ProviderId, string Username)> GetCompanyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId);
+ void AttachAndModifyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId, Action? initialize, Action modify);
}
diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs
index fb815f6983..e5ca10f0d3 100644
--- a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs
+++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs
@@ -99,6 +99,42 @@ public IQueryable GetOwnCompanyUserQuery(
(statusIds == null || statusIds.Contains(companyUser.Identity!.UserStatusId)));
}
+ public Func?>> GetOwnCompanyUserData(
+ Guid companyId,
+ Guid? companyUserId = null,
+ string? firstName = null,
+ string? lastName = null,
+ string? email = null,
+ IEnumerable? statusIds = null) =>
+ (skip, take) => Pagination.CreateSourceQueryAsync(
+ skip,
+ take,
+ _dbContext.CompanyUsers
+ .AsSplitQuery()
+ .Where(companyUser =>
+ companyUser.Identity!.CompanyId == companyId &&
+ (!companyUserId.HasValue || companyUser.Id == companyUserId.Value) &&
+ (firstName == null || companyUser.Firstname == firstName) &&
+ (lastName == null || companyUser.Lastname == lastName) &&
+ (email == null || EF.Functions.ILike(companyUser.Email!, $"%{email.EscapeForILike()}%")) &&
+ (statusIds == null || statusIds.Contains(companyUser.Identity!.UserStatusId)))
+ .GroupBy(x => x.Identity!.CompanyId),
+ x => x.OrderBy(cu => cu.Identity!.DateCreated),
+ cu => new CompanyUserTransferData(
+ cu.Id,
+ cu.Identity!.UserStatusId,
+ cu.Identity.DateCreated,
+ cu.Firstname,
+ cu.Lastname,
+ cu.Email,
+ cu.Identity!.IdentityAssignedRoles.Select(x => x.UserRole!).Select(userRole => new UserRoleData(
+ userRole.Id,
+ userRole.Offer!.AppInstances.First().IamClient!.ClientClientId,
+ userRole.UserRoleText)),
+ cu.CompanyUserAssignedIdentityProviders.Select(x => new IdpUserTransferId(x.IdentityProvider!.IamIdentityProvider!.IamIdpAlias, x.ProviderId))
+ ))
+ .SingleOrDefaultAsync();
+
public Task<(string? FirstName, string? LastName, string? Email)> GetUserEntityDataAsync(Guid companyUserId, Guid companyId) =>
_dbContext.CompanyUsers
.AsNoTracking()
@@ -129,14 +165,14 @@ public Task IsOwnCompanyUserWithEmailExisting(string email, Guid companyId
_dbContext.CompanyUsers
.AnyAsync(companyUser => companyUser.Identity!.CompanyId == companyId && companyUser.Email == email);
- public Task GetOwnCompanyUserDetailsUntrackedAsync(Guid companyUserId, Guid companyId) =>
+ public Task GetOwnCompanyUserDetailsUntrackedAsync(Guid companyUserId, Guid companyId) =>
_dbContext.CompanyUsers
.AsNoTracking()
.Where(companyUser =>
companyUser.Id == companyUserId &&
companyUser.Identity!.UserStatusId == UserStatusId.ACTIVE &&
companyUser.Identity!.CompanyId == companyId)
- .Select(companyUser => new CompanyUserDetails(
+ .Select(companyUser => new CompanyUserDetailTransferData(
companyUser.Id,
companyUser.Identity!.DateCreated,
companyUser.CompanyUserAssignedBusinessPartners.Select(assignedPartner =>
@@ -148,7 +184,8 @@ public Task IsOwnCompanyUserWithEmailExisting(string email, Guid companyId
.Select(offer => new CompanyUserAssignedRoleDetails(
offer.Id,
offer.UserRoles.Where(role => companyUser.Identity!.IdentityAssignedRoles.Select(x => x.UserRole).Contains(role)).Select(x => x.UserRoleText)
- )))
+ )),
+ companyUser.CompanyUserAssignedIdentityProviders.Select(cuidp => new IdpUserTransferId(cuidp.IdentityProvider!.IamIdentityProvider!.IamIdpAlias, cuidp.ProviderId)))
{
FirstName = companyUser.Firstname,
LastName = companyUser.Lastname,
@@ -163,18 +200,16 @@ public Task IsOwnCompanyUserWithEmailExisting(string email, Guid companyId
companyUser.Id == companyUserId &&
companyUser.Identity!.CompanyId == companyId)
.Select(companyUser => new ValueTuple, bool>(
- companyUser.CompanyUserAssignedBusinessPartners.Select(assignedPartner =>
- assignedPartner.BusinessPartnerNumber),
- true)
- )
+ companyUser.CompanyUserAssignedBusinessPartners.Select(assignedPartner => assignedPartner.BusinessPartnerNumber),
+ true))
.SingleOrDefaultAsync();
- public Task GetUserDetailsUntrackedAsync(Guid companyUserId, IEnumerable userRoleIds) =>
+ public Task GetUserDetailsUntrackedAsync(Guid companyUserId, IEnumerable userRoleIds) =>
_dbContext.CompanyUsers
.AsNoTracking()
.AsSplitQuery()
.Where(companyUser => companyUser.Id == companyUserId)
- .Select(companyUser => new CompanyOwnUserDetails(
+ .Select(companyUser => new CompanyOwnUserTransferDetails(
companyUser.Id,
companyUser.Identity!.DateCreated,
companyUser.CompanyUserAssignedBusinessPartners.Select(assignedPartner =>
@@ -192,7 +227,8 @@ public Task IsOwnCompanyUserWithEmailExisting(string email, Guid companyId
companyUser.Identity!.Company.Identities.Where(i => i.IdentityTypeId == IdentityTypeId.COMPANY_USER && i.IdentityAssignedRoles.Any(role => userRoleIds.Contains(role.UserRoleId))).Select(i => i.CompanyUser!)
.Select(admin => new CompanyUserAdminDetails(
admin.Id,
- admin.Email)))
+ admin.Email)),
+ companyUser.CompanyUserAssignedIdentityProviders.Select(cuidp => new IdpUserTransferId(cuidp.IdentityProvider!.IamIdentityProvider!.IamIdpAlias, cuidp.ProviderId)))
{
FirstName = companyUser.Firstname,
LastName = companyUser.Lastname,
@@ -445,23 +481,45 @@ public CompanyUserAssignedIdentityProvider AddCompanyUserAssignedIdentityProvide
public void RemoveCompanyUserAssignedIdentityProviders(IEnumerable<(Guid CompanyUserId, Guid IdentityProviderId)> companyUserIdentityProviderIds) =>
_dbContext.CompanyUserAssignedIdentityProviders.RemoveRange(companyUserIdentityProviderIds.Select(x => new CompanyUserAssignedIdentityProvider(x.CompanyUserId, x.IdentityProviderId, null!, null!)));
- public IAsyncEnumerable GetUserAssignedIdentityProviderForNetworkRegistration(Guid networkRegistrationId) =>
+ public IAsyncEnumerable GetUserAssignedIdentityProviderForNetworkRegistration(Guid networkRegistrationId) =>
_dbContext.CompanyUsers
.Where(cu =>
cu.Identity!.UserStatusId == UserStatusId.PENDING &&
cu.Identity.Company!.NetworkRegistration!.Id == networkRegistrationId)
.Select(cu =>
- new CompanyUserIdentityProviderProcessData(
+ new CompanyUserIdentityProviderProcessTransferData(
cu.Id,
cu.Firstname,
cu.Lastname,
cu.Email,
cu.Identity!.Company!.Name,
cu.Identity.Company.BusinessPartnerNumber,
- cu.CompanyUserAssignedIdentityProviders.Select(assigned => new ProviderLinkData(assigned.UserName, assigned.IdentityProvider!.IamIdentityProvider!.IamIdpAlias, assigned.ProviderId))
+ cu.CompanyUserAssignedIdentityProviders.Select(assigned =>
+ new ProviderLinkTransferData(
+ assigned.UserName,
+ assigned.IdentityProvider!.IamIdentityProvider!.IamIdpAlias,
+ assigned.ProviderId))
))
.ToAsyncEnumerable();
+ public Task<(bool Exists, string ProviderId, string Username)> GetCompanyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId) =>
+ _dbContext.CompanyUserAssignedIdentityProviders
+ .Where(x => x.IdentityProviderId == identityProviderId && x.CompanyUserId == companyUserId)
+ .Select(x => new ValueTuple(
+ true,
+ x.ProviderId,
+ x.UserName
+ ))
+ .SingleOrDefaultAsync();
+
+ public void AttachAndModifyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId, Action? initialize, Action modify)
+ {
+ var companyUser = new CompanyUserAssignedIdentityProvider(companyUserId, identityProviderId, null!, null!);
+ initialize?.Invoke(companyUser);
+ var updatedEntity = _dbContext.Attach(companyUser).Entity;
+ modify.Invoke(updatedEntity);
+ }
+
public IAsyncEnumerable GetNextIdentitiesForNetworkRegistration(Guid networkRegistrationId, IEnumerable validUserStates) =>
_dbContext.CompanyUsers
.Where(cu =>
diff --git a/src/portalbackend/PortalBackend.PortalEntities/Enums/ProcessTypeid.cs b/src/portalbackend/PortalBackend.PortalEntities/Enums/ProcessTypeId.cs
similarity index 100%
rename from src/portalbackend/PortalBackend.PortalEntities/Enums/ProcessTypeid.cs
rename to src/portalbackend/PortalBackend.PortalEntities/Enums/ProcessTypeId.cs
diff --git a/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs b/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
index b9e7284f16..88bfd4e084 100644
--- a/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
+++ b/src/processes/NetworkRegistration.Library/NetworkRegistrationHandler.cs
@@ -18,11 +18,11 @@
********************************************************************************/
using Microsoft.Extensions.Options;
+using Org.Eclipse.TractusX.Portal.Backend.Framework.Async;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Keycloak.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.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.NetworkRegistration.Library.DependencyInjection;
@@ -104,7 +104,15 @@ public NetworkRegistrationHandler(
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);
+ 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)
{
@@ -112,7 +120,21 @@ public NetworkRegistrationHandler(
}
}
- await SendMails(GetUserMailInformation(companyAssignedIdentityProviders), ospName).ConfigureAwait(false);
+ var displayNames = await companyAssignedIdentityProviders
+ .SelectMany(assigned => assigned.ProviderLinkData)
+ .Select(data => data.Alias!)
+ .Distinct()
+ .ToImmutableDictionaryAsync(async alias =>
+ await _provisioningManager.GetIdentityProviderDisplayName(alias).ConfigureAwait(false) ?? throw new ConflictException($"Display Name should not be null for alias: {alias}")).ConfigureAwait(false);
+
+ var userData = companyAssignedIdentityProviders.Select(userData => new UserMailInformation(
+ userData.Email ?? throw new UnexpectedConditionException("userData.Email should never be null here"),
+ userData.FirstName,
+ userData.LastName,
+ userData.ProviderLinkData.Select(data => displayNames[data.Alias!])));
+
+ await SendMails(userData, ospName).ConfigureAwait(false);
+
return new ValueTuple?, ProcessStepStatusId, bool, string?>(
null,
ProcessStepStatusId.DONE,
@@ -120,41 +142,9 @@ public NetworkRegistrationHandler(
null);
}
- private async IAsyncEnumerable GetUserMailInformation(IEnumerable companyUserIdentityProviderProcessData)
- {
- var mapping = new Dictionary();
-
- async Task GetDisplayName(string idpAlias)
- {
- if (!mapping.TryGetValue(idpAlias, out var displayName))
- {
- displayName = await _provisioningManager.GetIdentityProviderDisplayName(idpAlias).ConfigureAwait(false);
- if (string.IsNullOrWhiteSpace(displayName))
- {
- throw new ConflictException($"DisplayName for idpAlias {idpAlias} couldn't be determined");
- }
- mapping.Add(idpAlias, displayName);
- }
- return displayName;
- }
-
- foreach (var userData in companyUserIdentityProviderProcessData)
- {
- yield return new UserMailInformation(
- userData.Email ?? throw new UnexpectedConditionException("userData.Email should never be null here"),
- userData.FirstName,
- userData.LastName,
- await Task.WhenAll(
- userData.ProviderLinkData.Select(pld =>
- GetDisplayName(pld.Alias ?? throw new UnexpectedConditionException("providerLinkData.Alias should never be null here")))).ConfigureAwait(false));
- }
- }
-
- private static readonly IEnumerable MailTemplates = Enumerable.Repeat("OspWelcomeMail", 1);
-
- private async Task SendMails(IAsyncEnumerable companyUserWithRoleIdForCompany, string ospName)
+ private async Task SendMails(IEnumerable companyUserWithRoleIdForCompany, string ospName)
{
- await foreach (var (receiver, firstName, lastName, displayNames) in companyUserWithRoleIdForCompany)
+ foreach (var (receiver, firstName, lastName, displayNames) in companyUserWithRoleIdForCompany)
{
var userName = string.Join(" ", firstName, lastName);
var mailParameters = new Dictionary
@@ -167,7 +157,7 @@ private async Task SendMails(IAsyncEnumerable companyUserWi
{ "url", _settings.BasePortalAddress },
{ "idpAlias", string.Join(",", displayNames) }
};
- await _mailingService.SendMails(receiver, mailParameters, MailTemplates).ConfigureAwait(false);
+ await _mailingService.SendMails(receiver, mailParameters, Enumerable.Repeat("OspWelcomeMail", 1)).ConfigureAwait(false);
}
}
diff --git a/src/processes/Processes.Worker/Processes.Worker.csproj b/src/processes/Processes.Worker/Processes.Worker.csproj
index 595b6509d0..c50d9b584c 100644
--- a/src/processes/Processes.Worker/Processes.Worker.csproj
+++ b/src/processes/Processes.Worker/Processes.Worker.csproj
@@ -1,5 +1,4 @@
+
+
+
+ Org.Eclipse.TractusX.Portal.Backend.Framework.Synchronization.Executor.Tests
+ Org.Eclipse.TractusX.Portal.Backend.Framework.Synchronization.Executor.Tests
+ net7.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/tests/processes/Synchronization.Executor.Tests/Usings.cs b/tests/processes/Synchronization.Executor.Tests/Usings.cs
new file mode 100644
index 0000000000..65016aec51
--- /dev/null
+++ b/tests/processes/Synchronization.Executor.Tests/Usings.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
+ ********************************************************************************/
+
+global using AutoFixture;
+global using AutoFixture.AutoFakeItEasy;
+global using FakeItEasy;
+global using FluentAssertions;
+global using Xunit;