Skip to content

Commit

Permalink
feat(connector): add osp idp detail endpoint (#283)
Browse files Browse the repository at this point in the history
add /api/administration/identityprovider/network/identityproviders/managed/{identityProviderId}
endpoint to get idp information with the connected companies

---------

Refs: CPLP-3194
Reviewed-by: Norbert Truchsess <[email protected]>
Co-authored-by: Norbert Truchsess <[email protected]>
  • Loading branch information
Phil91 and ntruchsess authored Oct 10, 2023
1 parent dfeca44 commit cac5fd6
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ public interface IIdentityProviderBusinessLogic
ValueTask<UserIdentityProviderLinkData> CreateOrUpdateOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, UserLinkData userLinkData, Guid companyId);
ValueTask<UserIdentityProviderLinkData> GetOwnCompanyUserIdentityProviderLinkDataAsync(Guid companyUserId, Guid identityProviderId, Guid companyId);
ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync(Guid companyUserId, Guid identityProviderId, Guid companyId);
ValueTask<IdentityProviderDetailsWithConnectedCompanies> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,36 @@ public async ValueTask DeleteOwnCompanyUserIdentityProviderDataAsync(Guid compan
}
}

public async ValueTask<IdentityProviderDetailsWithConnectedCompanies> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId)
{
var companyId = _identityService.IdentityData.CompanyId;

var (alias, category, isOwnerCompany, typeId, connectedCompanies) = await _portalRepositories.GetInstance<IIdentityProviderRepository>().GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, companyId).ConfigureAwait(false);
if (!isOwnerCompany)
{
throw new ConflictException($"identityProvider {identityProviderId} is not associated with company {companyId}");
}

if (alias == null)
{
throw new NotFoundException($"identityProvider {identityProviderId} does not exist");
}

if (category == IdentityProviderCategoryId.KEYCLOAK_SAML && typeId is IdentityProviderTypeId.SHARED)
{
throw new ConflictException("Shared Idps must not use SAML");
}

var details = category switch
{
IdentityProviderCategoryId.KEYCLOAK_OIDC => await GetIdentityProviderDetailsOidc(identityProviderId, alias, category, typeId).ConfigureAwait(false),
IdentityProviderCategoryId.KEYCLOAK_SAML => await GetIdentityProviderDetailsSaml(identityProviderId, alias, typeId).ConfigureAwait(false),
_ => throw new UnexpectedConditionException($"unexpected value for category '{category}' of identityProvider '{identityProviderId}'")
};

return new(details.identityProviderId, details.alias, details.identityProviderCategoryId, details.IdentityProviderTypeId, details.displayName, details.redirectUrl, details.enabled, connectedCompanies);
}

public async IAsyncEnumerable<UserIdentityProviderData> GetOwnCompanyUsersIdentityProviderDataAsync(IEnumerable<Guid> identityProviderIds, Guid companyId, bool unlinkedUsersOnly)
{
var identityProviderAliasDatas = await GetOwnCompanyUsersIdentityProviderAliasDataInternalAsync(identityProviderIds, companyId).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ public async ValueTask<ActionResult<IdentityProviderDetails>> CreateOwnCompanyId
return (ActionResult<IdentityProviderDetails>)CreatedAtRoute(nameof(GetOwnCompanyIdentityProvider), new { identityProviderId = details.identityProviderId }, details);
}

/// <summary>
/// Gets a specific identity provider with the connected Companies
/// </summary>
/// <param name="identityProviderId">Id of the identity provider</param>
/// <returns>Returns details of the identity provider</returns>
/// <remarks>
/// Example: GET: api/administration/identityprovider/network/identityproviders/managed/{identityProviderId}
/// </remarks>
/// <response code="200">Return the details of the identityProvider.</response>
/// <response code="400">The user is not associated with the owner company.</response>
/// <response code="500">Unexpected value of protocol.</response>
/// <response code="502">Bad Gateway Service Error.</response>
[HttpGet]
[Authorize(Roles = "view_managed_idp")]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[Route("network/identityproviders/managed/{identityProviderId}")]
[ProducesResponseType(typeof(IdentityProviderDetailsWithConnectedCompanies), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status502BadGateway)]
public ValueTask<IdentityProviderDetailsWithConnectedCompanies> GetOwnIdentityProviderWithConnectedCompanies([FromRoute] Guid identityProviderId) =>
_businessLogic.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId);

