Skip to content

Commit

Permalink
fix(userUpload): send invites email when uploading multiple Users (ec…
Browse files Browse the repository at this point in the history
…lipse-tractusx#922)

* refactor usercreation creating mail-processes in new onSuccess-callback
* add creation of emails in UploadOwnCompanySharedIdpUsersAsync
* adjust unit-tests
Ref: eclipse-tractusx#920
---------
Co-authored-by: Norbert Truchsess <norbert.truchsess@t-online.de>
  • Loading branch information
tfjanjua authored Sep 16, 2024
1 parent 7573aba commit bb16156
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,38 +158,38 @@ public async Task<Guid> CreateOwnCompanyIdpUserAsync(Guid identityProviderId, Us
userCreationInfo.UserId,
UserStatusId.ACTIVE,
true
), 1).ToAsyncEnumerable())
), 1).ToAsyncEnumerable(),
creationData =>
{
var mailParameters = ImmutableDictionary.CreateBuilder<string, string>();
mailParameters.AddRange([
new("companyName", displayName),
new("nameCreatedBy", nameCreatedBy),
new("url", _settings.Portal.BasePortalAddress),
new("idpAlias", displayName),
]);
IEnumerable<string> mailTemplates = companyNameIdpAliasData.IsSharedIdp
? ["NewUserTemplate", "NewUserPasswordTemplate"]
: ["NewUserExternalIdpTemplate"];
if (companyNameIdpAliasData.IsSharedIdp)
{
mailParameters.Add(new("password", creationData.Password ?? throw new UnexpectedConditionException("password should never be null here")));
}
foreach (var template in mailTemplates)
{
mailingProcessCreation.CreateMailProcess(creationData.UserCreationInfo.Email, template, mailParameters.ToImmutable());
}
})
.FirstAsync()
.ConfigureAwait(false);

if (result.Error != null)
{
throw result.Error;
}

var mailParameters = new Dictionary<string, string>
{
{ "companyName", displayName },
{ "nameCreatedBy", nameCreatedBy },
{ "url", _settings.Portal.BasePortalAddress },
{ "idpAlias", displayName },
};

var mailTemplates = companyNameIdpAliasData.IsSharedIdp
? new[] { "NewUserTemplate", "NewUserPasswordTemplate" }
: new[] { "NewUserExternalIdpTemplate" };

if (companyNameIdpAliasData.IsSharedIdp)
{
mailParameters["password"] = result.Password;
}

foreach (var template in mailTemplates)
{
mailingProcessCreation.CreateMailProcess(userCreationInfo.Email, template, mailParameters.ToImmutableDictionary());
}

await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
return result.CompanyUserId;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;

namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic;

Expand Down Expand Up @@ -78,6 +77,10 @@ private async ValueTask<UserCreationStats> UploadOwnCompanyIdpUsersInternalAsync

var validRoleData = new List<UserRoleData>();

var displayName = companyNameIdpAliasData.IsSharedIdp
? null
: await _userProvisioningService.GetIdentityProviderDisplayName(companyNameIdpAliasData.IdpAlias).ConfigureAwait(ConfigureAwaitOptions.None) ?? companyNameIdpAliasData.IdpAlias;

var (numCreated, numLines, errors) = await CsvParser.ProcessCsvAsync(
stream,
line =>
Expand All @@ -99,17 +102,23 @@ await GetUserRoleDatas(parsed.Roles, validRoleData, _identityData.CompanyId).Con
UserStatusId.ACTIVE,
true);
},
lines => (companyNameIdpAliasData.IsSharedIdp
? _userProvisioningService
lines =>
_userProvisioningService
.CreateOwnCompanyIdpUsersAsync(
companyNameIdpAliasData,
lines,
creationData =>
{
if (companyNameIdpAliasData.IsSharedIdp)
return;
var mailParameters = ImmutableDictionary.CreateRange<string, string>([
new("nameCreatedBy", nameCreatedBy),
new("url", _settings.Portal.BasePortalAddress),
new("idpAlias", displayName ?? throw new UnexpectedConditionException("displayname should never be null here"))
]);
_mailingProcessCreation.CreateMailProcess(creationData.UserCreationInfo.Email, "NewUserExternalIdpTemplate", mailParameters);
},
cancellationToken)
: CreateOwnCompanyIdpUsersWithEmailAsync(
nameCreatedBy,
companyNameIdpAliasData,
lines,
cancellationToken))
.Select(x => (x.CompanyUserId != Guid.Empty, x.Error)),
cancellationToken).ConfigureAwait(false);

