Skip to content

Commit

Permalink
feat(invite): create application and company on invite
Browse files Browse the repository at this point in the history
Refs: #700
  • Loading branch information
Phil91 committed Aug 29, 2024
1 parent 983d132 commit adefd97
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,20 @@ private async Task ExecuteInvitationInternalAsync(CompanyInvitationData invitati
var processStepRepository = _portalRepositories.GetInstance<IProcessStepRepository>();
var processId = processStepRepository.CreateProcess(ProcessTypeId.INVITATION).Id;
processStepRepository.CreateProcessStep(ProcessStepTypeId.INVITATION_CREATE_CENTRAL_IDP, ProcessStepStatusId.TODO, processId);

var company = _portalRepositories.GetInstance<ICompanyRepository>().CreateCompany(organisationName);

var applicationRepository = _portalRepositories.GetInstance<IApplicationRepository>();
var applicationId = applicationRepository.CreateCompanyApplication(company.Id, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.INTERNAL).Id;
_portalRepositories.GetInstance<ICompanyInvitationRepository>().CreateCompanyInvitation(firstName, lastName, email, organisationName, processId, ci =>
{
ci.ApplicationId = applicationId;
if (!string.IsNullOrWhiteSpace(userName))
{
if (!string.IsNullOrWhiteSpace(userName))
{
ci.UserName = userName;
}
});
ci.UserName = userName;
}
});

