Expand settings for notifications.

This commit is contained in:
James Cole
2024-12-11 07:23:46 +01:00
parent c35ff3174a
commit c920070ce2
24 changed files with 476 additions and 252 deletions

View File

@@ -178,25 +178,6 @@ MANDRILL_SECRET=
SPARKPOST_SECRET=
MAILERSEND_API_KEY=
#
# Ntfy notification settings.
# defaults to "https://ntfy.sh", but needs a topic or it won't work.
# authentication is recommended but not required.
#
NTFY_SERVER=
NTFY_TOPIC=
NTFY_AUTH_ENABLED=false
NTFY_AUTH_USERNAME=
NTFY_AUTH_PASSWORD=
#
# Pushover notification Application/API Token and User token.
# Used if you want to receive notifications over pushover.
# Both must be configured for this channel to work.
#
PUSHOVER_APP_TOKEN=
PUSHOVER_USER_TOKEN=
# Firefly III can send you the following messages.
SEND_ERROR_MESSAGE=true

View File

@@ -23,23 +23,23 @@ declare(strict_types=1);
namespace FireflyIII\Events\Test;
use FireflyIII\User;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use Illuminate\Queue\SerializesModels;
class TestNotificationChannel
{
use SerializesModels;
public User $user;
public OwnerNotifiable $owner;
public string $channel;
/**
* Create a new event instance.
*/
public function __construct(string $channel, User $user)
public function __construct(string $channel, OwnerNotifiable $owner)
{
app('log')->debug(sprintf('Triggered TestNotificationChannel("%s") for user #%d (%s)', $channel, $user->id, $user->email));
$this->user = $user;
app('log')->debug(sprintf('Triggered TestNotificationChannel("%s")', $channel));
$this->owner = $owner;
$this->channel = $channel;
}
}

View File

