From 0bca358838f918a0bd92e1d9049ea74fa341d137 Mon Sep 17 00:00:00 2001 From: Ludy87 Date: Tue, 9 Dec 2025 10:40:50 +0100 Subject: [PATCH] Add user and channel access controls to Telegram bot Introduces configuration options to restrict Telegram bot access to specific user and channel IDs. Updates ApplicationProperties, TelegramPipelineBot, and settings.yml.template to support allow lists and enable/disable flags for user and channel access control. --- .../common/model/ApplicationProperties.java | 6 ++ .../service/telegram/TelegramPipelineBot.java | 96 ++++++++++++++++++- .../src/main/resources/settings.yml.template | 5 + 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index efe148d3b..ad04ff4b6 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -548,6 +548,7 @@ public class ApplicationProperties { private boolean ssoAutoLogin; private CustomMetadata customMetadata = new CustomMetadata(); + @Deprecated @Data public static class CustomMetadata { private boolean autoUpdateMetadata; @@ -583,6 +584,11 @@ public class ApplicationProperties { private String botToken; private String botUsername; private String pipelineInboxFolder = "telegram"; + private Boolean customFolderSubfix = false; + private Boolean enableAllowUserIDs = false; + private List allowUserIDs = new ArrayList<>(); + private Boolean enableAllowChannelIDs = false; + private List allowChannelIDs = new ArrayList<>(); private long processingTimeoutSeconds = 180; private long pollingIntervalMillis = 2000; } diff --git a/app/core/src/main/java/stirling/software/SPDF/service/telegram/TelegramPipelineBot.java b/app/core/src/main/java/stirling/software/SPDF/service/telegram/TelegramPipelineBot.java index 99277c072..d5030bbce 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/telegram/TelegramPipelineBot.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/telegram/TelegramPipelineBot.java @@ -3,6 +3,7 @@ package stirling.software.SPDF.service.telegram; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -33,6 +34,7 @@ import org.telegram.telegrambots.meta.api.objects.InputFile; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.PhotoSize; import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import jakarta.annotation.PostConstruct; @@ -79,6 +81,7 @@ public class TelegramPipelineBot extends TelegramLongPollingBot { public void onUpdateReceived(Update update) { Message message = null; + // 1) Regular messages if (update.hasMessage()) { message = update.getMessage(); } @@ -109,6 +112,93 @@ public class TelegramPipelineBot extends TelegramLongPollingBot { chatType, message); + User from = message.getFrom(); + if (telegramProperties.getEnableAllowUserIDs() + || telegramProperties.getEnableAllowChannelIDs()) { + List allowUserIDs = telegramProperties.getAllowUserIDs(); + List allowChannelIDs = telegramProperties.getAllowChannelIDs(); + switch (chatType) { + case "channel" -> { + // In private chats, messages are sent by users + if (telegramProperties.getEnableAllowChannelIDs()) { + if (allowChannelIDs.isEmpty()) { + log.info( + "No allowed channel IDs configured, disabling all access from" + + " private chat id={}", + chat.getId()); + } + if (from == null || !allowChannelIDs.contains(from.getId().longValue())) { + log.info( + "Ignoring message {} from user id={} in private chat id={} due" + + " to channel access restrictions", + message.getMessageId(), + from != null ? from.getId() : "unknown", + chat.getId()); + sendMessage( + chat.getId(), + "You are not authorized to use this bot. Please contact the" + + " administrator."); + return; + } + } + } + case "private" -> { + // In channels, messages are always sent on behalf of the channel + if (telegramProperties.getEnableAllowUserIDs()) { + if (allowUserIDs.isEmpty()) { + log.info( + "No allowed user IDs configured, disabling all access from" + + " channel id={}", + chat.getId()); + } + if (from == null || !allowUserIDs.contains(from.getId().longValue())) { + log.info( + "Ignoring message {} from channel id={} due to user access" + + " restrictions", + message.getMessageId(), + chat.getId()); + sendMessage( + chat.getId(), + "This channel is not authorized to use this bot. Please contact" + + " the administrator."); + return; + } + } + } + case "group", "supergroup" -> { + // group chats + } + default -> { + // private chats + } + } + boolean userAllowed = + !telegramProperties.getEnableAllowUserIDs() + || (from != null && allowUserIDs.contains(from.getId())); + boolean channelAllowed = + !telegramProperties.getEnableAllowChannelIDs() + || allowChannelIDs.contains(chat.getId()); + if (!userAllowed && !channelAllowed) { + log.info( + "Ignoring message {} from user id={} in chat id={} due to access restrictions", + message.getMessageId(), + from != null ? from.getId() : "unknown", + chat.getId()); + sendMessage( + chat.getId(), + "You are not authorized to use this bot. Please contact the administrator."); + return; + } + } + if (from != null) { + log.info( + "Message {} sent by user {} {} (id={})", + message.getMessageId(), + from.getFirstName(), + from.getLastName() != null ? from.getLastName() : "", + from.getId()); + } + if (message.hasDocument() || message.hasPhoto()) { handleIncomingFile(message); } @@ -248,8 +338,10 @@ public class TelegramPipelineBot extends TelegramLongPollingBot { } private URL buildDownloadUrl(String filePath) throws MalformedURLException { - return new URL( - String.format("https://api.telegram.org/file/bot%s/%s", getBotToken(), filePath)); + return URI.create( + String.format( + "https://api.telegram.org/file/bot%s/%s", getBotToken(), filePath)) + .toURL(); } private List waitForPipelineOutputs(PipelineFileInfo info) throws IOException { diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index bdd6cca30..fd1ad9754 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -110,6 +110,11 @@ telegram: botToken: '' # Telegram bot token obtained from BotFather botUsername: '' # Telegram bot username (without @) pipelineInboxFolder: telegram # Name of the pipeline inbox folder for Telegram uploads + customFolderSubfix: true # set to 'true' to allow users to specify custom target folders via UserID + enableAllowUserIDs: true # set to 'true' to restrict access to specific Telegram user IDs + allowUserIDs: [] # List of allowed Telegram user IDs (e.g. [123456789, 987654321]). Leave empty to allow all users. + enableAllowChannelIDs: true # set to 'true' to restrict access to specific Telegram channel IDs + allowChannelIDs: [] # List of allowed Telegram channel IDs (e.g. [-1001234567890, -1009876543210]). Leave empty to allow all channels. processingTimeoutSeconds: 180 # Maximum time in seconds to wait for processing a Telegram request pollingIntervalMillis: 2000 # Interval in milliseconds between polling for new messages