feat(core)!: ingestion security processor - #123
Conversation
…object and array payloads Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: ferial OUKOUKAS <75682459+foukou19@users.noreply.github.com>
Code Coverage OverviewLanguages: Java Java / code-coverage/jacocoThe overall line coverage in commit 55ca6d9 in the Show a line coverage summary of the most impacted files.
Updated |
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
…error responses Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
3b6ec83 to
d276814
Compare
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
…error responses Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
Signed-off-by: ferial OUKOUKAS <75682459+foukou19@users.noreply.github.com>
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
59523a8 to
1e9f829
Compare
ef960c1 to
346711c
Compare
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
143b0a3 to
c06eb85
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 2 comments.
Suppressed comments (8)
src/main/java/com/decathlon/idp_core/domain/port/WebhookSecurityStrategy.java:41
- The Domain port now defines an HTTP-header contract and names Infrastructure exceptions/status semantics. This reverses the required dependency direction: the Domain layer must not know about HTTP or Infrastructure. Keep creation-time domain validation on a domain port, and move runtime request authentication to an Infrastructure-owned strategy contract with adapter exceptions mapped by the ingestion route.
/// Validates an incoming webhook request at runtime.
///
/// @param headers the inbound HTTP headers
/// @param rawPayload the exact inbound payload bytes (before decoding)
/// @param config the persisted security configuration
/// @throws
/// com.decathlon.idp_core.infrastructure.adapters.ingestion.exception.WebhookAuthUnauthorizedException
/// when authentication is missing or malformed (401)
/// @throws
/// com.decathlon.idp_core.infrastructure.adapters.ingestion.exception.WebhookAuthForbiddenException
/// when authentication is provided but rejected (403)
void validateRequest(Map<String, Object> headers, byte[] rawPayload, Map<String, String> config);
src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java:146
expectedAudienceis silently ignored, although the security configuration contract supports camelCase variants. A caller using that spelling gets no audience validation at all. Resolve both spellings as is already done for the other JWT keys.
private String resolveOptionalExpectedAudience(Map<String, String> config) {
return config.get(KEY_EXPECTED_AUDIENCE_SNAKE_CASE);
src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java:130
- Audience validation is skipped when
expected_audienceis absent, and the provider'sJwtValidators.createDefault()only performs default timestamp-style validation. This accepts a valid token issued for a different recipient whenever its identity claim is allow-listed, enabling cross-service token replay. Require/derive the webhook audience and always validateaud.
if (StringUtils.hasText(optionalExpectedAudience)) {
validateAudienceClaim(jwt, optionalExpectedAudience);
}
src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/BasicAuthSecurityValidator.java:54
- HTTP authentication scheme names are case-insensitive, but this rejects valid
basic/mixed-case schemes. Use a case-insensitive prefix comparison while retaining the current credential extraction.
if (!authorization.startsWith("Basic ")) {
src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java:109
- HTTP authentication scheme names are case-insensitive, but this rejects otherwise valid
bearer/mixed-case authorization headers. Compare the scheme case-insensitively.
if (!authorization.startsWith(BEARER_PREFIX)
|| authorization.substring(BEARER_PREFIX.length()).isBlank()) {
src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java:65
- These new mandatory JWT keys are absent from the public webhook documentation:
docs/src/concepts/webhooks.md:113-119still says JWT requires onlyjwks_uri, and its example at lines 165-174 now produces a 400 response. Update the documentation and example withclient_id_field,client_id_values, and the audience contract.
String clientIdValues = WebhookSecurityConfigurationUtils.required(config,
KEY_CLIENT_ID_VALUES_SNAKE_CASE, KEY_CLIENT_ID_VALUES_CAMEL_CASE);
src/main/java/com/decathlon/idp_core/infrastructure/adapters/ingestion/exception_handler/WebhookErrorCode.java:18
- This error is also used for unsupported encodings and decompression-size violations, so reporting every failure as “invalid or corrupted” is inaccurate and hides the actionable cause. Preserve a description covering invalid, unsupported, and oversized payloads (or safely expose each
WebhookDecodingExceptionmessage).
INVALID_COMPRESSED_PAYLOAD("invalid_compressed_payload", HttpStatus.BAD_REQUEST,
LoggingLevel.WARN, "Invalid or corrupted compressed payload"),
src/test/java/com/decathlon/idp_core/infrastructure/adapters/ingestion/InboundWebhookIngestionRouteTest.java:360
- This test can pass on an authentication failure: Camel's handled exception route clears
exchange.getException()and instead writes a 401 response. Moreover, the production provider constructs its own Nimbus decoder (WebhookJwtDecoderProvider.java:17-23), so the test's primaryJwtDecodermock is unused and this fake token causes a real request toauth.example.com. Mock the provider/decoder and assert that no authentication error response was produced.
Exchange exchange = invokeValidateSecurityRoute(connector,
Map.of("Authorization", "Bearer " + token));
assertNull(exchange.getException());
| @Override | ||
| public void validateConfiguration(Map<String, String> config) { | ||
| WebhookSecurityConfigurationUtils.required(config, "username"); | ||
| WebhookSecurityConfigurationUtils.required(config, USERNAME_KEY); |
| String jwksUriValue = WebhookSecurityConfigurationUtils.required(config, | ||
| KEY_JWKS_URI_SNAKE_CASE, KEY_JWKS_URI_CAMEL_CASE); | ||
| if (jwksUriValue.isBlank()) { | ||
| throw new WebhookSecurityConfigurationException("Invalid jwks_uri for JWT_BEARER security"); |
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
c06eb85 to
55ca6d9
Compare
|



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