feat(core): add endpoint for creating inbound webhook connector#58
feat(core): add endpoint for creating inbound webhook connector#58foukou19 wants to merge 2 commits into
Conversation
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
|
|
|
|
c4d264e to
cb49b68
Compare
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
cb49b68 to
6e81bd7
Compare
| * @param securityType the security type to check (e.g., "BASIC_AUTH", "HMAC_SHA256") | ||
| * @return true if this strategy handles this security type | ||
| */ | ||
| boolean supports(String securityType); |
There was a problem hiding this comment.
validate security type using enum
There was a problem hiding this comment.
Pull request overview
This PR introduces inbound webhook connectors in IDP-Core: a management API to CRUD connector configurations (identifier/title, security strategy, dynamic mappings) and a generic public webhook ingestion endpoint that authenticates requests based on the stored connector security settings.
Changes:
- Add webhook connector persistence (Flyway migrations, JPA entities/repositories, Postgres adapter) and domain models/services for CRUD + validation.
- Add runtime webhook ingestion endpoint (
POST /webhooks/{configurationId}) with strategy-based security validation (HMAC, static token, basic auth, JWT bearer, none). - Add JSLT-based mapping validation plus docs and test coverage for the new behavior.
Reviewed changes
Copilot reviewed 87 out of 91 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/resources/integration_test/json/webhook/v1/putWebhook_409_title_already_exists.json | Test payload for update conflict (title) |
| src/test/resources/integration_test/json/webhook/v1/putWebhook_200.json | Test payload for successful update |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_409_identifier_already_exists.json | Test payload for create conflict (identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_mappings_empty.json | Test payload for create validation (empty mappings) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_invalid_security_type.json | Test payload for create validation (invalid security type) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_invalid_jslt.json | Test payload for create validation (invalid JSLT) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_identifier_missing.json | Test payload for create validation (missing identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_identifier_blank.json | Test payload for create validation (blank identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_201.json | Test payload for successful create |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityValidatorDispatcherTest.java | Unit tests for runtime strategy dispatch |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/StaticTokenSecurityValidatorTest.java | Unit tests for static token runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidatorTest.java | Unit tests for JWT bearer runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSignatureValidatorTest.java | Unit tests for HMAC digest helper |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/StaticTokenWebhookSecurityCreationValidatorTest.java | Unit tests for static token config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/JwtBearerWebhookSecurityCreationValidatorTest.java | Unit tests for JWT bearer config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/HmacSha256WebhookSecurityCreationValidatorTest.java | Unit tests for HMAC config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/BasicAuthWebhookSecurityCreationValidatorTest.java | Unit tests for basic auth config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/BasicAuthSecurityValidatorTest.java | Unit tests for basic auth runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/webhook/InboundWebhookMapperTest.java | Unit tests for API ↔ domain mapping |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/handler/ApiExceptionHandlerTest.java | Adds coverage for new webhook/mapping exceptions |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/InboundWebhookManagementControllerTest.java | Integration tests for webhook connector management API |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorValidationServiceTest.java | Unit tests for connector validation logic |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorServiceTest.java | Unit tests for connector CRUD orchestration |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/security/WebhookSecurityValidationServiceTest.java | Unit tests for security config validation service |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/EntityDynamicMappingValidationServiceTest.java | Unit tests for mapping ↔ template validation |
| src/main/resources/db/migration/V4_2__create_webhook_template_mapping_table.sql | Flyway migration for connector↔template mapping table |
| src/main/resources/db/migration/V4_1__create_webhook_connector_table.sql | Flyway migration for webhook connector table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/service/InboundWebhookHandler.java | Runtime handler: resolve connector + validate security |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityValidatorDispatcher.java | Runtime strategy dispatcher for security validators |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityConfigurationUtils.java | Shared utilities for config lookup/secret resolution |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookJwtDecoderProvider.java | Cached JWT decoder provider keyed by JWKS URI |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/StaticTokenSecurityValidator.java | Static token security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java | JWT bearer security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSignatureValidator.java | HMAC digest helper used by HMAC strategy |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSha256SecurityValidator.java | HMAC SHA-256 security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/BasicAuthSecurityValidator.java | Basic auth security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/StaticTokenConfig.java | Polymorphic security config model (static token) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/SecurityConfig.java | Polymorphic security config interface |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/JwtBearerConfig.java | Polymorphic security config model (JWT bearer) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/HmacConfig.java | Polymorphic security config model (HMAC) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/BasicAuthConfig.java | Polymorphic security config model (basic auth) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/controller/InboundWebhookController.java | Public webhook ingestion endpoint |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaWebhookTemplateMappingRepository.java | JPA repository for mapping table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaWebhookConnectorRepository.java | JPA repository for connector table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/PostgresWebhookConnectorAdapter.java | Implements connector repository port + mapping persistence |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/webhook/WebhookTemplateMappingJpaEntity.java | JPA entity for mapping table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/webhook/WebhookConnectorJpaEntity.java | JPA entity for connector table (JSONB columns) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/mapper/WebhookConnectorPersistenceMapper.java | MapStruct mapping: domain ↔ JPA |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/mapper/common/WebhookConnectorJsonbHelper.java | JSONB serialization/deserialization helper |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/configuration/JpaAuditingConfiguration.java | Enables JPA auditing for created/updated timestamps |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/entity_mapping/jslt/JsltEntityMappingValidator.java | Infrastructure validator for JSLT expressions |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/webhook/InboundWebhookMapper.java | API DTO ↔ domain mapping for inbound webhooks |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/handler/ApiExceptionHandler.java | Adds exception→HTTP mappings for webhook features |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookSecurityDtoOut.java | Outbound DTO for security type |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookMappingDtoOut.java | Outbound DTO for mapping rule |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookEntityMappingDtoOut.java | Outbound DTO for entity mapping |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookDtoOut.java | Outbound DTO for connector |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookSecurityContractDtoIn.java | Inbound DTO for {type, config} security contract |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookMappingDtoIn.java | Inbound DTO for mapping rule |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookEntityMappingDtoIn.java | Inbound DTO for entity mapping |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookCreateDtoIn.java | Inbound DTO for connector create/update |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/InboundWebhookManagementController.java | CRUD + listing API for connectors |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerDescription.java | Adds OpenAPI description constants |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerConfiguration.java | Adds Page schema for connector listing |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SecurityConfiguration.java | Permits /webhooks/** and disables CSRF there |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorValidationService.java | Domain validation: uniqueness + mapping + security |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorService.java | Domain service: connector CRUD orchestration |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/security/WebhookSecurityValidationService.java | Domain service: validate security configs |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/EntityDynamicMappingValidationService.java | Domain validation: mapping keys vs template + required fields |
| src/main/java/com/decathlon/idp_core/domain/service/entity_template/EntityTemplateValidationService.java | Adds template property/relation lookup validation helpers |
| src/main/java/com/decathlon/idp_core/domain/port/WebhookSecurityStrategy.java | Port contract for security strategies |
| src/main/java/com/decathlon/idp_core/domain/port/WebhookConnectorRepositoryPort.java | Port contract for connector persistence |
| src/main/java/com/decathlon/idp_core/domain/port/EntityDynamicMapperValidator.java | Port contract for mapping DSL validation |
| src/main/java/com/decathlon/idp_core/domain/model/webhook/WebhookSecurity.java | Domain model for security type + config |
| src/main/java/com/decathlon/idp_core/domain/model/webhook/WebhookConnector.java | Domain aggregate for connector configuration |
| src/main/java/com/decathlon/idp_core/domain/model/enums/WebhookSecurityType.java | Enum of supported security strategies |
| src/main/java/com/decathlon/idp_core/domain/model/entity_mapping/EntityDynamicMapping.java | Domain model for mapping rule |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookTemplateHasNoPropertiesException.java | Domain exception for invalid mapping vs template |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookSecurityConfigurationException.java | Domain exception for invalid security configuration |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorTitleAlreadyExistsException.java | Domain exception for title conflict |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorNotFoundException.java | Domain exception for missing connector |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorAlreadyExistException.java | Domain exception for identifier conflict |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookAuthenticationException.java | Domain exception for runtime auth failures |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/RelationNameNotFoundEntityTemplateRelationsException.java | Domain exception for invalid relation name |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/PropertyNameNotFoundEntityTemplatePropertiesException.java | Domain exception for invalid property name |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_mapping/EntityDynamicMappingConfigurationException.java | Domain exception for invalid mapping DSL |
| src/main/java/com/decathlon/idp_core/domain/constant/ValidationMessages.java | Adds webhook validation message constants |
| pom.xml | Adds JSLT dependency |
| docs/zensical.toml | Adds webhooks page to docs navigation |
| docs/src/concepts/webhooks.md | New docs page describing connectors, mappings, security |
| docs/src/concepts/index.md | Links webhooks concept from the concepts index |
| checkExpression(errors, "entityTitle", mapping.entityTitle()); | ||
|
|
||
| mapping.properties().forEach((key, expr) -> checkExpression(errors, "properties." + key, expr)); | ||
| mapping.relations().forEach((key, expr) -> checkExpression(errors, "relations." + key, expr)); | ||
|
|
| import com.decathlon.idp_core.domain.model.enums.WebhookSecurityType; | ||
| import com.decathlon.idp_core.domain.model.webhook.WebhookConnector; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookCreateDtoIn;import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookEntityMappingDtoIn; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookMappingDtoIn; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookSecurityContractDtoIn; |
|
|
||
| public void validateTitleUniqueness(String webhookTitle) { | ||
| if (webhookConnectorRepositoryPort.existsByTitle(webhookTitle)) { | ||
| throw new WebhookConnectorTitleAlreadyExistsException("A WebhookConnector with title " + webhookTitle + " already exists"); |
|
|
||
| public void validateIdentifierExists(String webhookConnectorIdentifier) { | ||
| if (!webhookConnectorRepositoryPort.existsByIdentifier(webhookConnectorIdentifier)) { | ||
| throw new WebhookConnectorNotFoundException("WebhookConnector with identifier " + webhookConnectorIdentifier + " not found"); |
| private final WebhookConnectorValidationService webhookConnectorValidationService; | ||
|
|
||
| public WebhookConnector getWebhookConnector(String identifier) { | ||
| return webhookConnectorRepositoryPort.findByIdentifier(identifier).orElseThrow(() -> new WebhookConnectorNotFoundException("WebhookConnector with identifier " + identifier + " not found")); |
| try { | ||
| Mac mac = Mac.getInstance("HmacSHA256"); | ||
| mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); | ||
| byte[] digest = mac.doFinal(payload); | ||
| return toHex(digest); | ||
| } catch (Exception exception) { | ||
| throw new WebhookAuthenticationException("Unable to compute HMAC signature"); | ||
| } |
| String secret = WebhookSecurityConfigurationUtils.getSecretFromEnvironment(alias); | ||
|
|
||
| String expected = prefix + signatureValidator.computeHexSha256(rawBody, secret); | ||
| if (!expected.equals(provided)) { | ||
| throw new WebhookAuthenticationException("Invalid HMAC signature"); | ||
| } |
| String expected = WebhookSecurityConfigurationUtils.getSecretFromEnvironment(alias); | ||
|
|
||
| if (!expected.equals(provided)) { | ||
| throw new WebhookAuthenticationException("Invalid static token"); | ||
| } |
| String expectedRaw = username + ":" + password; | ||
| String expected = "Basic " + Base64.getEncoder().encodeToString(expectedRaw.getBytes(StandardCharsets.UTF_8)); | ||
| if (!expected.equals(authorization)) { | ||
| throw new WebhookAuthenticationException("Invalid basic authentication credentials"); | ||
| } |
| private InboundWebhookMappingDtoOut fromEntityMappingToDto(EntityDynamicMapping mapping) { | ||
| return new InboundWebhookMappingDtoOut( | ||
| mapping.templateIdentifier(), | ||
| mapping.filter(), | ||
| new InboundWebhookEntityMappingDtoOut( | ||
| mapping.entityIdentifier(), | ||
| mapping.entityTitle(), | ||
| Map.copyOf(mapping.properties()), | ||
| Map.copyOf(mapping.relations()) | ||
| ) | ||
| ); |
|
|
||
| import java.util.Map; | ||
|
|
||
| import com.decathlon.idp_core.domain.model.webhook.WebhookConnector; |
There was a problem hiding this comment.
Delete unused import
|
|
||
| import java.util.Map; | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) |
There was a problem hiding this comment.
Avoid JSON annotation in domain. This is an insfrastructure concern.
| } | ||
|
|
||
| @Override | ||
| public void validateRequest(WebhookSecurity security, Map<String, String> headers, byte[] rawBody) { |
There was a problem hiding this comment.
I propose to have this in another validation service or as a security strategy to be used in the event treatment process. So this will stay as a configuration validation and not a webhook event security validation. So to implement in another iteration.
| if (webhookMappingRelations == null || webhookMappingRelations.isEmpty()) { | ||
| return; | ||
| } | ||
| webhookMappingRelations.keySet().forEach(relationName -> entityTemplateValidationService.validateRelationNameAlreadyExistInTemplate(entityTemplate.relationsDefinitions(), relationName)); |
There was a problem hiding this comment.
I would suggets to have this method in this class intead of creating a method in the entityTemplateValidation. We already have the necessary information for validating here.
| ); | ||
| } | ||
|
|
||
| private void validateRelationNameAlreadyExistInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { |
There was a problem hiding this comment.
| private void validateRelationNameAlreadyExistInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { | |
| private void validateRelationsExistsInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { | |
| if (webhookMappingRelations == null || webhookMappingRelations.isEmpty()) { | |
| return; | |
| } | |
| List<String> unknownRelations = webhookMappingRelations.keySet().stream() | |
| .filter(relationName -> entityTemplate.relationsDefinitions().stream() | |
| .noneMatch(rd -> rd.name().equals(relationName))) | |
| .toList(); | |
| if (!unknownRelations.isEmpty()) { | |
| throw new WebhookTemplateHasNoPropertiesException( | |
| String.format("The mapping references unknown relations: %s", String.join(", ", unknownRelations)) | |
| ); | |
| } | |
| } |
|
|
||
| } | ||
|
|
||
| private void validatePropertiesExistInTemplate(Map<String, String> mappingProperties, List<PropertyDefinition> templateProperties) { |
There was a problem hiding this comment.
I would suggets to have this method in this class intead of creating a method in the entityTemplateValidation. We already have the necessary information for validating here.
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class InboundWebhookHandler { |
There was a problem hiding this comment.
This componet will be implemented in the Generic Camel Route. Integration to discuss.
| @@ -0,0 +1,33 @@ | |||
| package com.decathlon.idp_core.domain.model.webhook; | |||
There was a problem hiding this comment.
| package com.decathlon.idp_core.domain.model.webhook; | |
| package com.decathlon.idp_core.domain.model.inbound_connectors.webhook; |
| } | ||
|
|
||
| @Named("mappingsToJson") | ||
| public String toJson(List<EntityDynamicMapping> mappings) { |
There was a problem hiding this comment.
I would suggest to have EntityDynamicMapping in a relation manner and not JsonB. It better for FK integrity, queryable, stable schema. It benefits integrity, performance, maintainability. At one moment we will maybe want to reuse mappings and having tehm in relational way would be a better fit.
| private static final Pattern TOKEN_PATTERN = Pattern.compile("Encountered\\s+\"([^\"]+)\""); | ||
|
|
||
| @Override | ||
| public void validate(EntityDynamicMapping mapping) { |
There was a problem hiding this comment.
I would suggest to perform the validatio after genretaing the whole JSLT script using Parser.compileString(fullScript);
af478c6 to
6e81bd7
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: ferial OUKOUKAS <75682459+foukou19@users.noreply.github.com>
|



PR Description
What this PR Provides
Fixes
Review
The reviewer must double-check these points:
!after the type/scope to identify the breakingchange in the release note and ensure we will release a major version.
How to test
Please refer (copy/paste) the test section from the User Story. This should include
(for example, ensure the data xxx exists in idp-back to be able to test the feature)
(for example, go to page xxx, fill the xxx field and click the 'send' button)
(for example, there is a link in the database between component X and component Y.
You can retrieve the information with a
GETrequest to the API)Breaking changes (if any)
Context of the Breaking Change
For example: we redefined the component types list in the DPAC referential
Result of the Breaking Change
For example: your component of type xxx will migrate to the type yyy