Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Gate): add business partner relations #1167

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C
- BPDM Gate & Orchestrator: Enhance the error handling mechanism for the orchestrator and gate components by extending the list of available error codes.([#1003](https://github.com/eclipse-tractusx/bpdm/pull/1003#pullrequestreview-2477395867))
- BPDM System Test: Tester module which performs automated end-to-end tests on an existing golden record process.([#1070](https://github.com/eclipse-tractusx/bpdm/pull/1070))
- BPDM Gate & Pool: Update linkage from AdditionalAddress to SiteMainAddress and LegalAddress to LegalAndSiteMainAddress.([#1121](https://github.com/eclipse-tractusx/bpdm/issues/1121))
- BPDM Gate: Add endpoints for managing business partner relations ([#1027](https://github.com/eclipse-tractusx/bpdm/issues/1027))

### Changed

Expand Down
12 changes: 6 additions & 6 deletions DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ
maven/mavencentral/io.cucumber/ci-environment/10.0.1, MIT, approved, #15218
maven/mavencentral/io.cucumber/cucumber-core/7.20.1, MIT AND (Apache-2.0 AND MIT), approved, #18204
maven/mavencentral/io.cucumber/cucumber-expressions/17.1.0, MIT, approved, #14271
maven/mavencentral/io.cucumber/cucumber-gherkin-messages/7.20.1, None, restricted, #18205
maven/mavencentral/io.cucumber/cucumber-gherkin-messages/7.20.1, MIT, approved, #18205
maven/mavencentral/io.cucumber/cucumber-gherkin/7.20.1, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/cucumber-java/7.20.1, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/cucumber-junit/7.20.1, None, restricted, #18206
maven/mavencentral/io.cucumber/cucumber-junit/7.20.1, MIT, approved, #18206
maven/mavencentral/io.cucumber/cucumber-plugin/7.20.1, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/cucumber-spring/7.20.1, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/datatable/7.20.1, Apache-2.0 AND MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/docstring/7.20.1, None, restricted, #18207
maven/mavencentral/io.cucumber/docstring/7.20.1, MIT, approved, #18207
maven/mavencentral/io.cucumber/gherkin/28.0.0, MIT, approved, #14276
maven/mavencentral/io.cucumber/html-formatter/21.7.0, Apache-2.0 AND MIT, approved, #18208
maven/mavencentral/io.cucumber/junit-xml-formatter/0.5.0, MIT, approved, clearlydefined
Expand Down Expand Up @@ -196,9 +196,9 @@ maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlyd
maven/mavencentral/org.skyscreamer/jsonassert/1.5.3, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.slf4j/jul-to-slf4j/2.0.16, MIT, approved, #7698
maven/mavencentral/org.slf4j/slf4j-api/2.0.16, MIT, approved, #5915
maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.8.1, , restricted, clearlydefined
maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.8.1, , restricted, clearlydefined
maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.8.1, , restricted, clearlydefined
maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.8.1, Apache-2.0, approved, #18209
maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.8.1, Apache-2.0, approved, #18210
maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.8.1, Apache-2.0, approved, #18211
maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.4.1, Apache-2.0, approved, #17576
maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.4.1, Apache-2.0, approved, #17574
maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.4.1, Apache-2.0, approved, #17570
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ package org.eclipse.tractusx.bpdm.test.containers

import dasniko.testcontainers.keycloak.KeycloakContainer
import org.eclipse.tractusx.bpdm.test.config.SelfClientConfigProperties
import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.TENANT_BPNL
import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.keycloakContainer
import org.keycloak.representations.idm.ClientRepresentation
import org.keycloak.representations.idm.ProtocolMapperRepresentation
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
Expand Down Expand Up @@ -105,17 +107,42 @@ abstract class CreateNewSelfClientInitializer: SelfClientInitializer(){

val createdClientUuid = clients.findByClientId(clientId).first().id


val newProtocolMapper = ProtocolMapperRepresentation().apply {
name = "BPN"
protocol = "openid-connect"
protocolMapper = "oidc-usermodel-attribute-mapper"
config = mapOf(
"introspection.token.claim" to "true",
"userinfo.token.claim" to "true",
"user.attribute" to "bpn",
"id.token.claim" to "true",
"access.token.claim" to "true",
"claim.name" to "bpn",
"jsonType.label" to "String"
)
}
clients
.get(createdClientUuid)
.protocolMappers
.createMapper(newProtocolMapper)

val newServiceAccount = clients
.get(createdClientUuid)
.serviceAccountUser

newServiceAccount.attributes = mutableMapOf(Pair("bpn", listOf(TENANT_BPNL)))

realm.users()
.get(newServiceAccount.id)
.update(newServiceAccount)

realm.users()
.get(newServiceAccount.id)
.roles()
.clientLevel(roleManagementClient.toRepresentation().id)
.add(listOf(role))


super.initialize(applicationContext)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.test.system.utils
package org.eclipse.tractusx.bpdm.test.testdata.gate

import com.neovisionaries.i18n.CountryCode
import org.eclipse.tractusx.bpdm.common.dto.AddressType
Expand All @@ -30,15 +30,14 @@ import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequ
import org.eclipse.tractusx.bpdm.gate.api.model.response.AddressRepresentationInputDto
import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto
import org.eclipse.tractusx.bpdm.gate.api.model.response.SiteRepresentationInputDto
import org.eclipse.tractusx.bpdm.test.system.config.TestRunData
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset
import kotlin.random.Random

class GateInputFactory(
private val testMetadata: TestMetadata,
private val testRunData: TestRunData
private val testRunData: TestRunData?
) {
val genericFullValidWithSiteWithoutAnyBpn = createAllFieldsFilled("genericFullValidWithSiteWithoutAnyBpn")
{ it.withoutAnyBpn().withAddressType(null) }
Expand All @@ -48,7 +47,7 @@ class GateInputFactory(
}

fun createFullValid(seed: String, externalId: String = seed): BusinessPartnerInputRequest {
return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData.toExternalId(externalId))
return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData?.toExternalId(externalId) ?: externalId)
}

inner class SeededTestDataCreator(
Expand All @@ -62,7 +61,7 @@ class GateInputFactory(
fun createAllFieldsFilled(): BusinessPartnerInputRequest{

return BusinessPartnerInputRequest(
externalId = "${seed}_${testRunData.testTime}",
externalId = testRunData?.let { "${seed}_${testRunData.testTime}" } ?: seed ,
nameParts = listRange.map { "Name Part $seed $it" },
identifiers = emptyList(),
roles = BusinessPartnerRole.entries,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.test.system.config
package org.eclipse.tractusx.bpdm.test.testdata.gate

import java.time.Instant

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ import java.util.stream.Collectors
class CustomJwtAuthenticationConverter(private val resourceId: String, private val requiredBpn: String = "") : Converter<Jwt, AbstractAuthenticationToken> {
private val defaultGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
override fun convert(source: Jwt): AbstractAuthenticationToken {
val authorities: Collection<GrantedAuthority> =
defaultGrantedAuthoritiesConverter.convert(source)!!.plus(extractResourceRoles(source, resourceId, requiredBpn)).toSet()

val bpn = source.claims["bpn"] as String? ?: ""
val tokenAttributes = mutableMapOf<String, Any>("bpn" to bpn)

val authorities: Collection<GrantedAuthority> =
defaultGrantedAuthoritiesConverter.convert(source)!!
.plus(extractResourceRoles(source, resourceId, requiredBpn))
.let { if(bpn.isNotBlank()) it.plus(SimpleGrantedAuthority(bpn)) else it }
.toSet()

return JwtAuthenticationToken(source, authorities, tokenAttributes.toString())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ import org.springframework.web.bind.annotation.ResponseStatus


@ResponseStatus(HttpStatus.BAD_REQUEST)
class BpdmValidationErrorException(
open class BpdmValidationErrorException(
val validationErrors: List<ValidationError>
): RuntimeException(validationErrors.joinToString())
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.eclipse.tractusx.bpdm.gate.api.GateBusinessPartnerApi.Companion.BUSINESS_PARTNER_PATH
import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto
import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse
import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -55,7 +55,7 @@ interface GatePartnerUploadApi {
ApiResponse(responseCode = "400", description = "On malformed Business partner upload request",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = PartnerUploadErrorResponse::class)
schema = Schema(implementation = GateErrorResponse::class)
)]),
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*******************************************************************************
* 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
******************************************************************************/

package org.eclipse.tractusx.bpdm.gate.api

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import jakarta.validation.Valid
import org.eclipse.tractusx.bpdm.common.dto.PageDto
import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest
import org.eclipse.tractusx.bpdm.gate.api.GateRelationApi.Companion.RELATIONS_PATH
import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto
import org.eclipse.tractusx.bpdm.gate.api.model.RelationType
import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest
import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest
import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse
import org.springdoc.core.annotations.ParameterObject
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*
import java.time.Instant

@RequestMapping(RELATIONS_PATH, produces = [MediaType.APPLICATION_JSON_VALUE])
interface GateRelationApi {

companion object{
const val RELATIONS_PATH = "${ApiCommons.BASE_PATH}/input/business-partner-relations"
}

@Operation(
summary = "Find business partner input relations",
description = "Find paginated list of business partner relations from the input stage. " +
"There are various filter criteria available. " +
"All filters are 'AND' filters."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "A paginated list of business partner relations for the input stage")
]
)
@GetMapping
fun get(
@Schema(description = "Only show relations with the given external identifiers")
@RequestParam externalIds: List<String>? = null,
@Schema(description = "Only show relations of the given type")
@RequestParam relationType: RelationType? = null,
@Schema(description = "Only show relations which have the given business partners as sources")
@RequestParam businessPartnerSourceExternalIds: List<String>? = null,
@Schema(description = "Only show relations which have the given business partners as targets")
@RequestParam businessPartnerTargetExternalIds: List<String>? = null,
@Schema(description = "Only show relations which have been modified after the given time stamp")
@RequestParam updatedAtFrom: Instant? = null,
@ParameterObject @Valid paginationRequest: PaginationRequest = PaginationRequest()
): PageDto<RelationDto>

@Operation(
summary = "Create a new business partner input relation",
description = "Create a new relation between two business partner entries on the input stage. " +
"The external identifier is optional and a new one will be automatically created if not given. " +
"A given external identifier has to be unique."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "201", description = "The created business partner input relation"),
ApiResponse(responseCode = "400", description = "If the business partner could not be created based on wrong or insufficient data provided such as non-existent business partners or violated relation constraints. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)]),
ApiResponse(responseCode = "409", description = "If a relation with the given external identifier already exists",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)]),
]
)
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
fun post(
@RequestBody requestBody: RelationPostRequest
): RelationDto

@Operation(
summary = "Update a business partner input relation",
description = "Update an existing business partner relation on the input stage. " +
"By using a request parameter it is also possible to create a relation if the relation with the given external identifier does not exist. "
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "The given business partner relation has been updated."),
ApiResponse(responseCode = "400", description = "On wrong or insufficient user provided data like references to non-existent business partners or relations that violate the relation constraints. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)])
]
)
@PutMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
fun put(
@Schema(description = "If true a business partner relation will be created even if a relation could not be found under the given external identifier.")
@RequestParam createIfNotExist: Boolean = false,
@RequestBody requestBody: RelationPutRequest
): RelationDto

@Operation(
summary = "Delete an existing business partner relation",
description = "Delete a relation between two business partners on the input stage."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "The specified relation has been deleted."),
ApiResponse(responseCode = "400", description = "On specifying a relation that does not exist. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)])
]
)
@DeleteMapping
fun delete(
@Schema(description = "The external identifier of the business partner relation to delete")
@RequestParam externalId: String
)


}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@

package org.eclipse.tractusx.bpdm.gate.api.client

import org.eclipse.tractusx.bpdm.gate.api.StatsApi

interface GateClient {

val businessParters: BusinessPartnerApiClient
Expand All @@ -32,4 +30,6 @@ interface GateClient {
val stats: StatsApiClient

val partnerUpload: PartnerUploadApiClient

val relation: RelationApiClient
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class GateClientImpl(

override val partnerUpload by lazy { createClient<PartnerUploadApiClient>() }

override val relation: RelationApiClient by lazy { createClient<RelationApiClient>() }

private inline fun <reified T> createClient() =
httpServiceProxyFactory.createClient(T::class.java)
}
Loading
Loading