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