feat: add command to toggle debug mode
- Add lib/Command/DebugToggle.php to enable/disable debug endpoint via CLI - Update lib/Controller/WebhookController.php to check debug status - Add support for base64 attachments in lib/Service/TalkService.php - Bump version in appinfo/info.xml to 1.1.0 - Update INSTALL.md and README.md documentation
This commit is contained in:
+26
@@ -131,6 +131,32 @@ Apprise sends a different JSON format (`title`, `body`, `type`, `attachments`)
|
|||||||
- The webhook endpoint is public (no auth required) but validates the auth token from the URL path
|
- The webhook endpoint is public (no auth required) but validates the auth token from the URL path
|
||||||
- Admin settings endpoints (`/save-config`, `/rooms`) require admin login
|
- Admin settings endpoints (`/save-config`, `/rooms`) require admin login
|
||||||
- Images are stored in the bot user's files and purged after the retention period
|
- Images are stored in the bot user's files and purged after the retention period
|
||||||
|
- **Debug endpoint disabled by default** — see below
|
||||||
- **Known limitations (to be fixed in a future release):**
|
- **Known limitations (to be fixed in a future release):**
|
||||||
- Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API
|
- Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API
|
||||||
- The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources
|
- The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources
|
||||||
|
|
||||||
|
### Debug endpoint
|
||||||
|
|
||||||
|
The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration,
|
||||||
|
database schema, and bot credentials. It is **disabled by default**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
php occ ncdiscordhook:debug:status
|
||||||
|
|
||||||
|
# Enable (WARNING: exposes sensitive data)
|
||||||
|
php occ ncdiscordhook:debug:enable
|
||||||
|
|
||||||
|
# Disable (default)
|
||||||
|
php occ ncdiscordhook:debug:disable
|
||||||
|
|
||||||
|
# Toggle current state
|
||||||
|
php occ ncdiscordhook:debug:toggle
|
||||||
|
```
|
||||||
|
|
||||||
|
After troubleshooting, disable it immediately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php occ ncdiscordhook:debug:disable
|
||||||
|
```
|
||||||
|
|||||||
@@ -137,6 +137,34 @@ The apprise webhook maps `title`, `body`, and `attachments` to the same Talk mes
|
|||||||
- Bot password is encrypted at rest using Nextcloud's crypto
|
- Bot password is encrypted at rest using Nextcloud's crypto
|
||||||
- Image download uses Nextcloud's HTTP client with local address blocking
|
- Image download uses Nextcloud's HTTP client with local address blocking
|
||||||
- Rate limiting should be handled at the web server or reverse proxy level
|
- Rate limiting should be handled at the web server or reverse proxy level
|
||||||
|
- **Debug endpoint disabled by default** — see below
|
||||||
|
|
||||||
|
### Debug endpoint
|
||||||
|
|
||||||
|
The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration,
|
||||||
|
database schema, and bot credentials. It is **disabled by default** and must
|
||||||
|
be explicitly enabled via the CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check current status
|
||||||
|
php occ ncdiscordhook:debug:status
|
||||||
|
|
||||||
|
# Enable (WARNING: exposes sensitive data)
|
||||||
|
php occ ncdiscordhook:debug:enable
|
||||||
|
|
||||||
|
# Disable (default)
|
||||||
|
php occ ncdiscordhook:debug:disable
|
||||||
|
|
||||||
|
# Toggle current state
|
||||||
|
php occ ncdiscordhook:debug:toggle
|
||||||
|
```
|
||||||
|
|
||||||
|
Never leave the debug endpoint enabled in production. After troubleshooting,
|
||||||
|
disable it immediately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php occ ncdiscordhook:debug:disable
|
||||||
|
```
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -5,7 +5,7 @@
|
|||||||
<name>NCdiscordhook</name>
|
<name>NCdiscordhook</name>
|
||||||
<summary>Discord webhook bridge for Nextcloud Talk with image support</summary>
|
<summary>Discord webhook bridge for Nextcloud Talk with image support</summary>
|
||||||
<description>Accepts Discord webhook-style JSON payloads and posts them into Nextcloud Talk rooms, preserving image attachments. Each room gets its own webhook URL with an auth token for security.</description>
|
<description>Accepts Discord webhook-style JSON payloads and posts them into Nextcloud Talk rooms, preserving image attachments. Each room gets its own webhook URL with an auth token for security.</description>
|
||||||
<version>1.0.0</version>
|
<version>1.1.0</version>
|
||||||
<licence>AGPL-3.0-or-later</licence>
|
<licence>AGPL-3.0-or-later</licence>
|
||||||
<author homepage="https://github.com/Mr-Newlove/nc_bot_webhooks/kyle">Kyle</author>
|
<author homepage="https://github.com/Mr-Newlove/nc_bot_webhooks/kyle">Kyle</author>
|
||||||
<namespace>NCdiscordhook</namespace>
|
<namespace>NCdiscordhook</namespace>
|
||||||
@@ -24,5 +24,9 @@
|
|||||||
<admin>OCA\NCdiscordhook\Settings\Admin</admin>
|
<admin>OCA\NCdiscordhook\Settings\Admin</admin>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
||||||
|
<commands>
|
||||||
|
<command>OCA\NCdiscordhook\Command\DebugToggle</command>
|
||||||
|
</commands>
|
||||||
|
|
||||||
<bugs>https://github.com/Mr-Newlove/nc_bot_webhooks/issues</bugs>
|
<bugs>https://github.com/Mr-Newlove/nc_bot_webhooks/issues</bugs>
|
||||||
</info>
|
</info>
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\NCdiscordhook\Command;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||||
|
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||||
|
use OCP\AppFramework\Http\Attribute\AdminRequired;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IAppConfig;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\Http\Client\IClientService;
|
||||||
|
use OCP\IGroupManager;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
use OCP\PreConditionNotMetException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the debug endpoint.
|
||||||
|
*
|
||||||
|
* The debug endpoint exposes internal configuration and database state.
|
||||||
|
* It is disabled by default and must be explicitly enabled via this command.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* # Enable debug
|
||||||
|
* php occ ncdiscordhook:debug:enable
|
||||||
|
*
|
||||||
|
* # Disable debug
|
||||||
|
* php occ ncdiscordhook:debug:disable
|
||||||
|
*
|
||||||
|
* # Toggle (disable if enabled, enable if disabled)
|
||||||
|
* php occ ncdiscordhook:debug:toggle
|
||||||
|
*
|
||||||
|
* # Check current status
|
||||||
|
* php occ ncdiscordhook:debug:status
|
||||||
|
*/
|
||||||
|
class DebugToggle extends Command {
|
||||||
|
private const APP_ID = 'ncdiscordhook';
|
||||||
|
private const DEBUG_KEY = 'debug_enabled';
|
||||||
|
|
||||||
|
private IAppConfig $appConfig;
|
||||||
|
|
||||||
|
public function __construct(IAppConfig $appConfig) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->appConfig = $appConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void {
|
||||||
|
$this
|
||||||
|
->setName('ncdiscordhook:debug:toggle')
|
||||||
|
->setDescription('Enable or disable the debug endpoint')
|
||||||
|
->addOption('enable', 'e', InputOption::VALUE_NONE, 'Enable the debug endpoint')
|
||||||
|
->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the debug endpoint')
|
||||||
|
->addOption('status', null, InputOption::VALUE_NONE, 'Show current debug endpoint status');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
if ($input->getOption('status')) {
|
||||||
|
$enabled = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
|
||||||
|
$io->success($enabled ? 'Debug endpoint is ENABLED' : 'Debug endpoint is DISABLED (default)');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('enable') && $input->getOption('disable')) {
|
||||||
|
$io->error('Cannot use both --enable and --disable at the same time.');
|
||||||
|
return Command::INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('enable')) {
|
||||||
|
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, true);
|
||||||
|
$io->warning('Debug endpoint is now ENABLED. Anyone can access /apps/ncdiscordhook/debug.');
|
||||||
|
$io->note('To disable later, run: php occ ncdiscordhook:debug:disable');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('disable')) {
|
||||||
|
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, false);
|
||||||
|
$io->success('Debug endpoint is now DISABLED.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No flag: toggle
|
||||||
|
$current = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
|
||||||
|
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, !$current);
|
||||||
|
$next = !$current ? 'enabled' : 'disabled';
|
||||||
|
$io->success("Debug endpoint is now $next.");
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -361,11 +361,26 @@ class WebhookController extends Controller {
|
|||||||
* Query params:
|
* Query params:
|
||||||
* - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/discord-webhook/{room}/{token})
|
* - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/discord-webhook/{room}/{token})
|
||||||
* - test_post=1: Actually POST a test message to the room
|
* - test_post=1: Actually POST a test message to the room
|
||||||
|
*
|
||||||
|
* This endpoint is disabled by default. Enable it via:
|
||||||
|
* php occ ncdiscordhook:debug:enable
|
||||||
|
*
|
||||||
|
* SECURITY: Never leave debug enabled in production. It exposes internal
|
||||||
|
* configuration, database schema, and bot credentials.
|
||||||
*/
|
*/
|
||||||
#[PublicPage]
|
#[PublicPage]
|
||||||
#[NoCSRFRequired]
|
#[NoCSRFRequired]
|
||||||
#[NoAdminRequired]
|
#[NoAdminRequired]
|
||||||
public function debug(): DataResponse {
|
public function debug(): DataResponse {
|
||||||
|
// Debug endpoint must be explicitly enabled via OCC command
|
||||||
|
$debugEnabled = $this->appConfig->getValueBool('ncdiscordhook', 'debug_enabled', false);
|
||||||
|
if (!$debugEnabled) {
|
||||||
|
return new DataResponse(
|
||||||
|
['error' => 'Debug endpoint is disabled. Enable it with: php occ ncdiscordhook:debug:enable'],
|
||||||
|
Http::STATUS_FORBIDDEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
$uid = $user !== null ? $user->getUID() : null;
|
$uid = $user !== null ? $user->getUID() : null;
|
||||||
$isAdmin = $user !== null && $this->groupManager->isAdmin($uid);
|
$isAdmin = $user !== null && $this->groupManager->isAdmin($uid);
|
||||||
@@ -376,6 +391,7 @@ class WebhookController extends Controller {
|
|||||||
'user_is_admin' => $isAdmin,
|
'user_is_admin' => $isAdmin,
|
||||||
'bot_user' => null,
|
'bot_user' => null,
|
||||||
'has_bot_password' => false,
|
'has_bot_password' => false,
|
||||||
|
'debug_enabled' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -704,6 +704,22 @@ class TalkService {
|
|||||||
$richObjects[] = $richObj;
|
$richObjects[] = $richObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif (!empty($attachment['base64'] ?? '')) {
|
||||||
|
// Base64-encoded attachment (Apprise library JSON method)
|
||||||
|
$fileData = base64_decode($attachment['base64'], true);
|
||||||
|
if ($fileData === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = $attachment['filename'] ?? $attachment['name'] ?? 'attachment';
|
||||||
|
$mimeType = $attachment['mimetype'] ?? $attachment['mimeType'] ?? 'application/octet-stream';
|
||||||
|
$uploadPath = $this->uploadImage($roomToken, $fileName, $fileData, $mimeType);
|
||||||
|
if ($uploadPath !== null) {
|
||||||
|
$richObj = $this->buildRichObject($uploadPath, $mimeType, $roomToken);
|
||||||
|
if ($richObj !== null) {
|
||||||
|
$richObjects[] = $richObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -877,7 +893,7 @@ class TalkService {
|
|||||||
// or the message is silently dropped.
|
// or the message is silently dropped.
|
||||||
$botDisplayName = $this->getBotDisplayNameForRoom($roomToken);
|
$botDisplayName = $this->getBotDisplayNameForRoom($roomToken);
|
||||||
|
|
||||||
// Build message body for Chat API v4
|
// Build message body for Chat API v1
|
||||||
$body = [
|
$body = [
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'actorType' => 'users',
|
'actorType' => 'users',
|
||||||
|
|||||||
Reference in New Issue
Block a user