diff --git a/css/settings.css b/css/settings.css index 206ec5b39..b8f30235e 100644 --- a/css/settings.css +++ b/css/settings.css @@ -25,3 +25,11 @@ table.activitysettings td.small label { .nav-icon-activity { background-image: url('../img/activity-dark.svg?v=1'); } + +#webhook_settings_form { + display:inline-block; +} + +#webhook_url { + width: 260px; +} diff --git a/js/admin.js b/js/admin.js index 893411167..cf17e9f92 100644 --- a/js/admin.js +++ b/js/admin.js @@ -32,5 +32,31 @@ $(document).ready(function() { 'activity', 'enable_email', $(this).attr('checked') === 'checked' ? 'yes' : 'no' ); - }) + }); + + $('#activity_webhook_enabled').on('change', function() { + OCP.AppConfig.setValue( + 'activity', 'enable_webhook', + $(this).attr('checked') === 'checked' ? 'yes' : 'no' + ); + }); + + $('#activity_webhook_ssl_verification').on('change', function() { + OCP.AppConfig.setValue( + 'activity', 'webhook_ssl_verification_enabled', + $(this).attr('checked') === 'checked' ? 'yes' : 'no' + ); + }); + + $('#webhook_settings_form').on('submit', function(event) { + event.preventDefault(); + OCP.AppConfig.setValue( + 'activity', 'webhook_url', + $("#webhook_url").val() + ); + OCP.AppConfig.setValue( + 'activity', 'webhook_token', + $("#webhook_token").val() + ); + }); }); diff --git a/lib/Consumer.php b/lib/Consumer.php index f9cc1ea03..88fa8ed9a 100644 --- a/lib/Consumer.php +++ b/lib/Consumer.php @@ -66,6 +66,7 @@ public function receive(IEvent $event) { $selfAction = $event->getAffectedUser() === $event->getAuthor(); $streamSetting = $this->userSettings->getUserSetting($event->getAffectedUser(), 'stream', $event->getType()); $emailSetting = $this->userSettings->getUserSetting($event->getAffectedUser(), 'email', $event->getType()); + $webhookSetting = $this->userSettings->getUserSetting($event->getAffectedUser(), 'webhook', $event->getType()); $emailSetting = ($emailSetting) ? $this->userSettings->getUserSetting($event->getAffectedUser(), 'setting', 'batchtime') : false; // User is not the author or wants to see their own actions @@ -76,6 +77,10 @@ public function receive(IEvent $event) { $this->data->send($event); } + if($webhookSetting) { + $this->data->sendWebhookRequest($event); + } + // User is not the author or wants to see their own actions $createEmail = !$selfAction || $this->userSettings->getUserSetting($event->getAffectedUser(), 'setting', 'selfemail'); diff --git a/lib/Controller/Settings.php b/lib/Controller/Settings.php index d82483ec1..ae333b6b3 100644 --- a/lib/Controller/Settings.php +++ b/lib/Controller/Settings.php @@ -105,6 +105,7 @@ public function personal( $notify_setting_selfemail = false) { $settings = $this->manager->getSettings(); + $webhookEnabled = $this->config->getAppValue('activity', 'enable_webhook', 'no') === 'yes'; foreach ($settings as $setting) { if ($setting->canChangeStream()) { $this->config->setUserValue( @@ -121,6 +122,14 @@ public function personal( (int) $this->request->getParam($setting->getIdentifier() . '_email', false) ); } + + if ($webhookEnabled) { + $this->config->setUserValue( + $this->user, 'activity', + 'notify_webhook_' . $setting->getIdentifier(), + (int) $this->request->getParam($setting->getIdentifier() . '_webhook', false) + ); + } } $email_batch_time = 3600; @@ -167,6 +176,7 @@ public function admin( $notify_setting_selfemail = false) { $settings = $this->manager->getSettings(); + $webhookEnabled = $this->config->getAppValue('activity', 'enable_webhook', 'no') === 'yes'; foreach ($settings as $setting) { if ($setting->canChangeStream()) { $this->config->setAppValue( @@ -183,6 +193,14 @@ public function admin( (int) $this->request->getParam($setting->getIdentifier() . '_email', false) ); } + + if ($webhookEnabled) { + $this->config->setAppValue( + 'activity', + 'notify_webhook_' . $setting->getIdentifier(), + (int) $this->request->getParam($setting->getIdentifier() . '_webhook', false) + ); + } } $email_batch_time = 3600; diff --git a/lib/Data.php b/lib/Data.php index 6f61de86d..fe078d646 100755 --- a/lib/Data.php +++ b/lib/Data.php @@ -42,13 +42,17 @@ class Data { /** @var IDBConnection */ protected $connection; + /** @var WebhookHelper */ + protected $webhookHelper; + /** * @param IManager $activityManager * @param IDBConnection $connection */ - public function __construct(IManager $activityManager, IDBConnection $connection) { + public function __construct(IManager $activityManager, IDBConnection $connection, WebhookHelper $webhookHelper) { $this->activityManager = $activityManager; $this->connection = $connection; + $this->webhookHelper = $webhookHelper; } /** @@ -102,6 +106,32 @@ public function send(IEvent $event) { return true; } + /** + * Send an http(s) request for the event to the webhook url + * + * @param IEvent $event + * @return bool + */ + public function sendWebhookRequest(IEvent $event) { + $content = [ + 'app' => $event->getApp(), + 'type' => $event->getType(), + 'affecteduser' => $event->getAffectedUser(), + 'user' => $event->getAuthor(), + 'timestamp' => (int) $event->getTimestamp(), + 'subject' => $event->getSubject(), + 'subjectparams' => $event->getSubjectParameters(), + 'message' => $event->getMessage(), + 'messageparams' => $event->getMessageParameters(), + 'object_type' => $event->getObjectType(), + 'object_id' => (int) $event->getObjectId(), + 'object_name' => $event->getObjectName(), + 'link' => $event->getLink(), + ]; + $this->webhookHelper->sendWebhookRequest($content); + return true; + } + /** * Send an event as email * diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php index 44d4ea5ed..971aa16f8 100755 --- a/lib/FilesHooks.php +++ b/lib/FilesHooks.php @@ -220,6 +220,7 @@ protected function addNotificationsForFileAction($filePath, $activityType, $subj $fileId, $path, true, !empty($filteredStreamUsers[$user]), $filteredEmailUsers[$user] ?? false, + $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'webhook', $activityType), $activityType ); } @@ -405,6 +406,7 @@ protected function fileRenaming($oldPath, $newPath) { $fileId, $path . '/' . $fileName, true, !empty($filteredStreamUsers[$user]), $filteredEmailUsers[$user] ?? false, + $this->userSettings->filterUsersBySetting(array_keys($affectedUsers), 'webhook', Files::TYPE_SHARE_CHANGED), Files::TYPE_SHARE_CHANGED ); } @@ -505,6 +507,7 @@ protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileN $fileId, $path . '/' . $oldFileName, true, !empty($filteredStreamUsers[$user]), $filteredEmailUsers[$user] ?? false, + $this->userSettings->filterUsersBySetting($users, 'webhook', Files::TYPE_SHARE_DELETED), Files::TYPE_SHARE_DELETED ); } @@ -544,6 +547,7 @@ protected function generateAddActivities($users, $pathMap, $fileId, $fileName) { $fileId, $path . '/' . $fileName, true, !empty($filteredStreamUsers[$user]), $filteredEmailUsers[$user] ?? false, + $this->userSettings->filterUsersBySetting($users, 'webhook', Files::TYPE_SHARE_CREATED), Files::TYPE_SHARE_CREATED ); } @@ -590,6 +594,7 @@ protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $afterPathMap[$user] . '/' . $fileName, true, !empty($filteredStreamUsers[$user]), $filteredEmailUsers[$user] ?? false, + $this->userSettings->filterUsersBySetting($users, 'webhook', Files::TYPE_SHARE_CHANGED), Files::TYPE_SHARE_CHANGED ); } @@ -703,7 +708,8 @@ protected function shareWithUser($shareWith, $fileSource, $itemType, $fileTarget $shareWith, 'shared_with_by', [[$fileSource => $fileTarget], $this->currentUser->getUserIdentifier()], (int) $fileSource, $fileTarget, $itemType === 'file', $this->userSettings->getUserSetting($shareWith, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($shareWith, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($shareWith, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($shareWith, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($shareWith, 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($shareWith, 'webhook', Files_Sharing::TYPE_SHARED) ); } @@ -760,7 +766,8 @@ protected function shareByLink($fileSource, $itemType, $linkOwner) { $linkOwner, 'shared_link_self', [[$fileSource => $path]], (int) $fileSource, $path, $itemType === 'file', $this->userSettings->getUserSetting($linkOwner, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($linkOwner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($linkOwner, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($linkOwner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($linkOwner, 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($linkOwner, 'webhook', Files_Sharing::TYPE_SHARED) ); } @@ -806,7 +813,8 @@ protected function unshareFromUser(IShare $share) { $share->getSharedWith(), 'unshared_by', [[$share->getNodeId() => $share->getTarget()], $this->currentUser->getUserIdentifier()], $share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file', $this->userSettings->getUserSetting($share->getSharedWith(), 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($share->getSharedWith(), 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($share->getSharedWith(), 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($share->getSharedWith(), 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($share->getSharedWith(), 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($share->getSharedWith(), 'webhook', Files_Sharing::TYPE_SHARED) ); } @@ -875,7 +883,9 @@ protected function unshareLink(IShare $share) { $owner, $actionSharer, [[$share->getNodeId() => $share->getTarget()]], $share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file', $this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false, + + $this->userSettings->getUserSetting($owner, 'webhook', Files_Sharing::TYPE_SHARED) ); if ($share->getSharedBy() !== $share->getShareOwner()) { @@ -884,7 +894,8 @@ protected function unshareLink(IShare $share) { $owner, $actionOwner, [[$share->getNodeId() => $share->getTarget()], $share->getSharedBy()], $share->getNodeId(), $share->getTarget(), $share->getNodeType() === 'file', $this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($owner, 'webhook', Files_Sharing::TYPE_SHARED) ); } } @@ -925,7 +936,8 @@ protected function addNotificationsForGroupUsers(array $usersInGroup, $actionUse $user, $actionUser, [[$fileSource => $path], $this->currentUser->getUserIdentifier()], $fileSource, $path, ($itemType === 'file'), !empty($filteredStreamUsersInGroup[$user]), - $filteredEmailUsersInGroup[$user] ?? false + $filteredEmailUsersInGroup[$user] ?? false, + $this->userSettings->filterUsersBySetting($userIds, 'webhook', Files_Sharing::TYPE_SHARED) ); } } @@ -979,7 +991,8 @@ protected function shareNotificationForSharer($subject, $shareWith, $fileSource, $sharer, $subject, [[$fileSource => $path], $shareWith], $fileSource, $path, ($itemType === 'file'), $this->userSettings->getUserSetting($sharer, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($sharer, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($sharer, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($sharer, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($sharer, 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($sharer, 'webhook', Files_Sharing::TYPE_SHARED) ); } @@ -1005,7 +1018,8 @@ protected function reshareNotificationForSharer($owner, $subject, $shareWith, $f $owner, $subject, [[$fileSource => $path], $this->currentUser->getUserIdentifier(), $shareWith], $fileSource, $path, ($itemType === 'file'), $this->userSettings->getUserSetting($owner, 'stream', Files_Sharing::TYPE_SHARED), - $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false + $this->userSettings->getUserSetting($owner, 'email', Files_Sharing::TYPE_SHARED) ? $this->userSettings->getUserSetting($owner, 'setting', 'batchtime') : false, + $this->userSettings->getUserSetting($owner, 'webhook', Files_Sharing::TYPE_SHARED) ); } @@ -1070,9 +1084,10 @@ protected function shareNotificationForOriginalOwners($currentOwner, $subject, $ * @param bool $isFile If the item is a file, we link to the parent directory * @param bool $streamSetting * @param int $emailSetting + * @param bool $webhookSetting * @param string $type */ - protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $type = Files_Sharing::TYPE_SHARED) { + protected function addNotificationsForUser($user, $subject, $subjectParams, $fileId, $path, $isFile, $streamSetting, $emailSetting, $webhookSetting, $type = Files_Sharing::TYPE_SHARED) { if (!$streamSetting && !$emailSetting) { return; } @@ -1114,5 +1129,9 @@ protected function addNotificationsForUser($user, $subject, $subjectParams, $fil $latestSend = time() + $emailSetting; $this->activityData->storeMail($event, $latestSend); } + + if($webhookSetting) { + $this->activityData->sendWebhookRequest($event); + } } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 3b9c4e3e9..bd2f15061 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -84,6 +84,7 @@ public function getForm() { if ($setting->canChangeMail()) { $methods[] = IExtension::METHOD_MAIL; } + $methods[] = 'webhook'; $identifier = $setting->getIdentifier(); @@ -91,6 +92,7 @@ public function getForm() { 'desc' => $setting->getName(), IExtension::METHOD_MAIL => $this->userSettings->getConfigSetting('email', $identifier), IExtension::METHOD_STREAM => $this->userSettings->getConfigSetting('stream', $identifier), + 'webhook' => $this->userSettings->getConfigSetting('webhook', $identifier), 'methods' => $methods, ); } @@ -110,6 +112,10 @@ public function getForm() { 'activities' => $activities, 'is_email_set' => true, 'email_enabled' => $this->config->getAppValue('activity', 'enable_email', 'yes') === 'yes', + 'webhook_enabled' => $this->config->getAppValue('activity', 'enable_webhook', 'no') === 'yes', + 'webhook_url' => $this->config->getAppValue('activity', 'webhook_url', ''), + 'webhook_token' => $this->config->getAppValue('activity', 'webhook_token', ''), + 'webhook_ssl_verification_enabled' => $this->config->getAppValue('activity', 'webhook_ssl_verification_enabled', 'yes') === 'yes', 'setting_batchtime' => $settingBatchTime, @@ -119,6 +125,7 @@ public function getForm() { 'methods' => [ IExtension::METHOD_MAIL => $this->l10n->t('Mail'), IExtension::METHOD_STREAM => $this->l10n->t('Stream'), + 'webhook' => $this->l10n->t('Webhook'), ], ], 'blank'); } diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index e5847d92c..1a8fd3f73 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -83,9 +83,10 @@ public function getForm() { return $a->getPriority() > $b->getPriority(); }); + $webhookEnabled = $this->config->getAppValue('activity', 'enable_webhook', 'no') === 'yes'; $activities = []; foreach ($settings as $setting) { - if (!$setting->canChangeStream() && !$setting->canChangeMail()) { + if (!$setting->canChangeStream() && !$setting->canChangeMail() && !$webhookEnabled) { // No setting can be changed => don't display continue; } @@ -97,6 +98,9 @@ public function getForm() { if ($setting->canChangeMail()) { $methods[] = IExtension::METHOD_MAIL; } + if ($webhookEnabled) { + $methods[] = 'webhook'; + } $identifier = $setting->getIdentifier(); @@ -104,6 +108,7 @@ public function getForm() { 'desc' => $setting->getName(), IExtension::METHOD_MAIL => $this->userSettings->getUserSetting($this->user, 'email', $identifier), IExtension::METHOD_STREAM => $this->userSettings->getUserSetting($this->user, 'stream', $identifier), + 'webhook' => $this->userSettings->getUserSetting($this->user, 'webhook', $identifier), 'methods' => $methods, ); } @@ -129,6 +134,9 @@ public function getForm() { IExtension::METHOD_STREAM => $this->l10n->t('Stream'), ]; } + if ($webhookEnabled) { + $methods['webhook'] = $this->l10n->t('Webhook'); + } return new TemplateResponse('activity', 'settings/personal', [ 'setting' => 'personal', diff --git a/lib/WebhookHelper.php b/lib/WebhookHelper.php new file mode 100644 index 000000000..d61bcc502 --- /dev/null +++ b/lib/WebhookHelper.php @@ -0,0 +1,74 @@ + + * + * @author Tinko Bartels + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\Activity; + +use OCP\IConfig; +use OCP\ILogger; + +class WebhookHelper { + + /** @var IConfig */ + protected $config; + + /** @var ILogger */ + protected $logger; + + /** + * @param IConfig $config + * @param ILogger $logger + */ + public function __construct(IConfig $config, ILogger $logger) { + $this->config = $config; + $this->logger = $logger; + } + + /** + * @param array $content + */ + public function sendWebhookRequest($content) { + $url = $this->config->getAppValue('activity', 'webhook_url', ''); + $token = $this->config->getAppValue('activity', 'webhook_token', ''); + $ssl_verification = $this->config->getAppValue('activity', 'webhook_ssl_verification_enabled', 'yes') === 'yes'; + $content_string = json_encode($content); + $headers = array( 'X-Nextcloud-Token: '.$token, 'Content-Type: application/json', 'Content-Length: '.strlen($content_string) ); + $ch = curl_init(); + curl_setopt($ch,CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_POSTFIELDS, $content_string); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $ssl_verification?2:0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $ssl_verification); + $result = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + if(curl_error($ch)) { + $error_msg = curl_error($ch); + $this->logger->error("Webhook-Error: ".$error_msg); + } + if($httpCode>=400) { + $this->logger->error("Webhook-Error: HTTP code ".$httpCode); + } + curl_close($ch); + } + +} diff --git a/templates/settings/admin.php b/templates/settings/admin.php index 09217fcf5..bcbad4772 100644 --- a/templates/settings/admin.php +++ b/templates/settings/admin.php @@ -28,10 +28,31 @@

t('Activity')); ?>

+ + /> + + +
+ + + +
+
+ + /> + +
/> +