Skip to content
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
24 changes: 12 additions & 12 deletions .github/scripts/check_flyway_migration_order.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ MODIFIED_FILES=$(git diff --name-only --diff-filter=M "$BASE_REF" HEAD -- "$MIGR
BASE_MAX_FILE=""
BASE_MAX_KEY="0"
while IFS= read -r file; do
[ -z "$file" ] && continue
[[ -z "$file" ]] && continue
if [[ ! $(basename "$file") =~ $NAME_PATTERN ]]; then
continue
fi
Expand All @@ -69,35 +69,35 @@ while IFS= read -r file; do
fi
done <<< "$BASE_FILES"

if [ -n "$BASE_MAX_FILE" ]; then
if [[ -n "$BASE_MAX_FILE" ]]; then
printf -- "Highest migration on %s: %s (version %s)\n" "$BASE_REF" "$(basename "$BASE_MAX_FILE")" "$(version_label "$BASE_MAX_FILE")"
else
printf -- "No migration found on %s\n" "$BASE_REF"
fi

if [ -z "$ADDED_FILES" ] && [ -z "$DELETED_FILES" ] && [ -z "$MODIFIED_FILES" ]; then
if [[ -z "$ADDED_FILES" ]] && [[ -z "$DELETED_FILES" ]] && [[ -z "$MODIFIED_FILES" ]]; then
printf -- "No migration change detected in this pull request.\n"
: > "$REPORT_FILE"
exit 0
fi

# Rule 1: an already merged migration must never be modified or deleted
while IFS= read -r file; do
[ -z "$file" ] && continue
[[ -z "$file" ]] && continue
ERRORS+=("\`$file\` is modified. A migration already merged on \`$BASE_REF\` has been applied on running environments: its checksum is frozen. Create a new migration instead.")
EXIT_CODE=1
done <<< "$MODIFIED_FILES"

while IFS= read -r file; do
[ -z "$file" ] && continue
[[ -z "$file" ]] && continue
ERRORS+=("\`$file\` is deleted or renamed. A migration already merged on \`$BASE_REF\` must be kept as is. Create a new migration instead.")
EXIT_CODE=1
done <<< "$DELETED_FILES"

# Rule 2: added migrations must be well named and strictly above the highest merged version
SEEN_KEYS=()
while IFS= read -r file; do
[ -z "$file" ] && continue
[[ -z "$file" ]] && continue
name=$(basename "$file")

if [[ ! $name =~ $NAME_PATTERN ]]; then
Expand All @@ -109,14 +109,14 @@ while IFS= read -r file; do
key=$(version_key "$file")
label=$(version_label "$file")

if [ -n "$BASE_MAX_FILE" ] && awk -v a="$key" -v b="$BASE_MAX_KEY" 'BEGIN { exit !(a <= b) }'; then
if [[ -n "$BASE_MAX_FILE" ]] && awk -v a="$key" -v b="$BASE_MAX_KEY" 'BEGIN { exit !(a <= b) }'; then
ERRORS+=("\`$name\` (version \`$label\`) is lower than or equal to \`$(basename "$BASE_MAX_FILE")\` (version \`$(version_label "$BASE_MAX_FILE")\`) already merged on \`$BASE_REF\`. Renumber it above \`$(version_label "$BASE_MAX_FILE")\` so Flyway applies it on every environment.")
EXIT_CODE=1
continue
fi

for seen in ${SEEN_KEYS[@]+"${SEEN_KEYS[@]}"}; do
if [ "${seen%%:*}" = "$key" ]; then
if [[ "${seen%%:*}" = "$key" ]]; then
ERRORS+=("\`$name\` uses version \`$label\` already used by \`${seen#*:}\` in this pull request. Flyway versions must be unique.")
EXIT_CODE=1
fi
Expand All @@ -128,11 +128,11 @@ done <<< "$ADDED_FILES"

# Build the markdown report
{
if [ "$EXIT_CODE" -eq 0 ]; then
if [[ "$EXIT_CODE" -eq 0 ]]; then
printf -- "No Flyway migration ordering issue detected.\n\n"
printf -- "| Added migration | Version |\n|---|---|\n"
while IFS= read -r file; do
[ -z "$file" ] && continue
[[ -z "$file" ]] && continue
printf -- "| \`%s\` | \`%s\` |\n" "$(basename "$file")" "$(version_label "$file")"
done <<< "$ADDED_FILES"
else
Expand All @@ -141,7 +141,7 @@ done <<< "$ADDED_FILES"
printf -- "- %s\n" "$error"
done
printf -- "\nHighest migration currently on \`%s\`: " "$BASE_REF"
if [ -n "$BASE_MAX_FILE" ]; then
if [[ -n "$BASE_MAX_FILE" ]]; then
printf -- "\`%s\`\n" "$(basename "$BASE_MAX_FILE")"
else
printf -- "none\n"
Expand All @@ -151,7 +151,7 @@ done <<< "$ADDED_FILES"

cat "$REPORT_FILE"

if [ "$EXIT_CODE" -ne 0 ]; then
if [[ "$EXIT_CODE" -ne 0 ]]; then
for error in "${ERRORS[@]}"; do
printf -- "::error:: %s\n" "$(printf -- '%s' "$error" | tr -d '`')"
done
Expand Down
6 changes: 3 additions & 3 deletions .github/scripts/check_navigation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ echo "🔍 Checking for broken navigation links in ${SITE_DIR}..."

# Find files with 'none' placeholders in navigation
while IFS= read -r file; do
if [ -n "$file" ]; then
if [[ -n "$file" ]]; then
# Get line numbers where 'none' appears in md-ellipsis context
line_nums=$(grep -n "^\s*none\s*$" "$file" | cut -d: -f1 2>/dev/null || true)

Expand All @@ -39,7 +39,7 @@ while IFS= read -r file; do
fi
done < <(find "$SITE_DIR" -name "*.html" -type f)

if [ $EXIT_CODE -eq 0 ]; then
if [[ $EXIT_CODE -eq 0 ]]; then
echo "✅ No broken navigation links found"
else
echo ""
Expand All @@ -54,7 +54,7 @@ else
# Try to find it in the source directory
found_files=$(find "$SRC_DIR" -name "$filename" 2>/dev/null || true)

if [ -n "$found_files" ]; then
if [[ -n "$found_files" ]]; then
echo "$found_files" | while read -r found; do
echo " ❌ $found"
done
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/extract_specs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ PR_BRANCH=$(git rev-parse --abbrev-ref HEAD)
git fetch origin main
git checkout origin/main
# 1. Check if the static swagger file exists on the 'main' branch
if [ -f "$MAIN_SWAGGER" ]; then
if [[ -f "$MAIN_SWAGGER" ]]; then
# If it exists, copy it as the reference spec
cp "$MAIN_SWAGGER" "specs/static-swagger-main.yaml"
else
Expand All @@ -22,7 +22,7 @@ else
mvn clean package -B

JAR_PATH=$(find target -maxdepth 1 -name "*.jar" | head -n 1)
if [ -z "$JAR_PATH" ]; then
if [[ -z "$JAR_PATH" ]]; then
printf -- "::error:: No JAR found in target/ folder.\n"
exit 1
fi
Expand All @@ -39,7 +39,7 @@ else

printf -- "Waiting for API endpoint (%s) to be reachable...\n" "$API_URL"

while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do
while [[ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]]; do
# Use curl with --fail and --silent to check for a successful connection (HTTP 2xx response)
if curl --fail --silent -o /dev/null "$API_URL"; then
printf -- "API is up after %d seconds.\n" "$((ATTEMPT * SLEEP_TIME))"
Expand All @@ -50,7 +50,7 @@ else
ATTEMPT=$((ATTEMPT + 1))
done
# Check if the maximum number of attempts was reached (timeout)
if [ "$ATTEMPT" -eq "$MAX_ATTEMPTS" ]; then
if [[ "$ATTEMPT" -eq "$MAX_ATTEMPTS" ]]; then
printf -- "::error:: Application failed to start and expose Swagger endpoint after %d attempts.\n" "$MAX_ATTEMPTS"
printf -- "Swagger endpoint unreachable. Dumping logs:\n"
pgrep -a java || printf -- "No Java process found\n"
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/summarize_owasp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ SUMMARY_FILE="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
echo "### OWASP Dependency Check Summary" >> "$SUMMARY_FILE"

SARIF_REPORT=""
if [ -f "$PWD/target/security-reports/dependency-check-report.sarif" ]; then
if [[ -f "$PWD/target/security-reports/dependency-check-report.sarif" ]]; then
SARIF_REPORT="$PWD/target/security-reports/dependency-check-report.sarif"
elif [ -f "$PWD/target/dependency-check-report.sarif" ]; then
elif [[ -f "$PWD/target/dependency-check-report.sarif" ]]; then
SARIF_REPORT="$PWD/target/dependency-check-report.sarif"
fi

if [ -n "$SARIF_REPORT" ]; then
if [[ -n "$SARIF_REPORT" ]]; then
echo "| Vulnerability | CVSS | Package |" >> "$SUMMARY_FILE"
echo "|:--|:--:|:--|" >> "$SUMMARY_FILE"

if command -v jq >/dev/null 2>&1; then
VULN_COUNT=$(jq '.runs[].results | length' "$SARIF_REPORT" | awk '{s+=$1} END {print s}')
if [ "$VULN_COUNT" -gt 0 ]; then
if [[ "$VULN_COUNT" -gt 0 ]]; then
jq -r '.runs[].results[] | [.ruleId, (.properties.cvssScore // "N/A"), (.message.text | split("\n")[0])] | @tsv' "$SARIF_REPORT" |
while IFS=$'\t' read -r ruleId cvss message; do
printf "| %s | %s | %s |\n" "$ruleId" "$cvss" "$message" >> "$SUMMARY_FILE"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ buildNumber.properties

.env
application-secret.*

flyway-migration-order-report.md

# BlueJ files
*.ctxt
Expand Down
9 changes: 5 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ FROM eclipse-temurin:25-jre-alpine

ENV PORT=8080

RUN apk add --no-cache shadow && \
groupadd -r idp-core-group && \
useradd --no-log-init -r -g idp-core-group idp-core-user && \
apk del shadow
RUN addgroup -S idp-core-group && \
adduser -S -D -H -G idp-core-group idp-core-user

USER idp-core-user:idp-core-group

WORKDIR /opt
COPY ./target/idp-core*.jar /opt/app.jar

HEALTHCHECK --interval=30s --timeout=5s --start-period=25s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:$PORT/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "/opt/app.jar"]
2 changes: 2 additions & 0 deletions docs/src/concepts/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ Retrieve a paginated list of entities for a given template:
GET /api/v1/entities/{templateIdentifier}?page=0&size=20&sort=identifier,asc
```

`size` must be between 1 and the configured maximum (default 500, set with `MAX_PAGE_SIZE`), values outside this range return `400 Bad Request`.

### Get Entity by Identifier

Retrieve a specific entity using its template and entity identifiers:
Expand Down
6 changes: 3 additions & 3 deletions docs/src/concepts/entity-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ Request shape (`EntitySearchRequestDtoIn`):
- `filter` is a tree of group nodes and criterion nodes. A group node has `connector` (one of `AND`,`OR`) and a non-empty `criteria` array. A criterion node must include `field`, `operation` and `value`.
- Supported `operation` values: `EQ`, `NEQ`, `CONTAINS`, `NOT_CONTAINS`, `STARTS_WITH`, `ENDS_WITH`, `GT`, `GTE`, `LT`, `LTE`.
- Free-text `query` (optional) performs a case-insensitive contains search across identifier, name, template identifier and all property values.
- Pagination: `page` is zero-based, `size` defaults to 20. Maximum allowed `size` is 500.
- Pagination: `page` is zero-based, `size` defaults to 20. Maximum allowed `size` is 500 by default (configurable with `MAX_PAGE_SIZE`).

### What it does

Expand All @@ -202,7 +202,7 @@ curl -sS -X POST "http://localhost:8084/api/v1/entities/search" \

- `query` (optional): free-text string (max 255 chars) searched case-insensitively in `identifier`, `name`, `templateIdentifier`, and property values.
- `filter` (optional): nested `FilterNodeDtoIn` JSON structure. Use a `template` criterion to scope results to a specific template when needed.
- `page` / `size`: pagination (zero-based `page`). `size` defaults to 20 and the server enforces a maximum of 500.
- `page` / `size`: pagination (zero-based `page`). `size` defaults to 20 and the server enforces a maximum of 500 by default (configurable with `MAX_PAGE_SIZE`).
- `sort`: `field:asc|desc`. Allowed sort fields: `identifier`, `name`, `templateIdentifier`.

### Operator semantics
Expand All @@ -221,7 +221,7 @@ curl -sS -X POST "http://localhost:8084/api/v1/entities/search" \
- Maximum filter nesting depth: 5 levels. Requests exceeding this fail with `400 Bad Request` and a descriptive error message.
- Maximum total criterion count across the tree: 50. Requests exceeding this fail with `400 Bad Request`.
- Maximum `query` length: 255 characters.
- Maximum `size` (page size): 500.
- Maximum `size` (page size): 500 by default, configurable with `MAX_PAGE_SIZE`.

## Next Steps

Expand Down
11 changes: 11 additions & 0 deletions docs/src/features/entity-dynamic-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ if (.is_private == true) "PRIVATE_" + .name else .name
}
```

### JSLT Execution Limits

To protect the platform against denial-of-service, JSLT processing is bounded:

| Limit | Value | Exceeded behavior |
| ------------------ | ------------------------ | --------------------------------------- |
| Payload size | 1 MB (UTF-8 bytes) | Rejected before parsing (400) |
| JSON nesting depth | 64 levels | Rejected before the tree is built (400) |
| Expression length | 10,000 characters | Rejected at validation (422) |
| Evaluation timeout | 5 seconds per evaluation | Evaluation aborted (422) |

> [!TIP]
> Use the **dry-run endpoint** (POST `/api/v1/entity_dynamic_mappings/dry-run`) to test
> your JSLT expressions before deploying them to production. This helps catch syntax
Expand Down
4 changes: 3 additions & 1 deletion docs/src/features/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ again at runtime.
| `STATIC_TOKEN` | `header_name`, `secret_alias` | Compares a header value with a secret loaded from the environment |
| `BASIC_AUTH` | `username`, `secret_alias` | Compares the `Authorization: Basic ...` header with the configured username and secret |
| `JWT_BEARER` | `jwks_uri`, `client_id_field`, `client_id_values` | Validates the bearer token against a JWKS endpoint, then checks caller identity claim allow-list |
| `NONE` | none (`config` is optional) | Skips authentication |
| `NONE` | none (`config` is optional) | Skips authentication. Disabled unless `IDP_WEBHOOK_SECURITY_ALLOW_NONE=true` |

> [!IMPORTANT]
> Configuration keys accept `snake_case` and `camelCase` variants where supported. HMAC prefix uses the `prefix` key and has a default set to `sha256=`.
Expand Down Expand Up @@ -157,6 +157,8 @@ value in the connector configuration.

For `NONE`, you can omit `config`, set it to `null`, or pass `{}`. Any non-empty `config` is rejected.

`NONE` is disabled by default. Set the `IDP_WEBHOOK_SECURITY_ALLOW_NONE` environment variable to `true` to allow creating or updating connectors with `NONE`. When the flag is off, a request with `NONE` or with the `security` section omitted is rejected with `400 Bad Request`. Connectors that already use `NONE` continue to ingest events.

### JWT_BEARER Configuration Reference

- `jwks_uri` (required): literal HTTPS URL used to validate JWT signatures.
Expand Down
14 changes: 5 additions & 9 deletions docs/src/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,12 @@ Configure CORS if your frontend loads from a different origin or if no API gatew
spring:
web:
cors:
allowed-origins:
- http://localhost:3000
- https://your-frontend.com
allowed-methods:
- GET
- POST
- PUT
- DELETE
allowed-headers: "*"
allowed-origins: http://localhost:3000,https://your-frontend.com
allowed-origin-patterns: https://*.your-domain.com
```

Set exact origins with `spring.web.cors.allowed-origins` or the `SPRING_WEB_CORS_ALLOWED_ORIGINS` environment variable. Set wildcard origin patterns with `spring.web.cors.allowed-origin-patterns` or the `SPRING_WEB_CORS_ALLOWED_ORIGIN_PATTERNS` environment variable. Separate multiple values with commas.

---

## Security Configuration
Expand Down Expand Up @@ -153,6 +148,7 @@ All configuration can be overridden using environment variables:
| `SPRING_DATASOURCE_PASSWORD` | DB password | `secret` |
| `SERVER_PORT` | HTTP port | `8084` |
| `LOGGING_LEVEL_ROOT` | Log level | `INFO` |
| `MAX_PAGE_SIZE` | Maximum `size` accepted by search and listing endpoints (default `500`) | `1000` |

### Naming Convention

Expand Down
10 changes: 5 additions & 5 deletions docs/src/static/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ paths:
default: '0'
- name: size
in: query
description: Number of items per page. Defaults to 20.
description: Number of items per page. Defaults to 20. Must be between 1 and 500.
content:
"*/*":
schema:
Expand Down Expand Up @@ -614,7 +614,7 @@ paths:
default: '0'
- name: size
in: query
description: Number of items per page. Defaults to 20.
description: Number of items per page. Defaults to 20. Must be between 1 and 500.
content:
"*/*":
schema:
Expand Down Expand Up @@ -732,7 +732,7 @@ paths:
default: '0'
- name: size
in: query
description: Number of items per page. Defaults to 20.
description: Number of items per page. Defaults to 20. Must be between 1 and 500.
content:
"*/*":
schema:
Expand Down Expand Up @@ -804,7 +804,7 @@ paths:
default: '0'
- name: size
in: query
description: Number of items per page. Defaults to 20.
description: Number of items per page. Defaults to 20. Must be between 1 and 500.
required: false
content:
"*/*":
Expand Down Expand Up @@ -1853,7 +1853,7 @@ components:
type: integer
format: int32
default: 20
description: Number of items per page. Defaults to 20.
description: Number of items per page. Defaults to 20. Must be between 1 and 500.
example: 20
sort:
type: string
Expand Down
Empty file removed flyway-migration-order-report.md
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.decathlon.idp_core.domain.constant;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/// Domain constants for JSLT mapping safety limits (DoS prevention).
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class JsltConstraints {

/// Maximum size (in UTF-8 bytes) of a raw payload accepted for mapping.
public static final int MAX_PAYLOAD_BYTES = 1024 * 1024;

/// Maximum JSON nesting depth of a raw payload accepted for mapping.
public static final int MAX_JSON_DEPTH = 64;

/// Maximum length (in characters) of a JSLT expression.
public static final int MAX_EXPRESSION_LENGTH = 10_000;

/// Maximum wall-clock time (in milliseconds) allowed for one JSLT evaluation.
public static final long EVALUATION_TIMEOUT_MS = 5_000L;
}
Loading
Loading