Skip to content

Commit

Permalink
Merge pull request #10368 from nextcloud/bugfix/noid/ensure-bot-uniqu…
Browse files Browse the repository at this point in the history
…eness

feat(bot)!: Ensure bot uniqueness
  • Loading branch information
nickvergessen authored Aug 25, 2023
2 parents 85bd830 + 335f7d6 commit 2161f97
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 154 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
]]></description>

<version>18.0.0-dev.4</version>
<version>18.0.0-dev.5</version>
<licence>agpl</licence>

<author>Daniel Calviño Sánchez</author>
Expand Down
275 changes: 138 additions & 137 deletions docs/occ.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
use OCA\Talk\Events\AttendeesAddedEvent;
use OCA\Talk\Events\AttendeesRemovedEvent;
use OCA\Talk\Events\BotInstallEvent;
use OCA\Talk\Events\BotUninstallEvent;
use OCA\Talk\Events\RoomEvent;
use OCA\Talk\Events\SendCallNotificationEvent;
use OCA\Talk\Federation\CloudFederationProviderTalk;
Expand Down Expand Up @@ -127,6 +128,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
$context->registerEventListener(AddFeaturePolicyEvent::class, FeaturePolicyListener::class);
$context->registerEventListener(BotInstallEvent::class, BotListener::class);
$context->registerEventListener(BotUninstallEvent::class, BotListener::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
$context->registerEventListener(GroupChangedEvent::class, DisplayNameListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
Expand Down
26 changes: 23 additions & 3 deletions lib/Command/Bot/Uninstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
use OC\Core\Command\Base;
use OCA\Talk\Model\BotConversationMapper;
use OCA\Talk\Model\BotServerMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Uninstall extends Base {
Expand All @@ -47,17 +49,35 @@ protected function configure(): void {
->setDescription('Uninstall a bot from the server')
->addArgument(
'id',
InputArgument::REQUIRED,
InputArgument::OPTIONAL,
'The ID of the bot'
)
->addOption(
'url',
null,
InputOption::VALUE_REQUIRED,
'The URL of the bot (required when no ID is given, ignored otherwise)'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$botId = (int) $input->getArgument('id');

$this->botConversationMapper->deleteByBotId($botId);
$this->botServerMapper->deleteById($botId);
try {
if ($botId === 0) {
$url = $input->getOption('url');
$bot = $this->botServerMapper->findByUrl($url);
} else {
$bot = $this->botServerMapper->findById($botId);
}
} catch (DoesNotExistException) {
$output->writeln('<error>Bot not found</error>');
return 1;
}

$this->botConversationMapper->deleteByBotId($bot->getId());
$this->botServerMapper->deleteById($bot->getId());

$output->writeln('<info>Bot uninstalled</info>');
return 0;
Expand Down
26 changes: 13 additions & 13 deletions lib/Command/Developer/UpdateDocs.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ protected function describeInputDefinition(Command $command): string {
if (\count($definition->getOptions()) > 0) {
$text .= "\n";

$text .= "| Options | Accept value | Is value required | Is multiple | Default |\n";
$text .= '|---|---|---|---|---|';
$text .= "| Options | Description | Accept value | Is value required | Is multiple | Default |\n";
$text .= '|---|---|---|---|---|---|';
foreach ($definition->getOptions() as $option) {
$describeInputOption = $this->describeInputOption($option);
if ($describeInputOption) {
Expand All @@ -135,26 +135,26 @@ protected function describeInputArgument(InputArgument $argument): string {
$description = $argument->getDescription();

return
'| `'.($argument->getName() ?: '<none>')."` | " .
($description ? preg_replace('/\s*[\r\n]\s*/', " ", $description) : '') . ' | ' .
($argument->isRequired() ? 'yes' : 'no')." | " .
($argument->isArray() ? 'yes' : 'no')." | " .
'`' . str_replace("\n", '', var_export($argument->getDefault(), true)) . "` |";
'| `'.($argument->getName() ?: '<none>') . '` | ' .
($description ? preg_replace('/\s*[\r\n]\s*/', ' ', $description) : '') . ' | ' .
($argument->isRequired() ? 'yes' : 'no') . ' | ' .
($argument->isArray() ? 'yes' : 'no') . ' | ' .
($argument->isRequired() ? '*Required*' : '`' . str_replace("\n", '', var_export($argument->getDefault(), true)) . '`') . ' |';
}

protected function describeInputOption(InputOption $option): string {
$name = '--'.$option->getName();
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
$name .= '\|-'.str_replace('|', '\|-', $option->getShortcut());
}
$description = $option->getDescription();

return
'| `'.$name.'` | ' .
'| `' . $name . '` | ' .
($description ? preg_replace('/\s*[\r\n]\s*/', " ", $description) : '') . ' | '.
($option->acceptValue() ? 'yes' : 'no')." | " .
($option->isValueRequired() ? 'yes' : 'no')." | " .
($option->isArray() ? 'yes' : 'no')." | " .
str_replace("\n", '', var_export($option->getDefault(), true)).'` |';
($option->acceptValue() ? 'yes' : 'no') . ' | ' .
($option->isValueRequired() ? 'yes' : 'no') . ' | ' .
($option->isArray() ? 'yes' : 'no') . ' | ' .
($option->isValueRequired() ? '*Required*' : '`' . str_replace("\n", '', var_export($option->getDefault(), true)) . '`') . ' |';
}
}
45 changes: 45 additions & 0 deletions lib/Events/BotUninstallEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Talk\Events;

use OCP\EventDispatcher\Event;

class BotUninstallEvent extends Event {
public function __construct(
protected string $secret,
protected string $url,
) {
parent::__construct();
}

public function getSecret(): string {
return $this->secret;
}

public function getUrl(): string {
return $this->url;
}
}
15 changes: 15 additions & 0 deletions lib/Listener/BotListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Events\BotInstallEvent;
use OCA\Talk\Events\BotUninstallEvent;
use OCA\Talk\Events\ChatEvent;
use OCA\Talk\Events\ChatParticipantEvent;
use OCA\Talk\Model\Bot;
use OCA\Talk\Model\BotConversationMapper;
use OCA\Talk\Model\BotServer;
use OCA\Talk\Model\BotServerMapper;
use OCA\Talk\Service\BotService;
Expand All @@ -47,6 +49,7 @@
class BotListener implements IEventListener {
public function __construct(
protected BotServerMapper $botServerMapper,
protected BotConversationMapper $botConversationMapper,
) {
}

Expand Down Expand Up @@ -78,6 +81,9 @@ public function handle(Event $event): void {
if ($event instanceof BotInstallEvent) {
$this->handleBotInstallEvent($event);
}
if ($event instanceof BotUninstallEvent) {
$this->handleBotUninstallEvent($event);
}
}

protected function handleBotInstallEvent(BotInstallEvent $event): void {
Expand All @@ -98,4 +104,13 @@ protected function handleBotInstallEvent(BotInstallEvent $event): void {
$this->botServerMapper->insert($bot);
}
}

protected function handleBotUninstallEvent(BotUninstallEvent $event): void {
try {
$bot = $this->botServerMapper->findByUrlAndSecret($event->getUrl(), $event->getSecret());
$this->botConversationMapper->deleteByBotId($bot->getId());
$this->botServerMapper->delete($bot);
} catch (DoesNotExistException) {
}
}
}
3 changes: 3 additions & 0 deletions lib/Migration/Version18000Date20230504205823.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt

$table->setPrimaryKey(['id']);
$table->addIndex(['state'], 'talk_bots_server_state');
$table->addUniqueIndex(['url_hash'], 'talk_bots_server_urlhash');
$table->addUniqueIndex(['secret'], 'talk_bots_server_secret');

$table = $schema->createTable('talk_bots_conversation');
$table->addColumn('id', Types::BIGINT, [
Expand All @@ -109,6 +111,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
$table->setPrimaryKey(['id']);
$table->addIndex(['token', 'state'], 'talk_bots_convo_token');
$table->addIndex(['bot_id'], 'talk_bots_convo_id');
$table->addUniqueIndex(['bot_id', 'token'], 'talk_bots_convo_uniq');
return $schema;
}

Expand Down
63 changes: 63 additions & 0 deletions lib/Migration/Version18000Date20230824123939.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2023, Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Talk\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version18000Date20230824123939 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$modified = false;
$table = $schema->getTable('talk_bots_server');
if (!$table->hasIndex('talk_bots_server_urlhash')) {
$table->addUniqueIndex(['url_hash'], 'talk_bots_server_urlhash');
$modified = true;
}
if (!$table->hasIndex('talk_bots_server_secret')) {
$table->addUniqueIndex(['secret'], 'talk_bots_server_secret');
$modified = true;
}

$table = $schema->getTable('talk_bots_conversation');
if (!$table->hasIndex('talk_bots_convo_uniq')) {
$table->addUniqueIndex(['bot_id', 'token'], 'talk_bots_convo_uniq');
$modified = true;
}

return $modified ? $schema : null;
}
}
14 changes: 14 additions & 0 deletions lib/Model/BotServerMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ public function findByUrlAndSecret(string $url, string $secret): BotServer {
return $this->findEntity($query);
}

/**
* @throws DoesNotExistException
*/
public function findByUrl(string $url): BotServer {
$urlHash = sha1($url);

$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('url_hash', $query->createNamedParameter($urlHash)));

return $this->findEntity($query);
}

public function deleteById(int $botId): int {
$query = $this->db->getQueryBuilder();
$query->delete($this->getTableName())
Expand Down

0 comments on commit 2161f97

Please sign in to comment.