@@ -117,14 +117,8 @@ class AdminEventHandler
*/
public function sendTestNotification(TestNotificationChannel $event): void
{
Log::debug(sprintf('Now in sendTestNotification(#%d, "%s")', $event->user->id, $event->channel));
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
Log::debug(sprintf('Now in sendTestNotification("%s")', $event->channel));
if (!$repository->hasRole($event->user, 'owner')) {
Log::error(sprintf('User #%d is not an owner.', $event->user->id));
return;
}
switch($event->channel) {
case 'email':
$class = TestNotificationEmail::class;
@@ -145,7 +139,7 @@ class AdminEventHandler
Log::debug(sprintf('Will send %s as a notification.', $class));
try {
Notification::send($event->user, new $class($event->user->email));
Notification::send($event->owner, new $class($event->owner));
} catch (\Exception $e) { // @phpstan-ignore-line
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\Test\TestNotificationChannel;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\NotificationRequest;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -40,12 +41,21 @@ class NotificationController extends Controller
$mainTitleIcon = 'fa-hand-spock-o';
$subTitle = (string) trans('firefly.title_owner_notifications');
$subTitleIcon = 'envelope-o';
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data;
// notification settings:
$slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
$ntfyServer = app('fireflyconfig')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = app('fireflyconfig')->get('ntfy_auth', false)->data;
$ntfyUser = app('fireflyconfig')->getEncrypted('ntfy_user', '')->data;
$ntfyPass = app('fireflyconfig')->getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels');
$forcedAvailability = [];
// admin notification settings:
$notifications = [];
foreach (config('notifications.notifications.owner') as $key => $info) {
@@ -60,18 +70,24 @@ class NotificationController extends Controller
}
// validate presence of of Ntfy settings.
if('' === (string)config('ntfy-notification-channel.topic')) {
if ('' === $ntfyTopic) {
Log::warning('No topic name for Ntfy, channel is disabled.');
$forcedAvailability['ntfy'] = false;
}
// validate pushover
if('' === (string)config('services.pushover.token') || '' === (string)config('services.pushover.user_token')) {
if ('' === $pushoverAppToken || '' === $pushoverUserToken) {
Log::warning('No Pushover token, channel is disabled.');
$forcedAvailability['pushover'] = false;
}
return view('admin.notifications.index', compact('title', 'subTitle', 'forcedAvailability', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl', 'notifications'));
return view('admin.notifications.index',
compact(
'title', 'subTitle', 'forcedAvailability', 'mainTitleIcon', 'subTitleIcon', 'channels',
'slackUrl', 'notifications',
'pushoverAppToken', 'pushoverUserToken',
'ntfyServer', 'ntfyTopic', 'ntfyAuth', 'ntfyUser', 'ntfyPass'
));
}
public function postIndex(NotificationRequest $request): RedirectResponse
@@ -83,12 +99,17 @@ class NotificationController extends Controller
app('fireflyconfig')->set(sprintf('notification_%s', $key), $all[$key]);
}
}
if ('' === $all['slack_url']) {
app('fireflyconfig')->delete('slack_webhook_url');
$variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
foreach ($variables as $variable) {
if ('' === $all[$variable]) {
app('fireflyconfig')->delete($variable);
}
if ('' !== $all['slack_url']) {
app('fireflyconfig')->set('slack_webhook_url', $all['slack_url']);
if ('' !== $all[$variable]) {
app('fireflyconfig')->setEncrypted($variable, $all[$variable]);
}
}
app('fireflyconfig')->set('ntfy_auth', $all['ntfy_auth'] ?? false);
session()->flash('success', (string) trans('firefly.notification_settings_saved'));
@@ -109,10 +130,9 @@ class NotificationController extends Controller
case 'slack':
case 'pushover':
case 'ntfy':
/** @var User $user */
$user = auth()->user();
$owner = new OwnerNotifiable();
app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel));
event(new TestNotificationChannel($channel, $user));
event(new TestNotificationChannel($channel, $owner));
session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
}

View File

@@ -23,8 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Rules\Admin\IsValidDiscordUrl;
use FireflyIII\Rules\Admin\IsValidSlackUrl;
use FireflyIII\Rules\Admin\IsValidSlackOrDiscordUrl;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -44,7 +43,16 @@ class NotificationRequest extends FormRequest
}
$return[$key] = $value;
}
$return['slack_url'] = $this->convertString('slack_url');
$return['slack_webhook_url'] = $this->convertString('slack_webhook_url');
$return['pushover_app_token'] = $this->convertString('pushover_app_token');
$return['pushover_user_token'] = $this->convertString('pushover_user_token');
$return['ntfy_server'] = $this->convertString('ntfy_server');
$return['ntfy_topic'] = $this->convertString('ntfy_topic');
$return['ntfy_auth'] = $this->convertBoolean($this->get('ntfy_auth'));
$return['ntfy_user'] = $this->convertString('ntfy_user');
$return['ntfy_pass'] = $this->convertString('ntfy_pass');
return $return;
}
@@ -54,7 +62,10 @@ class NotificationRequest extends FormRequest
public function rules(): array
{
$rules = [
'slack_url' => ['nullable', 'url', 'min:1', new IsValidSlackUrl()],
'slack_webhook_url' => ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', 'url', 'min:1'],
'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'],
'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'],
];
foreach (config('notifications.notifications.owner') as $key => $info) {
$rules[sprintf('notification_%s', $key)] = 'in:0,1';

View File

@@ -107,6 +107,9 @@ class UserInvitation extends Notification
*/
public function via($notifiable)
{
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
return ['mail', 'slack'];

View File

@@ -0,0 +1,70 @@
<?php
/*
* AdminNotifiable.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Notifiables;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
use NotificationChannels\Pushover\PushoverReceiver;
class OwnerNotifiable
{
public function routeNotificationForSlack(): string
{
$res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (is_array($res)) {
$res = '';
}
return (string) $res;
}
public function routeNotificationForPushover()
{
$pushoverAppToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
return PushoverReceiver::withUserKey($pushoverUserToken)
->withApplicationToken($pushoverAppToken);
}
/**
* Get the notification routing information for the given driver.
*
* @param string $driver
* @param null|Notification $notification
*
* @return mixed
*/
public function routeNotificationFor($driver, $notification = null)
{
$method = 'routeNotificationFor' . Str::studly($driver);
if (method_exists($this, $method)) {
return $this->{$method}($notification); // @phpstan-ignore-line
}
return match ($driver) {
'mail' => (string) config('firefly.site_owner'),
default => null,
};
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* ReturnsAvailableChannels.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications;
use FireflyIII\Support\Notifications\UrlValidator;
class ReturnsAvailableChannels
{
public static function returnChannels(string $type): array {
$channels = ['mail'];
if('owner' === $type) {
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
}
// only the owner can get notifications over
}
return $channels;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* ReturnsSettings.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
class ReturnsSettings
{
public static function getSettings(string $channel, string $type, ?User $user): array
{
if ('ntfy' === $channel) {
return self::getNtfySettings($type, $user);
}
throw new FireflyException(sprintf('Cannot handle channel "%s"', $channel));
}
private static function getNtfySettings(string $type, ?User $user)
{
$settings = [
'ntfy_server' => 'https://ntfy.sh',
'ntfy_topic' => '',
'ntfy_auth' => false,
'ntfy_user' => '',
'ntfy_pass' => '',
];
if ('owner' === $type) {
$settings['ntfy_server'] = FireflyConfig::getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$settings['ntfy_topic'] = FireflyConfig::getEncrypted('ntfy_topic', '')->data;
$settings['ntfy_auth'] = FireflyConfig::get('ntfy_auth', false)->data;
$settings['ntfy_user'] = FireflyConfig::getEncrypted('ntfy_user', '')->data;
$settings['ntfy_pass'] = FireflyConfig::getEncrypted('ntfy_pass', '')->data;
}
return $settings;
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
@@ -35,26 +36,26 @@ class TestNotificationEmail extends Notification
{
use Queueable;
private string $address;
private OwnerNotifiable $owner;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
public function __construct(OwnerNotifiable $owner)
{
$this->address = $address;
$this->owner = $owner;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
public function toArray(OwnerNotifiable $notifiable)
{
return [
];
@@ -69,23 +70,14 @@ class TestNotificationEmail extends Notification
*
* @return MailMessage
*/
public function toMail($notifiable)
public function toMail(OwnerNotifiable $notifiable)
{
$address = (string) config('firefly.site_owner');
return (new MailMessage())
->markdown('emails.admin-test', ['email' => $this->address])
->markdown('emails.admin-test', ['email' => $address])
->subject((string) trans('email.admin_test_subject'));
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {}
/**
* Get the notification's delivery channels.
*
@@ -95,7 +87,7 @@ class TestNotificationEmail extends Notification
*
* @return array
*/
public function via($notifiable)
public function via(OwnerNotifiable $notifiable)
{
return ['mail'];
}

View File

@@ -24,9 +24,10 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Notifications\ReturnsSettings;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Ntfy\Message;
use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel;
@@ -40,14 +41,14 @@ class TestNotificationNtfy extends Notification
{
use Queueable;
private string $address;
public OwnerNotifiable $owner;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
public function __construct(OwnerNotifiable $owner)
{
$this->address = $address;
$this->owner = $owner;
}
/**
@@ -66,51 +67,34 @@ class TestNotificationNtfy extends Notification
}
public function toNtfy(mixed $notifiable): Message
public function toNtfy(OwnerNotifiable $notifiable): Message
{
$settings = ReturnsSettings::getSettings('ntfy', 'owner', null);
// overrule config.
config(['ntfy-notification-channel.server' => $settings['ntfy_server']]);
config(['ntfy-notification-channel.topic' => $settings['ntfy_topic']]);
if ($settings['ntfy_auth']) {
// overrule auth as well.
config(['ntfy-notification-channel.authentication.enabled' => true]);
config(['ntfy-notification-channel.authentication.username' => $settings['ntfy_user']]);
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
}
$message = new Message();
$message->topic(config('ntfy-notification-channel.topic'));
$message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.admin_test_subject'));
$message->body((string) trans('email.admin_test_message', ['channel' => 'ntfy']));
$message->tags(['white_check_mark', 'ok_hand']);
$message->tags(['white_check_mark']);
return $message;
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
public function via(OwnerNotifiable $notifiable)
{
return [NtfyChannel::class];
}

View File

@@ -24,6 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
@@ -42,73 +44,45 @@ class TestNotificationPushover extends Notification
{
use Queueable;
private string $address;
private OwnerNotifiable $owner;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
public function __construct(OwnerNotifiable $owner)
{
$this->address = $address;
$this->owner = $owner;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
public function toArray(OwnerNotifiable $notifiable)
{
return [
];
}
public function toPushover(mixed $notifiable): PushoverMessage
public function toPushover(OwnerNotifiable $notifiable): PushoverMessage
{
Log::debug('Now in toPushover()');
return PushoverMessage::create((string)trans('email.admin_test_message', ['channel' => 'Pushover']))
->title((string)trans('email.admin_test_subject'));
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
public function via(OwnerNotifiable $notifiable)
{
return [PushoverChannel::class];
}

View File

@@ -24,11 +24,14 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
//use Illuminate\Notifications\Slack\SlackMessage;
use Illuminate\Support\Facades\Log;
use Illuminate\Notifications\Messages\SlackMessage;
/**
* Class TestNotification
@@ -37,76 +40,61 @@ class TestNotificationSlack extends Notification
{
use Queueable;
private string $address;
private OwnerNotifiable $owner;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
public function __construct(OwnerNotifiable $owner)
{
$this->address = $address;
$this->owner =$owner;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
public function toArray(OwnerNotifiable $notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {
public function toSlack(OwnerNotifiable $notifiable)
{
// since it's an admin notification, grab the URL from fireflyconfig
$url = app('fireflyconfig')->get('slack_webhook_url', '')->data;
// return (new SlackMessage)
// ->text((string)trans('email.admin_test_subject'))
// ->to($url);
return (new SlackMessage())
->content((string)trans('email.admin_test_subject'))
->to($url);
$url = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if ('' !== $url) {
return new SlackMessage()->content((string)trans('email.admin_test_subject'))->to($url);
//return new SlackMessage()->text((string) trans('email.admin_test_subject'))->to($url);
}
Log::error('Empty slack URL, cannot send notification.');
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
* @param OwnerNotifiable $notifiable
*
* @return array
*/
public function via($notifiable)
public function via(OwnerNotifiable $notifiable)
{
return ['slack'];
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Rules\Admin;
use FireflyIII\Support\Validation\ValidatesAmountsTrait;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
class IsValidSlackOrDiscordUrl implements ValidationRule
{
use ValidatesAmountsTrait;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
$value = (string)$value;
if('' === $value) {
return;
}
if(!str_starts_with($value, 'https://hooks.slack.com/services/') && !str_starts_with($value, 'https://discord.com/api/webhooks/')) {
$fail('validation.active_url')->translate();
$message = sprintf('IsValidSlackUrl: "%s" is not a discord or slack URL.', substr($value, 0, 255));
Log::debug($message);
Log::channel('audit')->info($message);
}
}
}

View File

@@ -24,7 +24,7 @@ class IsValidSlackUrl implements ValidationRule
if(!str_starts_with($value, 'https://hooks.slack.com/services/')) {
$fail('validation.active_url')->translate();
$message = sprintf('IsValidSlackUrl: "%s" is not a discord URL.', substr($value, 0, 255));
$message = sprintf('IsValidSlackUrl: "%s" is not a slack URL.', substr($value, 0, 255));
Log::debug($message);
Log::channel('audit')->info($message);
}

View File

@@ -289,6 +289,26 @@ class ExpandedForm
return $html;
}
/**
* @throws FireflyException
*/
public function passwordWithValue(string $name, string $value, ?array $options = null): string
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
try {
$html = view('form.password', compact('classes', 'value','name', 'label', 'options'))->render();
} catch (\Throwable $e) {
app('log')->debug(sprintf('Could not render passwordWithValue(): %s', $e->getMessage()));
$html = 'Could not render passwordWithValue.';
throw new FireflyException($html, 0, $e);
}
return $html;
}
/**
* Function to render a percentage.

View File

@@ -25,7 +25,10 @@ namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Configuration;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
/**
* Class FireflyConfig.
@@ -46,6 +49,25 @@ class FireflyConfig
return 1 === Configuration::where('name', $name)->count();
}
public function getEncrypted(string $name, $default = null): ?Configuration
{
$result = $this->get($name, $default);
if (null === $result) {
return null;
}
if ('' === $result->data) {
Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
return $result;
}
try {
$result->data = decrypt($result->data);
} catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result;
}
return $result;
}
/**
* @param null|bool|int|string $default
*
@@ -78,10 +100,18 @@ class FireflyConfig
return $this->set($name, $default);
}
/**
* @param mixed $value
*/
public function set(string $name, $value): Configuration
public function setEncrypted(string $name, mixed $value): Configuration
{
try {
$encrypted = encrypt($value);
} catch (EncryptException $e) {
Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage()));
throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name));
}
return $this->set($name, $encrypted);
}
public function set(string $name, mixed $value): Configuration
{
try {
$config = Configuration::whereName($name)->whereNull('deleted_at')->first();

View File

@@ -36,7 +36,6 @@ use FireflyIII\Models\Category;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Preference;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Role;
@@ -54,7 +53,6 @@ use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\UserRegistration;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Test\TestNotificationDiscord;
use FireflyIII\Notifications\Test\TestNotificationSlack;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -65,7 +63,6 @@ use Illuminate\Notifications\Notification;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Token;
use NotificationChannels\Pushover\PushoverReceiver;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -102,12 +99,6 @@ class User extends Authenticatable
throw new NotFoundHttpException();
}
public function routeNotificationForPushover()
{
return PushoverReceiver::withUserKey((string) config('services.pushover.user_token'))
->withApplicationToken((string) config('services.pushover.token'));
//return (string) config('services.pushover.token');
}
/**
* Link to accounts.
@@ -382,7 +373,6 @@ class User extends Authenticatable
}
return match ($driver) {
'database' => $this->notifications(),
'mail' => $email,
default => null,
};
@@ -404,30 +394,41 @@ class User extends Authenticatable
return $this->belongsToMany(Role::class);
}
public function routeNotificationForPushover(Notification $notification)
{
// this check does not validate if the user is owner, Should be done by notification itself.
$appToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$userToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
if (property_exists($notification, 'type') && $notification->type === 'owner') {
return PushoverReceiver::withUserKey($userToken)
->withApplicationToken($appToken);
}
throw new FireflyException('No pushover token found.');
// return PushoverReceiver::withUserKey((string) config('services.pushover.user_token'))
// ->withApplicationToken((string) config('services.pushover.token'));
//return (string) config('services.pushover.token');
}
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(Notification $notification): ?string
{
// this check does not validate if the user is owner, Should be done by notification itself.
$res = app('fireflyconfig')->get('slack_webhook_url', '')->data;
$res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (is_array($res)) {
$res = '';
}
$res = (string) $res;
// not the best way to do this, but alas.
if ($notification instanceof TestNotificationSlack) {
if (property_exists($notification, 'type') && $notification->type === 'owner') {
return $res;
}
if ($notification instanceof TestNotificationDiscord) {
$res = app('fireflyconfig')->get('discord_webhook_url', '')->data;
if (is_array($res)) {
$res = '';
}
return (string)$res;
}
// not the best way to do this, but alas.
if ($notification instanceof UserInvitation) {
return $res;
}
@@ -437,7 +438,7 @@ class User extends Authenticatable
if ($notification instanceof VersionCheckResult) {
return $res;
}
$pref = app('preferences')->getForUser($this, 'slack_webhook_url', '')->data;
$pref = app('preferences')->getEncryptedForUser($this, 'slack_webhook_url', '')->data;
if (is_array($pref)) {
return '';
}

View File

@@ -24,8 +24,8 @@ return [
'channels' => [
'email' => ['enabled' => true, 'ui_configurable' => 0,],
'slack' => ['enabled' => true, 'ui_configurable' => 1,],
'ntfy' => ['enabled' => true, 'ui_configurable' => 0,],
'pushover' => ['enabled' => true, 'ui_configurable' => 0,],
'ntfy' => ['enabled' => true, 'ui_configurable' => 1,],
'pushover' => ['enabled' => true, 'ui_configurable' => 1,],
'gotify' => ['enabled' => false, 'ui_configurable' => 0,],
'pushbullet' => ['enabled' => false, 'ui_configurable' => 0,],
],

View File

@@ -3,12 +3,12 @@
// config for Wijourdil/NtfyNotificationChannel
return [
'server' => env('NTFY_SERVER', 'https://ntfy.sh'),
'topic' => env('NTFY_TOPIC', ''),
'server' => 'https://ntfy.sh',
'topic' => '',
'authentication' => [
'enabled' => (bool) env('NTFY_AUTH_ENABLED', false),
'username' => env('NTFY_AUTH_USERNAME', ''),
'password' => env('NTFY_AUTH_PASSWORD', ''),
'enabled' => false,
'username' => '',
'password' => '',
],
];

View File

@@ -183,6 +183,7 @@ return [
'file',
'staticText',
'password',
'passwordWithValue',
'nonSelectableAmount',
'number',
'amountNoCurrency',

View File

@@ -2497,8 +2497,9 @@ return [
'notification_settings' => 'Settings for notifications',
'notification_settings_saved' => 'The notification settings have been saved',
'available_channels_title' => 'Available channels',
'available_channels_expl' => 'These channels are available to send notifications over. To test your confiuration, use the buttons below. Please note that the buttons have no spam control.',
'available_channels_expl' => 'These channels are available to send notifications over. To test your configuration, use the buttons below. Please note that the buttons have no spam control.',
'notification_channel_name_email' => 'Email',
'slack_discord_double' => 'The Slack notification channel can also send notifications to Discord.',
'notification_channel_name_slack' => 'Slack',
'notification_channel_name_ntfy' => 'Ntfy.sh',
'notification_channel_name_pushover' => 'Pushover',

View File

@@ -264,5 +264,12 @@ return [
'webhook_delivery' => 'Delivery',
'webhook_response' => 'Response',
'webhook_trigger' => 'Trigger',
'pushover_app_token' => 'Pushover app token',
'pushover_user_token' => 'Pushover user token',
'ntfy_server' => 'Ntfy server',
'ntfy_topic' => 'Ntfy topic',
'ntfy_auth' => 'Ntfy authentication enabled',
'ntfy_user' => 'Ntfy username',
'ntfy_pass' => 'Ntfy password',
];
// Ignore this comment

View File

@@ -24,7 +24,16 @@
</div>
{% endfor %}
<p style="margin-top:2em;">{{ 'channel_settings'|_ }}</p>
{{ ExpandedForm.text('slack_url', slackUrl, {'label' : 'slack_url_label'|_}) }}
{{ ExpandedForm.text('slack_webhook_url', slackUrl, {'label' : 'slack_url_label'|_, helpText: trans('firefly.slack_discord_double')}) }}
{{ ExpandedForm.text('pushover_app_token', pushoverAppToken, {}) }}
{{ ExpandedForm.text('pushover_user_token', pushoverUserToken, {}) }}
{{ ExpandedForm.text('ntfy_server', ntfyServer, {}) }}
{{ ExpandedForm.text('ntfy_topic', ntfyTopic, {}) }}
{{ ExpandedForm.checkbox('ntfy_auth','1', ntfyAuth, {}) }}
{{ ExpandedForm.text('ntfy_user', ntfyUser, {}) }}
{{ ExpandedForm.passwordWithValue('ntfy_pass', ntfyPass, {}) }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-success">