Skip to content

Commit

Permalink
feat: enable multiple technical user for offers (#1197)
Browse files Browse the repository at this point in the history
Refs: #1136
Reviewed-by: Karsten Thiems <[email protected]>
  • Loading branch information
Phil91 authored Dec 18, 2024
1 parent 75db806 commit 4cdba4c
Show file tree
Hide file tree
Showing 20 changed files with 191 additions and 208 deletions.
32 changes: 16 additions & 16 deletions docs/api/administration-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4314,7 +4314,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceAccountCreationInfo'
$ref: '#/components/schemas/TechnicalUserCreationInfo'
responses:
'200':
description: The service account was created.
Expand Down Expand Up @@ -7688,21 +7688,6 @@ components:
type: string
nullable: true
additionalProperties: false
ServiceAccountCreationInfo:
type: object
properties:
name:
type: string
description:
type: string
authenticationType:
$ref: '#/components/schemas/IamClientAuthMethod'
roleIds:
type: array
items:
type: string
format: uuid
additionalProperties: false
ServiceAccountDetails:
type: object
properties:
Expand Down Expand Up @@ -7747,6 +7732,21 @@ components:
authenticationType:
$ref: '#/components/schemas/IamClientAuthMethod'
additionalProperties: false
TechnicalUserCreationInfo:
type: object
properties:
name:
type: string
description:
type: string
authenticationType:
$ref: '#/components/schemas/IamClientAuthMethod'
roleIds:
type: array
items:
type: string
format: uuid
additionalProperties: false
TechnicalUserData:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

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

public interface IServiceAccountBusinessLogic
public interface ITechnicalUserBusinessLogic
{
Task<IEnumerable<ServiceAccountDetails>> CreateOwnCompanyServiceAccountAsync(ServiceAccountCreationInfo serviceAccountCreationInfos);
Task<IEnumerable<ServiceAccountDetails>> CreateOwnCompanyServiceAccountAsync(TechnicalUserCreationInfo technicalUserCreationInfos);
Task DeleteOwnCompanyServiceAccountAsync(Guid serviceAccountId);
Task<ServiceAccountConnectorOfferData> GetOwnCompanyServiceAccountDetailsAsync(Guid serviceAccountId);
Task<ServiceAccountDetails> UpdateOwnCompanyServiceAccountDetailsAsync(Guid serviceAccountId, ServiceAccountEditableDetails serviceAccountDetails);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,30 @@

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

public class ServiceAccountBusinessLogic(
public class TechnicalUserBusinessLogic(
IProvisioningManager provisioningManager,
IPortalRepositories portalRepositories,
IOptions<ServiceAccountSettings> options,
IServiceAccountCreation serviceAccountCreation,
ITechnicalUserCreation technicalUserCreation,
IIdentityService identityService,
IServiceAccountManagement serviceAccountManagement)
: IServiceAccountBusinessLogic
: ITechnicalUserBusinessLogic
{
private readonly IIdentityData _identityData = identityService.IdentityData;
private readonly ServiceAccountSettings _settings = options.Value;

private const string CompanyId = "companyId";

public async Task<IEnumerable<ServiceAccountDetails>> CreateOwnCompanyServiceAccountAsync(ServiceAccountCreationInfo serviceAccountCreationInfos)
public async Task<IEnumerable<ServiceAccountDetails>> CreateOwnCompanyServiceAccountAsync(TechnicalUserCreationInfo technicalUserCreationInfos)
{
if (serviceAccountCreationInfos.IamClientAuthMethod != IamClientAuthMethod.SECRET)
if (technicalUserCreationInfos.IamClientAuthMethod != IamClientAuthMethod.SECRET)
{
throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_AUTH_SECRET_ARGUMENT, parameters: [new("authenticationType", serviceAccountCreationInfos.IamClientAuthMethod.ToString())]);//TODO implement other authenticationTypes
throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_AUTH_SECRET_ARGUMENT, parameters: [new("authenticationType", technicalUserCreationInfos.IamClientAuthMethod.ToString())]);//TODO implement other authenticationTypes
}

if (string.IsNullOrWhiteSpace(serviceAccountCreationInfos.Name))
if (string.IsNullOrWhiteSpace(technicalUserCreationInfos.Name))
{
throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_NAME_EMPTY_ARGUMENT, parameters: [new("name", serviceAccountCreationInfos.Name)]);
throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_NAME_EMPTY_ARGUMENT, parameters: [new("name", technicalUserCreationInfos.Name)]);
}

var companyId = _identityData.CompanyId;
Expand All @@ -76,11 +76,11 @@ public async Task<IEnumerable<ServiceAccountDetails>> CreateOwnCompanyServiceAcc
throw ConflictException.Create(AdministrationServiceAccountErrors.SERVICE_BPN_NOT_SET_CONFLICT, [new(CompanyId, companyId.ToString())]);
}