await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/********************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

using Microsoft.EntityFrameworkCore;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
Expand Down Expand Up @@ -73,6 +73,16 @@ public Task<Guid> GetCompanyInvitationForProcessId(Guid processId) =>
))
.SingleOrDefaultAsync();

public Task<(bool Exists, Guid CompanyId, string? IdpName)> GetIdpAndCompanyId(Guid invitationId) =>
_context.CompanyInvitations
.Where(x => x.Id == invitationId)
.Select(x => new ValueTuple<bool, Guid, string?>(
true,
x.Application!.CompanyId,
x.IdpName
))
.SingleOrDefaultAsync();

public void AttachAndModifyCompanyInvitation(Guid invitationId, Action<CompanyInvitation>? initialize, Action<CompanyInvitation> modify)
{
var entity = new CompanyInvitation(invitationId, null!, null!, null!, null!, Guid.Empty);
Expand All @@ -87,16 +97,15 @@ public void AttachAndModifyCompanyInvitation(Guid invitationId, Action<CompanyIn
.Select(x => x.IdpName)
.SingleOrDefaultAsync();

public Task<(string OrgName, string? IdpName, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetUpdateCentralIdpUrlData(Guid invitationId) =>
public Task<(string OrgName, string? IdpName, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector)> GetUpdateCentralIdpUrlData(Guid invitationId) =>
_context.CompanyInvitations
.Where(x => x.Id == invitationId)
.Select(x => new ValueTuple<string, string?, string?, byte[]?, byte[]?, int?>(
.Select(x => new ValueTuple<string, string?, string?, byte[]?, byte[]?>(
x.OrganisationName,
x.IdpName,
x.ClientId,
x.ClientSecret,
x.InitializationVector,
x.EncryptionMode))
x.InitializationVector))
.SingleOrDefaultAsync();

public Task<string?> GetServiceAccountUserIdForInvitation(Guid invitationId) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public interface ICompanyInvitationRepository
Task<string?> GetOrganisationNameForInvitation(Guid invitationId);
Task<(bool Exists, Guid? ApplicationId, Guid? CompanyId, string CompanyName, IEnumerable<(Guid IdpId, string IdpName)> IdpInformation, UserInvitationInformation UserInformation)> GetInvitationUserData(Guid companyInvitationId);
Task<(bool Exists, string OrgName, string? IdpName)> GetIdpAndOrgName(Guid invitationId);
Task<(bool Exists, Guid CompanyId, string? IdpName)> GetIdpAndCompanyId(Guid invitationId);
void AttachAndModifyCompanyInvitation(Guid invitationId, Action<CompanyInvitation>? initialize, Action<CompanyInvitation> modify);
Task<string?> GetIdpNameForInvitationId(Guid invitationId);
Task<(string OrgName, string? IdpName, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetUpdateCentralIdpUrlData(Guid invitationId);
Task<(string OrgName, string? IdpName, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector)> GetUpdateCentralIdpUrlData(Guid invitationId);
Task<string?> GetServiceAccountUserIdForInvitation(Guid invitationId);
}
39 changes: 15 additions & 24 deletions src/processes/Invitation.Executor/InvitationProcessService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.Processes.Invitation.Executor.DependencyInjection;
using Org.Eclipse.TractusX.Portal.Backend.Processes.Mailing.Library;
Expand Down Expand Up @@ -136,7 +137,7 @@ public InvitationProcessService(
public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> UpdateCentralIdpUrl(Guid invitationId)
{
var companyInvitationRepository = _portalRepositories.GetInstance<ICompanyInvitationRepository>();
var (orgName, idpName, clientId, clientSecret, initializationVector, encryptionMode) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);
var (orgName, idpName, clientId, clientSecret, initializationVector) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);

if (string.IsNullOrWhiteSpace(idpName))
{
Expand All @@ -148,28 +149,23 @@ public InvitationProcessService(
throw new ConflictException("ClientId must not be null");
}

var secret = Decrypt(clientSecret, initializationVector, encryptionMode);
var secret = Decrypt(clientSecret, initializationVector);

await _idpManagement.UpdateCentralIdentityProviderUrlsAsync(idpName, orgName, _settings.InitialLoginTheme, clientId, secret).ConfigureAwait(false);

return (Enumerable.Repeat(ProcessStepTypeId.INVITATION_CREATE_SHARED_CLIENT, 1), ProcessStepStatusId.DONE, true, null);
}

private string Decrypt(byte[]? clientSecret, byte[]? initializationVector, int? encryptionMode)
private string Decrypt(byte[]? clientSecret, byte[]? initializationVector)
{
var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex);

if (clientSecret == null)
{
throw new ConflictException("ClientSecret must not be null");
}

if (encryptionMode == null)
{
throw new ConflictException("EncryptionMode must not be null");
}

var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == encryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {encryptionMode} is not configured");

return CryptoHelper.Decrypt(clientSecret, initializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode);
return cryptoHelper.Decrypt(clientSecret, initializationVector);
}

public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCentralIdpOrgMapper(Guid invitationId)
Expand All @@ -181,6 +177,7 @@ private string Decrypt(byte[]? clientSecret, byte[]? initializationVector, int?
{
throw new ConflictException($"Invitation {invitationId} does not exist");
}

if (string.IsNullOrWhiteSpace(idpName))
{
throw new ConflictException(IdpNotSetErrorMessage);
Expand All @@ -194,7 +191,7 @@ private string Decrypt(byte[]? clientSecret, byte[]? initializationVector, int?
public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSharedIdpRealm(Guid invitationId)
{
var companyInvitationRepository = _portalRepositories.GetInstance<ICompanyInvitationRepository>();
var (orgName, idpName, clientId, clientSecret, initializationVector, encryptionMode) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);
var (orgName, idpName, clientId, clientSecret, initializationVector) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);

if (string.IsNullOrWhiteSpace(idpName))
{
Expand All @@ -206,7 +203,7 @@ private string Decrypt(byte[]? clientSecret, byte[]? initializationVector, int?
throw new ConflictException("ClientId must not be null");
}

var secret = Decrypt(clientSecret, initializationVector, encryptionMode);
var secret = Decrypt(clientSecret, initializationVector);

await _idpManagement
.CreateSharedRealmIdpClientAsync(idpName, _settings.InitialLoginTheme, orgName, clientId, secret)
Expand All @@ -218,7 +215,7 @@ await _idpManagement
public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSharedClient(Guid invitationId)
{
var companyInvitationRepository = _portalRepositories.GetInstance<ICompanyInvitationRepository>();
var (_, idpName, clientId, clientSecret, initializationVector, encryptionMode) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);
var (_, idpName, clientId, clientSecret, initializationVector) = await companyInvitationRepository.GetUpdateCentralIdpUrlData(invitationId).ConfigureAwait(ConfigureAwaitOptions.None);

if (string.IsNullOrWhiteSpace(idpName))
{
Expand All @@ -230,7 +227,7 @@ await _idpManagement
throw new ConflictException("ClientId must not be null");
}

var secret = Decrypt(clientSecret, initializationVector, encryptionMode);
var secret = Decrypt(clientSecret, initializationVector);

await _idpManagement
.CreateSharedClientAsync(idpName, clientId, secret)
Expand Down Expand Up @@ -259,7 +256,7 @@ await _idpManagement
public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateIdpDatabase(Guid companyInvitationId)
{
var companyInvitationRepository = _portalRepositories.GetInstance<ICompanyInvitationRepository>();
var (exists, orgName, idpName) = await companyInvitationRepository.GetIdpAndOrgName(companyInvitationId).ConfigureAwait(ConfigureAwaitOptions.None);
var (exists, companyId, idpName) = await companyInvitationRepository.GetIdpAndCompanyId(companyInvitationId).ConfigureAwait(ConfigureAwaitOptions.None);
if (!exists)
{
throw new NotFoundException($"CompanyInvitation {companyInvitationId} does not exist");
Expand All @@ -270,17 +267,11 @@ await _idpManagement
throw new ConflictException("IdpName must be set for the company invitation");
}

var company = _portalRepositories.GetInstance<ICompanyRepository>().CreateCompany(orgName);

var identityProviderRepository = _portalRepositories.GetInstance<IIdentityProviderRepository>();
var identityProvider = identityProviderRepository.CreateIdentityProvider(IdentityProviderCategoryId.KEYCLOAK_OIDC, IdentityProviderTypeId.SHARED, company.Id, null);
identityProvider.Companies.Add(company);
var identityProvider = identityProviderRepository.CreateIdentityProvider(IdentityProviderCategoryId.KEYCLOAK_OIDC, IdentityProviderTypeId.SHARED, companyId, null);
identityProvider.Companies.Add(new Company(companyId, null!, CompanyStatusId.PENDING, DateTimeOffset.UtcNow));
identityProviderRepository.CreateIamIdentityProvider(identityProvider.Id, idpName);

var applicationRepository = _portalRepositories.GetInstance<IApplicationRepository>();
var applicationId = applicationRepository.CreateCompanyApplication(company.Id, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.INTERNAL).Id;
companyInvitationRepository.AttachAndModifyCompanyInvitation(companyInvitationId, x => { x.ApplicationId = null; }, x => { x.ApplicationId = applicationId; });

return (Enumerable.Repeat(ProcessStepTypeId.INVITATION_CREATE_USER, 1), ProcessStepStatusId.DONE, true, null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.Busin

public class InvitationBusinessLogicTests
{
private static readonly Guid ApplicationId = Guid.NewGuid();
private static readonly Guid CompanyId = Guid.NewGuid();
private readonly IFixture _fixture;
private readonly IProcessStepRepository _processStepRepository;
private readonly ICompanyInvitationRepository _companyInvitationRepository;
private readonly ICompanyRepository _companyRepository;
private readonly IApplicationRepository _applicationRepository;
private readonly IPortalRepositories _portalRepositories;
private readonly InvitationBusinessLogic _sut;

Expand All @@ -46,9 +50,13 @@ public InvitationBusinessLogicTests()
_portalRepositories = A.Fake<IPortalRepositories>();
_processStepRepository = A.Fake<IProcessStepRepository>();
_companyInvitationRepository = A.Fake<ICompanyInvitationRepository>();
_companyRepository = A.Fake<ICompanyRepository>();
_applicationRepository = A.Fake<IApplicationRepository>();

A.CallTo(() => _portalRepositories.GetInstance<IProcessStepRepository>()).Returns(_processStepRepository);
A.CallTo(() => _portalRepositories.GetInstance<ICompanyInvitationRepository>()).Returns(_companyInvitationRepository);
A.CallTo(() => _portalRepositories.GetInstance<ICompanyRepository>()).Returns(_companyRepository);
A.CallTo(() => _portalRepositories.GetInstance<IApplicationRepository>()).Returns(_applicationRepository);

_sut = new InvitationBusinessLogic(_portalRepositories);
}
Expand Down Expand Up @@ -93,7 +101,9 @@ public async Task ExecuteInvitation_WithValidData_CreatesExpected()

processes.Should().ContainSingle().And.Satisfy(x => x.ProcessTypeId == ProcessTypeId.INVITATION);
processSteps.Should().ContainSingle().And.Satisfy(x => x.ProcessStepTypeId == ProcessStepTypeId.INVITATION_CREATE_CENTRAL_IDP && x.ProcessStepStatusId == ProcessStepStatusId.TODO);
invitations.Should().ContainSingle().And.Satisfy(x => x.ProcessId == processes.Single().Id && x.UserName == "testUserName");
invitations.Should().ContainSingle().And.Satisfy(x => x.ProcessId == processes.Single().Id && x.UserName == "testUserName" && x.ApplicationId == ApplicationId);
A.CallTo(() => _companyRepository.CreateCompany(A<string>._, null)).MustHaveHappenedOnceExactly();
A.CallTo(() => _applicationRepository.CreateCompanyApplication(CompanyId, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.INTERNAL, null)).MustHaveHappenedOnceExactly();
}

[Theory]
Expand Down Expand Up @@ -504,6 +514,7 @@ public async Task RetriggerInvitationCreateUser_WithNotExistingProcess_ThrowsExc

private void SetupFakesForInvite(List<Process> processes, List<ProcessStep> processSteps, List<CompanyInvitation> invitations)
{
var company = new Company(CompanyId, "testOrg", CompanyStatusId.PENDING, DateTimeOffset.UtcNow);
var createdProcessId = Guid.NewGuid();
A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.INVITATION))
.Invokes((ProcessTypeId processTypeId) =>
Expand All @@ -527,6 +538,9 @@ private void SetupFakesForInvite(List<Process> processes, List<ProcessStep> proc
setOptionalFields?.Invoke(entity);
invitations.Add(entity);
});
A.CallTo(() => _companyRepository.CreateCompany(A<string>._, A<Action<Company>>._)).Returns(company);
A.CallTo(() => _applicationRepository.CreateCompanyApplication(company.Id, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.INTERNAL, null))
.Returns(new CompanyApplication(ApplicationId, company.Id, CompanyApplicationStatusId.CREATED, CompanyApplicationTypeId.INTERNAL, DateTimeOffset.UtcNow));
}

private void SetupFakesForRetrigger(List<ProcessStep> processSteps)
Expand Down
Loading

0 comments on commit adefd97

Please sign in to comment.