This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/27716-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 516bd9dfd02fbca4df593185ec21ce4c49cd6467 Author: Guillaume Nodet <[email protected]> AuthorDate: Tue Oct 13 11:41:58 2026 +0000 [backport camel-4.18.x] CAMEL-25564: camel-telegram - verify the Bot API secret token on webhook updates --- .../apache/camel/catalog/components/telegram.json | 4 +- .../telegram/TelegramEndpointConfigurer.java | 12 ++ .../telegram/TelegramEndpointUriFactory.java | 7 +- .../apache/camel/component/telegram/telegram.json | 4 +- .../src/main/docs/telegram-component.adoc | 12 ++ .../component/telegram/TelegramConfiguration.java | 30 +++++ .../camel/component/telegram/TelegramEndpoint.java | 52 +++++++- .../camel/component/telegram/TelegramService.java | 10 ++ .../telegram/TelegramWebhookProcessor.java | 34 ++++- .../telegram/service/TelegramAsyncHandler.java | 12 +- .../service/TelegramServiceRestBotAPIAdapter.java | 32 +++-- .../telegram/TelegramWebhookCallTest.java | 82 ++++++++++-- .../telegram/TelegramWebhookSecretTokenTest.java | 132 ++++++++++++++++++++ .../TelegramServiceRestBotAPIAdapterErrorTest.java | 138 +++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 27 ++++ .../dsl/TelegramEndpointBuilderFactory.java | 59 +++++++++ 16 files changed, 620 insertions(+), 27 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json index eab0e6c682ef..1628dabc0292 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json @@ -72,6 +72,8 @@ "startScheduler": { "index": 27, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, - "authorizationToken": { "index": 30, "kind": "parameter", "displayName": "Authorization Token", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The authorization token for using the bot (ask the BotFather)" } + "authorizationToken": { "index": 30, "kind": "parameter", "displayName": "Authorization Token", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The authorization token for using the bot [...] + "webhookSecretToken": { "index": 31, "kind": "parameter", "displayName": "Webhook Secret Token", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The secret token that Telegram sends in the X-Telegram [...] + "webhookSecretTokenEnabled": { "index": 32, "kind": "parameter", "displayName": "Webhook Secret Token Enabled", "group": "security", "label": "consumer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "false", "defaultValue": true, "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "descrip [...] } } diff --git a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java index adb41c6ee40e..3a83db6e02f8 100644 --- a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java +++ b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java @@ -77,6 +77,10 @@ public class TelegramEndpointConfigurer extends PropertyConfigurerSupport implem case "timeout": target.getConfiguration().setTimeout(property(camelContext, java.lang.Integer.class, value)); return true; case "usefixeddelay": case "useFixedDelay": target.setUseFixedDelay(property(camelContext, boolean.class, value)); return true; + case "webhooksecrettoken": + case "webhookSecretToken": target.getConfiguration().setWebhookSecretToken(property(camelContext, java.lang.String.class, value)); return true; + case "webhooksecrettokenenabled": + case "webhookSecretTokenEnabled": target.getConfiguration().setWebhookSecretTokenEnabled(property(camelContext, boolean.class, value)); return true; default: return false; } } @@ -138,6 +142,10 @@ public class TelegramEndpointConfigurer extends PropertyConfigurerSupport implem case "timeout": return java.lang.Integer.class; case "usefixeddelay": case "useFixedDelay": return boolean.class; + case "webhooksecrettoken": + case "webhookSecretToken": return java.lang.String.class; + case "webhooksecrettokenenabled": + case "webhookSecretTokenEnabled": return boolean.class; default: return null; } } @@ -200,6 +208,10 @@ public class TelegramEndpointConfigurer extends PropertyConfigurerSupport implem case "timeout": return target.getConfiguration().getTimeout(); case "usefixeddelay": case "useFixedDelay": return target.isUseFixedDelay(); + case "webhooksecrettoken": + case "webhookSecretToken": return target.getConfiguration().getWebhookSecretToken(); + case "webhooksecrettokenenabled": + case "webhookSecretTokenEnabled": return target.getConfiguration().isWebhookSecretTokenEnabled(); default: return null; } } diff --git a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java index 52616c4907c8..110d6dad9a33 100644 --- a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java +++ b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java @@ -23,7 +23,7 @@ public class TelegramEndpointUriFactory extends org.apache.camel.support.compone private static final Set<String> SECRET_PROPERTY_NAMES; private static final Map<String, String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(31); + Set<String> props = new HashSet<>(33); props.add("authorizationToken"); props.add("backoffErrorThreshold"); props.add("backoffIdleThreshold"); @@ -55,9 +55,12 @@ public class TelegramEndpointUriFactory extends org.apache.camel.support.compone props.add("timeout"); props.add("type"); props.add("useFixedDelay"); + props.add("webhookSecretToken"); + props.add("webhookSecretTokenEnabled"); PROPERTY_NAMES = Collections.unmodifiableSet(props); - Set<String> secretProps = new HashSet<>(1); + Set<String> secretProps = new HashSet<>(2); secretProps.add("authorizationToken"); + secretProps.add("webhookSecretToken"); SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); Map<String, String> prefixes = new HashMap<>(1); prefixes.put("schedulerProperties", "scheduler."); diff --git a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json index eab0e6c682ef..1628dabc0292 100644 --- a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json +++ b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json @@ -72,6 +72,8 @@ "startScheduler": { "index": 27, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, - "authorizationToken": { "index": 30, "kind": "parameter", "displayName": "Authorization Token", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The authorization token for using the bot (ask the BotFather)" } + "authorizationToken": { "index": 30, "kind": "parameter", "displayName": "Authorization Token", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The authorization token for using the bot [...] + "webhookSecretToken": { "index": 31, "kind": "parameter", "displayName": "Webhook Secret Token", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "description": "The secret token that Telegram sends in the X-Telegram [...] + "webhookSecretTokenEnabled": { "index": 32, "kind": "parameter", "displayName": "Webhook Secret Token Enabled", "group": "security", "label": "consumer,security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "security": "insecure:dev", "insecureValue": "false", "defaultValue": true, "configurationClass": "org.apache.camel.component.telegram.TelegramConfiguration", "configurationField": "configuration", "descrip [...] } } diff --git a/components/camel-telegram/src/main/docs/telegram-component.adoc b/components/camel-telegram/src/main/docs/telegram-component.adoc index 429c296e1ef7..911ab2ff903c 100644 --- a/components/camel-telegram/src/main/docs/telegram-component.adoc +++ b/components/camel-telegram/src/main/docs/telegram-component.adoc @@ -346,5 +346,17 @@ You need to ensure that your server is exposed to the internet and to pass the r Refer to the *camel-webhook* component documentation for instructions on how to set it. +Camel registers the webhook with a secret token, which Telegram sends back in the `X-Telegram-Bot-Api-Secret-Token` +header of every webhook request. A webhook request that does not carry the token is rejected with HTTP 403, and does +not reach the route. By default, the token is derived from the authorization token, so it stays the same across +restarts and replicas. If the webhook is registered outside Camel, set the `webhookSecretToken` option, and pass the +same value as the `secret_token` parameter of the `setWebhook` method of the Telegram Bot API. + +The default webhook path is a hash of the endpoint URI, so adding an option to the URI, such as `webhookSecretToken`, +changes the path. When the webhook is registered outside Camel, set the path with the `webhookPath` option of the +*camel-webhook* component. + +Setting `webhookSecretTokenEnabled=false` registers the webhook without a secret token, and accepts webhook requests +without verifying them, as earlier versions did. include::spring-boot:partial$starter.adoc[] diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java index 6054e657f123..0e88199bc750 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java @@ -61,6 +61,20 @@ public class TelegramConfiguration { optionalPrefix = "consumer.", defaultValue = "100", label = "consumer") private Integer limit = 100; + @UriParam(description = "The secret token that Telegram sends in the X-Telegram-Bot-Api-Secret-Token header of every webhook request." + + " In webhook mode, Camel registers the webhook with this token, and rejects a webhook request that does not" + + " carry it with HTTP 403. Only the characters A-Z, a-z, 0-9, _ and - are allowed, up to 256 characters." + + " When not set, a token derived from the authorization token is used. Set it when the webhook is" + + " registered outside Camel, and register the webhook with the same secret_token.", + label = "consumer,security", security = "secret") + private String webhookSecretToken; + + @UriParam(description = "Whether, in webhook mode, Camel registers the webhook with a secret token and rejects a webhook request" + + " that does not carry it. Set it to false to restore the behaviour of earlier versions, where the webhook" + + " was registered without a secret token and webhook requests were not verified.", + label = "consumer,security", defaultValue = "true", security = "insecure:dev", insecureValue = "false") + private boolean webhookSecretTokenEnabled = true; + @UriParam(label = "advanced", description = "Can be used to set an alternative base URI, e.g. when you want to test the component against a mock Telegram API") private String baseUri; @@ -132,6 +146,22 @@ public class TelegramConfiguration { this.limit = limit; } + public String getWebhookSecretToken() { + return webhookSecretToken; + } + + public void setWebhookSecretToken(String webhookSecretToken) { + this.webhookSecretToken = webhookSecretToken; + } + + public boolean isWebhookSecretTokenEnabled() { + return webhookSecretTokenEnabled; + } + + public void setWebhookSecretTokenEnabled(boolean webhookSecretTokenEnabled) { + this.webhookSecretTokenEnabled = webhookSecretTokenEnabled; + } + public String getBaseUri() { return baseUri; } diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java index b3530de0724b..f1f248bb383e 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java @@ -19,8 +19,15 @@ package org.apache.camel.component.telegram; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.http.HttpClient; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import org.apache.camel.Category; import org.apache.camel.Component; @@ -46,6 +53,7 @@ import org.slf4j.LoggerFactory; category = { Category.CLOUD, Category.API, Category.CHAT }, headersClass = TelegramConstants.class) public class TelegramEndpoint extends ScheduledPollEndpoint implements WebhookCapableEndpoint, EndpointServiceLocation { private static final Logger LOG = LoggerFactory.getLogger(TelegramEndpoint.class); + private static final Pattern WEBHOOK_SECRET_TOKEN_PATTERN = Pattern.compile("[A-Za-z0-9_-]{1,256}"); @UriParam private TelegramConfiguration configuration; @@ -122,16 +130,56 @@ public class TelegramEndpoint extends ScheduledPollEndpoint implements WebhookCa @Override public Processor createWebhookHandler(Processor next) { - return new TelegramWebhookProcessor(next); + if (!configuration.isWebhookSecretTokenEnabled()) { + LOG.warn("The webhook secret token is disabled (webhookSecretTokenEnabled=false): webhook requests to {} are not" + + " verified", + this); + return new TelegramWebhookProcessor(next, null); + } + return new TelegramWebhookProcessor(next, resolveWebhookSecretToken()); } @Override public void registerWebhook() throws Exception { - if (!telegramService.setWebhook(webhookConfiguration.computeFullExternalUrl())) { + String url = webhookConfiguration.computeFullExternalUrl(); + boolean registered = configuration.isWebhookSecretTokenEnabled() + ? telegramService.setWebhook(url, resolveWebhookSecretToken()) + : telegramService.setWebhook(url); + if (!registered) { throw new RuntimeCamelException("The Telegram API refused to register a webhook"); } } + /** + * Returns the secret token of the webhook: the configured one, or else one derived from the authorization token, + * which stays the same across restarts and replicas. + */ + String resolveWebhookSecretToken() { + String token = configuration.getWebhookSecretToken(); + if (token == null) { + return deriveWebhookSecretToken(configuration.getAuthorizationToken()); + } + if (!WEBHOOK_SECRET_TOKEN_PATTERN.matcher(token).matches()) { + throw new IllegalArgumentException( + "The webhookSecretToken must be 1 to 256 characters long, using only A-Z, a-z, 0-9, _ and -"); + } + return token; + } + + static String deriveWebhookSecretToken(String authorizationToken) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + // the authorization token (the secret) is the key, and a fixed label is the message: the derived token + // cannot be computed without the authorization token, and changes only when the authorization token does + mac.init(new SecretKeySpec(authorizationToken.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + byte[] digest = mac.doFinal("camel-telegram-webhook-secret-token".getBytes(StandardCharsets.UTF_8)); + // base64url without padding only uses the characters that Telegram allows in a secret token + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } catch (GeneralSecurityException e) { + throw new RuntimeCamelException("Cannot derive the webhook secret token", e); + } + } + @Override public void unregisterWebhook() throws Exception { if (!telegramService.removeWebhook()) { diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java index 387c4c773156..3610ac29e03e 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java @@ -38,6 +38,16 @@ public interface TelegramService { boolean setWebhook(String url); + /** + * Registers the webhook. Telegram sends the given secret token in the {@code X-Telegram-Bot-Api-Secret-Token} + * header of every webhook request. + * + * @param url the URL of the webhook + * @param secretToken the secret token + * @return {@code true} if Telegram registered the webhook + */ + boolean setWebhook(String url, String secretToken); + boolean removeWebhook(); } diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java index 3479555b58b2..2876a708a720 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java @@ -17,6 +17,8 @@ package org.apache.camel.component.telegram; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.AsyncCallback; @@ -26,21 +28,51 @@ import org.apache.camel.Processor; import org.apache.camel.component.telegram.model.Update; import org.apache.camel.support.AsyncProcessorConverterHelper; import org.apache.camel.support.AsyncProcessorSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.apache.camel.component.telegram.util.TelegramMessageHelper.populateExchange; public class TelegramWebhookProcessor extends AsyncProcessorSupport { + /** + * The header in which Telegram sends the secret token the webhook was registered with. + */ + public static final String SECRET_TOKEN_HEADER = "X-Telegram-Bot-Api-Secret-Token"; + + private static final Logger LOG = LoggerFactory.getLogger(TelegramWebhookProcessor.class); private static final ObjectMapper MAPPER = new ObjectMapper(); private AsyncProcessor next; + private final byte[] secretToken; - public TelegramWebhookProcessor(Processor next) { + /** + * @param next the processor that receives the updates + * @param secretToken the secret token that a webhook request must carry, or {@code null} to accept every webhook + * request without verifying it + */ + public TelegramWebhookProcessor(Processor next, String secretToken) { this.next = AsyncProcessorConverterHelper.convert(next); + this.secretToken = secretToken != null ? secretToken.getBytes(StandardCharsets.UTF_8) : null; } @Override public boolean process(Exchange exchange, AsyncCallback callback) { + if (secretToken != null) { + String token = exchange.getIn().getHeader(SECRET_TOKEN_HEADER, String.class); + if (token == null || !MessageDigest.isEqual(secretToken, token.getBytes(StandardCharsets.UTF_8))) { + LOG.warn("Rejecting a Telegram webhook request with a missing or invalid {} header." + + " A webhook registered outside Camel must use the webhookSecretToken as its secret_token.", + SECRET_TOKEN_HEADER); + exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 403); + exchange.getMessage().setBody(""); + callback.done(true); + return true; + } + } + // the token is not passed on to the route + exchange.getIn().removeHeader(SECRET_TOKEN_HEADER); + // Let's process the body and transform it into a Telegram incoming message Update update; try (InputStream source = exchange.getIn().getBody(InputStream.class)) { diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java index 3b180356d1a8..c6b9ad6b4068 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; @@ -38,7 +39,8 @@ import org.slf4j.LoggerFactory; public class TelegramAsyncHandler { private static final Logger LOG = LoggerFactory.getLogger(TelegramAsyncHandler.class); - private final String uri; + // describes the request without its URI, which contains the authorization token + private final String request; private final Class<? extends MessageResult> resultClass; private final ObjectMapper mapper; private final Exchange exchange; @@ -46,7 +48,7 @@ public class TelegramAsyncHandler { TelegramAsyncHandler(String uri, Class<? extends MessageResult> resultClass, ObjectMapper mapper, Exchange exchange, AsyncCallback callback) { - this.uri = uri; + this.request = TelegramServiceRestBotAPIAdapter.describe("POST", URI.create(uri)); this.resultClass = resultClass; this.mapper = mapper; this.exchange = exchange; @@ -97,7 +99,7 @@ public class TelegramAsyncHandler { if (success) { if (LOG.isTraceEnabled()) { final String body = IOHelper.toString(r); - LOG.trace("Received body for {}: {}", uri, body); + LOG.trace("Received body for {}: {}", request, body); result = mapper.readValue(body, resultClass); } else { result = mapper.readValue(r, resultClass); @@ -106,12 +108,12 @@ public class TelegramAsyncHandler { exchange.getMessage().setBody(result); } else { throw new RuntimeCamelException( - uri + " responded: " + response.statusCode() + IOHelper.toString(r)); + request + " responded: " + response.statusCode() + IOHelper.toString(r)); } } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } catch (IOException ex) { - throw new RuntimeCamelException("Could not parse the response from " + uri, ex); + throw new RuntimeCamelException("Could not parse the response from " + request, ex); } finally { callback.done(false); } diff --git a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java index e96a3bb184f5..a509dfbb7eb1 100644 --- a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java +++ b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java @@ -19,9 +19,11 @@ package org.apache.camel.component.telegram.service; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -152,6 +154,7 @@ public class TelegramServiceRestBotAPIAdapter implements TelegramService { } <T> T sendSyncRequest(final HttpRequest request, Class<T> resultType) { + final String description = describe(request.method(), request.uri()); try { final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); @@ -159,29 +162,44 @@ public class TelegramServiceRestBotAPIAdapter implements TelegramService { if (code >= 200 && code < 300) { final String responseBody = response.body(); if (LOG.isTraceEnabled()) { - LOG.trace("Received body for {} {}: {}", request.method(), request.uri(), responseBody); + LOG.trace("Received body for {}: {}", description, responseBody); } return mapper.readValue(responseBody, resultType); } else { throw new TelegramException( - "Could not " + request.method() + " " + request.uri() + ": " + response.statusCode() + " " - + response.body(), + "Could not " + description + ": " + response.statusCode() + " " + response.body(), response.statusCode(), response.body()); } } catch (JsonProcessingException e) { - throw new RuntimeCamelException( - "Could not parse the response from " + request.method() + " " + request.uri(), e); + throw new RuntimeCamelException("Could not parse the response from " + description, e); } catch (IOException e) { - throw new RuntimeCamelException("Could not request " + request.method() + " " + request.uri(), e); + throw new RuntimeCamelException("Could not request " + description, e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeCamelException(e); } } + /** + * Describes a request to the Bot API in logs and exception messages by its HTTP method and the method of the Bot + * API, such as {@code GET getUpdates}. The request URI is not used, as it contains the authorization token. + */ + static String describe(String httpMethod, URI uri) { + String path = uri.getPath(); + return httpMethod + " " + path.substring(path.lastIndexOf('/') + 1); + } + @Override public boolean setWebhook(String url) { - final String uri = baseUri + "/setWebhook?url=" + url; + return setWebhook(url, null); + } + + @Override + public boolean setWebhook(String url, String secretToken) { + String uri = baseUri + "/setWebhook?url=" + url; + if (secretToken != null) { + uri += "&secret_token=" + URLEncoder.encode(secretToken, StandardCharsets.UTF_8); + } final HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(uri)).GET(); diff --git a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java index da43e2747376..33418af7ae70 100644 --- a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java +++ b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java @@ -28,26 +28,80 @@ import org.apache.camel.component.webhook.WebhookEndpoint; import org.apache.camel.test.AvailablePortFinder; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** - * Tests a producer that sends media information. + * Tests the webhook endpoint that receives the updates from Telegram. */ public class TelegramWebhookCallTest extends TelegramTestSupport { + private static final String CONFIGURED_SECRET_TOKEN = "configured_Secret-token-42"; + private int port; @Test public void testWebhookCall() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:endpoint"); + mock.expectedBodiesReceived("aho"); + mock.message(0).header(TelegramWebhookProcessor.SECRET_TOKEN_HEADER).isNull(); + + Exchange response = call("webhook", TelegramEndpoint.deriveWebhookSecretToken("mock-token")); + + assertEquals(204, response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)); + mock.assertIsSatisfied(); + } + + @Test + void testWebhookCallWithConfiguredSecretToken() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:endpoint"); + mock.expectedBodiesReceived("aho"); + + Exchange response = call("webhook-configured-token", CONFIGURED_SECRET_TOKEN); + + assertEquals(204, response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)); + mock.assertIsSatisfied(); + } + + @Test + void testWebhookCallWithoutSecretTokenIsRejected() throws Exception { + Exchange response = call("webhook", null); + + assertEquals(403, response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)); + assertEquals(0, getMockEndpoint("mock:endpoint").getReceivedCounter()); + } + + @Test + void testWebhookCallWithWrongSecretTokenIsRejected() throws Exception { + Exchange response = call("webhook", CONFIGURED_SECRET_TOKEN); + + assertEquals(403, response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)); + assertEquals(0, getMockEndpoint("mock:endpoint").getReceivedCounter()); + } + + @Test + void testWebhookCallWithoutSecretTokenWhenDisabled() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:endpoint"); + mock.expectedBodiesReceived("aho"); + + Exchange response = call("webhook-secret-token-disabled", null); + + assertEquals(204, response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class)); + mock.assertIsSatisfied(); + } + + private Exchange call(String routeId, String secretToken) throws Exception { WebhookConfiguration config - = ((WebhookEndpoint) context().getRoute("webhook").getConsumer().getEndpoint()).getConfiguration(); + = ((WebhookEndpoint) context().getRoute(routeId).getConsumer().getEndpoint()).getConfiguration(); String url = config.computeFullExternalUrl(); try (InputStream content = getClass().getClassLoader().getResourceAsStream("messages/webhook-call.json")) { - MockEndpoint mock = getMockEndpoint("mock:endpoint"); - mock.expectedBodiesReceived("aho"); - mock.expectedMinimumMessageCount(1); - - template().sendBodyAndHeader("netty-http:" + url, content, Exchange.CONTENT_TYPE, "application/json"); - mock.assertIsSatisfied(); + return template().request("netty-http:" + url + "?throwExceptionOnFailure=false", exchange -> { + exchange.getIn().setBody(content); + exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "application/json"); + if (secretToken != null) { + exchange.getIn().setHeader(TelegramWebhookProcessor.SECRET_TOKEN_HEADER, secretToken); + } + }); } } @@ -69,6 +123,18 @@ public class TelegramWebhookCallTest extends TelegramTestSupport { .id("webhook") .convertBodyTo(String.class) .to("mock:endpoint"); + + from("webhook:telegram:bots?authorizationToken=mock-token&webhookAutoRegister=false&webhookSecretToken=" + + CONFIGURED_SECRET_TOKEN) + .id("webhook-configured-token") + .convertBodyTo(String.class) + .to("mock:endpoint"); + + from("webhook:telegram:bots?authorizationToken=mock-token&webhookAutoRegister=false" + + "&webhookSecretTokenEnabled=false") + .id("webhook-secret-token-disabled") + .convertBodyTo(String.class) + .to("mock:endpoint"); } }; } diff --git a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java new file mode 100644 index 000000000000..3043b7ca3b13 --- /dev/null +++ b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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. + */ +package org.apache.camel.component.telegram; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.telegram.model.WebhookResult; +import org.apache.camel.component.telegram.util.TelegramTestSupport; +import org.apache.camel.component.telegram.util.TelegramTestUtil; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.test.AvailablePortFinder; +import org.junit.jupiter.api.Test; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests the secret token that the webhook is registered with. + */ +class TelegramWebhookSecretTokenTest extends TelegramTestSupport { + + private final List<String> setWebhookQueries = new CopyOnWriteArrayList<>(); + + @Test + void derivedSecretTokenIsStableAndUsesOnlyTheAllowedCharacters() { + String token = TelegramEndpoint.deriveWebhookSecretToken("mock-token"); + + assertEquals(token, TelegramEndpoint.deriveWebhookSecretToken("mock-token")); + assertNotEquals(token, TelegramEndpoint.deriveWebhookSecretToken("another-token")); + assertTrue(token.matches("[A-Za-z0-9_-]{43}"), token); + } + + @Test + void invalidConfiguredSecretTokenIsRefused() { + TelegramEndpoint endpoint = context().getEndpoint( + "telegram:bots?authorizationToken=mock-token&webhookSecretToken=invalid.token", TelegramEndpoint.class); + + assertThrows(IllegalArgumentException.class, endpoint::resolveWebhookSecretToken); + } + + @Test + void registrationSendsTheDerivedSecretToken() throws Exception { + String query = register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com"); + + assertEquals("url=http://my-domain.com/my-test&secret_token=" + TelegramEndpoint.deriveWebhookSecretToken("mock-token"), + query); + } + + @Test + void registrationSendsTheConfiguredSecretToken() throws Exception { + String query = register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com" + + "&webhookSecretToken=configured_Secret-token-42"); + + assertEquals("url=http://my-domain.com/my-test&secret_token=configured_Secret-token-42", query); + } + + @Test + void registrationSendsNoSecretTokenWhenDisabled() throws Exception { + String query = register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com" + + "&webhookSecretTokenEnabled=false"); + + assertEquals("url=http://my-domain.com/my-test", query); + } + + /** + * Starts a webhook route against a mock Telegram API, and returns the query of the setWebhook call. + */ + private String register(String webhookUri) throws Exception { + WebhookResult result = new WebhookResult(); + result.setOk(true); + result.setResult(true); + String response = TelegramTestUtil.serialize(result); + + try (DefaultCamelContext mockContext = new DefaultCamelContext()) { + mockContext.addRoutes(new RouteBuilder() { + @Override + public void configure() { + String api = "netty-http:http://localhost:" + port.getPort() + "/botmock-token/"; + from(api + "setWebhook?httpMethodRestrict=GET") + .process(exchange -> setWebhookQueries + .add(exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class))) + .setBody(constant(response)); + from(api + "deleteWebhook?httpMethodRestrict=GET") + .setBody(constant(response)); + } + }); + mockContext.start(); + + context().addRoutes(new RouteBuilder() { + @Override + public void configure() { + restConfiguration() + .host("localhost") + .port(AvailablePortFinder.getNextAvailable()); + + from(webhookUri).to("mock:endpoint"); + } + }); + context().start(); + await().atMost(5, TimeUnit.SECONDS).until(() -> !setWebhookQueries.isEmpty()); + context().stop(); + } + assertEquals(1, setWebhookQueries.size()); + return setWebhookQueries.get(0); + } + + @Override + public boolean isUseRouteBuilder() { + return false; + } +} diff --git a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java new file mode 100644 index 000000000000..d41ec9e4d908 --- /dev/null +++ b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://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. + */ +package org.apache.camel.component.telegram.service; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Optional; + +import javax.net.ssl.SSLSession; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.telegram.TelegramException; +import org.apache.camel.component.telegram.model.MessageResult; +import org.apache.camel.component.telegram.util.TelegramMockRoutes; +import org.apache.camel.component.telegram.util.TelegramTestSupport; +import org.apache.camel.support.DefaultExchange; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests that the errors of the Bot API requests do not show the request URI, which contains the authorization token. + */ +class TelegramServiceRestBotAPIAdapterErrorTest extends TelegramTestSupport { + + @Test + void describeUsesTheMethodOfTheBotApi() { + assertEquals("GET getUpdates", + TelegramServiceRestBotAPIAdapter.describe("GET", + URI.create("https://api.telegram.org/bot123456:authorization-token/getUpdates?offset=1&limit=100"))); + } + + @Test + void errorDoesNotShowTheRequestUri() { + TelegramServiceRestBotAPIAdapter adapter = new TelegramServiceRestBotAPIAdapter( + HttpClient.newHttpClient(), "http://localhost:" + port.getPort(), "mock-token", 1024); + + TelegramException e = assertThrows(TelegramException.class, + () -> adapter.setWebhook("http://my-domain.com/my-test", "my-secret-token")); + + assertTrue(e.getMessage().startsWith("Could not GET setWebhook: 401"), e.getMessage()); + assertFalse(e.getMessage().contains("mock-token"), e.getMessage()); + assertFalse(e.getMessage().contains("my-secret-token"), e.getMessage()); + } + + @Test + void asyncErrorDoesNotShowTheRequestUri() { + TelegramAsyncHandler handler = new TelegramAsyncHandler( + "http://localhost:" + port.getPort() + "/botmock-token/sendMessage", MessageResult.class, new ObjectMapper(), + new DefaultExchange(context())); + + RuntimeCamelException e = assertThrows(RuntimeCamelException.class, + () -> handler.handleCompressedResponse(response(400, "{\"ok\":false}"))); + + assertEquals("POST sendMessage responded: 400{\"ok\":false}", e.getMessage()); + } + + private static HttpResponse<InputStream> response(int statusCode, String body) { + return new HttpResponse<>() { + @Override + public int statusCode() { + return statusCode; + } + + @Override + public HttpRequest request() { + return null; + } + + @Override + public Optional<HttpResponse<InputStream>> previousResponse() { + return Optional.empty(); + } + + @Override + public HttpHeaders headers() { + return HttpHeaders.of(Map.of(), (name, value) -> true); + } + + @Override + public InputStream body() { + return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public Optional<SSLSession> sslSession() { + return Optional.empty(); + } + + @Override + public URI uri() { + return null; + } + + @Override + public HttpClient.Version version() { + return HttpClient.Version.HTTP_1_1; + } + }; + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return getMockRoutes(); + } + + @Override + protected TelegramMockRoutes createMockRoutes() { + return new TelegramMockRoutes(port.getPort()) + .addErrorEndpoint("setWebhook", "GET", 401); + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc index 0f189294ba46..0ba3dbfb0635 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc @@ -11,6 +11,33 @@ Note that manual migration is still required. See the xref:camel-upgrade-recipes-tool.adoc[documentation] page for details. ==== +== Upgrading from 4.18.5 to 4.18.6 + +=== camel-telegram - webhook requests are verified with a secret token + +In webhook mode, Camel now registers the webhook with a secret token, which Telegram sends in the +`X-Telegram-Bot-Api-Secret-Token` header of every webhook request, and rejects a webhook request that does not carry +the token with HTTP 403. The token is the value of the new `webhookSecretToken` option or, when that option is not +set, a value derived from the authorization token. A client that calls the webhook endpoint directly, for example in +a test, must send the header too. + +When Camel registers the webhook at startup (`webhookAutoRegister=true`, the default), nothing needs to change: each +upgraded instance registers the webhook with the token, and the instances that are not upgraded yet ignore the header. + +When the webhook is registered in another way, through the `WebhookRoutePolicy` or outside Camel, the registration +made with the previous version has no secret token, and the upgraded instances reject every update until the webhook +is registered again with it. Register the webhook again first, then upgrade the instances that receive the updates: +instances of the previous version ignore the header. With the `WebhookRoutePolicy`, run the registration with the new +version. Outside Camel, set `webhookSecretToken`, and pass the same value as the `secret_token` parameter of +`setWebhook`. + +The default webhook path is a hash of the endpoint URI, so adding an option to the URI, such as `webhookSecretToken`, +changes the path. When the webhook is not registered by Camel at startup, set `webhookPath` to keep the path that is +registered. + +To restore the previous behaviour, set `webhookSecretTokenEnabled=false`: the webhook is then registered without a +secret token, and webhook requests are not verified. + == Upgrading from 4.18.4 to 4.18.5 === camel-docling - output path handling diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java index 140878e86aa9..31abbbf12546 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java @@ -692,6 +692,65 @@ public interface TelegramEndpointBuilderFactory { doSetProperty("authorizationToken", authorizationToken); return this; } + /** + * The secret token that Telegram sends in the + * X-Telegram-Bot-Api-Secret-Token header of every webhook request. In + * webhook mode, Camel registers the webhook with this token, and + * rejects a webhook request that does not carry it with HTTP 403. Only + * the characters A-Z, a-z, 0-9, _ and - are allowed, up to 256 + * characters. When not set, a token derived from the authorization + * token is used. Set it when the webhook is registered outside Camel, + * and register the webhook with the same secret_token. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param webhookSecretToken the value to set + * @return the dsl builder + */ + default TelegramEndpointConsumerBuilder webhookSecretToken(String webhookSecretToken) { + doSetProperty("webhookSecretToken", webhookSecretToken); + return this; + } + /** + * Whether, in webhook mode, Camel registers the webhook with a secret + * token and rejects a webhook request that does not carry it. Set it to + * false to restore the behaviour of earlier versions, where the webhook + * was registered without a secret token and webhook requests were not + * verified. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: security + * + * @param webhookSecretTokenEnabled the value to set + * @return the dsl builder + */ + default TelegramEndpointConsumerBuilder webhookSecretTokenEnabled(boolean webhookSecretTokenEnabled) { + doSetProperty("webhookSecretTokenEnabled", webhookSecretTokenEnabled); + return this; + } + /** + * Whether, in webhook mode, Camel registers the webhook with a secret + * token and rejects a webhook request that does not carry it. Set it to + * false to restore the behaviour of earlier versions, where the webhook + * was registered without a secret token and webhook requests were not + * verified. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: true + * Group: security + * + * @param webhookSecretTokenEnabled the value to set + * @return the dsl builder + */ + default TelegramEndpointConsumerBuilder webhookSecretTokenEnabled(String webhookSecretTokenEnabled) { + doSetProperty("webhookSecretTokenEnabled", webhookSecretTokenEnabled); + return this; + } } /**