serviceAccountCreationInfos.UserRoleIds.Except(result.TechnicalUserRoleIds)
technicalUserCreationInfos.UserRoleIds.Except(result.TechnicalUserRoleIds)
.IfAny(unassignable => throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_ROLES_NOT_ASSIGN_ARGUMENT, parameters: [new("unassignable", string.Join(",", unassignable)), new("userRoleIds", string.Join(",", result.TechnicalUserRoleIds))]));

const TechnicalUserTypeId TechnicalUserTypeId = TechnicalUserTypeId.OWN;
var (_, _, serviceAccounts) = await serviceAccountCreation.CreateServiceAccountAsync(serviceAccountCreationInfos, companyId, [result.Bpn], TechnicalUserTypeId, false, true, new ServiceAccountCreationProcessData(ProcessTypeId.DIM_TECHNICAL_USER, null)).ConfigureAwait(ConfigureAwaitOptions.None);
var (_, _, serviceAccounts) = await technicalUserCreation.CreateTechnicalUsersAsync(technicalUserCreationInfos, companyId, [result.Bpn], TechnicalUserTypeId, false, true, new ServiceAccountCreationProcessData(ProcessTypeId.DIM_TECHNICAL_USER, null)).ConfigureAwait(ConfigureAwaitOptions.None);

