Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Email option in occ user:add command #40726

Merged
merged 4 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions core/Command/User/Add.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Anupam Kumar <[email protected]>
* @author Arthur Schiwon <[email protected]>
* @author Christoph Wurst <[email protected]>
* @author Joas Schilling <[email protected]>
Expand All @@ -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;
Expand All @@ -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')
Expand All @@ -61,6 +73,12 @@ protected function configure() {
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,
Expand All @@ -72,6 +90,12 @@ protected function configure() {
'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'
);
}

Expand All @@ -82,12 +106,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
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');
Expand All @@ -105,21 +137,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
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 {
Expand Down Expand Up @@ -154,6 +185,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$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;
}
}
170 changes: 170 additions & 0 deletions tests/Core/Command/User/AddTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php
/**
* @copyright Copyright (c) 2021, Philip Gatzka ([email protected])
*
* @author Philip Gatzka <[email protected]>
*
* @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' => [
'[email protected]',
true,
true,
],
'Invalid E-Mail' => [
'info@@example.com',
false,
false,
],
'No E-Mail' => [
'',
false,
false,
],
'Valid E-Mail, but no mail should be sent' => [
'[email protected]',
true,
false,
],
];
}
}
Loading