diff --git a/core/Command/User/Inactive.php b/core/Command/User/Inactive.php new file mode 100644 index 000000000000..5ad91df08e78 --- /dev/null +++ b/core/Command/User/Inactive.php @@ -0,0 +1,81 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @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 OC\Core\Command\User; + +use OC\Core\Command\Base; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Inactive extends Base { + /** @var \OCP\IUserManager */ + protected $userManager; + + /** + * @param IUserManager $userManager + */ + public function __construct(IUserManager $userManager) { + parent::__construct(); + $this->userManager = $userManager; + } + + protected function configure() { + $this + ->setName('user:inactive') + ->setDescription('reports users who are known to owncloud, but have not logged in for a certain number of days') + ->addArgument( + 'days', + InputArgument::REQUIRED, + 'Integer number of days that the user has not logged in since' + ); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $days = $input->getArgument('days'); + if(!is_int($days) || $days < 1) { + throw new InvalidArgumentException('Days must be integer and above zero'); + } + + $now = time(); + + $inactive = []; + $this->userManager->callForSeenUsers(function(IUser $user) use (&$inactive, $days, $now) { + $lastLogin = $user->getLastLogin(); + // Difference between now and last login, into days, and rounded down + $daysSinceLastLogin = floor(($now - $lastLogin) / (60*60*24)); + if($daysSinceLastLogin >= $days) { + $inactive[] = [ + 'uid' => $user->getUID(), + 'displayName' => $user->getDisplayName(), + 'inactiveSinceDays' => $daysSinceLastLogin + ]; + } + }); + + $this->writeArrayInOutputFormat($input, $output, $inactive); + + } +} diff --git a/core/register_command.php b/core/register_command.php index 5a3605212e9c..3fb7138f48a2 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -144,6 +144,7 @@ $application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager())); $application->add(new OC\Core\Command\User\Setting(\OC::$server->getUserManager(), \OC::$server->getConfig(), \OC::$server->getDatabaseConnection())); $application->add(new OC\Core\Command\User\SyncBackend(\OC::$server->getAccountMapper(), \OC::$server->getConfig(), \OC::$server->getUserManager(), \OC::$server->getLogger())); + $application->add(new \OC\Core\Command\User\Inactive(\OC::$server->getUserManager())); $application->add(new OC\Core\Command\Group\Add(\OC::$server->getGroupManager())); $application->add(new OC\Core\Command\Group\Delete(\OC::$server->getGroupManager())); diff --git a/tests/Core/Command/User/InactiveTest.php b/tests/Core/Command/User/InactiveTest.php new file mode 100644 index 000000000000..342179646c49 --- /dev/null +++ b/tests/Core/Command/User/InactiveTest.php @@ -0,0 +1,135 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH. + * @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 Tests\Core\Command\User; + +use OC\Core\Command\Base; +use OC\Core\Command\User\Inactive; +use OCP\IUser; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Test\TestCase; + +class InactiveTest extends TestCase { + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleInput; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $consoleOutput; + + /** @var \Symfony\Component\Console\Command\Command */ + protected $command; + + protected function setUp() { + parent::setUp(); + + $userManager = $this->userManager = $this->getMockBuilder('OCP\IUserManager') + ->disableOriginalConstructor() + ->getMock(); + $this->consoleInput = $this->createMock('Symfony\Component\Console\Input\InputInterface'); + $this->consoleOutput = $this->createMock('Symfony\Component\Console\Output\OutputInterface'); + + /** @var \OCP\IUserManager $userManager */ + $this->command = new Inactive($userManager); + } + + public function invalidDays() { + return [ + [0], + ['test'], + [-1] + ]; + } + + /** + * @dataProvider invalidDays + */ + public function testWithInvalidDays($days) { + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('days') + ->willReturn($days); + + $this->expectException(InvalidArgumentException::class); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + protected function dummyUser($lastLogin) { + $user = $this->createMock(IUser::class); + $user->expects($this->once()) + ->method('getLastLogin') + ->willReturn($lastLogin); + + return $user; + } + + public function validDays() { + return [ + 'no users' => [[], 10, 0], + '1 recent user, excluded' => [[$this->dummyUser(time()-2*24*60*60)], 10, 0], + '1 recent user, included' => [[$this->dummyUser(time()-2*24*60*60)], 1, 1], + '2 users 1 includd' => [ + [ + $this->dummyUser(time()-5*24*60*60), + $this->dummyUser(time()-10*24*60*60) + ], 7, 1], + ]; + } + + /** + * @dataProvider validDays + * + * @param $users + * @param $days + * @param $expectedCount + */ + public function testWithValidDays($users, $days, $expectedCount) { + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('days') + ->willReturn($days); + + // Work with json output + $this->consoleInput->expects($this->once()) + ->method('getOption') + ->with('output') + ->willReturn(Base::OUTPUT_FORMAT_JSON); + + $this->userManager->expects($this->once()) + ->method('callForSeenUsers') + ->willReturnCallback(function($callback) use ($users, $expectedCount) { + foreach($users as $user) { + $callback($user); + } + }); + + $this->consoleOutput->expects($this->once()) + ->method('writeLn') + ->with($this->callback(function($output) use ($expectedCount) { + return self::isJson($output) && count(json_decode($output)) === $expectedCount; + })); + + self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + +}