Skip to content

Commit

Permalink
feat(connector): adjust connector deletion
Browse files Browse the repository at this point in the history
* add flag to define whether a linked service account should be deleted
* adjust deletion logic for service accounts when deleting a connector

Refs: #966 #967
  • Loading branch information
Phil91 committed Sep 10, 2024
1 parent 57f5120 commit 66c05e4
Show file tree
Hide file tree
Showing 20 changed files with 448 additions and 266 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class ConnectorsBusinessLogic(
IOptions<ConnectorsSettings> options,
ISdFactoryBusinessLogic sdFactoryBusinessLogic,
IIdentityService identityService,
IServiceAccountManagement serviceAccountManagement,
ILogger<ConnectorsBusinessLogic> logger)
: IConnectorsBusinessLogic
{
Expand Down Expand Up @@ -242,22 +243,26 @@ await sdFactoryBusinessLogic
}

/// <inheritdoc/>
public async Task DeleteConnectorAsync(Guid connectorId)
public async Task DeleteConnectorAsync(Guid connectorId, bool deleteServiceAccount)
{
var companyId = _identityData.CompanyId;
var connectorsRepository = portalRepositories.GetInstance<IConnectorsRepository>();
var result = await connectorsRepository.GetConnectorDeleteDataAsync(connectorId, companyId).ConfigureAwait(ConfigureAwaitOptions.None) ?? throw NotFoundException.Create(AdministrationConnectorErrors.CONNECTOR_NOT_FOUND, new ErrorParameter[] { new("connectorId", connectorId.ToString()) });
var processStepsToFilter = new[]
{
ProcessStepTypeId.CREATE_DIM_TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_CREATE_DIM_TECHNICAL_USER,
ProcessStepTypeId.AWAIT_CREATE_DIM_TECHNICAL_USER_RESPONSE,
ProcessStepTypeId.RETRIGGER_AWAIT_CREATE_DIM_TECHNICAL_USER_RESPONSE
};

var result = await connectorsRepository.GetConnectorDeleteDataAsync(connectorId, companyId, processStepsToFilter).ConfigureAwait(ConfigureAwaitOptions.None) ?? throw NotFoundException.Create(AdministrationConnectorErrors.CONNECTOR_NOT_FOUND, new ErrorParameter[] { new("connectorId", connectorId.ToString()) });
if (!result.IsProvidingOrHostCompany)
{
throw ForbiddenException.Create(AdministrationConnectorErrors.CONNECTOR_NOT_PROVIDER_COMPANY_NOR_HOST, new ErrorParameter[] { new("companyId", companyId.ToString()), new("connectorId", connectorId.ToString()) });
}

if (result.ServiceAccountId.HasValue && result.UserStatusId != UserStatusId.INACTIVE)
if (result is { ServiceAccountId: not null, UserStatusId: UserStatusId.ACTIVE or UserStatusId.PENDING } && deleteServiceAccount)
{
portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(result.ServiceAccountId.Value, null, i =>
{
i.UserStatusId = UserStatusId.INACTIVE;
});
await serviceAccountManagement.DeleteServiceAccount(result.ServiceAccountId!.Value, result.DeleteServiceAccountData).ConfigureAwait(false);
}

switch (result.ConnectorStatus)
Expand Down Expand Up @@ -290,6 +295,7 @@ private async Task DeleteUpdateConnectorDetail(Guid connectorId, IConnectorsRepo
{
connectorsRepository.AttachAndModifyConnector(connectorId, null, con =>
{
con.CompanyServiceAccountId = null;
con.StatusId = ConnectorStatusId.INACTIVE;
con.DateLastChanged = DateTimeOffset.UtcNow;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public interface IConnectorsBusinessLogic
/// Remove a connector from persistence layer by id.
/// </summary>
/// <param name="connectorId">ID of the connector to be deleted.</param>
Task DeleteConnectorAsync(Guid connectorId);
/// <param name="deleteServiceAccount">if <c>true</c> the linked service account will be deleted, otherwise the connection to the connector will just be removed</param>
Task DeleteConnectorAsync(Guid connectorId, bool deleteServiceAccount);

/// <summary>
/// Retrieve connector end point along with bpns
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* 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 Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;

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

public interface IServiceAccountManagement
{
Task DeleteServiceAccount(Guid serviceAccountId, DeleteServiceAccountData result);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities;
using Org.Eclipse.TractusX.Portal.Backend.Processes.Library;
Expand All @@ -44,7 +43,8 @@ public class ServiceAccountBusinessLogic(
IPortalRepositories portalRepositories,
IOptions<ServiceAccountSettings> options,
IServiceAccountCreation serviceAccountCreation,
IIdentityService identityService)
IIdentityService identityService,
IServiceAccountManagement serviceAccountManagement)
: IServiceAccountBusinessLogic
{
private readonly IIdentityData _identityData = identityService.IdentityData;
Expand Down Expand Up @@ -119,34 +119,7 @@ public async Task<int> DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId
}

// serviceAccount
var userStatus = UserStatusId.DELETED;
switch (result)
{
case { IsDimServiceAccount: true, CreationProcessInProgress: false }:
userStatus = await CreateDeletionProcess(serviceAccountId, result).ConfigureAwait(ConfigureAwaitOptions.None);
break;
case { IsDimServiceAccount: true, CreationProcessInProgress: true }:
throw ConflictException.Create(AdministrationServiceAccountErrors.TECHNICAL_USER_CREATION_IN_PROGRESS);
default:
if (!string.IsNullOrWhiteSpace(result.ClientClientId))
{
await provisioningManager.DeleteCentralClientAsync(result.ClientClientId).ConfigureAwait(ConfigureAwaitOptions.None);
}

break;
}

portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(
serviceAccountId,
i =>
{
i.UserStatusId = UserStatusId.PENDING;
},
i =>
{
i.UserStatusId = userStatus;
});
portalRepositories.GetInstance<IUserRolesRepository>().DeleteCompanyUserAssignedRoles(result.UserRoleIds.Select(userRoleId => (serviceAccountId, userRoleId)));
await serviceAccountManagement.DeleteServiceAccount(serviceAccountId, new DeleteServiceAccountData(result.UserRoleIds, result.ClientClientId, result.IsDimServiceAccount, result.CreationProcessInProgress, result.ProcessId)).ConfigureAwait(ConfigureAwaitOptions.None);
ModifyConnectorForDeleteServiceAccount(serviceAccountId, result);

return await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
Expand All @@ -168,28 +141,6 @@ private void ModifyConnectorForDeleteServiceAccount(Guid serviceAccountId, OwnSe
}
}

private async Task<UserStatusId> CreateDeletionProcess(Guid serviceAccountId, OwnServiceAccountData result)
{
var processId = result.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]);

var processData = await portalRepositories.GetInstance<IProcessStepRepository>()
.GetProcessDataForServiceAccountDeletionCallback(processId, null)
.ConfigureAwait(ConfigureAwaitOptions.None);

var context = processData.ProcessData.CreateManualProcessData(null,
portalRepositories, () => $"externalId {processId}");

context.ProcessSteps.Where(step => step.ProcessStepTypeId != ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER).IfAny(pending =>
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, [new("serviceAccountId", serviceAccountId.ToString()), new("processStepTypeIds", string.Join(",", pending))]));

if (context.ProcessSteps.Any(step => step.ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER))
return UserStatusId.DELETED;

context.ScheduleProcessSteps([ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER]);
context.FinalizeProcessStep();
return UserStatusId.PENDING_DELETION;
}

public async Task<ServiceAccountConnectorOfferData> GetOwnCompanyServiceAccountDetailsAsync(Guid serviceAccountId)
{
var companyId = _identityData.CompanyId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/********************************************************************************
* 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 Org.Eclipse.TractusX.Portal.Backend.Administration.Service.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.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.Library;
using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library;

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

public class ServiceAccountManagement(IProvisioningManager provisioningManager, IPortalRepositories portalRepositories) : IServiceAccountManagement
{
public async Task DeleteServiceAccount(Guid serviceAccountId, DeleteServiceAccountData result)
{
var userStatus = UserStatusId.DELETED;
switch (result)
{
case { IsDimServiceAccount: true, CreationProcessInProgress: false }:
userStatus = await CreateDeletionProcess(serviceAccountId, result.ProcessId).ConfigureAwait(ConfigureAwaitOptions.None);
break;
case { IsDimServiceAccount: true, CreationProcessInProgress: true }:
throw ConflictException.Create(AdministrationServiceAccountErrors.TECHNICAL_USER_CREATION_IN_PROGRESS);
default:
if (!string.IsNullOrWhiteSpace(result.ClientClientId))
{
await provisioningManager.DeleteCentralClientAsync(result.ClientClientId).ConfigureAwait(ConfigureAwaitOptions.None);
}

break;
}

portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(
serviceAccountId,
i =>
{
i.UserStatusId = UserStatusId.PENDING;
},
i =>
{
i.UserStatusId = userStatus;
});
portalRepositories.GetInstance<IUserRolesRepository>().DeleteCompanyUserAssignedRoles(result.UserRoleIds.Select(userRoleId => (serviceAccountId, userRoleId)));
}

private async Task<UserStatusId> CreateDeletionProcess(Guid serviceAccountId, Guid? processId)
{
if (processId == null)
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new ErrorParameter("serviceAccountId", serviceAccountId.ToString())]);
}

var processData = await portalRepositories.GetInstance<IProcessStepRepository>()
.GetProcessDataForServiceAccountDeletionCallback(processId.Value, null)
.ConfigureAwait(ConfigureAwaitOptions.None);

var context = processData.ProcessData.CreateManualProcessData(null,
portalRepositories, () => $"externalId {processId}");

context.ProcessSteps.Where(step => step.ProcessStepTypeId != ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER).IfAny(pending =>
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, [new ErrorParameter("serviceAccountId", serviceAccountId.ToString()), new("processStepTypeIds", string.Join<ProcessStep>(",", pending))]));

if (context.ProcessSteps.Any(step => step.ProcessStepTypeId == ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER))
return UserStatusId.DELETED;

context.ScheduleProcessSteps([ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER]);
context.FinalizeProcessStep();
return UserStatusId.PENDING_DELETION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ public async Task<CreatedAtRouteResult> CreateManagedConnectorAsync([FromForm] M
/// Removes a connector from persistence layer by id.
/// </summary>
/// <param name="connectorId" example="5636F9B9-C3DE-4BA5-8027-00D17A2FECFB">ID of the connector to be deleted.</param>
/// <remarks>Example: DELETE: /api/administration/connectors/5636F9B9-C3DE-4BA5-8027-00D17A2FECFB</remarks>
/// <param name="deleteServiceAccount">if <c>true</c> the linked service account will be deleted, otherwise the connection to the connector will just be removed</param>
/// <remarks>Example: DELETE: /api/administration/connectors/{connectorId}?deleteServiceAccount=true</remarks>
/// <response code="204">Empty response on success.</response>
/// <response code="404">Record not found.</response>
/// <response code="409">Connector status does not match a deletion scenario. Deletion declined.</response>
Expand All @@ -161,9 +162,9 @@ public async Task<CreatedAtRouteResult> CreateManagedConnectorAsync([FromForm] M
[ProducesResponseType(typeof(IActionResult), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)]
public async Task<IActionResult> DeleteConnectorAsync([FromRoute] Guid connectorId)
public async Task<IActionResult> DeleteConnectorAsync([FromRoute] Guid connectorId, [FromQuery] bool deleteServiceAccount = false)
{
await logic.DeleteConnectorAsync(connectorId);
await logic.DeleteConnectorAsync(connectorId, deleteServiceAccount);
return NoContent();
}

Expand Down
1 change: 1 addition & 0 deletions src/administration/Administration.Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ await WebAppHelper
builder.Services
.AddTransient<ISubscriptionConfigurationBusinessLogic, SubscriptionConfigurationBusinessLogic>()
.AddTransient<IServiceAccountManagement, ServiceAccountManagement>()
.AddPartnerRegistration(builder.Configuration)
.AddNetworkRegistrationProcessHelper()
.AddIssuerComponentService(builder.Configuration.GetSection("Issuer"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public record DeleteConnectorData(
ConnectorStatusId ConnectorStatus,
IEnumerable<ConnectorOfferSubscription> ConnectorOfferSubscriptions,
UserStatusId? UserStatusId,
Guid? ServiceAccountId
Guid? ServiceAccountId,
DeleteServiceAccountData DeleteServiceAccountData
);
public record ConnectorOfferSubscription(Guid AssignedOfferSubscriptionIds, OfferSubscriptionStatusId OfferSubscriptionStatus);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* 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
********************************************************************************/

namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;

public record DeleteServiceAccountData(
IEnumerable<Guid> UserRoleIds,
string? ClientClientId,
bool IsDimServiceAccount,
bool CreationProcessInProgress,
Guid? ProcessId
);
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,3 @@ public record OwnServiceAccountData(
bool CreationProcessInProgress,
Guid? ProcessId
);

public record ProcessData(Guid ProcessId, IEnumerable<Guid> ProcessStepIds);
Loading

0 comments on commit 66c05e4

Please sign in to comment.