Skip to content

Commit

Permalink
implement optimistic locking for serviceaccounts
Browse files Browse the repository at this point in the history
  • Loading branch information
ntruchsess committed Aug 2, 2024
1 parent c9d9a56 commit 3b364e1
Show file tree
Hide file tree
Showing 44 changed files with 789 additions and 677 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models;
using Org.Eclipse.TractusX.Portal.Backend.Dim.Library.Models;
using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider;
using Org.Eclipse.TractusX.Portal.Backend.Framework.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq;
using Org.Eclipse.TractusX.Portal.Backend.Framework.Models;
Expand All @@ -45,13 +43,11 @@ public class ServiceAccountBusinessLogic(
IPortalRepositories portalRepositories,
IOptions<ServiceAccountSettings> options,
IServiceAccountCreation serviceAccountCreation,
IIdentityService identityService,
IDateTimeProvider dateTimeProvider)
IIdentityService identityService)
: IServiceAccountBusinessLogic
{
private readonly IIdentityData _identityData = identityService.IdentityData;
private readonly ServiceAccountSettings _settings = options.Value;
private readonly TimeSpan _lockExpiryTime = new(options.Value.LockExpirySeconds * 10000000L);

private const string CompanyId = "companyId";

Expand Down Expand Up @@ -115,16 +111,28 @@ public async Task<int> DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_USERID_ACTIVATION_ACTIVE_CONFLICT);
}

if (!result.ServiceAccount.TryLock(dateTimeProvider.OffsetNow.Add(_lockExpiryTime)))
// serviceAccount
if (result.IsDimServiceAccount)
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_LOCKED, [new("serviceAccountId", serviceAccountId.ToString())]);
}
var processId = result.ProcessId ?? throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]);

// save the lock of the service account here to make sure no process overwrites it
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
var processData = await portalRepositories.GetInstance<IProcessStepRepository>()
.GetProcessDataForServiceAccountDeletionCallback(processId, null)
.ConfigureAwait(ConfigureAwaitOptions.None);

// serviceAccount
if (!string.IsNullOrWhiteSpace(result.ClientClientId) && !result.IsDimServiceAccount)
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))
{
context.ScheduleProcessSteps([ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER]);
context.FinalizeProcessStep();
}
}
else if (!string.IsNullOrWhiteSpace(result.ClientClientId))
{
await provisioningManager.DeleteCentralClientAsync(result.ClientClientId).ConfigureAwait(ConfigureAwaitOptions.None);
portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(serviceAccountId, null, i =>
Expand All @@ -133,17 +141,6 @@ public async Task<int> DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId
});
}

if (result.IsDimServiceAccount)
{
if (result.ProcessId == null)
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, [new("serviceAccountId", serviceAccountId.ToString())]);
}

var processStepRepository = portalRepositories.GetInstance<ProcessStepRepository>();
processStepRepository.CreateProcessStep(ProcessStepTypeId.DELETE_DIM_TECHNICAL_USER, ProcessStepStatusId.TODO, result.ProcessId.Value);
}

portalRepositories.GetInstance<IUserRolesRepository>().DeleteCompanyUserAssignedRoles(result.UserRoleIds.Select(userRoleId => (serviceAccountId, userRoleId)));

if (result.ConnectorId != null)
Expand All @@ -159,7 +156,6 @@ public async Task<int> DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId
});
}

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

Expand Down Expand Up @@ -267,24 +263,16 @@ public async Task<ServiceAccountDetails> UpdateOwnCompanyServiceAccountDetailsAs
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_INACTIVE_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString())]);
}

if (result.ServiceAccount.ClientClientId == null)
if (result.ClientClientId == null)
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_CLIENTID_NOT_NULL_CONFLICT, [new("serviceAccountId", serviceAccountId.ToString())]);
}

if (!result.ServiceAccount.TryLock(dateTimeProvider.OffsetNow.Add(_lockExpiryTime)))
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_LOCKED, [new("serviceAccountId", serviceAccountId.ToString())]);
}

// save the lock of the service account here to make sure no process overwrites it
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);