Expand All @@ -120,53 +129,6 @@ await GetUserRoleDatas(parsed.Roles, validRoleData, _identityData.CompanyId).Con
errors.Select(error => CreateUserCreationError(error.Line, error.Error)));
}

private async IAsyncEnumerable<(Guid CompanyUserId, string UserName, string? Password, Exception? Error)> CreateOwnCompanyIdpUsersWithEmailAsync(string nameCreatedBy, CompanyNameIdpAliasData companyNameIdpAliasData, IAsyncEnumerable<UserCreationRoleDataIdpInfo> userCreationInfos, [EnumeratorCancellation] CancellationToken cancellationToken)
{
if (companyNameIdpAliasData.IsSharedIdp)
{
throw new UnexpectedConditionException($"unexpected call to {nameof(CreateOwnCompanyIdpUsersWithEmailAsync)} for shared-idp");
}

UserCreationRoleDataIdpInfo? userCreationInfo = null;

var displayName = await _userProvisioningService.GetIdentityProviderDisplayName(companyNameIdpAliasData.IdpAlias).ConfigureAwait(ConfigureAwaitOptions.None) ?? companyNameIdpAliasData.IdpAlias;

await foreach (var result in
_userProvisioningService
.CreateOwnCompanyIdpUsersAsync(
companyNameIdpAliasData,
userCreationInfos
.Select(info =>
{
userCreationInfo = info;
return info;
}),
cancellationToken)
.WithCancellation(cancellationToken)
.ConfigureAwait(false))
{
if (userCreationInfo == null)
{
throw new UnexpectedConditionException("userCreationInfo should never be null here");
}
if (result.Error != null || result.CompanyUserId == Guid.Empty || string.IsNullOrEmpty(userCreationInfo.Email))
{
yield return result;
continue;
}

var mailParameters = ImmutableDictionary.CreateRange(new[]
{
KeyValuePair.Create("nameCreatedBy", nameCreatedBy),
KeyValuePair.Create("url", _settings.Portal.BasePortalAddress),
KeyValuePair.Create("idpAlias", displayName)
});
_mailingProcessCreation.CreateMailProcess(userCreationInfo.Email, "NewUserExternalIdpTemplate", mailParameters);

yield return (result.CompanyUserId, result.UserName, result.Password, null);
}
}

