Skip to content

Commit

Permalink
feat(n2n): move keycloak user creation to process (#280)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Reviewed-by: Norbert Truchsess <[email protected]>
  • Loading branch information
Phil91 authored Oct 10, 2023
1 parent 8df7832 commit dfeca44
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<PartnerRegistrationSettings> options)
public NetworkBusinessLogic(IPortalRepositories portalRepositories, IIdentityService identityService, IUserProvisioningService userProvisioningService, INetworkRegistrationProcessHelper processHelper, IOptions<PartnerRegistrationSettings> options)
{
_portalRepositories = portalRepositories;
_identityService = identityService;
_userProvisioningService = userProvisioningService;
_processHelper = processHelper;
_mailingService = mailingService;
_settings = options.Value;
}

Expand All @@ -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<IApplicationRepository>().CreateCompanyApplication(companyId, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.EXTERNAL,
Expand All @@ -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<IUserRepository>();
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);
}
}
}
Expand All @@ -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<string, string?, string?>(x.Email, x.FirstName, x.LastName)), companyName).ConfigureAwait(false);
}

private Guid CreatePartnerCompany(ICompanyRepository companyRepository, PartnerRegistrationData data)
Expand Down Expand Up @@ -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<string, string>
{
{ "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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private Task<IEnumerable<UserRoleData>> GetOwnCompanyUserRoleData(IEnumerable<st
{
Task.FromResult(Enumerable.Empty<UserRoleData>());
}

return _userProvisioningService.GetOwnCompanyPortalRoleDatas(_settings.Portal.KeycloakClientID, roles, companyId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
style="Margin:0;padding-top:20px;padding-bottom:20px;padding-left:30px;padding-right:30px;text-align: left;">
<p
style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:arial, 'helvetica neue', helvetica, sans-serif;line-height:21px;color:#333333;font-size:14px">
Dear {userName},<br /><br /> your registration at the Catena-X dataspace got based on your request successfully generated by {osp}. <br /><br /> 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. <br /><br /> Please follow the link below to access your registration data and to confirm the company role related terms & conditions. <br /> You may want to update the company roles by selecting additional roles.</p>
Dear {userName},<br /><br /> your registration at the Catena-X dataspace got based on your request successfully generated by {osp} for idps {idpAliasse}. <br /><br /> 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. <br /><br /> Please follow the link below to access your registration data and to confirm the company role related terms & conditions. <br /> You may want to update the company roles by selecting additional roles.</p>
</td>
</tr>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ public interface INetworkRepository
Task<(bool RegistrationIdExists, VerifyProcessData processData)> IsValidRegistration(Guid externalId, IEnumerable<ProcessStepTypeId> processStepTypeIds);
Task<(bool Exists, IEnumerable<(Guid CompanyApplicationId, CompanyApplicationStatusId CompanyApplicationStatusId, string? CallbackUrl)> CompanyApplications, IEnumerable<(CompanyRoleId CompanyRoleId, IEnumerable<Guid> AgreementIds)> CompanyRoleAgreementIds, Guid? ProcessId)> GetSubmitData(Guid companyId);
Task<(OspDetails? OspDetails, Guid? ExternalId, string? Bpn, Guid ApplicationId, IEnumerable<string> Comments)> GetCallbackData(Guid networkRegistrationId, ProcessStepTypeId processStepTypeId);
Task<string?> GetOspCompanyName(Guid networkRegistrationId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,9 @@ public Task<Guid> GetNetworkRegistrationDataForProcessIdAsync(Guid processId) =>
.Select(step => step.Message!)
: new List<string>()))
.SingleOrDefaultAsync();

public Task<string?> GetOspCompanyName(Guid networkRegistrationId) =>
_context.NetworkRegistrations.Where(x => x.Id == networkRegistrationId)
.Select(x => x.OnboardingServiceProvider!.Name)
.SingleOrDefaultAsync();
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -32,6 +31,9 @@ public class NetworkRegistrationProcessSettings
[Required]
[DistinctValues("x => x.ClientId")]
public IEnumerable<UserRoleConfig> InitialRoles { get; set; } = null!;

[Required(AllowEmptyStrings = false)]
public string BasePortalAddress { get; set; } = null!;
}

public static class NetworkRegistrationHandlerExtensions
Expand All @@ -41,6 +43,7 @@ public static IServiceCollection AddNetworkRegistrationHandler(this IServiceColl
var section = config.GetSection("NetworkRegistration");
services.AddOptions<NetworkRegistrationProcessSettings>()
.Bind(section)
.ValidateDataAnnotations()
.ValidateDistinctValues(section)
.ValidateOnStart();

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> IdpAliasse);
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\mailing\Mailing.SendMail\Mailing.SendMail.csproj" />
<ProjectReference Include="..\..\portalbackend\PortalBackend.PortalEntities\PortalBackend.PortalEntities.csproj" />
<ProjectReference Include="..\..\provisioning\Provisioning.Library\Provisioning.Library.csproj" />
<ProjectReference Include="..\Processes.Worker.Library\Processes.Worker.Library.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -36,23 +37,33 @@ 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<NetworkRegistrationProcessSettings> options)
{
_portalRepositories = portalRepositories;
_userProvisioningService = userProvisioningService;
_provisioningManager = provisioningManager;
_mailingService = mailingService;

_settings = options.Value;
}

public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SynchronizeUser(Guid networkRegistrationId)
{
var userRepository = _portalRepositories.GetInstance<IUserRepository>();
var userRoleRepository = _portalRepositories.GetInstance<IUserRolesRepository>();
var ospName = await _portalRepositories.GetInstance<INetworkRepository>().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()
Expand All @@ -75,30 +86,55 @@ 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)
{
throw new ServiceException(e.Message, true);
}
}

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<IEnumerable<ProcessStepTypeId>?, ProcessStepStatusId, bool, string?>(
null,
ProcessStepStatusId.DONE,
false,
null);
}

private async Task SendMails(IEnumerable<UserMailInformation> 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<string, string>
{
{ "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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down
Loading

0 comments on commit dfeca44

Please sign in to comment.