ClientAuthData? authData;
if (result.ServiceAccount.CompanyServiceAccountKindId == CompanyServiceAccountKindId.INTERNAL)
if (result.CompanyServiceAccountKindId == CompanyServiceAccountKindId.INTERNAL)
{
var internalClientId = await provisioningManager.UpdateCentralClientAsync(
result.ServiceAccount.ClientClientId,
result.ClientClientId,
new ClientConfigData(
serviceAccountDetails.Name,
serviceAccountDetails.Description,
Expand All @@ -297,23 +285,33 @@ public async Task<ServiceAccountDetails> UpdateOwnCompanyServiceAccountDetailsAs
authData = null;
}

result.ServiceAccount.Name = serviceAccountDetails.Name;
result.ServiceAccount.Description = serviceAccountDetails.Description;
serviceAccountRepository.AttachAndModifyCompanyServiceAccount(
serviceAccountId,
result.ServiceAccountVersion,
sa =>
{
sa.Name = result.Name;
sa.Description = result.Description;
},
sa =>
{
sa.Name = serviceAccountDetails.Name;
sa.Description = serviceAccountDetails.Description;
});

result.ServiceAccount.ReleaseLock();
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);

return new ServiceAccountDetails(
result.ServiceAccount.Id,
result.ServiceAccount.ClientClientId,
serviceAccountId,
result.ClientClientId,
serviceAccountDetails.Name,
serviceAccountDetails.Description,
result.UserStatusId,
authData?.IamClientAuthMethod,
result.UserRoleDatas,
result.ServiceAccount.CompanyServiceAccountTypeId,
result.CompanyServiceAccountTypeId,
authData?.Secret,
result.ServiceAccount.OfferSubscriptionId);
result.OfferSubscriptionId);
}

public Task<Pagination.Response<CompanyServiceAccountData>> GetOwnCompanyServiceAccountsDataAsync(int page, int size, string? clientId, bool? isOwner, bool filterForInactive, IEnumerable<UserStatusId>? userStatusIds)
Expand Down Expand Up @@ -349,37 +347,29 @@ public async Task HandleServiceAccountCreationCallback(Guid processId, Authentic
{
throw new ConflictException($"ServiceAccountId must be set for process {processId}");
}

switch (processData.ProcessTypeId)
if (processData.ServiceAccountVersion is null)
{
case ProcessTypeId.OFFER_SUBSCRIPTION:
HandleOfferSubscriptionTechnicalUserCallback(processData.ServiceAccountId.Value, callbackData, context);
break;
case ProcessTypeId.DIM_TECHNICAL_USER:
CreateDimServiceAccount(callbackData, processData.ServiceAccountId.Value);
break;
default:
throw new ControllerArgumentException($"process {processId} has invalid processType {processData.ProcessTypeId}");
throw new UnexpectedConditionException("ServiceAccountVersion or IdentityVersion should never be null here");
}

CreateDimServiceAccount(callbackData, processData.ServiceAccountId.Value, processData.ServiceAccountVersion.Value);

if (processData.ProcessTypeId == ProcessTypeId.OFFER_SUBSCRIPTION)
{
context.ScheduleProcessSteps([ProcessStepTypeId.TRIGGER_ACTIVATE_SUBSCRIPTION]);
}
context.FinalizeProcessStep();
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
}

private void HandleOfferSubscriptionTechnicalUserCallback(Guid serviceAccountId, AuthenticationDetail callbackData, ManualProcessStepData context)
{
CreateDimServiceAccount(callbackData, serviceAccountId);
context.ScheduleProcessSteps([ProcessStepTypeId.TRIGGER_ACTIVATE_SUBSCRIPTION]);
}

