feat: Email option in `occ user:add` commandtags/v29.0.0beta1
@@ -2,6 +2,7 @@ | |||
/** | |||
* @copyright Copyright (c) 2016, ownCloud, Inc. | |||
* | |||
* @author Anupam Kumar <kyteinsky@gmail.com> | |||
* @author Arthur Schiwon <blizzz@arthur-schiwon.de> | |||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* @author Joas Schilling <coding@schilljs.com> | |||
@@ -26,10 +27,16 @@ | |||
namespace OC\Core\Command\User; | |||
use OC\Files\Filesystem; | |||
use OCA\Settings\Mailer\NewUserMailHelper; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IAppConfig; | |||
use OCP\IGroup; | |||
use OCP\IGroupManager; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\Mail\IMailer; | |||
use OCP\Security\Events\GenerateSecurePasswordEvent; | |||
use OCP\Security\ISecureRandom; | |||
use Symfony\Component\Console\Command\Command; | |||
use Symfony\Component\Console\Helper\QuestionHelper; | |||
use Symfony\Component\Console\Input\InputArgument; | |||
@@ -42,11 +49,16 @@ class Add extends Command { | |||
public function __construct( | |||
protected IUserManager $userManager, | |||
protected IGroupManager $groupManager, | |||
protected IMailer $mailer, | |||
private IAppConfig $appConfig, | |||
private NewUserMailHelper $mailHelper, | |||
private IEventDispatcher $eventDispatcher, | |||
private ISecureRandom $secureRandom, | |||
) { | |||
parent::__construct(); | |||
} | |||
protected function configure() { | |||
protected function configure(): void { | |||
$this | |||
->setName('user:add') | |||
->setDescription('adds an account') | |||
@@ -61,6 +73,12 @@ class Add extends Command { | |||
InputOption::VALUE_NONE, | |||
'read password from environment variable OC_PASS' | |||
) | |||
->addOption( | |||
'generate-password', | |||
null, | |||
InputOption::VALUE_NONE, | |||
'Generate a secure password. A welcome email with a reset link will be sent to the user via an email if --email option and newUser.sendEmail config are set' | |||
) | |||
->addOption( | |||
'display-name', | |||
null, | |||
@@ -72,6 +90,12 @@ class Add extends Command { | |||
'g', | |||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, | |||
'groups the account should be added to (The group will be created if it does not exist)' | |||
) | |||
->addOption( | |||
'email', | |||
null, | |||
InputOption::VALUE_REQUIRED, | |||
'When set, users may register using the default email verification workflow' | |||
); | |||
} | |||
@@ -82,12 +106,20 @@ class Add extends Command { | |||
return 1; | |||
} | |||
$password = ''; | |||
// Setup password. | |||
if ($input->getOption('password-from-env')) { | |||
$password = getenv('OC_PASS'); | |||
if (!$password) { | |||
$output->writeln('<error>--password-from-env given, but OC_PASS is empty!</error>'); | |||
return 1; | |||
} | |||
} elseif ($input->getOption('generate-password')) { | |||
$passwordEvent = new GenerateSecurePasswordEvent(); | |||
$this->eventDispatcher->dispatchTyped($passwordEvent); | |||
$password = $passwordEvent->getPassword() ?? $this->secureRandom->generate(20); | |||
} elseif ($input->isInteractive()) { | |||
/** @var QuestionHelper $helper */ | |||
$helper = $this->getHelper('question'); | |||
@@ -105,21 +137,20 @@ class Add extends Command { | |||
return 1; | |||
} | |||
} else { | |||
$output->writeln("<error>Interactive input or --password-from-env is needed for entering a password!</error>"); | |||
$output->writeln("<error>Interactive input or --password-from-env or --generate-password is needed for setting a password!</error>"); | |||
return 1; | |||
} | |||
try { | |||
$user = $this->userManager->createUser( | |||
$input->getArgument('uid'), | |||
$password | |||
$password, | |||
); | |||
} catch (\Exception $e) { | |||
$output->writeln('<error>' . $e->getMessage() . '</error>'); | |||
return 1; | |||
} | |||
if ($user instanceof IUser) { | |||
$output->writeln('<info>The account "' . $user->getUID() . '" was created successfully</info>'); | |||
} else { | |||
@@ -154,6 +185,30 @@ class Add extends Command { | |||
$output->writeln('Account "' . $user->getUID() . '" added to group "' . $group->getGID() . '"'); | |||
} | |||
} | |||
$email = $input->getOption('email'); | |||
if (!empty($email)) { | |||
if (!$this->mailer->validateMailAddress($email)) { | |||
$output->writeln(\sprintf( | |||
'<error>The given email address "%s" is invalid. Email not set for the user.</error>', | |||
$email, | |||
)); | |||
return 1; | |||
} | |||
$user->setSystemEMailAddress($email); | |||
if ($this->appConfig->getValueString('core', 'newUser.sendEmail', 'yes') === 'yes') { | |||
try { | |||
$this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true)); | |||
$output->writeln('Welcome email sent to ' . $email); | |||
} catch (\Exception $e) { | |||
$output->writeln('Unable to send the welcome email to ' . $email); | |||
} | |||
} | |||
} | |||
return 0; | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
<?php | |||
/** | |||
* @copyright Copyright (c) 2021, Philip Gatzka (philip.gatzka@mailbox.org) | |||
* | |||
* @author Philip Gatzka <philip.gatzka@mailbox.org> | |||
* | |||
* @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/>. | |||
* | |||
*/ | |||
declare(strict_types=1); | |||
namespace Core\Command\User; | |||
use OC\Core\Command\User\Add; | |||
use OCA\Settings\Mailer\NewUserMailHelper; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\IAppConfig; | |||
use OCP\IGroupManager; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\Mail\IEMailTemplate; | |||
use OCP\mail\IMailer; | |||
use OCP\Security\ISecureRandom; | |||
use Symfony\Component\Console\Input\InputInterface; | |||
use Symfony\Component\Console\Output\OutputInterface; | |||
use Test\TestCase; | |||
class AddTest extends TestCase { | |||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $userManager; | |||
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $groupManager; | |||
/** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $mailer; | |||
/** @var IAppConfig|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $appConfig; | |||
/** @var NewUserMailHelper|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $mailHelper; | |||
/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $eventDispatcher; | |||
/** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $secureRandom; | |||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $user; | |||
/** @var InputInterface|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $consoleInput; | |||
/** @var OutputInterface|\PHPUnit\Framework\MockObject\MockObject */ | |||
private $consoleOutput; | |||
/** @var Add */ | |||
private $addCommand; | |||
public function setUp(): void { | |||
parent::setUp(); | |||
$this->userManager = static::createMock(IUserManager::class); | |||
$this->groupManager = static::createStub(IGroupManager::class); | |||
$this->mailer = static::createMock(IMailer::class); | |||
$this->appConfig = static::createMock(IAppConfig::class); | |||
$this->mailHelper = static::createMock(NewUserMailHelper::class); | |||
$this->eventDispatcher = static::createStub(IEventDispatcher::class); | |||
$this->secureRandom = static::createStub(ISecureRandom::class); | |||
$this->user = static::createMock(IUser::class); | |||
$this->consoleInput = static::createMock(InputInterface::class); | |||
$this->consoleOutput = static::createMock(OutputInterface::class); | |||
$this->addCommand = new Add( | |||
$this->userManager, | |||
$this->groupManager, | |||
$this->mailer, | |||
$this->appConfig, | |||
$this->mailHelper, | |||
$this->eventDispatcher, | |||
$this->secureRandom | |||
); | |||
} | |||
/** | |||
* @dataProvider addEmailDataProvider | |||
*/ | |||
public function testAddEmail( | |||
?string $email, | |||
bool $isEmailValid, | |||
bool $shouldSendEmail, | |||
): void { | |||
$this->user->expects($isEmailValid ? static::once() : static::never()) | |||
->method('setSystemEMailAddress') | |||
->with(static::equalTo($email)); | |||
$this->userManager->method('createUser') | |||
->willReturn($this->user); | |||
$this->appConfig->method('getValueString') | |||
->willReturn($shouldSendEmail ? 'yes' : 'no'); | |||
$this->mailer->method('validateMailAddress') | |||
->willReturn($isEmailValid); | |||
$this->mailHelper->method('generateTemplate') | |||
->willReturn(static::createMock(IEMailTemplate::class)); | |||
$this->mailHelper->expects($isEmailValid && $shouldSendEmail ? static::once() : static::never()) | |||
->method('sendMail'); | |||
$this->consoleInput->method('getOption') | |||
->will(static::returnValueMap([ | |||
['generate-password', 'true'], | |||
['email', $email], | |||
['group', []], | |||
])); | |||
$this->invokePrivate($this->addCommand, 'execute', [ | |||
$this->consoleInput, | |||
$this->consoleOutput | |||
]); | |||
} | |||
/** | |||
* @return array | |||
*/ | |||
public function addEmailDataProvider(): array { | |||
return [ | |||
'Valid E-Mail' => [ | |||
'info@example.com', | |||
true, | |||
true, | |||
], | |||
'Invalid E-Mail' => [ | |||
'info@@example.com', | |||
false, | |||
false, | |||
], | |||
'No E-Mail' => [ | |||
'', | |||
false, | |||
false, | |||
], | |||
'Valid E-Mail, but no mail should be sent' => [ | |||
'info@example.com', | |||
true, | |||
false, | |||
], | |||
]; | |||
} | |||
} |