diff --git a/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs index b31101c334..4c4fdd5415 100644 --- a/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -472,7 +471,7 @@ public async Task ApproveCredential(Guid credentialId, CancellationToken cancell throw new ConflictException("The VerifiedCredentialExternalTypeUseCaseDetail must be set"); } - var typeValue = data.Type.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.Type.ToString()} does not exists"); + var typeValue = data.Type.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.Type} does not exists"); var content = JsonSerializer.Serialize(new { Type = data.Type, CredentialId = credentialId }, Options); _portalRepositories.GetInstance().CreateNotification(data.RequesterData.RequesterId, NotificationTypeId.CREDENTIAL_APPROVAL, false, n => { @@ -540,7 +539,7 @@ public async Task RejectCredential(Guid credentialId) throw new ConflictException($"Credential {credentialId} must be {CompanySsiDetailStatusId.PENDING}"); } - var typeValue = type.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {type.ToString()} does not exists"); + var typeValue = type.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {type} does not exists"); var content = JsonSerializer.Serialize(new { Type = type, CredentialId = credentialId }, Options); _portalRepositories.GetInstance().CreateNotification(requesterId, NotificationTypeId.CREDENTIAL_REJECTED, false, n => { diff --git a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs index 236d7a3e6e..0e4c64fa16 100644 --- a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional diff --git a/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs index 8fbf210953..fe00780d27 100644 --- a/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IIdentityProviderBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -36,7 +35,6 @@ public interface IIdentityProviderBusinessLogic IAsyncEnumerable GetOwnCompanyUsersIdentityProviderDataAsync(IEnumerable identityProviderIds, bool unlinkedUsersOnly); (Stream FileStream, string ContentType, string FileName, Encoding Encoding) GetOwnCompanyUsersIdentityProviderLinkDataStream(IEnumerable identityProviderIds, bool unlinkedUsersOnly); ValueTask UploadOwnCompanyUsersIdentityProviderLinkDataAsync(IFormFile document, CancellationToken cancellationToken); - ValueTask CreateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, UserIdentityProviderLinkData identityProviderLinkData); ValueTask CreateOrUpdateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, UserLinkData userLinkData); ValueTask GetOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId); ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync(Guid companyUserId, Guid identityProviderId); diff --git a/src/administration/Administration.Service/BusinessLogic/IPartnerNetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IPartnerNetworkBusinessLogic.cs index 17ef29b9fa..f05681b97b 100644 --- a/src/administration/Administration.Service/BusinessLogic/IPartnerNetworkBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IPartnerNetworkBusinessLogic.cs @@ -26,6 +26,6 @@ public interface IPartnerNetworkBusinessLogic /// Get all member activecompanies bpn /// /// Ids of BPN - IAsyncEnumerable GetAllMemberCompaniesBPNAsync(IEnumerable? bpnIds); + IAsyncEnumerable GetAllMemberCompaniesBPNAsync(IEnumerable? bpnIds); } } diff --git a/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs index 17b8904bb9..635323ff3e 100644 --- a/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IUserBusinessLogic.cs @@ -31,8 +31,6 @@ public interface IUserBusinessLogic IAsyncEnumerable CreateOwnCompanyUsersAsync(IEnumerable userList); Task CreateOwnCompanyIdpUserAsync(Guid identityProviderId, UserCreationInfoIdp userCreationInfo); Task> GetOwnCompanyUserDatasAsync(int page, int size, GetOwnCompanyUsersFilter filter); - [Obsolete("to be replaced by UserRolesBusinessLogic.GetAppRolesAsync. Remove as soon frontend is adjusted")] - IAsyncEnumerable GetClientRolesAsync(Guid appId, string? languageShortName = null); Task GetOwnCompanyUserDetailsAsync(Guid userId); Task AddOwnCompanyUsersBusinessPartnerNumbersAsync(Guid userId, IEnumerable businessPartnerNumbers); Task AddOwnCompanyUsersBusinessPartnerNumberAsync(Guid userId, string businessPartnerNumber); diff --git a/src/administration/Administration.Service/BusinessLogic/IUserRolesBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IUserRolesBusinessLogic.cs index 76f6022206..b541e243f1 100644 --- a/src/administration/Administration.Service/BusinessLogic/IUserRolesBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IUserRolesBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -45,13 +44,4 @@ public interface IUserRolesBusinessLogic /// /// messages Task> ModifyAppUserRolesAsync(Guid appId, Guid companyUserId, IEnumerable roles); - - /// - /// Update Role to User - /// - /// app Id - /// User and Role Information like CompanyUser Id and Role Name - /// messages - [Obsolete("to be replaced by endpoint UserRolesBusinessLogic.ModifyAppUserRolesAsync. Remove as soon frontend is adjusted")] - Task> ModifyUserRoleAsync(Guid appId, UserRoleInfo userRoleInfo); } diff --git a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs index 9794ba5fd2..87a4109617 100644 --- a/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/IdentityProviderBusinessLogic.cs @@ -150,7 +150,7 @@ private async ValueTask CreateOwnCompanyIdentityProvide { IamIdentityProviderProtocol.OIDC => await GetIdentityProviderDetailsOidc(identityProviderId, alias, IdentityProviderCategoryId.KEYCLOAK_OIDC, typeId, null).ConfigureAwait(false), IamIdentityProviderProtocol.SAML => await GetIdentityProviderDetailsSaml(identityProviderId, alias, typeId).ConfigureAwait(false), - _ => throw new UnexpectedConditionException($"unexpected value of protocol: '{protocol.ToString()}'") + _ => throw new UnexpectedConditionException($"unexpected value of protocol: '{protocol}'") }; } @@ -268,7 +268,7 @@ public async ValueTask UpdateOwnCompanyIdentityProvider await UpdateIdentityProviderSaml(alias, details).ConfigureAwait(false); return await GetIdentityProviderDetailsSaml(identityProviderId, alias, typeId).ConfigureAwait(false); default: - throw new ControllerArgumentException($"unexpected value for category '{category.ToString()}' of identityProvider '{identityProviderId}'"); + throw new ControllerArgumentException($"unexpected value for category '{category}' of identityProvider '{identityProviderId}'"); } } @@ -587,32 +587,6 @@ private async ValueTask GetIdentityProviderDetailsSaml( }; } - public async ValueTask CreateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, UserIdentityProviderLinkData identityProviderLinkData) - { - var companyId = _identityData.CompanyId; - var (iamUserId, alias) = await GetUserAliasDataAsync(companyUserId, identityProviderLinkData.identityProviderId, companyId).ConfigureAwait(false); - - try - { - await _provisioningManager.AddProviderUserLinkToCentralUserAsync( - iamUserId, - new IdentityProviderLink( - alias, - identityProviderLinkData.userId, - identityProviderLinkData.userName)) - .ConfigureAwait(false); - } - catch (KeycloakEntityConflictException ce) - { - throw new ConflictException($"identityProviderLink for identityProvider {identityProviderLinkData.identityProviderId} already exists for user {companyUserId}", ce); - } - - return new UserIdentityProviderLinkData( - identityProviderLinkData.identityProviderId, - identityProviderLinkData.userId, - identityProviderLinkData.userName); - } - public async ValueTask CreateOrUpdateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, UserLinkData userLinkData) { var companyId = _identityData.CompanyId; diff --git a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs index 0d8b899652..a32dfb555a 100644 --- a/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/NetworkBusinessLogic.cs @@ -275,7 +275,7 @@ await data.ValidateDatabaseData( }, out var idpAliasDataTask) ? await idpAliasDataTask!.ConfigureAwait(false) - : (IDictionary?)null; + : default(IDictionary?); var idpIds = idpAliase?.Keys ?? Enumerable.Empty(); var allIdpIds = singleIdpAlias == null diff --git a/src/administration/Administration.Service/BusinessLogic/PartnerNetworkBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/PartnerNetworkBusinessLogic.cs index 819436a29f..362fb69d50 100644 --- a/src/administration/Administration.Service/BusinessLogic/PartnerNetworkBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/PartnerNetworkBusinessLogic.cs @@ -37,6 +37,6 @@ public PartnerNetworkBusinessLogic(IPortalRepositories portalRepositories) } /// - public IAsyncEnumerable GetAllMemberCompaniesBPNAsync(IEnumerable? bpnIds) => - _portalRepositories.GetInstance().GetAllMemberCompaniesBPNAsync(bpnIds); + public IAsyncEnumerable GetAllMemberCompaniesBPNAsync(IEnumerable? bpnIds) => + _portalRepositories.GetInstance().GetAllMemberCompaniesBPNAsync(bpnIds?.Select(x => x.ToUpper())); } diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationStatusBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationStatusBusinessLogic.cs index b2ab0d625f..e4329c10fa 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationStatusBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationStatusBusinessLogic.cs @@ -20,14 +20,13 @@ using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; using Org.Eclipse.TractusX.Portal.Backend.OnboardingServiceProvider.Library.DependencyInjection; 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.Enums; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; -using System.Security.Cryptography; -using System.Text; namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; @@ -60,40 +59,33 @@ public async Task SetCallbackAddress(OnboardingServiceProviderCallbackRequestDat throw new ForbiddenException($"Only {CompanyRoleId.ONBOARDING_SERVICE_PROVIDER} are allowed to set the callback url"); } - using var aes = Aes.Create(); - aes.Key = Encoding.UTF8.GetBytes(_settings.EncryptionKey); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.PKCS7; - var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); - using (var memoryStream = new MemoryStream()) + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == _settings.EncrptionConfigIndex) ?? throw new ConfigurationException($"EncryptionModeIndex {_settings.EncrptionConfigIndex} is not configured"); + var (secret, initializationVector) = CryptoHelper.Encrypt(requestData.ClientSecret, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + + if (ospDetails != null) + { + companyRepository.AttachAndModifyOnboardingServiceProvider(companyId, osp => + { + osp.CallbackUrl = ospDetails.CallbackUrl; + osp.AuthUrl = ospDetails.AuthUrl; + osp.ClientId = ospDetails.ClientId; + osp.ClientSecret = ospDetails.ClientSecret; + osp.EncryptionMode = ospDetails.EncryptionMode; + osp.InitializationVector = ospDetails.InitializationVector; + }, + osp => + { + osp.CallbackUrl = requestData.CallbackUrl; + osp.AuthUrl = requestData.AuthUrl; + osp.ClientId = requestData.ClientId; + osp.ClientSecret = secret; + osp.EncryptionMode = cryptoConfig.Index; + osp.InitializationVector = initializationVector; + }); + } + else { - using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - { - using var sw = new StreamWriter(cryptoStream, Encoding.UTF8); - sw.Write(requestData.ClientSecret); - } - var secret = memoryStream.ToArray(); - if (ospDetails != null) - { - companyRepository.AttachAndModifyOnboardingServiceProvider(companyId, osp => - { - osp.CallbackUrl = ospDetails.CallbackUrl; - osp.AuthUrl = ospDetails.AuthUrl; - osp.ClientId = ospDetails.ClientId; - osp.ClientSecret = secret; - }, - osp => - { - osp.CallbackUrl = requestData.CallbackUrl; - osp.AuthUrl = requestData.AuthUrl; - osp.ClientId = requestData.ClientId; - osp.ClientSecret = secret; - }); - } - else - { - companyRepository.CreateOnboardingServiceProviderDetails(companyId, requestData.CallbackUrl, requestData.AuthUrl, requestData.ClientId, secret); - } + companyRepository.CreateOnboardingServiceProviderDetails(companyId, requestData.CallbackUrl, requestData.AuthUrl, requestData.ClientId, secret, initializationVector, cryptoConfig.Index); } await _portalRepositories.SaveAsync().ConfigureAwait(false); } diff --git a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs index f35bdf0b02..33166440ed 100644 --- a/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/UserBusinessLogic.cs @@ -123,13 +123,13 @@ private async IAsyncEnumerable CreateOwnCompanyUsersInternalAsync(IEnume var companyDisplayName = await _userProvisioningService.GetIdentityProviderDisplayName(companyNameIdpAliasData.IdpAlias).ConfigureAwait(false); - await foreach (var (_, userName, password, error) in _userProvisioningService.CreateOwnCompanyIdpUsersAsync(companyNameIdpAliasData, userCreationInfoIdps).ConfigureAwait(false)) + await foreach (var (companyUserId, userName, password, error) in _userProvisioningService.CreateOwnCompanyIdpUsersAsync(companyNameIdpAliasData, userCreationInfoIdps).ConfigureAwait(false)) { var email = emailData[userName]; if (error != null) { - _logger.LogError(error, "Error while creating user {UserName} ({Email})", userName, email); + _logger.LogError(error, "Error while creating user {companyUserId}", companyUserId); continue; } @@ -148,7 +148,7 @@ private async IAsyncEnumerable CreateOwnCompanyUsersInternalAsync(IEnume } catch (Exception e) { - _logger.LogError(e, "Error sending email to {Email} after creating user {UserName}", email, userName); + _logger.LogError(e, "Error sending email after creating user {companyUserId}", companyUserId); } yield return email; @@ -220,7 +220,7 @@ public async Task CreateOwnCompanyIdpUserAsync(Guid identityProviderId, Us } catch (Exception e) { - _logger.LogError(e, "Error sending email to {Email} after creating user {UserName}", userCreationInfo.Email, userCreationInfo.UserName); + _logger.LogError(e, "Error sending email after creating user {CompanyUserId}", result.CompanyUserId); } return result.CompanyUserId; @@ -272,26 +272,6 @@ public async Task CreateOwnCompanyIdpUserAsync(Guid identityProviderId, Us private async Task GetDisplayName(string alias) => await _provisioningManager.GetIdentityProviderDisplayName(alias).ConfigureAwait(false) ?? throw new ConflictException($"Display Name should not be null for alias: {alias}"); - [Obsolete("to be replaced by UserRolesBusinessLogic.GetAppRolesAsync. Remove as soon frontend is adjusted")] - public async IAsyncEnumerable GetClientRolesAsync(Guid appId, string? languageShortName = null) - { - var appRepository = _portalRepositories.GetInstance(); - if (!await appRepository.CheckAppExistsById(appId).ConfigureAwait(false)) - { - throw new NotFoundException($"app {appId} does not found"); - } - - if (languageShortName != null && !await _portalRepositories.GetInstance().IsValidLanguageCode(languageShortName)) - { - throw new ArgumentException($"language {languageShortName} does not exist"); - } - - await foreach (var roles in appRepository.GetClientRolesAsync(appId, languageShortName ?? Constants.DefaultLanguage).ConfigureAwait(false)) - { - yield return new ClientRoles(roles.RoleId, roles.Role, roles.Description); - } - } - public async Task GetOwnCompanyUserDetailsAsync(Guid userId) { var companyId = _identityData.CompanyId; diff --git a/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs index 67e9721271..68f008dbe2 100644 --- a/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/UserRolesBusinessLogic.cs @@ -116,15 +116,6 @@ public Task> ModifyAppUserRolesAsync(Guid appId, Gui }, _options), NotificationTypeId.ROLE_UPDATE_APP_OFFER); }); - [Obsolete("to be replaced by endpoint UserRolesBusinessLogic.ModifyAppUserRolesAsync. Remove as soon frontend is adjusted")] - public Task> ModifyUserRoleAsync(Guid appId, UserRoleInfo userRoleInfo) => - ModifyUserRolesInternal( - () => _portalRepositories.GetInstance() - .GetAppAssignedIamClientUserDataUntrackedAsync(appId, userRoleInfo.CompanyUserId, _identityData.CompanyId), - (Guid companyUserId, IEnumerable roles, Guid offerId) => _portalRepositories.GetInstance() - .GetAssignedAndMatchingAppRoles(companyUserId, roles, offerId).Select(x => new UserRoleModificationData(x.UserRoleText, x.RoleId, x.IsAssigned, true)), - appId, userRoleInfo.CompanyUserId, userRoleInfo.Roles, _identityData.CompanyId, null); - private async Task> ModifyUserRolesInternal( Func> getIamUserData, Func, Guid, IAsyncEnumerable> getUserRoleModificationData, @@ -211,13 +202,14 @@ await _provisioningManager.AssignClientRolesToCentralUserAsync(iamUserId, client // Assign the roles in keycloak, check if all roles were added foreach client, if not throw an exception with the client and the roles that were not assigned. .Select(assigned => ( Client: assigned.Client, - UnassingedRoles: rolesToAdd.ExceptBy(assigned.Roles, toAdd => toAdd.CompanyUserRoleText))) + UnassingedRoles: rolesToAdd.ExceptBy(assigned.Roles, toAdd => toAdd.CompanyUserRoleText), + Error: assigned.Error)) .Where(x => x.UnassingedRoles.Any()) .IfAny(async unassigned => throw new ServiceException($"The following roles could not be added to the clients: \n {string.Join( "\n", await unassigned - .Select(item => $"Client: {item.Client}, Roles: {string.Join(", ", item.UnassingedRoles.Select(r => r.CompanyUserRoleText))}") + .Select(item => $"Client: {item.Client}, Roles: {string.Join(", ", item.UnassingedRoles.Select(r => r.CompanyUserRoleText))}, Error: {item.Error?.Message}") .ToListAsync() .ConfigureAwait(false))}")) .ConfigureAwait(false); diff --git a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs index 99e3e8b5cc..52bfbc1929 100644 --- a/src/administration/Administration.Service/Controllers/IdentityProviderController.cs +++ b/src/administration/Administration.Service/Controllers/IdentityProviderController.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -299,43 +298,6 @@ public IActionResult GetOwnCompanyUsersIdentityProviderFileAsync([FromQuery] IEn public ValueTask UploadOwnCompanyUsersIdentityProviderFileAsync([FromForm(Name = "document")] IFormFile document, CancellationToken cancellationToken) => _businessLogic.UploadOwnCompanyUsersIdentityProviderLinkDataAsync(document, cancellationToken); - /// - /// Adds the user to the given identity provider - /// - /// Id of the company user - /// The link data for the identity provider - /// Returns the link data - /// - /// Example: POST: api/administration/identityprovider/owncompany/users/A744E2AA-55AA-4511-9F42-80371220BE26/identityprovider - /// - /// Returns the link data. - /// user is not associated with a company. - /// user does not belong to company of companyUserId. - /// companyUserId does not exist. - /// identityProviderLink for identityProvider already exists for user. - /// companyUserId is not linked to keycloak - /// Bad Gateway Service Error. - [Obsolete("use CreateOrUpdateOwnCompanyUserIdentityProviderDataAsync (PUT api/administration/identityprovider/owncompany/users/{companyUserId/identityprovider/{identityProviderId}) instead")] - [HttpPost] - [Authorize(Roles = "modify_user_account")] - [Authorize(Policy = PolicyTypes.ValidCompany)] - [Route("owncompany/users/{companyUserId}/identityprovider")] - [ProducesResponseType(typeof(UserIdentityProviderLinkData), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status403Forbidden)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status502BadGateway)] - public async ValueTask> AddOwnCompanyUserIdentityProviderDataAsync([FromRoute] Guid companyUserId, [FromBody] UserIdentityProviderLinkData identityProviderLinkData) - { - var linkData = await _businessLogic.CreateOwnCompanyUserIdentityProviderLinkDataAsync(companyUserId, identityProviderLinkData).ConfigureAwait(false); - return (ActionResult)CreatedAtRoute( - nameof(GetOwnCompanyUserIdentityProviderDataAsync), - new { companyUserId = companyUserId, identityProviderId = linkData.identityProviderId }, - linkData); - } - /// /// Updates the given user for the given identity provider /// @@ -421,6 +383,6 @@ public ValueTask GetOwnCompanyUserIdentityProvider public async ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync([FromRoute] Guid companyUserId, [FromRoute] Guid identityProviderId) { await _businessLogic.DeleteOwnCompanyUserIdentityProviderDataAsync(companyUserId, identityProviderId).ConfigureAwait(false); - return (ActionResult)NoContent(); + return NoContent(); } } diff --git a/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs b/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs index c13bdb39a3..32738526d8 100644 --- a/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs +++ b/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs @@ -58,6 +58,6 @@ public PartnerNetworkController(IPartnerNetworkBusinessLogic logic) [Route("memberCompanies")] [ProducesResponseType(StatusCodes.Status200OK)] [PublicUrl(CompanyRoleId.ACTIVE_PARTICIPANT, CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] - public IAsyncEnumerable GetAllMemberCompaniesBPNAsync([FromQuery] IEnumerable? bpnIds = null) => + public IAsyncEnumerable GetAllMemberCompaniesBPNAsync([FromQuery] IEnumerable? bpnIds = null) => _logic.GetAllMemberCompaniesBPNAsync(bpnIds); } diff --git a/src/administration/Administration.Service/Controllers/ServiceAccountController.cs b/src/administration/Administration.Service/Controllers/ServiceAccountController.cs index 90aa534b11..824449b1a8 100644 --- a/src/administration/Administration.Service/Controllers/ServiceAccountController.cs +++ b/src/administration/Administration.Service/Controllers/ServiceAccountController.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -125,7 +124,7 @@ public Task GetServiceAccountDetails([FromRout /// Record was not found. Service account is either not existing or not connected to the respective company. /// Undefined client for service account. [HttpPut] - [Authorize(Roles = "add_tech_user_management")] // TODO check whether we also want an edit role + [Authorize(Roles = "add_tech_user_management")] [Authorize(Policy = PolicyTypes.ValidCompany)] [Route("owncompany/serviceaccounts/{serviceAccountId}")] [ProducesResponseType(typeof(ServiceAccountDetails), StatusCodes.Status200OK)] diff --git a/src/administration/Administration.Service/Controllers/UserController.cs b/src/administration/Administration.Service/Controllers/UserController.cs index e5335d751a..ebf267b711 100644 --- a/src/administration/Administration.Service/Controllers/UserController.cs +++ b/src/administration/Administration.Service/Controllers/UserController.cs @@ -367,26 +367,6 @@ public IAsyncEnumerable GetCoreOfferRoles([FromQuery] string? la public IAsyncEnumerable GetAppRolesAsync([FromRoute] Guid appId, [FromQuery] string? languageShortName = null) => _rolesLogic.GetAppRolesAsync(appId, languageShortName); - /// - /// Gets the client roles for the given app. - /// - /// Id of the app which roles should be returned. - /// OPTIONAL: The language short name. - /// Returns the client roles for the given app. - /// Example: GET: api/administration/user/app/D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645/roles - /// Returns the client roles. - /// The language does not exist. - /// The app was not found. - [Obsolete("to be replaced by endpoint /user/owncompany/roles/apps/{appid}. Remove as soon frontend is adjusted")] - [HttpGet] - [Authorize(Roles = "view_client_roles")] - [Route("app/{appId}/roles")] - [ProducesResponseType(typeof(IAsyncEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] - public IAsyncEnumerable GetClientRolesAsync([FromRoute] Guid appId, string? languageShortName = null) => - _logic.GetClientRolesAsync(appId, languageShortName); - /// /// Gets the user details for the current user. /// @@ -481,27 +461,6 @@ public Task DeleteOwnUser([FromRoute] Guid companyUserId) => roleName, hasRole)); - /// - /// Updates the roles for the user - /// - /// Id of the application - /// - /// - /// Example: PUT: api/administration/user/app/D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645/roles - /// Roles got successfully updated user account. - /// Invalid User roles for client - /// User not found - [Obsolete("to be replaced by endpoint /user/owncompany/users/{companyUserId}/apps/{appId}/roles. remove as soon frontend has been adjusted")] - [HttpPut] - [Authorize(Roles = "modify_user_account")] - [Authorize(Policy = PolicyTypes.ValidCompany)] - [Route("app/{appId}/roles")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] - public Task> ModifyUserRolesAsync([FromRoute] Guid appId, [FromBody] UserRoleInfo userRoleInfo) => - _rolesLogic.ModifyUserRoleAsync(appId, userRoleInfo); - /// /// Delete BPN assigned to user from DB and Keycloack. /// diff --git a/src/administration/Administration.Service/Models/UserIdentityProviderData.cs b/src/administration/Administration.Service/Models/UserIdentityProviderData.cs index 37796090b7..516e7b0739 100644 --- a/src/administration/Administration.Service/Models/UserIdentityProviderData.cs +++ b/src/administration/Administration.Service/Models/UserIdentityProviderData.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional diff --git a/src/administration/Administration.Service/appsettings.json b/src/administration/Administration.Service/appsettings.json index 9c23bd3e37..450bcd75ab 100644 --- a/src/administration/Administration.Service/appsettings.json +++ b/src/administration/Administration.Service/appsettings.json @@ -423,6 +423,14 @@ "BasePortalAddress": "" }, "OnboardingServiceProvider": { - "EncryptionKey": "" + "EncrptionConfigIndex": 0, + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] } } diff --git a/src/externalsystems/Bpdm.Library/BpdmService.cs b/src/externalsystems/Bpdm.Library/BpdmService.cs index 960e8ac9c8..0b7d48cae7 100644 --- a/src/externalsystems/Bpdm.Library/BpdmService.cs +++ b/src/externalsystems/Bpdm.Library/BpdmService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 Microsoft and BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -52,7 +51,7 @@ public BpdmService(ITokenService tokenService, IOptions opt /// public async Task PutInputLegalEntity(BpdmTransferData data, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var requestData = new BpdmLegalEntityData[] { @@ -119,7 +118,7 @@ await httpClient.PutAsJsonAsync("/companies/test-company/api/catena/input/busine public async Task SetSharingStateToReady(string externalId, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var content = new { externalIds = Enumerable.Repeat(externalId, 1) }; await httpClient.PostAsJsonAsync("/companies/test-company/api/catena/sharing-state/ready", content, Options, cancellationToken) @@ -129,7 +128,7 @@ await httpClient.PostAsJsonAsync("/companies/test-company/api/catena/sharing-sta public async Task FetchInputLegalEntity(string externalId, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var data = Enumerable.Repeat(externalId, 1); var result = await httpClient.PostAsJsonAsync("/companies/test-company/api/catena/output/business-partners/search", data, Options, cancellationToken) @@ -154,7 +153,7 @@ public async Task FetchInputLegalEntity(string extern public async Task GetSharingState(Guid applicationId, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var url = $"/companies/test-company/api/catena/sharing-state?externalIds={applicationId}"; var result = await httpClient.GetAsync(url, cancellationToken) diff --git a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs index a0759a69ea..d40e2f4c2e 100644 --- a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs +++ b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 Microsoft and BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -138,9 +137,6 @@ await HandlePullLegalEntityInternal(context, result.CompanyId, result.BpdmData, return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult(ProcessStepStatusId.TODO, null, null, null, false, null); } - // TODO: clarify whether it should be an error if businessPartnerNumber has been set locally while bpdm-answer was outstanding - // TODO: clarify whether it should be an error if address- or identifier-data returned by bpdm does not match what is stored in portal-db or modify in portal-db based on bpdm-response - _portalRepositories.GetInstance().AttachAndModifyCompany( companyId, company => diff --git a/src/externalsystems/Clearinghouse.Library/ClearinghouseService.cs b/src/externalsystems/Clearinghouse.Library/ClearinghouseService.cs index 830de1e220..2919dd8f8a 100644 --- a/src/externalsystems/Clearinghouse.Library/ClearinghouseService.cs +++ b/src/externalsystems/Clearinghouse.Library/ClearinghouseService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 Microsoft and BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -40,7 +39,7 @@ public ClearinghouseService(ITokenService tokenService, IOptions public async Task TriggerCompanyDataPost(ClearinghouseTransferData data, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); async ValueTask<(bool, string?)> CreateErrorMessage(HttpResponseMessage errorResponse) => (false, (await errorResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false))); diff --git a/src/externalsystems/Custodian.Library/CustodianService.cs b/src/externalsystems/Custodian.Library/CustodianService.cs index 08a96fa66c..67b8e11601 100644 --- a/src/externalsystems/Custodian.Library/CustodianService.cs +++ b/src/externalsystems/Custodian.Library/CustodianService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -50,7 +49,7 @@ public CustodianService(ITokenService tokenService, IDateTimeProvider dateTimePr /// public async Task GetWalletByBpnAsync(string bpn, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var result = await httpClient.GetAsync("/api/wallets".AppendToPathEncoded(bpn), cancellationToken) .CatchingIntoServiceExceptionFor("custodian-get", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); @@ -76,7 +75,7 @@ public async Task GetWalletByBpnAsync(string bpn, CancellationToken public async Task CreateWalletAsync(string bpn, string name, CancellationToken cancellationToken) { const string walletUrl = "/api/wallets"; - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var requestBody = new { name = name, bpn = bpn }; async ValueTask<(bool, string?)> CreateErrorMessage(HttpResponseMessage errorResponse) => (false, (await errorResponse.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false))?.Message); @@ -103,7 +102,7 @@ public async Task CreateWalletAsync(string bpn, string name, Cancellatio /// public async Task SetMembership(string bpn, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var requestBody = new { bpn = bpn }; async ValueTask<(bool, string?)> CustomErrorHandling(HttpResponseMessage errorResponse) => ( @@ -121,7 +120,7 @@ public async Task SetMembership(string bpn, CancellationToken cancellati /// public async Task TriggerFrameworkAsync(string bpn, UseCaseDetailData useCaseDetailData, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var requestBody = new CustodianFrameworkRequest ( @@ -138,7 +137,7 @@ await httpClient.PostAsJsonAsync("/api/credentials/issuer/framework", requestBod /// public async Task TriggerDismantlerAsync(string bpn, VerifiedCredentialTypeId credentialTypeId, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var requestBody = new CustodianDismantlerRequest ( diff --git a/src/externalsystems/OfferProvider.Library/OfferProviderService.cs b/src/externalsystems/OfferProvider.Library/OfferProviderService.cs index 951df01732..1890e32840 100644 --- a/src/externalsystems/OfferProvider.Library/OfferProviderService.cs +++ b/src/externalsystems/OfferProvider.Library/OfferProviderService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -46,7 +45,7 @@ public OfferProviderService(ITokenService tokenService, IOptions public async Task TriggerOfferProvider(OfferThirdPartyAutoSetupData autoSetupData, string autoSetupUrl, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) .ConfigureAwait(false); await httpClient.PostAsJsonAsync(autoSetupUrl, autoSetupData, cancellationToken) .CatchingIntoServiceExceptionFor("trigger-offer-provider") @@ -58,7 +57,7 @@ await httpClient.PostAsJsonAsync(autoSetupUrl, autoSetupData, cancellationToken) /// public async Task TriggerOfferProviderCallback(OfferProviderCallbackData callbackData, string callbackUrl, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) + using var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) .ConfigureAwait(false); await httpClient.PostAsJsonAsync(callbackUrl, callbackData, cancellationToken) .CatchingIntoServiceExceptionFor("trigger-offer-provider-callback") diff --git a/src/externalsystems/OnboardingServiceProvider.Library/DependencyInjection/OnboardingServiceProviderSettings.cs b/src/externalsystems/OnboardingServiceProvider.Library/DependencyInjection/OnboardingServiceProviderSettings.cs index 57fb96aff6..5338d5b85f 100644 --- a/src/externalsystems/OnboardingServiceProvider.Library/DependencyInjection/OnboardingServiceProviderSettings.cs +++ b/src/externalsystems/OnboardingServiceProvider.Library/DependencyInjection/OnboardingServiceProviderSettings.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -18,15 +17,16 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; using System.ComponentModel.DataAnnotations; namespace Org.Eclipse.TractusX.Portal.Backend.OnboardingServiceProvider.Library.DependencyInjection; public class OnboardingServiceProviderSettings { - /// - /// The key used to encrypt the callback data - /// - [Required(AllowEmptyStrings = false)] - public string EncryptionKey { get; set; } = null!; + [Required] + public IEnumerable EncryptionConfigs { get; set; } = null!; + + [Required] + public int EncrptionConfigIndex { get; set; } } diff --git a/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderBusinessLogic.cs b/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderBusinessLogic.cs index c8dd39585e..8a2ac39f65 100644 --- a/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderBusinessLogic.cs +++ b/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderBusinessLogic.cs @@ -20,13 +20,12 @@ using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; using Org.Eclipse.TractusX.Portal.Backend.OnboardingServiceProvider.Library.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.OnboardingServiceProvider.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; -using System.Security.Cryptography; -using System.Text; namespace Org.Eclipse.TractusX.Portal.Backend.OnboardingServiceProvider.Library; @@ -82,22 +81,14 @@ public OnboardingServiceProviderBusinessLogic(IOnboardingServiceProviderService throw new ArgumentException($"{processStepTypeId} is not supported"); } - using var aes = Aes.Create(); - aes.Key = Encoding.UTF8.GetBytes(_settings.EncryptionKey); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.PKCS7; - var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); - using (var msDecrypt = new MemoryStream(data.OspDetails.ClientSecret)) - { - using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); - using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8); - var secret = srDecrypt.ReadToEnd(); - await _onboardingServiceProviderService.TriggerProviderCallback( - new OspTriggerDetails(data.OspDetails.CallbackUrl, data.OspDetails.AuthUrl, data.OspDetails.ClientId, secret), - new OnboardingServiceProviderCallbackData(data.ExternalId, data.ApplicationId, data.Bpn, applicationStatusId, comment), - cancellationToken) - .ConfigureAwait(false); - } + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == data.OspDetails.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {data.OspDetails.EncryptionMode} is not configured"); + var secret = CryptoHelper.Decrypt(data.OspDetails.ClientSecret, data.OspDetails.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + + await _onboardingServiceProviderService.TriggerProviderCallback( + new OspTriggerDetails(data.OspDetails.CallbackUrl, data.OspDetails.AuthUrl, data.OspDetails.ClientId, secret), + new OnboardingServiceProviderCallbackData(data.ExternalId, data.ApplicationId, data.Bpn, applicationStatusId, comment), + cancellationToken) + .ConfigureAwait(false); return (Enumerable.Empty(), ProcessStepStatusId.DONE, false, null); } diff --git a/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderService.cs b/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderService.cs index cb2735ffe6..ada840688a 100644 --- a/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderService.cs +++ b/src/externalsystems/OnboardingServiceProvider.Library/OnboardingServiceProviderService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -46,7 +45,7 @@ public async Task TriggerProviderCallback(OspTriggerDetails ospDetails, On ClientId = ospDetails.ClientId, ClientSecret = ospDetails.ClientSecret }; - var httpClient = await _tokenService.GetAuthorizedClient(settings, cancellationToken) + using var httpClient = await _tokenService.GetAuthorizedClient(settings, cancellationToken) .ConfigureAwait(false); await httpClient.PostAsJsonAsync(ospDetails.CallbackUrl, callbackData, cancellationToken) .CatchingIntoServiceExceptionFor("trigger-onboarding-provider") diff --git a/src/externalsystems/SdFactory.Library/SdFactoryService.cs b/src/externalsystems/SdFactory.Library/SdFactoryService.cs index ed276ec515..f61c2b6822 100644 --- a/src/externalsystems/SdFactory.Library/SdFactoryService.cs +++ b/src/externalsystems/SdFactory.Library/SdFactoryService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -54,7 +53,6 @@ public async Task RegisterConnectorAsync(Guid connectorId, string selfDescriptio { var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) .ConfigureAwait(false); - // TODO The hardcoded values (headquarterCountry, legalCountry, sdType, issuer) will be fetched from the user input or db in future var requestModel = new ConnectorSdFactoryRequestModel( connectorId.ToString(), SdFactoryRequestModelSdType.ServiceOffering, @@ -65,7 +63,7 @@ public async Task RegisterConnectorAsync(Guid connectorId, string selfDescriptio _settings.SdFactoryIssuerBpn, businessPartnerNumber); - await httpClient.PostAsJsonAsync((string?)null, requestModel, cancellationToken) + await httpClient.PostAsJsonAsync(default(string?), requestModel, cancellationToken) .CatchingIntoServiceExceptionFor("sd-factory-connector-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); } @@ -84,7 +82,7 @@ public async Task RegisterSelfDescriptionAsync(Guid applicationId, IEnumerable<( businessPartnerNumber, _settings.SdFactoryIssuerBpn); - await httpClient.PostAsJsonAsync((string?)null, requestModel, cancellationToken) + await httpClient.PostAsJsonAsync(default(string?), requestModel, cancellationToken) .CatchingIntoServiceExceptionFor("sd-factory-selfdescription-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); } } diff --git a/src/framework/Framework.Async/AsyncGroupByExtensions.cs b/src/framework/Framework.Async/AsyncGroupByExtensions.cs index 72592f78d3..9516102fc1 100644 --- a/src/framework/Framework.Async/AsyncGroupByExtensions.cs +++ b/src/framework/Framework.Async/AsyncGroupByExtensions.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional diff --git a/src/framework/Framework.Async/Directory.Build.props b/src/framework/Framework.Async/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Async/Directory.Build.props +++ b/src/framework/Framework.Async/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Cors/Directory.Build.props b/src/framework/Framework.Cors/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Cors/Directory.Build.props +++ b/src/framework/Framework.Cors/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.DBAccess/Directory.Build.props b/src/framework/Framework.DBAccess/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.DBAccess/Directory.Build.props +++ b/src/framework/Framework.DBAccess/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.DateTimeProvider/Directory.Build.props b/src/framework/Framework.DateTimeProvider/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.DateTimeProvider/Directory.Build.props +++ b/src/framework/Framework.DateTimeProvider/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.DependencyInjection/Directory.Build.props b/src/framework/Framework.DependencyInjection/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.DependencyInjection/Directory.Build.props +++ b/src/framework/Framework.DependencyInjection/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props b/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling.Controller/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.ErrorHandling.Web/Directory.Build.props b/src/framework/Framework.ErrorHandling.Web/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.ErrorHandling.Web/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling.Web/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.ErrorHandling.Web/GeneralHttpErrorHandler.cs b/src/framework/Framework.ErrorHandling.Web/GeneralHttpErrorHandler.cs index e4bb639686..6b5ff4e958 100644 --- a/src/framework/Framework.ErrorHandling.Web/GeneralHttpErrorHandler.cs +++ b/src/framework/Framework.ErrorHandling.Web/GeneralHttpErrorHandler.cs @@ -35,17 +35,42 @@ public class GeneralHttpErrorHandler private readonly IErrorMessageService? _errorMessageService; private readonly ILogger _logger; - private static readonly IReadOnlyDictionary Metadata = new Dictionary + private static readonly IReadOnlyDictionary Metadata = ImmutableDictionary.CreateRange(new[] { - { HttpStatusCode.BadRequest, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.1", "One or more validation errors occurred.") }, - { HttpStatusCode.Conflict, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.8", "The resorce is in conflict with the current request.") }, - { HttpStatusCode.NotFound, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4", "Cannot find representation of target resource.") }, - { HttpStatusCode.Forbidden, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3", "Access to requested resource is not permitted.") }, - { HttpStatusCode.UnsupportedMediaType, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.13", "The server cannot process this type of content") }, - { HttpStatusCode.BadGateway, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.3", "Error accessing external resource.") }, - { HttpStatusCode.ServiceUnavailable, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.4", "Service is currently unavailable.") }, - { HttpStatusCode.InternalServerError, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1", "The server encountered an unexpected condition.") } - }.ToImmutableDictionary(); + KeyValuePair.Create(HttpStatusCode.BadRequest, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.1", "One or more validation errors occurred.")), + KeyValuePair.Create(HttpStatusCode.Conflict, new MetaData("https://tools.ietf.org/html/rfc7231#section-6.5.8", "The resorce is in conflict with the current request.")), + KeyValuePair.Create(HttpStatusCode.NotFound, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4", "Cannot find representation of target resource.")), + KeyValuePair.Create(HttpStatusCode.Forbidden, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3", "Access to requested resource is not permitted.")), + KeyValuePair.Create(HttpStatusCode.UnsupportedMediaType, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.13", "The server cannot process this type of content")), + KeyValuePair.Create(HttpStatusCode.BadGateway, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.3", "Error accessing external resource.")), + KeyValuePair.Create(HttpStatusCode.ServiceUnavailable, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.4", "Service is currently unavailable.")), + KeyValuePair.Create(HttpStatusCode.InternalServerError, new MetaData("https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1", "The server encountered an unexpected condition.")), + }); + + private static KeyValuePair)>? MessageFunc, LogLevel LogLevel)> CreateErrorEntry( + HttpStatusCode httpStatusCode, + Func)>? messageFunc = null, + LogLevel logLevel = LogLevel.Information + ) where T : class => + KeyValuePair.Create)>?, LogLevel)>( + typeof(T), + (httpStatusCode, + messageFunc == null + ? null + : (Exception e) => messageFunc.Invoke(e as T ?? throw new UnexpectedConditionException($"Exception type {e.GetType()} should always be of type {typeof(T)} here")), + logLevel)); + + private static readonly IReadOnlyDictionary)>? MessageFunc, LogLevel LogLevel)> ErrorTypes = ImmutableDictionary.CreateRange(new[] + { + CreateErrorEntry(HttpStatusCode.BadRequest, argumentException => (argumentException.ParamName, Enumerable.Repeat(argumentException.Message, 1))), + CreateErrorEntry(HttpStatusCode.BadRequest, caException => (caException.ParamName, Enumerable.Repeat(caException.Message, 1))), + CreateErrorEntry(HttpStatusCode.NotFound), + CreateErrorEntry(HttpStatusCode.Conflict), + CreateErrorEntry(HttpStatusCode.Forbidden), + CreateErrorEntry(HttpStatusCode.BadGateway, serviceException => (serviceException.Source, new[] { serviceException.StatusCode == null ? "remote service call failed" : $"remote service returned status code: {(int) serviceException.StatusCode} {serviceException.StatusCode}", serviceException.Message })), + CreateErrorEntry(HttpStatusCode.UnsupportedMediaType), + CreateErrorEntry(HttpStatusCode.InternalServerError, configurationException => (configurationException.Source, new[] { $"Invalid service configuration: {configurationException.Message}" })) + }); public GeneralHttpErrorHandler(RequestDelegate next, ILogger logger, IErrorMessageService? errorMessageService = null) //TODO make errorMessageService mandatory as soon all dependant services are adjusted accordingly { @@ -74,64 +99,10 @@ public async Task Invoke(HttpContext context) } } - private static (HttpStatusCode StatusCode, Func)>? MessageFunc, LogLevel LogLevel) GetErrorInformation(Exception error) - { - HttpStatusCode statusCode; - var logLevel = LogLevel.Information; - Func)>? messageFunc = null; - - if (error is ArgumentException argumentException) - { - statusCode = HttpStatusCode.BadRequest; - messageFunc = _ => (argumentException.ParamName, Enumerable.Repeat(argumentException.Message, 1)); - } - - else if (error is ControllerArgumentException caException) - { - statusCode = HttpStatusCode.BadRequest; - messageFunc = _ => (caException.ParamName, Enumerable.Repeat(caException.Message, 1)); - } - else if (error is NotFoundException) - { - statusCode = HttpStatusCode.NotFound; - } - else if (error is ConflictException) - { - statusCode = HttpStatusCode.Conflict; - } - else if (error is ForbiddenException) - { - statusCode = HttpStatusCode.Forbidden; - } - else if (error is ServiceException serviceException) - { - statusCode = HttpStatusCode.BadGateway; - var serviceStatus = serviceException.StatusCode; - messageFunc = ex => (ex.Source, new[] - { - serviceStatus == null - ? "remote service call failed" - : $"remote service returned status code: {(int) serviceStatus} {serviceStatus}", - error.Message - }); - } - else if (error is UnsupportedMediaTypeException) - { - statusCode = HttpStatusCode.UnsupportedMediaType; - } - else if (error is ConfigurationException) - { - statusCode = HttpStatusCode.InternalServerError; - messageFunc = ex => (ex.Source, new[] { $"Invalid service configuration: {ex.Message}" }); - } - else - { - statusCode = HttpStatusCode.InternalServerError; - logLevel = LogLevel.Error; - } - - return (statusCode, messageFunc, logLevel); - } + private static (HttpStatusCode StatusCode, Func)>? MessageFunc, LogLevel LogLevel) GetErrorInformation(Exception error) => + ErrorTypes.TryGetValue(error.GetType(), out var mapping) + ? mapping + : (HttpStatusCode.InternalServerError, null, LogLevel.Error); private ErrorResponse CreateErrorResponse(HttpStatusCode statusCode, Exception error, string errorId, string message, IEnumerable? details, Func)>? getSourceAndMessages) { diff --git a/src/framework/Framework.ErrorHandling/Directory.Build.props b/src/framework/Framework.ErrorHandling/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.ErrorHandling/Directory.Build.props +++ b/src/framework/Framework.ErrorHandling/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.HttpClientExtensions/Directory.Build.props b/src/framework/Framework.HttpClientExtensions/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.HttpClientExtensions/Directory.Build.props +++ b/src/framework/Framework.HttpClientExtensions/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.IO/CsvParser.cs b/src/framework/Framework.IO/CsvParser.cs index e08cc3af8f..ec851b0845 100644 --- a/src/framework/Framework.IO/CsvParser.cs +++ b/src/framework/Framework.IO/CsvParser.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -92,7 +91,7 @@ public static IEnumerable TrailingStringItemsNotNullOrWhiteSpace(IEnumer Func, IAsyncEnumerable<(bool Processed, Exception? Error)>> processLines, CancellationToken cancellationToken) { - var reader = new StreamReader(new CancellableStream(stream, cancellationToken), Encoding.UTF8); + using var reader = new StreamReader(new CancellableStream(stream, cancellationToken), Encoding.UTF8); var numProcessed = 0; var errors = new List<(int Line, Exception Error)>(); diff --git a/src/framework/Framework.IO/Directory.Build.props b/src/framework/Framework.IO/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.IO/Directory.Build.props +++ b/src/framework/Framework.IO/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Linq/Directory.Build.props b/src/framework/Framework.Linq/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Linq/Directory.Build.props +++ b/src/framework/Framework.Linq/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Linq/DuplicatesExtension.cs b/src/framework/Framework.Linq/DuplicatesExtension.cs index 26395cc10f..6cba928bc0 100644 --- a/src/framework/Framework.Linq/DuplicatesExtension.cs +++ b/src/framework/Framework.Linq/DuplicatesExtension.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -25,24 +24,12 @@ public static class DuplicatesExtension public static IEnumerable Duplicates(this IEnumerable source) { var buffer = new HashSet(); - foreach (var t in source) - { - if (!buffer.Add(t)) - { - yield return t; - } - } + return source.Select(x => (Valid: buffer.Add(x), Value: x)).Where(x => !x.Valid).Select(x => x.Value); } public static IEnumerable DuplicatesBy(this IEnumerable source, Func selector) { var buffer = new HashSet(); - foreach (var t in source) - { - if (!buffer.Add(selector(t))) - { - yield return t; - } - } + return source.Select(x => (Valid: buffer.Add(selector(x)), Value: x)).Where(x => !x.Valid).Select(x => x.Value); } } diff --git a/src/framework/Framework.Linq/IfAnyExtension.cs b/src/framework/Framework.Linq/IfAnyExtension.cs index 764acc1809..2326bd00c3 100644 --- a/src/framework/Framework.Linq/IfAnyExtension.cs +++ b/src/framework/Framework.Linq/IfAnyExtension.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional diff --git a/src/framework/Framework.Linq/ObjectToEnumerableExtension.cs b/src/framework/Framework.Linq/ObjectToEnumerableExtension.cs index 137352bdba..e37341fac4 100644 --- a/src/framework/Framework.Linq/ObjectToEnumerableExtension.cs +++ b/src/framework/Framework.Linq/ObjectToEnumerableExtension.cs @@ -29,13 +29,8 @@ public static IEnumerable ToIEnumerable(this object value) var enumerator = value?.GetType().GetMethod("GetEnumerator")?.Invoke(value, null) ?? throw new ArgumentException($"object instance does not implement IEnumerable ({value?.GetType()})"); var moveNext = enumerator?.GetType().GetMethod("MoveNext") ?? throw new UnexpectedConditionException("method 'moveNext' should never be null here"); var current = enumerator?.GetType().GetProperty("Current")?.GetMethod ?? throw new UnexpectedConditionException("property 'Current' should never be null here"); - while (true) + while ((moveNext.Invoke(enumerator, null) ?? throw new UnexpectedConditionException($"failed to enumerate object {value}: moveNext should never return null here")) is true) { - var hasNext = moveNext.Invoke(enumerator, null) ?? throw new UnexpectedConditionException($"failed to enumerate object {value}: moveNext should never return null here"); - if (hasNext is not true) - { - yield break; - } yield return current.Invoke(enumerator, null) ?? throw new UnexpectedConditionException($"failed to enumerate object {value}: Current should never return null here"); } } diff --git a/src/framework/Framework.Logging/Directory.Build.props b/src/framework/Framework.Logging/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Logging/Directory.Build.props +++ b/src/framework/Framework.Logging/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Models/Configuration/EncryptionModeConfig.cs b/src/framework/Framework.Models/Configuration/EncryptionModeConfig.cs new file mode 100644 index 0000000000..805a61ef93 --- /dev/null +++ b/src/framework/Framework.Models/Configuration/EncryptionModeConfig.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 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.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; +using System.Security.Cryptography; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; + +public class EncryptionModeConfig +{ + [Required] + public int Index { get; set; } + + [Required(AllowEmptyStrings = false)] + public string EncryptionKey { get; set; } = null!; + + [Required] + [ValidateEnumValue] + public CipherMode CipherMode { get; set; } + + [Required] + [ValidateEnumValue] + public PaddingMode PaddingMode { get; set; } +} diff --git a/src/framework/Framework.Models/Directory.Build.props b/src/framework/Framework.Models/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Models/Directory.Build.props +++ b/src/framework/Framework.Models/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Models/Encryption/CryptoHelper.cs b/src/framework/Framework.Models/Encryption/CryptoHelper.cs new file mode 100644 index 0000000000..d044bc59d6 --- /dev/null +++ b/src/framework/Framework.Models/Encryption/CryptoHelper.cs @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2021, 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 System.Security.Cryptography; +using System.Text; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; + +public static class CryptoHelper +{ + public static (byte[] Result, byte[] InitializationVector) Encrypt(string data, byte[] encryptionKey, CipherMode cipherMode, PaddingMode paddingMode) + { + using var aes = Aes.Create(); + aes.Mode = cipherMode; + aes.Padding = paddingMode; + var encryptor = aes.CreateEncryptor(encryptionKey, aes.IV); + using var memoryStream = new MemoryStream(); + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + using var sw = new StreamWriter(cryptoStream, Encoding.UTF8); + sw.Write(data); + } + return (memoryStream.ToArray(), aes.IV); + } + + public static string Decrypt(byte[] data, byte[]? initializationVector, byte[] encryptionKey, CipherMode cipherMode, PaddingMode paddingMode) + { + using var aes = Aes.Create(); + aes.Mode = cipherMode; + aes.Padding = paddingMode; + aes.Key = encryptionKey; + if (initializationVector != null) + { + aes.IV = initializationVector; + } + var decryptor = aes.CreateDecryptor(); + using var msDecrypt = new MemoryStream(data); + using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); + using var srDecrypt = new StreamReader(csDecrypt, Encoding.UTF8); + return srDecrypt.ReadToEnd(); + } +} diff --git a/src/framework/Framework.Models/Pagination.cs b/src/framework/Framework.Models/Pagination.cs index e325e0ea22..815069ba1b 100644 --- a/src/framework/Framework.Models/Pagination.cs +++ b/src/framework/Framework.Models/Pagination.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -101,7 +100,7 @@ public static async Task> CreateResponseAsync(int page, int size, count, count / size + Math.Clamp(count % size, 0, 1), page, - data.Count()), + data.Count), data); } @@ -121,22 +120,20 @@ public static async Task> CreateResponseAsync(int page, int size, source.Data); } - public static IQueryable?> CreateSourceQueryAsync(int skip, int take, IQueryable> query, Expression, IOrderedEnumerable>>? orderBy, Expression> select) where TEntity : class + public static IQueryable?> CreateSourceQueryAsync(int skip, int take, IQueryable> query, Expression, IOrderedEnumerable>>? orderBy, Expression> select) where TEntity : class { var paramGroup = Expression.Parameter(typeof(IGrouping), "group"); - var selector = Expression.Lambda, Pagination.Source>>( - Expression.New(typeof(Pagination.Source).GetConstructor(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, new[] { typeof(int), typeof(IEnumerable) })!, - new Expression[] { - Expression.Call(typeof(Enumerable), "Count", new Type[] { typeof(TEntity) }, paramGroup), - Expression.Call(typeof(Enumerable), "Select", new Type[] { typeof(TEntity), typeof(T) }, new Expression[] { - Expression.Call(typeof(Enumerable), "Take", new Type[] { typeof(TEntity) }, new Expression [] { - Expression.Call(typeof(Enumerable), "Skip", new Type[] { typeof(TEntity) }, new Expression[] { - orderBy == null ? paramGroup : Expression.Invoke(orderBy, paramGroup), - Expression.Constant(skip) }), - Expression.Constant(take) }), - select }) - } + var selector = Expression.Lambda, Source>>( + Expression.New(typeof(Source).GetConstructor(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, new[] { typeof(int), typeof(IEnumerable) })!, + Expression.Call(typeof(Enumerable), "Count", new[] { typeof(TEntity) }, paramGroup), + Expression.Call(typeof(Enumerable), "Select", new[] { typeof(TEntity), typeof(T) }, + Expression.Call(typeof(Enumerable), "Take", new[] { typeof(TEntity) }, + Expression.Call(typeof(Enumerable), "Skip", new[] { typeof(TEntity) }, + orderBy == null ? paramGroup : Expression.Invoke(orderBy, paramGroup), + Expression.Constant(skip)), + Expression.Constant(take)), + select) ), paramGroup); diff --git a/src/framework/Framework.Models/Validation/BaseOptionEnumerableValidation.cs b/src/framework/Framework.Models/Validation/BaseOptionEnumerableValidation.cs index 0894ed569c..ca76563f20 100644 --- a/src/framework/Framework.Models/Validation/BaseOptionEnumerableValidation.cs +++ b/src/framework/Framework.Models/Validation/BaseOptionEnumerableValidation.cs @@ -98,7 +98,7 @@ var x when x.GetInterfaces().Contains(typeof(IEnumerable)) && (configSection.GetSection(propertyName).Get(property.PropertyType) as IEnumerable) ?.ToIEnumerable() .Select((_, i) => configSection.GetSection($"{propertyName}:{i}")) - .SelectMany(section => GetValidationErrors(genericType!, section)), - _ => null - } ?? Enumerable.Empty(); + .SelectMany(section => GetValidationErrors(genericType!, section)) ?? Enumerable.Empty(), + _ => Enumerable.Empty() + }; } diff --git a/src/framework/Framework.Models/Validation/ValidateEnumValuesAttribute.cs b/src/framework/Framework.Models/Validation/ValidateEnumValuesAttribute.cs index 724602b6e7..ccc483ec5e 100644 --- a/src/framework/Framework.Models/Validation/ValidateEnumValuesAttribute.cs +++ b/src/framework/Framework.Models/Validation/ValidateEnumValuesAttribute.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -41,13 +40,11 @@ public class ValidateEnumValuesAttribute : ValidationAttribute } var values = type.GetEnumValues(); - foreach (var item in value!.ToIEnumerable()) + if (value!.ToIEnumerable().Where(item => item is null || Array.BinarySearch(values, item) < 0).IfAny(x => x.First(), out var item)) { - if (item is null || Array.BinarySearch(values, item) < 0) - { - return new ValidationResult($"{item} is not a valid value for {type}. Valid values are: {string.Join(", ", type.GetEnumNames())}"); - } + return new ValidationResult($"{item} is not a valid value for {type}. Valid values are: {string.Join(", ", type.GetEnumNames())}"); } + return null; } } diff --git a/src/framework/Framework.Models/ValidationExpressions.cs b/src/framework/Framework.Models/ValidationExpressions.cs index 3efc662680..9b6bf2e720 100644 --- a/src/framework/Framework.Models/ValidationExpressions.cs +++ b/src/framework/Framework.Models/ValidationExpressions.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -22,7 +21,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models; public static class ValidationExpressions { - public const string Name = @"^.+$"; // TODO: should be @"^(([A-Za-zÀ-ÿ]{1,40}?([-,.'\s]?[A-Za-zÀ-ÿ]{1,40}?)){1,8})$"; + public const string Name = @"^.+$"; public const string Bpn = @"^(BPNL|bpnl)[\w|\d]{12}$"; public const string Company = @"^\d*?[A-Za-zÀ-ÿ]\d?([A-Za-z0-9À-ÿ-_+=.,:;!?'\x22&#@()]\s?){2,40}$"; } diff --git a/src/framework/Framework.Seeding/Directory.Build.props b/src/framework/Framework.Seeding/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Seeding/Directory.Build.props +++ b/src/framework/Framework.Seeding/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Swagger/Directory.Build.props b/src/framework/Framework.Swagger/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Swagger/Directory.Build.props +++ b/src/framework/Framework.Swagger/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Token/Directory.Build.props b/src/framework/Framework.Token/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Token/Directory.Build.props +++ b/src/framework/Framework.Token/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Token/TokenService.cs b/src/framework/Framework.Token/TokenService.cs index bda9355159..0f08468bb2 100644 --- a/src/framework/Framework.Token/TokenService.cs +++ b/src/framework/Framework.Token/TokenService.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -45,7 +44,7 @@ public async Task GetAuthorizedClient(KeyVaultAuthSettings settin settings.Scope, settings.TokenAddress); - var token = await this.GetTokenAsync(tokenParameters, cancellationToken).ConfigureAwait(false); + var token = await GetTokenAsync(tokenParameters, cancellationToken).ConfigureAwait(false); var httpClient = _httpClientFactory.CreateClient(typeof(T).Name); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); @@ -63,8 +62,9 @@ public async Task GetAuthorizedClient(KeyVaultAuthSettings settin {"client_secret", settings.ClientSecret}, {"scope", settings.Scope} }; - var content = new FormUrlEncodedContent(formParameters); - var response = await _httpClientFactory.CreateClient(settings.HttpClientName).PostAsync(settings.TokenUrl, content, cancellationToken) + using var content = new FormUrlEncodedContent(formParameters); + using var httpClient = _httpClientFactory.CreateClient(settings.HttpClientName); + using var response = await httpClient.PostAsync(settings.TokenUrl, content, cancellationToken) .CatchingIntoServiceExceptionFor("token-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/framework/Framework.Web/Directory.Build.props b/src/framework/Framework.Web/Directory.Build.props index ef267766b7..90377caa4f 100644 --- a/src/framework/Framework.Web/Directory.Build.props +++ b/src/framework/Framework.Web/Directory.Build.props @@ -19,7 +19,7 @@ - 1.1.0 + 1.2.0 diff --git a/src/framework/Framework.Web/JwtBearerConfigurationHealthCheck.cs b/src/framework/Framework.Web/JwtBearerConfigurationHealthCheck.cs index 4726de6eac..6fa55e1d9a 100644 --- a/src/framework/Framework.Web/JwtBearerConfigurationHealthCheck.cs +++ b/src/framework/Framework.Web/JwtBearerConfigurationHealthCheck.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional diff --git a/src/keycloak/Keycloak.ErrorHandling/FlurlErrorHandler.cs b/src/keycloak/Keycloak.ErrorHandling/FlurlErrorHandler.cs index 2580a96012..bdf2176644 100644 --- a/src/keycloak/Keycloak.ErrorHandling/FlurlErrorHandler.cs +++ b/src/keycloak/Keycloak.ErrorHandling/FlurlErrorHandler.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -25,7 +24,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; -public class FlurlErrorHandler +public static class FlurlErrorHandler { public static void ConfigureErrorHandler(ILogger logger, bool isDevelopment) { @@ -36,14 +35,14 @@ public static void ConfigureErrorHandler(ILogger logger, bool isDevelopment) if (isDevelopment) { var request = call.HttpRequestMessage == null ? "" : $"{call.HttpRequestMessage.Method} {call.HttpRequestMessage.RequestUri} HTTP/{call.HttpRequestMessage.Version}\n{call.HttpRequestMessage.Headers}\n"; - var requestBody = call.RequestBody == null ? "\n" : call.RequestBody.ToString() + "\n\n"; + var requestBody = call.RequestBody == null ? "\n" : call.RequestBody + "\n\n"; var response = call.HttpResponseMessage == null ? "" : call.HttpResponseMessage.ReasonPhrase + "\n"; var responseContent = call.HttpResponseMessage?.Content == null ? "" : call.HttpResponseMessage.Content.ReadAsStringAsync().Result + "\n"; - logger.LogError(call.Exception, request + requestBody + response + responseContent); + logger.LogError(call.Exception, "{Request}{Body}{Response}{Content}", request, requestBody, response, responseContent); } else { - logger.LogError(call.Exception, message); + logger.LogError(call.Exception, "{Message}", message); } if (call.HttpResponseMessage != null) diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/ClientRoles.cs b/src/keycloak/Keycloak.ErrorHandling/KeycloakInvalidResponseException.cs similarity index 61% rename from src/portalbackend/PortalBackend.DBAccess/Models/ClientRoles.cs rename to src/keycloak/Keycloak.ErrorHandling/KeycloakInvalidResponseException.cs index 60d8062a3c..f52435504a 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Models/ClientRoles.cs +++ b/src/keycloak/Keycloak.ErrorHandling/KeycloakInvalidResponseException.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -18,20 +17,15 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models -{ - [Obsolete("only referenced by code that is marked as obsolte")] - public class ClientRoles - { - public ClientRoles(Guid roleId, string role, string description) - { - RoleId = roleId; - Role = role; - Description = description; - } +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; - public Guid RoleId { get; set; } - public string Role { get; set; } - public string Description { get; set; } - } +[Serializable] +public class KeycloakInvalidResponseException : Exception +{ + public KeycloakInvalidResponseException() { } + public KeycloakInvalidResponseException(string message) : base(message) { } + public KeycloakInvalidResponseException(string message, Exception inner) : base(message, inner) { } + protected KeycloakInvalidResponseException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } diff --git a/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs b/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs index aeaed96a15..9a0b145538 100644 --- a/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs @@ -116,25 +116,31 @@ public async Task CreateAuthenticationExecutionConfigurationAsync(string realm, .PostJsonAsync(authenticatorConfig, cancellationToken) .ConfigureAwait(false); - public async Task LowerAuthenticationExecutionPriorityAsync(string realm, string executionId) => + public async Task LowerAuthenticationExecutionPriorityAsync(string realm, string executionId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/executions/") .AppendPathSegment(executionId, true) .AppendPathSegment("/lower-priority") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } - public async Task RaiseAuthenticationExecutionPriorityAsync(string realm, string executionId) => + public async Task RaiseAuthenticationExecutionPriorityAsync(string realm, string executionId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/executions/") .AppendPathSegment(executionId, true) .AppendPathSegment("/raise-priority") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } public async Task CreateAuthenticationFlowAsync(string realm, AuthenticationFlow authenticationFlow, CancellationToken cancellationToken = default) => await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) @@ -316,25 +322,31 @@ public async Task DeleteRequiredActionAsync(string realm, string requiredActionA .DeleteAsync() .ConfigureAwait(false); - public async Task LowerRequiredActionPriorityAsync(string realm, string requiredActionAlias) => + public async Task LowerRequiredActionPriorityAsync(string realm, string requiredActionAlias) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/required-actions/") .AppendPathSegment(requiredActionAlias, true) .AppendPathSegment("/lower-priority") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } - public async Task RaiseRequiredActionPriorityAsync(string realm, string requiredActionAlias) => + public async Task RaiseRequiredActionPriorityAsync(string realm, string requiredActionAlias) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/required-actions/") .AppendPathSegment(requiredActionAlias, true) .AppendPathSegment("/raise-priority") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } public async Task>> GetUnregisteredRequiredActionsAsync(string realm) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) diff --git a/src/keycloak/Keycloak.Library/ClientAttributeCertificate/KeycloakClient.cs b/src/keycloak/Keycloak.Library/ClientAttributeCertificate/KeycloakClient.cs index 0a4652115f..767ca5a9ea 100644 --- a/src/keycloak/Keycloak.Library/ClientAttributeCertificate/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/ClientAttributeCertificate/KeycloakClient.cs @@ -53,17 +53,21 @@ public async Task GetKeyStoreForClientAsync(string realm, string clientI .ReceiveBytes() .ConfigureAwait(false); - public async Task GenerateCertificateWithNewKeyPairAsync(string realm, string clientId, string attribute) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) - .AppendPathSegment("/admin/realms/") - .AppendPathSegment(realm, true) - .AppendPathSegment("/clients/") - .AppendPathSegment(clientId, true) - .AppendPathSegment("/certificates/") - .AppendPathSegment(attribute, true) - .AppendPathSegment("/generate") - .PostAsync(new StringContent("")) - .ReceiveJson() - .ConfigureAwait(false); + public async Task GenerateCertificateWithNewKeyPairAsync(string realm, string clientId, string attribute) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + .AppendPathSegment("/admin/realms/") + .AppendPathSegment(realm, true) + .AppendPathSegment("/clients/") + .AppendPathSegment(clientId, true) + .AppendPathSegment("/certificates/") + .AppendPathSegment(attribute, true) + .AppendPathSegment("/generate") + .PostAsync(stringContent) + .ReceiveJson() + .ConfigureAwait(false); + } public async Task GenerateCertificateWithNewKeyPairAndGetKeyStoreAsync(string realm, string clientId, string attribute, KeyStoreConfig keyStoreConfig) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") diff --git a/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs b/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs index 47f5306eed..8b7c01fb1d 100644 --- a/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs @@ -99,16 +99,19 @@ public async Task DeleteClientAsync(string realm, string clientId) => .DeleteAsync() .ConfigureAwait(false); - public async Task GenerateClientSecretAsync(string realm, string clientId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GenerateClientSecretAsync(string realm, string clientId) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/client-secret") - .PostJsonAsync(new StringContent("")) + .PostJsonAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } public async Task GetClientSecretAsync(string realm, string clientId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -130,7 +133,9 @@ public async Task> GetDefaultClientScopesAsync(string r .GetJsonAsync>() .ConfigureAwait(false); - public async Task UpdateDefaultClientScopeAsync(string realm, string clientId, string clientScopeId) => + public async Task UpdateDefaultClientScopeAsync(string realm, string clientId, string clientScopeId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) @@ -138,9 +143,9 @@ public async Task UpdateDefaultClientScopeAsync(string realm, string clientId, s .AppendPathSegment(clientId, true) .AppendPathSegment("/default-client-scopes/") .AppendPathSegment(clientScopeId, true) - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); - + } public async Task DeleteDefaultClientScopeAsync(string realm, string clientId, string clientScopeId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") @@ -326,7 +331,9 @@ public async Task> GetOptionalClientScopesAsync(string .GetJsonAsync>() .ConfigureAwait(false); - public async Task UpdateOptionalClientScopeAsync(string realm, string clientId, string clientScopeId) => + public async Task UpdateOptionalClientScopeAsync(string realm, string clientId, string clientScopeId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) @@ -334,8 +341,9 @@ public async Task UpdateOptionalClientScopeAsync(string realm, string clientId, .AppendPathSegment(clientId, true) .AppendPathSegment("/optional-client-scopes/") .AppendPathSegment(clientScopeId, true) - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); + } public async Task DeleteOptionalClientScopeAsync(string realm, string clientId, string clientScopeId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -348,27 +356,33 @@ public async Task DeleteOptionalClientScopeAsync(string realm, string clientId, .DeleteAsync() .ConfigureAwait(false); - public async Task PushClientRevocationPolicyAsync(string realm, string clientId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task PushClientRevocationPolicyAsync(string realm, string clientId) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/push-revocation") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } - public async Task GenerateClientRegistrationAccessTokenAsync(string realm, string clientId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GenerateClientRegistrationAccessTokenAsync(string realm, string clientId) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/registration-access-token") - .PostJsonAsync(new StringContent("")) + .PostJsonAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } // [Obsolete("Not working yet")] - seems to work fine? public async Task GetUserForServiceAccountAsync(string realm, string clientId) => diff --git a/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs b/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs index 8e8b00e345..0626501fa8 100644 --- a/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs @@ -104,38 +104,50 @@ public async Task DeleteAdminEventsAsync(string realm) => .DeleteAsync() .ConfigureAwait(false); - public async Task ClearKeysCacheAsync(string realm) => + public async Task ClearKeysCacheAsync(string realm) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clear-keys-cache") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } - public async Task ClearRealmCacheAsync(string realm) => + public async Task ClearRealmCacheAsync(string realm) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clear-realm-cache") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } - public async Task ClearUserCacheAsync(string realm) => + public async Task ClearUserCacheAsync(string realm) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clear-user-cache") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } - public async Task BasePathForImportingClientsAsync(string realm, string description) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task BasePathForImportingClientsAsync(string realm, string description) + { + using var stringContent = new StringContent(description); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-description-converter") - .PostAsync(new StringContent(description)) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } public async Task>> GetClientSessionStatsAsync(string realm) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -153,14 +165,17 @@ public async Task> GetRealmDefaultClientScopesAsync(str .GetJsonAsync>() .ConfigureAwait(false); - public async Task UpdateRealmDefaultClientScopeAsync(string realm, string clientScopeId) => + public async Task UpdateRealmDefaultClientScopeAsync(string realm, string clientScopeId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/default-default-client-scopes/") .AppendPathSegment(clientScopeId, true) - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); + } public async Task DeleteRealmDefaultClientScopeAsync(string realm, string clientScopeId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -179,14 +194,17 @@ public async Task> GetRealmGroupHierarchyAsync(string realm) .GetJsonAsync>() .ConfigureAwait(false); - public async Task UpdateRealmGroupAsync(string realm, string groupId) => + public async Task UpdateRealmGroupAsync(string realm, string groupId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/default-groups/") .AppendPathSegment(groupId, true) - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); + } public async Task DeleteRealmGroupAsync(string realm, string groupId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -205,14 +223,17 @@ public async Task> GetRealmOptionalClientScopesAsync(st .GetJsonAsync>() .ConfigureAwait(false); - public async Task UpdateRealmOptionalClientScopeAsync(string realm, string clientScopeId) => + public async Task UpdateRealmOptionalClientScopeAsync(string realm, string clientScopeId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/default-optional-client-scopes/") .AppendPathSegment(clientScopeId, true) - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); + } public async Task DeleteRealmOptionalClientScopeAsync(string realm, string clientScopeId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -280,14 +301,17 @@ public async Task GetRealmGroupByPathAsync(string realm, string path) => .GetJsonAsync() .ConfigureAwait(false); - public async Task RemoveUserSessionsAsync(string realm) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task RemoveUserSessionsAsync(string realm) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/logout-all") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } public async Task RealmPartialExportAsync(string realm, bool? exportClients = null, bool? exportGroupsAndRoles = null) { @@ -297,12 +321,13 @@ public async Task RealmPartialExportAsync(string realm, bool? exportClien [nameof(exportGroupsAndRoles)] = exportGroupsAndRoles, }; + using var stringContent = new StringContent(""); return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/partial-export") .SetQueryParams(queryParams) - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); } @@ -315,14 +340,17 @@ public async Task RealmPartialImportAsync(string realm, PartialImport rep) => .PostJsonAsync(rep) .ConfigureAwait(false); - public async Task PushRealmRevocationPolicyAsync(string realm) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task PushRealmRevocationPolicyAsync(string realm) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/push-revocation") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } public async Task DeleteUserSessionAsync(string realm, string session) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) @@ -349,14 +377,17 @@ public async Task TestLdapConnectionAsync(string realm, string? action = null, s .AddString(nameof(useTruststoreSpi), useTruststoreSpi)) .ConfigureAwait(false); - public async Task TestSmtpConnectionAsync(string realm, string config) => + public async Task TestSmtpConnectionAsync(string realm, string config) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/testSMTPConnection/") .AppendPathSegment(config, true) - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } public async Task GetRealmUsersManagementPermissionsAsync(string realm) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) diff --git a/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs b/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs index 220f186b04..82394c7094 100755 --- a/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs @@ -80,15 +80,18 @@ public async Task> GetRoleChildrenAsync(string realm, string r .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); - public async Task RemoveRolesFromCompositeAsync(string realm, string roleId, IEnumerable roles) => + public async Task RemoveRolesFromCompositeAsync(string realm, string roleId, IEnumerable roles) + { + using var jsonContent = new CapturedJsonContent(_serializer.Serialize(roles)); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles-by-id/") .AppendPathSegment(roleId, true) .AppendPathSegment("/composites") - .SendJsonAsync(HttpMethod.Delete, new CapturedJsonContent(_serializer.Serialize(roles))) + .SendJsonAsync(HttpMethod.Delete, jsonContent) .ConfigureAwait(false); + } public async Task> GetClientRolesForCompositeByIdAsync(string realm, string roleId, string clientId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) diff --git a/src/keycloak/Keycloak.Library/UserStorageProvider/KeycloakClient.cs b/src/keycloak/Keycloak.Library/UserStorageProvider/KeycloakClient.cs index 353b208a51..e7ea196601 100644 --- a/src/keycloak/Keycloak.Library/UserStorageProvider/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/UserStorageProvider/KeycloakClient.cs @@ -32,43 +32,54 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { [Obsolete("Not working yet")] - public async Task RemoveImportedUsersAsync(string realm, string storageProviderId) => + public async Task RemoveImportedUsersAsync(string realm, string storageProviderId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/user-storage/") .AppendPathSegment(storageProviderId, true) .AppendPathSegment("/remove-imported-users") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } [Obsolete("Not working yet")] - public async Task TriggerUserSynchronizationAsync(string realm, string storageProviderId, UserSyncActions action) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task TriggerUserSynchronizationAsync(string realm, string storageProviderId, UserSyncActions action) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/user-storage/") .AppendPathSegment(storageProviderId, true) .AppendPathSegment("/sync") .SetQueryParam(nameof(action), action == UserSyncActions.Full ? "triggerFullSync" : "triggerChangedUsersSync") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } [Obsolete("Not working yet")] - public async Task UnlinkImportedUsersAsync(string realm, string storageProviderId) => + public async Task UnlinkImportedUsersAsync(string realm, string storageProviderId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/user-storage/") .AppendPathSegment(storageProviderId, true) .AppendPathSegment("/unlink-users") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } [Obsolete("Not working yet")] - public async Task TriggerLdapMapperSynchronizationAsync(string realm, string storageProviderId, string mapperId, LdapMapperSyncActions direction) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task TriggerLdapMapperSynchronizationAsync(string realm, string storageProviderId, string mapperId, LdapMapperSyncActions direction) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/user-storage/") @@ -77,7 +88,8 @@ public async Task TriggerLdapMapperSynchronizationAsync(s .AppendPathSegment(mapperId, true) .AppendPathSegment("/sync") .SetQueryParam(nameof(direction), direction == LdapMapperSyncActions.FedToKeycloak ? "fedToKeycloak" : "keycloakToFed") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson() .ConfigureAwait(false); + } } diff --git a/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs b/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs index 0bd8f7d805..9bb9131440 100644 --- a/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs @@ -233,26 +233,32 @@ public async Task DeleteUserGroupAsync(string realm, string userId, string group .DeleteAsync() .ConfigureAwait(false); - public async Task> ImpersonateUserAsync(string realm, string userId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> ImpersonateUserAsync(string realm, string userId) + { + using var stringContent = new StringContent(""); + return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/impersonation") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ReceiveJson>() .ConfigureAwait(false); + } - public async Task RemoveUserSessionsAsync(string realm, string userId) => + public async Task RemoveUserSessionsAsync(string realm, string userId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/logout") - .PostAsync(new StringContent("")) + .PostAsync(stringContent) .ConfigureAwait(false); + } [Obsolete("Not working yet")] public async Task> GetUserOfflineSessionsAsync(string realm, string userId, string clientId) => @@ -266,15 +272,18 @@ public async Task> GetUserOfflineSessionsAsync(string r .GetJsonAsync>() .ConfigureAwait(false); - public async Task RemoveUserTotpAsync(string realm, string userId) => + public async Task RemoveUserTotpAsync(string realm, string userId) + { + using var stringContent = new StringContent(""); await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/remove-totp") - .PutAsync(new StringContent("")) + .PutAsync(stringContent) .ConfigureAwait(false); + } public async Task ResetUserPasswordAsync(string realm, string userId, Credentials credentials) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs index 097e84d59c..f3ae4baede 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -307,12 +306,12 @@ private bool CompareFlowExecutions(AuthenticationFlowExecution execution, Authen private Task<(bool IsEqual, AuthenticatorConfig? AuthenticatorConfig)> CompareExecutions(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, CancellationToken cancellationToken) => (execution.ProviderId != update.Authenticator || execution.Requirement != update.Requirement) - ? Task.FromResult((false, (AuthenticatorConfig?)null)) + ? Task.FromResult<(bool, AuthenticatorConfig?)>((false, null)) : ((execution.AuthenticationConfig, update.AuthenticatorConfig) switch { - (null, null) => Task.FromResult((true, (AuthenticatorConfig?)null)), - (null, _) => Task.FromResult((false, (AuthenticatorConfig?)null)), - (_, null) => Task.FromResult((false, (AuthenticatorConfig?)null)), + (null, null) => Task.FromResult<(bool, AuthenticatorConfig?)>((true, null)), + (null, _) => Task.FromResult<(bool, AuthenticatorConfig?)>((false, null)), + (_, null) => Task.FromResult<(bool, AuthenticatorConfig?)>((false, null)), (_, _) => CompareAuthenticationConfig(execution.AuthenticationConfig, update.AuthenticatorConfig, cancellationToken) }); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index 0fab2fb82a..bea6bfee5d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -155,7 +154,7 @@ private static ClientScope CreateClientScope(string? id, ClientScopeModel client Description = clientScope.Description, Protocol = clientScope.Protocol, Attributes = clientScope.Attributes == null ? null : CreateClientScopeAttributes(clientScope.Attributes), - ProtocolMappers = includeProtocolMappers ? clientScope?.ProtocolMappers?.Select(x => ProtocolMappersUpdater.CreateProtocolMapper(x.Id, x)) : null + ProtocolMappers = includeProtocolMappers ? clientScope.ProtocolMappers?.Select(x => ProtocolMappersUpdater.CreateProtocolMapper(x.Id, x)) : null }; private static bool CompareClientScope(ClientScope scope, ClientScopeModel update) => diff --git a/src/keycloak/Keycloak.Seeding/Program.cs b/src/keycloak/Keycloak.Seeding/Program.cs index f698493e48..111f04dbe2 100644 --- a/src/keycloak/Keycloak.Seeding/Program.cs +++ b/src/keycloak/Keycloak.Seeding/Program.cs @@ -72,7 +72,7 @@ Log.Information("Building keycloak-seeder completed"); - var tokenSource = new CancellationTokenSource(); + using var tokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (s, e) => { Log.Information("Canceling..."); diff --git a/src/mailing/Mailing.SendMail/SendMail.cs b/src/mailing/Mailing.SendMail/SendMail.cs index 1b3af9b6a2..b69a2a046a 100644 --- a/src/mailing/Mailing.SendMail/SendMail.cs +++ b/src/mailing/Mailing.SendMail/SendMail.cs @@ -1,5 +1,4 @@ /******************************************************************************** - * Copyright (c) 2021, 2023 BMW Group AG * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional @@ -36,18 +35,13 @@ public SendMail(IOptions mailSettings) Task ISendMail.Send(string sender, string recipient, string subject, string body, bool useHtml) { - var message = new MimeMessage(); + using var message = new MimeMessage(); message.From.Add(MailboxAddress.Parse(sender)); message.To.Add(MailboxAddress.Parse(recipient)); message.Subject = subject; - if (useHtml) - { - message.Body = new TextPart("html") { Text = body }; - } - else - { - message.Body = new TextPart("plain") { Text = body }; - } + message.Body = useHtml + ? new TextPart("html") { Text = body } + : new TextPart("plain") { Text = body }; return _send(message); } diff --git a/src/mailing/Mailing.Template/EmailTemplates/additional_user_invitation_with_message.html b/src/mailing/Mailing.Template/EmailTemplates/additional_user_invitation_with_message.html index b74a19255e..341614867a 100644 --- a/src/mailing/Mailing.Template/EmailTemplates/additional_user_invitation_with_message.html +++ b/src/mailing/Mailing.Template/EmailTemplates/additional_user_invitation_with_message.html @@ -160,7 +160,6 @@