await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
return serviceAccounts.Select(sa => new ServiceAccountDetails(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers
[EnvironmentRoute("MVC_ROUTING_BASEPATH", "serviceaccount")]
[Produces("application/json")]
[Consumes("application/json")]
public class ServiceAccountController(IServiceAccountBusinessLogic logic) : ControllerBase
public class ServiceAccountController(ITechnicalUserBusinessLogic logic) : ControllerBase
{
/// <summary>
/// Creates a new technical user / service account with selected role under the same org as the requester
/// </summary>
/// <param name="serviceAccountCreationInfo"></param>
/// <param name="technicalUserCreationInfo"></param>
/// <returns></returns>
/// <remarks>Example: POST: api/administration/serviceaccount/owncompany/serviceaccounts</remarks>
/// <response code="200">The service account was created.</response>
Expand All @@ -54,8 +54,8 @@ public class ServiceAccountController(IServiceAccountBusinessLogic logic) : Cont
[ProducesResponseType(typeof(IEnumerable<ServiceAccountDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
public Task<IEnumerable<ServiceAccountDetails>> ExecuteCompanyUserCreation([FromBody] ServiceAccountCreationInfo serviceAccountCreationInfo) =>
logic.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfo);
public Task<IEnumerable<ServiceAccountDetails>> ExecuteCompanyUserCreation([FromBody] TechnicalUserCreationInfo technicalUserCreationInfo) =>
logic.CreateOwnCompanyServiceAccountAsync(technicalUserCreationInfo);

/// <summary>
/// Deletes the service account with the given id
Expand Down
2 changes: 1 addition & 1 deletion src/administration/Administration.Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ await WebAppHelper
builder.Services.AddTransient<IRegistrationBusinessLogic, RegistrationBusinessLogic>()
.ConfigureRegistrationSettings(builder.Configuration.GetSection("Registration"));

builder.Services.AddTransient<IServiceAccountBusinessLogic, ServiceAccountBusinessLogic>()
builder.Services.AddTransient<ITechnicalUserBusinessLogic, TechnicalUserBusinessLogic>()
.ConfigureServiceAccountSettings(builder.Configuration.GetSection("ServiceAccount"));

builder.Services.AddTransient<IDocumentsBusinessLogic, DocumentsBusinessLogic>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public interface ITechnicalUserProfileService
/// </summary>
/// <param name="offerId">Id of the offer</param>
/// <returns></returns>
Task<IEnumerable<ServiceAccountCreationInfo>> GetTechnicalUserProfilesForOffer(Guid offerId, OfferTypeId offerTypeId);
Task<IEnumerable<TechnicalUserCreationInfo>> GetTechnicalUserProfilesForOffer(Guid offerId, OfferTypeId offerTypeId);

/// <summary>
/// Gets the technical user profiles for the specific offer subscription
/// </summary>
/// <param name="subscriptionId">Id of the offer</param>
/// <returns></returns>
Task<IEnumerable<ServiceAccountCreationInfo>> GetTechnicalUserProfilesForOfferSubscription(Guid subscriptionId);
Task<IEnumerable<TechnicalUserCreationInfo>> GetTechnicalUserProfilesForOfferSubscription(Guid subscriptionId);
}
67 changes: 34 additions & 33 deletions src/marketplace/Offers.Library/Service/OfferSetupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class OfferSetupService : IOfferSetupService
{
private readonly IPortalRepositories _portalRepositories;
private readonly IProvisioningManager _provisioningManager;
private readonly IServiceAccountCreation _serviceAccountCreation;
private readonly ITechnicalUserCreation _technicalUserCreation;
private readonly INotificationService _notificationService;
private readonly IOfferSubscriptionProcessService _offerSubscriptionProcessService;
private readonly IMailingProcessCreation _mailingProcessCreation;
Expand All @@ -59,7 +59,7 @@ public class OfferSetupService : IOfferSetupService
/// </summary>
/// <param name="portalRepositories">Factory to access the repositories</param>
/// <param name="provisioningManager">Access to the provisioning manager</param>
/// <param name="serviceAccountCreation">Access to the service account creation</param>
/// <param name="technicalUserCreation">Access to the service account creation</param>
/// <param name="notificationService">Creates notifications for the user</param>
/// <param name="offerSubscriptionProcessService">Access to offer subscription process service</param>
/// <param name="technicalUserProfileService">Access to the technical user profile service</param>
Expand All @@ -70,7 +70,7 @@ public class OfferSetupService : IOfferSetupService
public OfferSetupService(
IPortalRepositories portalRepositories,
IProvisioningManager provisioningManager,
IServiceAccountCreation serviceAccountCreation,
ITechnicalUserCreation technicalUserCreation,
INotificationService notificationService,
IOfferSubscriptionProcessService offerSubscriptionProcessService,
ITechnicalUserProfileService technicalUserProfileService,
Expand All @@ -81,7 +81,7 @@ public OfferSetupService(
{
_portalRepositories = portalRepositories;
_provisioningManager = provisioningManager;
_serviceAccountCreation = serviceAccountCreation;
_technicalUserCreation = technicalUserCreation;
_notificationService = notificationService;
_offerSubscriptionProcessService = offerSubscriptionProcessService;
_technicalUserProfileService = technicalUserProfileService;
Expand Down Expand Up @@ -139,12 +139,12 @@ private async Task<OfferAutoSetupResponseData> AutoSetupOfferMultiInstance(Offer

var technicalUserClientId = clientInfoData?.ClientId ?? $"{offerDetails.OfferName}-{offerDetails.CompanyName}";
var createTechnicalUserData = new CreateTechnicalUserData(offerDetails.CompanyId, offerDetails.OfferName, offerDetails.Bpn, technicalUserClientId, offerTypeId == OfferTypeId.APP, true);
var (_, processId, technicalUsers) = await CreateTechnicalUserForSubscription(data.RequestId, createTechnicalUserData, null).ConfigureAwait(ConfigureAwaitOptions.None);
var technicalUsers = await CreateTechnicalUserForSubscription(data.RequestId, createTechnicalUserData, null).ConfigureAwait(ConfigureAwaitOptions.None);

offerSubscriptionsRepository.AttachAndModifyOfferSubscription(data.RequestId, subscription =>
{
subscription.OfferSubscriptionStatusId = OfferSubscriptionStatusId.ACTIVE;
subscription.ProcessId = processId;
subscription.ProcessId = technicalUsers.ProcessId;
});

await CreateNotifications(itAdminRoles, offerTypeId, offerDetails, _identityData.IdentityId).ConfigureAwait(ConfigureAwaitOptions.None);
Expand All @@ -157,40 +157,41 @@ private async Task<OfferAutoSetupResponseData> AutoSetupOfferMultiInstance(Offer

await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None);
return new OfferAutoSetupResponseData(
technicalUsers.Select(x => new TechnicalUserInfoData(x.ServiceAccountId, x.UserRoleData.Select(ur => ur.UserRoleText), x.ServiceAccountData?.AuthData.Secret, x.ClientId)),
technicalUsers.ServiceAccounts.Select(x => new TechnicalUserInfoData(x.ServiceAccountId, x.UserRoleData.Select(ur => ur.UserRoleText), x.ServiceAccountData?.AuthData.Secret, x.ClientId)),
clientInfoData);
}

private async Task<(bool HasExternalServiceAccount, Guid? ProcessId, IEnumerable<CreatedServiceAccountData> ServiceAccounts)> CreateTechnicalUserForSubscription(Guid subscriptionId, CreateTechnicalUserData data, Guid? processId)
{
var technicalUserInfoCreations = await _technicalUserProfileService.GetTechnicalUserProfilesForOfferSubscription(subscriptionId).ConfigureAwait(ConfigureAwaitOptions.None);

ServiceAccountCreationInfo? serviceAccountCreationInfo;
try
{
serviceAccountCreationInfo = technicalUserInfoCreations.SingleOrDefault();
}
catch (InvalidOperationException)
if (!technicalUserInfoCreations.Any())
{
throw new UnexpectedConditionException($"There should only be one or none technical user profile configured for {subscriptionId}");
return (false, null, []);
}

if (serviceAccountCreationInfo == null)
var serviceAccounts = new List<CreatedServiceAccountData>();
var hasExternalServiceAccount = false;
var processIdToUse = processId;
foreach (var serviceAccountCreationInfo in technicalUserInfoCreations)
{
return (false, null, []);
var serviceAccountResult = await _technicalUserCreation
.CreateTechnicalUsersAsync(
serviceAccountCreationInfo,
data.CompanyId,
data.Bpn == null ? Enumerable.Empty<string>() : Enumerable.Repeat(data.Bpn, 1),
TechnicalUserTypeId.MANAGED,
data.EnhanceTechnicalUserName,
data.Enabled,
new ServiceAccountCreationProcessData(ProcessTypeId.OFFER_SUBSCRIPTION, processIdToUse),
sa => { sa.OfferSubscriptionId = subscriptionId; })
.ConfigureAwait(ConfigureAwaitOptions.None);
processIdToUse = serviceAccountResult.ProcessId;
serviceAccounts.AddRange(serviceAccountResult.TechnicalUsers);
hasExternalServiceAccount = hasExternalServiceAccount || serviceAccountResult.HasExternalTechnicalUser;
}

return await _serviceAccountCreation
.CreateServiceAccountAsync(
serviceAccountCreationInfo,
data.CompanyId,
data.Bpn == null ? Enumerable.Empty<string>() : Enumerable.Repeat(data.Bpn, 1),
TechnicalUserTypeId.MANAGED,
data.EnhanceTechnicalUserName,
data.Enabled,
new ServiceAccountCreationProcessData(ProcessTypeId.OFFER_SUBSCRIPTION, processId),
sa => { sa.OfferSubscriptionId = subscriptionId; })
.ConfigureAwait(ConfigureAwaitOptions.None);
return (hasExternalServiceAccount, processIdToUse, serviceAccounts);
}

/// <inheritdoc />
Expand Down Expand Up @@ -275,8 +276,8 @@ private async IAsyncEnumerable<IEnumerable<TechnicalUserInfoData>> CreateTechnic
var creationData = await _technicalUserProfileService.GetTechnicalUserProfilesForOffer(offerId, offerTypeId).ConfigureAwait(ConfigureAwaitOptions.None);
foreach (var creationInfo in creationData)
{
var (_, _, result) = await _serviceAccountCreation
.CreateServiceAccountAsync(
var (_, _, result) = await _technicalUserCreation
.CreateTechnicalUsersAsync(
creationInfo,
data.CompanyId,
data.Bpn == null ? Enumerable.Empty<string>() : [data.Bpn],
Expand Down Expand Up @@ -543,12 +544,12 @@ public async Task CreateSingleInstanceSubscriptionDetail(Guid offerSubscriptionI

var technicalUserClientId = data.ClientId ?? $"{data.OfferName}-{data.CompanyName}";
var createTechnicalUserData = new CreateTechnicalUserData(data.CompanyId, data.OfferName, data.Bpn, technicalUserClientId, true, false);
var (hasExternalServiceAccount, _, serviceAccounts) = await CreateTechnicalUserForSubscription(offerSubscriptionId, createTechnicalUserData, processId).ConfigureAwait(ConfigureAwaitOptions.None);
var technicalClientIds = serviceAccounts.Select(x => x.ClientId);
var technicalUsers = await CreateTechnicalUserForSubscription(offerSubscriptionId, createTechnicalUserData, processId).ConfigureAwait(ConfigureAwaitOptions.None);
var technicalClientIds = technicalUsers.ServiceAccounts.Select(sa => sa.ClientId);

var content = JsonSerializer.Serialize(new
{
technicalClientIds,
technicalClientIds
});

await _notificationService.CreateNotifications(
Expand All @@ -561,7 +562,7 @@ await _notificationService.CreateNotifications(

return new ValueTuple<IEnumerable<ProcessStepTypeId>?, ProcessStepStatusId, bool, string?>(
[
hasExternalServiceAccount
technicalUsers.HasExternalServiceAccount
? ProcessStepTypeId.OFFERSUBSCRIPTION_CREATE_DIM_TECHNICAL_USER
: ProcessStepTypeId.MANUAL_TRIGGER_ACTIVATE_SUBSCRIPTION
],
Expand Down
Loading

0 comments on commit 4cdba4c

Please sign in to comment.