/// <summary>
/// Gets a specific identity provider
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/********************************************************************************
* 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.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;

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

public record IdentityProviderDetailsWithConnectedCompanies
(
Guid IdentityProviderId,
string? Alias,
IdentityProviderCategoryId IdentityProviderCategoryId,
IdentityProviderTypeId IdentityProviderTypeId,
string? DisplayName,
string? RedirectUrl,
bool? Enabled,
IEnumerable<ConnectedCompanyData> ConnectedCompanies
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/********************************************************************************
* 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
********************************************************************************/

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

public record ConnectedCompanyData
(
Guid CompanyId,
string CompanyName
);
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public interface IIdentityProviderRepository
Task<string?> GetSharedIdentityProviderIamAliasDataUntrackedAsync(Guid companyId);
Task<IdpUser?> GetIdpCategoryIdByUserIdAsync(Guid companyUserId, Guid userCompanyId);
Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnOrOwnerCompany, IdentityProviderTypeId TypeId)> GetOwnCompanyIdentityProviderAliasUntrackedAsync(Guid identityProviderId, Guid companyId);
Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnerCompany, IdentityProviderTypeId TypeId, IEnumerable<ConnectedCompanyData> ConnectedCompanies)> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId, Guid companyId);
Task<(bool IsOwner, string? Alias, IdentityProviderCategoryId IdentityProviderCategory, IdentityProviderTypeId IdentityProviderTypeId, IEnumerable<(Guid CompanyId, IEnumerable<string> Aliase)>? CompanyIdAliase)> GetOwnCompanyIdentityProviderUpdateDataUntrackedAsync(Guid identityProviderId, Guid companyId, bool queryAliase);
IAsyncEnumerable<(Guid IdentityProviderId, IdentityProviderCategoryId CategoryId, string? Alias, IdentityProviderTypeId TypeId)> GetCompanyIdentityProviderCategoryDataUntracked(Guid companyId);
IAsyncEnumerable<(Guid IdentityProviderId, string Alias)> GetOwnCompanyIdentityProviderAliasDataUntracked(Guid companyId, IEnumerable<Guid> identityProviderIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ public IamIdentityProvider CreateIamIdentityProvider(Guid identityProviderId, st
identityProvider.IdentityProviderTypeId))
.SingleOrDefaultAsync();

public Task<(string? Alias, IdentityProviderCategoryId IamIdentityProviderCategory, bool IsOwnerCompany, IdentityProviderTypeId TypeId, IEnumerable<ConnectedCompanyData> ConnectedCompanies)> GetOwnIdentityProviderWithConnectedCompanies(Guid identityProviderId, Guid companyId) =>
_context.IdentityProviders
.Where(identityProvider => identityProvider.Id == identityProviderId)
.Select(identityProvider =>
new ValueTuple<string?, IdentityProviderCategoryId, bool, IdentityProviderTypeId, IEnumerable<ConnectedCompanyData>>(
identityProvider.IamIdentityProvider!.IamIdpAlias,
identityProvider.IdentityProviderCategoryId,
identityProvider.OwnerId == companyId,
identityProvider.IdentityProviderTypeId,
identityProvider.Companies.Select(c => new ConnectedCompanyData(c.Id, c.Name))
))
.SingleOrDefaultAsync();

public Task<(bool IsOwner, string? Alias, IdentityProviderCategoryId IdentityProviderCategory, IdentityProviderTypeId IdentityProviderTypeId, IEnumerable<(Guid CompanyId, IEnumerable<string> Aliase)>? CompanyIdAliase)> GetOwnCompanyIdentityProviderUpdateDataUntrackedAsync(Guid identityProviderId, Guid companyId, bool queryAliase) =>
_context.IdentityProviders
.Where(identityProvider => identityProvider.Id == identityProviderId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using Org.Eclipse.TractusX.Portal.Backend.Framework.IO;
using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities;
using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums;
Expand Down Expand Up @@ -2054,6 +2055,163 @@ public async Task GetOwnCompanyUsersIdentityProviderDataAsync_WithoutMatchingIdp

#endregion

#region GetOwnIdentityProviderWithConnectedCompanies

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithDifferentCompany_ThrowsConflictException()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns((string.Empty, IdentityProviderCategoryId.KEYCLOAK_OIDC, false, IdentityProviderTypeId.OWN, Enumerable.Empty<ConnectedCompanyData>()));

// Act
async Task Act() => await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
var ex = await Assert.ThrowsAsync<ConflictException>(Act);
ex.Message.Should().Be($"identityProvider {identityProviderId} is not associated with company {_companyId}");
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithAliasNull_ThrowsNotFoundException()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns((null, IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Empty<ConnectedCompanyData>()));

// Act
async Task Act() => await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
var ex = await Assert.ThrowsAsync<NotFoundException>(Act);
ex.Message.Should().Be($"identityProvider {identityProviderId} does not exist");
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithOidcWithoutExistingKeycloakClient_CallsExpected()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns(("cl1", IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Empty<ConnectedCompanyData>()));
A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataOIDCAsync("cl1"))
.Throws(new KeycloakEntityNotFoundException("cl1 not existing"));