private static void ValidateUserCreationRoles(IEnumerable<string> roles)
{
if (!roles.Any())
Expand Down Expand Up @@ -199,7 +161,8 @@ private async ValueTask<UserCreationStats> UploadOwnCompanySharedIdpUsersInterna
{
using var stream = document.OpenReadStream();

var (companyNameIdpAliasData, _) = await _userProvisioningService.GetCompanyNameSharedIdpAliasData(_identityData.IdentityId).ConfigureAwait(ConfigureAwaitOptions.None);
var (companyNameIdpAliasData, nameCreatedBy) = await _userProvisioningService.GetCompanyNameSharedIdpAliasData(_identityData.IdentityId).ConfigureAwait(ConfigureAwaitOptions.None);
var displayName = await _userProvisioningService.GetIdentityProviderDisplayName(companyNameIdpAliasData.IdpAlias).ConfigureAwait(ConfigureAwaitOptions.None) ?? companyNameIdpAliasData.IdpAlias;

var validRoleData = new List<UserRoleData>();

Expand Down Expand Up @@ -229,6 +192,18 @@ await GetUserRoleDatas(parsed.Roles, validRoleData, _identityData.CompanyId).Con
.CreateOwnCompanyIdpUsersAsync(
companyNameIdpAliasData,
lines,
creationData =>
{
var mailParameters = ImmutableDictionary.CreateRange<string, string>([
new("password", creationData.Password ?? ""),
new("companyName", displayName),
new("nameCreatedBy", nameCreatedBy),
new("url", _settings.Portal.BasePortalAddress),
new("passwordResendUrl", _settings.Portal.PasswordResendAddress),
]);
_mailingProcessCreation.CreateMailProcess(creationData.UserCreationInfo.Email, "NewUserTemplate", mailParameters);
_mailingProcessCreation.CreateMailProcess(creationData.UserCreationInfo.Email, "NewUserPasswordTemplate", mailParameters);
},
cancellationToken)
.Select(x => (x.CompanyUserId != Guid.Empty, x.Error)),
cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ await _idpManagement
true
)}.ToAsyncEnumerable();

var (companyUserId, _, password, error) = await _userProvisioningService.CreateOwnCompanyIdpUsersAsync(companyNameIdpAliasData, userCreationInfoIdps, cancellationToken).SingleAsync(cancellationToken).ConfigureAwait(false);
var (companyUserId, _, password, error) = await _userProvisioningService.CreateOwnCompanyIdpUsersAsync(companyNameIdpAliasData, userCreationInfoIdps, cancellationToken: cancellationToken).SingleAsync(cancellationToken).ConfigureAwait(false);

if (error is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ public record UserCreationRoleDataIdpInfo(
UserStatusId UserStatusId,
bool Enabled
);

public record UserCreationCallbackData(
UserCreationRoleDataIdpInfo UserCreationInfo,
string? Password
);
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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<UserCreationRoleDataIdpInfo> userCreationInfos, CancellationToken cancellationToken = default);
IAsyncEnumerable<(Guid CompanyUserId, string UserName, string? Password, Exception? Error)> CreateOwnCompanyIdpUsersAsync(CompanyNameIdpAliasData companyNameIdpAliasData, IAsyncEnumerable<UserCreationRoleDataIdpInfo> userCreationInfos, Action<UserCreationCallbackData>? onSuccessfulCreation = null, CancellationToken cancellationToken = default);
Task HandleCentralKeycloakCreation(UserCreationRoleDataIdpInfo user, Guid companyUserId, string companyName, string? businessPartnerNumber, Identity? identity, IEnumerable<IdentityProviderLink> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public UserProvisioningService(IProvisioningManager provisioningManager, IPortal
public async IAsyncEnumerable<(Guid CompanyUserId, string UserName, string? Password, Exception? Error)> CreateOwnCompanyIdpUsersAsync(
CompanyNameIdpAliasData companyNameIdpAliasData,
IAsyncEnumerable<UserCreationRoleDataIdpInfo> userCreationInfos,
Action<UserCreationCallbackData>? onSuccessfulCreation = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var userRepository = _portalRepositories.GetInstance<IUserRepository>();
Expand All @@ -76,6 +77,7 @@ public UserProvisioningService(IProvisioningManager provisioningManager, IPortal

var providerUserId = await CreateSharedIdpUserOrReturnUserId(user, alias, nextPassword, isSharedIdp).ConfigureAwait(ConfigureAwaitOptions.None);
await HandleCentralKeycloakCreation(user, companyUserId, companyName, businessPartnerNumber, identity, Enumerable.Repeat(new IdentityProviderLink(alias, providerUserId, user.UserName), 1), userRepository, userRolesRepository).ConfigureAwait(ConfigureAwaitOptions.None);
onSuccessfulCreation?.Invoke(new(user, nextPassword));
}
catch (Exception e) when (e is not OperationCanceledException)
{
Expand Down
Loading

0 comments on commit bb16156

Please sign in to comment.