private void CreateDimServiceAccount(AuthenticationDetail callbackData, Guid serviceAccountId)
private void CreateDimServiceAccount(AuthenticationDetail callbackData, Guid serviceAccountId, Guid serviceAccountVersion)
{
var serviceAccountRepository = portalRepositories.GetInstance<IServiceAccountRepository>();
portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(serviceAccountId,
i => { i.UserStatusId = UserStatusId.PENDING; },
i => { i.UserStatusId = UserStatusId.ACTIVE; });

serviceAccountRepository.AttachAndModifyCompanyServiceAccount(serviceAccountId,
serviceAccountRepository.AttachAndModifyCompanyServiceAccount(serviceAccountId, serviceAccountVersion,
sa => { sa.ClientClientId = null; },
sa => { sa.ClientClientId = callbackData.ClientId; });

Expand All @@ -399,25 +389,14 @@ public async Task HandleServiceAccountDeletionCallback(Guid processId)
var context = processData.ProcessData.CreateManualProcessData(ProcessStepTypeId.AWAIT_DELETE_DIM_TECHNICAL_USER,
portalRepositories, () => $"externalId {processId}");

if (processData.ServiceAccount is null)
{
throw new ConflictException($"ServiceAccountId must be set for process {processId}");
}

if (!processData.ServiceAccount.TryLock(dateTimeProvider.OffsetNow.Add(_lockExpiryTime)))
{
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_LOCKED, [new("serviceAccountId", processData.ServiceAccount.Id.ToString())]);
}

// save the lock of the service account here to make sure no process overwrites it
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);

portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(processData.ServiceAccount.Id, null, i =>
{
i.UserStatusId = UserStatusId.INACTIVE;
});
portalRepositories.GetInstance<IUserRepository>().AttachAndModifyIdentity(
processData.ServiceAccountId ?? throw new ConflictException($"ServiceAccountId must be set for process {processId}"),
null,
i =>
{
i.UserStatusId = UserStatusId.INACTIVE;
});

processData.ServiceAccount.ReleaseLock();
context.FinalizeProcessStep();
await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public class AdministrationServiceAccountErrorMessageContainer : IErrorMessageCo
{ AdministrationServiceAccountErrors.SERVICE_INACTIVE_CONFLICT, "serviceAccount {serviceAccountId} is already INACTIVE"},
{ AdministrationServiceAccountErrors.SERVICE_CLIENTID_NOT_NULL_CONFLICT, "clientClientId of serviceAccount {serviceAccountId} should not be null"},
{ AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS, "Service Account {serviceAccountId} is not linked to a process" },
{ AdministrationServiceAccountErrors.SERVICE_ACCOUNT_LOCKED, "Service Account {serviceAccountId} is locked by another process" }
{ AdministrationServiceAccountErrors.SERVICE_ACCOUNT_LOCKED, "Service Account {serviceAccountId} is locked by another process" },
{ AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, "Service Account {serviceAccountId} has pending process steps {processStepTypeIds}"}
}.ToImmutableDictionary(x => (int)x.Key, x => x.Value);

public Type Type { get => typeof(AdministrationServiceAccountErrors); }
Expand All @@ -60,5 +61,6 @@ public enum AdministrationServiceAccountErrors
SERVICE_INACTIVE_CONFLICT,
SERVICE_CLIENTID_NOT_NULL_CONFLICT,
SERVICE_ACCOUNT_NOT_LINKED_TO_PROCESS,
SERVICE_ACCOUNT_LOCKED
SERVICE_ACCOUNT_LOCKED,
SERVICE_ACCOUNT_PENDING_PROCESS_STEPS
}
2 changes: 1 addition & 1 deletion src/framework/Framework.Async/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.Cors/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.DBAccess/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/********************************************************************************
* Copyright (c) 2022 BMW Group AG
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
Expand All @@ -22,7 +21,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Framework.DBAccess;

public static class VersionedEntityExtensions
{
public static void UpdateVersion(this ILockableEntity entity)
public static void UpdateVersion(this IVersionedEntity entity)
{
entity.Version = Guid.NewGuid();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.IO/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.Linq/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.Logging/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.Models/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/framework/Framework.Seeding/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<Project>
<PropertyGroup>
<VersionPrefix>2.4.2</VersionPrefix>
<VersionPrefix>2.5.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
</PropertyGroup>
</Project>
Loading

0 comments on commit 3b364e1

Please sign in to comment.