// Act
var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
result.DisplayName.Should().BeNull();
result.Enabled.Should().BeNull();
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidOidc_CallsExpected()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var companyId = Guid.NewGuid();

var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns(("cl1", IdentityProviderCategoryId.KEYCLOAK_OIDC, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataOIDCAsync("cl1"))
.Returns(_fixture.Build<IdentityProviderConfigOidc>().With(x => x.Enabled, true).With(x => x.DisplayName, "dis-oidc").Create());
A.CallTo(() => _provisioningManager.GetIdentityProviderMappers("cl1"))
.Returns(_fixture.CreateMany<IdentityProviderMapperModel>(3).ToAsyncEnumerable());

// Act
var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
result.DisplayName.Should().Be("dis-oidc");
result.Enabled.Should().BeTrue();
result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithSamlWithoutExistingKeycloakClient_CallsExpected()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var companyId = Guid.NewGuid();

var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns(("saml-alias", IdentityProviderCategoryId.KEYCLOAK_SAML, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataSAMLAsync("saml-alias"))
.Throws(new KeycloakEntityNotFoundException("saml-alias"));

// Act
var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
result.DisplayName.Should().BeNull();
result.Enabled.Should().BeNull();
result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidSaml_CallsExpected()
{
// Arrange
var identityProviderId = Guid.NewGuid();
var companyId = Guid.NewGuid();

var sut = new IdentityProviderBusinessLogic(
_portalRepositories,
_provisioningManager,
_identityService,
_options,
_logger);
A.CallTo(() => _identityProviderRepository.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId, _companyId))
.Returns(("saml-alias", IdentityProviderCategoryId.KEYCLOAK_SAML, true, IdentityProviderTypeId.OWN, Enumerable.Repeat(new ConnectedCompanyData(companyId, "Test Company"), 1)));
A.CallTo(() => _provisioningManager.GetCentralIdentityProviderDataSAMLAsync("saml-alias"))
.Returns(_fixture.Build<IdentityProviderConfigSaml>().With(x => x.Enabled, true).With(x => x.DisplayName, "dis-saml").Create());
A.CallTo(() => _provisioningManager.GetIdentityProviderMappers("saml-alias"))
.Returns(_fixture.CreateMany<IdentityProviderMapperModel>(2).ToAsyncEnumerable());

// Act
var result = await sut.GetOwnIdentityProviderWithConnectedCompanies(identityProviderId).ConfigureAwait(false);

// Assert
result.DisplayName.Should().Be("dis-saml");
result.Enabled.Should().BeTrue();
result.ConnectedCompanies.Should().ContainSingle().And.Satisfy(x => x.CompanyId == companyId);
}

#endregion

#region Setup

private void SetupCreateOwnCompanyIdentityProvider(IamIdentityProviderProtocol protocol = IamIdentityProviderProtocol.OIDC, ICollection<IdentityProvider>? idps = null, ICollection<CompanyIdentityProvider>? companyIdps = null, ICollection<IamIdentityProvider>? iamIdps = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,17 @@ public async Task DeleteOwnCompanyIdentityProvider_WithValidData_ReturnsOk()
//Assert
A.CallTo(() => _logic.DeleteCompanyIdentityProviderAsync(id)).MustHaveHappenedOnceExactly();
}

[Fact]
public async Task GetOwnIdentityProviderWithConnectedCompanies_WithValidData_ReturnsOk()
{
//Arrange
var id = Guid.NewGuid();

//Act
await this._controller.GetOwnIdentityProviderWithConnectedCompanies(id).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.GetOwnIdentityProviderWithConnectedCompanies(id)).MustHaveHappenedOnceExactly();
}
}
Loading

0 comments on commit cac5fd6

Please sign in to comment.