Skip to content

Commit

Permalink
Merge pull request #36216 from owncloud/feature/user-sync-api
Browse files Browse the repository at this point in the history
[API] user-sync OCS API
  • Loading branch information
DeepDiver1975 authored Sep 26, 2019
2 parents 31ea320 + a3dcbf4 commit af2e241
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 9 deletions.
16 changes: 16 additions & 0 deletions core/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
use OC\Core\Controller\TokenController;
use OC\Core\Controller\TwoFactorChallengeController;
use OC\Core\Controller\UserController;
use OC\Core\Controller\UserSyncController;
use OC\User\AccountMapper;
use OC\User\SyncService;
use OC_Defaults;
use OCP\AppFramework\App;
use OCP\BackgroundJob\IJobList;
Expand Down Expand Up @@ -153,6 +156,19 @@ public function __construct(array $urlParams= []) {
$serverContainer->getEventDispatcher()
);
});
$container->registerService('UserSyncController', static function (SimpleContainer $c) {
$syncService = new SyncService(
$c->query(IConfig::class),
$c->query(ILogger::class),
$c->query(AccountMapper::class)
);
return new UserSyncController(
$c->query('AppName'),
$c->query('Request'),
$syncService,
$c->query('UserManager')
);
});
$container->registerService('CronController', static function (SimpleContainer $c) {
return new CronController(
$c->query('AppName'),
Expand Down
3 changes: 1 addition & 2 deletions core/Command/User/SyncBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@ private function syncSingleUser(
$dummy = new Account(); // to prevent null pointer when writing messages
if (\count($users) === 1) {
// Run the sync using the internal username if mapped
$syncService->run($backend, new \ArrayIterator([$users[0]]), function () {
});
$syncService->run($backend, new \ArrayIterator([$users[0]]));
} else {
// Not found
$this->handleRemovedUsers([$uid => $dummy], $input, $output, $missingAccountsAction);
Expand Down
97 changes: 97 additions & 0 deletions core/Controller/UserSyncController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2019, 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 <http://www.gnu.org/licenses/>
*
*/

declare(strict_types = 1);

namespace OC\Core\Controller;

use ArrayIterator;
use OC\OCS\Result;
use OC\User\SyncService;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserManager;

/**
* Class UserSyncController
*
* External systems for user provisioning can use this API to trigger a single-user
* sync to update user settings from an external user management (e.g. LDAP) or even
* to create the account in ownCloud if necessary.
*
* In contrary to the occ user:sync command this API will not disable or delete the
* user if the user no longer exists. The external user provisioning system can use
* existing APIs to disable or delete a user.
*
* @package OC\Core\Controller
*/
class UserSyncController extends OCSController {

/**
* @var SyncService
*/
private $syncService;
/**
* @var IUserManager
*/
private $userManager;

/**
* UserSyncController constructor.
*
* @param string $appName
* @param IRequest $request
* @param SyncService $syncService
* @param IUserManager $userManager
*/
public function __construct($appName,
IRequest $request,
SyncService $syncService,
IUserManager $userManager) {
parent::__construct($appName, $request);
$this->syncService = $syncService;
$this->userManager = $userManager;
}

/**
* @NoCSRFRequired
*
* @param string $userId
* @return Result
*/
public function syncUser($userId): Result {
foreach ($this->userManager->getBackends() as $backEnd) {
$users = $backEnd->getUsers($userId, 2);
if (\count($users) > 1) {
$backEndName = \get_class($backEnd);
return new Result([], 409, "Multiple users returned from backend($backEndName) for: $userId. Cancelling sync.");
}

if (\count($users) === 1) {
// Run the sync using the internal username if mapped
$this->syncService->run($backEnd, new ArrayIterator([$users[0]]));
return new Result();
}
}

return new Result([], 404, "User is not known in any user backend: $userId");
}
}
1 change: 1 addition & 0 deletions core/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
['root' => '/cloud', 'name' => 'Cloud#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Cloud#getCurrentUser', 'url' => '/user', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Roles#getRoles', 'url' => '/roles', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'UserSync#syncUser', 'url' => '/user-sync/{userId}', 'verb' => 'POST'],
]
]);

Expand Down
6 changes: 4 additions & 2 deletions lib/private/User/SyncService.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private function checkIfAccountReappeared(Account $a, array &$removed, array &$r
* @param \Traversable $userIds of users
* @param \Closure $callback is called for every user to progress display
*/
public function run(UserInterface $backend, \Traversable $userIds, \Closure $callback) {
public function run(UserInterface $backend, \Traversable $userIds, \Closure $callback = null) {
// update existing and insert new users
foreach ($userIds as $uid) {
try {
Expand All @@ -138,7 +138,9 @@ public function run(UserInterface $backend, \Traversable $userIds, \Closure $cal
}

// call the callback
$callback($uid);
if ($callback) {
$callback($uid);
}
}
}

Expand Down
111 changes: 111 additions & 0 deletions tests/Core/Controller/UserSyncControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2019, 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 <http://www.gnu.org/licenses/>
*
*/
declare(strict_types = 1);

namespace Tests\Core\Controller;

use OC\Core\Controller\UserSyncController;
use OC\OCS\Result;
use OC\User\SyncService;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\UserInterface;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

/**
* Class RolesControllerTest
*
* @package OC\Core\Controller
*/
class UserSyncControllerTest extends TestCase {
/**
* @var UserSyncController
*/
private $controller;
/**
* @var MockObject | IUserManager
*/
private $userManager;
/**
* @var MockObject | SyncService
*/
private $syncService;

protected function setUp() {
/** @var IRequest $request */
$request = $this->createMock(IRequest::class);
$this->syncService = $this->createMock(SyncService::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->controller = new UserSyncController('core', $request, $this->syncService, $this->userManager);

return parent::setUp();
}

public function testSimpleSync() {
$user = $this->createMock(IUser::class);
$userBackend = $this->createMock(UserInterface::class);
$userBackend->method('getUsers')->willReturn([$user]);
$this->userManager->method('getBackends')->willReturn([$userBackend]);
$this->syncService->expects(self::once())->method('run')->with($userBackend, new \ArrayIterator([$user]));

$result = $this->controller->syncUser('alice');

$this->assertEquals([], $result->getData());
$this->assertEquals(['status' => 'ok',
'statuscode' => 100,
'message' => null
], $result->getMeta());
}

public function testUnknownUser() {
$userBackend = $this->createMock(UserInterface::class);
$userBackend->method('getUsers')->willReturn([]);
$this->userManager->method('getBackends')->willReturn([$userBackend]);
$this->syncService->expects(self::never())->method('run');

$result = $this->controller->syncUser('alice');

$this->assertEquals([], $result->getData());
$this->assertEquals(['status' => 'failure',
'statuscode' => 404,
'message' => 'User is not known in any user backend: alice'
], $result->getMeta());
}

public function testConflict() {
$user = $this->createMock(IUser::class);
$userBackend = $this->createMock(UserInterface::class);
$userBackend->method('getUsers')->willReturn([$user, $user]);
$this->userManager->method('getBackends')->willReturn([$userBackend]);
$this->syncService->expects(self::never())->method('run');

$result = $this->controller->syncUser('alice');

$this->assertEquals([], $result->getData());
$backEndClass = \get_class($userBackend);
$this->assertEquals(['status' => 'failure',
'statuscode' => 409,
'message' => "Multiple users returned from backend($backEndClass) for: alice. Cancelling sync."
], $result->getMeta());
}
}
8 changes: 3 additions & 5 deletions tests/lib/User/SyncServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public function testSetupNewAccount() {
// Ignore state flag

$s = new SyncService($this->config, $this->logger, $this->mapper);
$s->run($backend, new AllUsersIterator($backend), function ($uid) {
});
$s->run($backend, new AllUsersIterator($backend));

static::invokePrivate($s, 'syncHome', [$account, $backend]);
}
Expand All @@ -135,8 +134,7 @@ public function testSetupNewAccountLogsErrorOnException() {
$this->logger->expects($this->at(1))->method('logException');

$s = new SyncService($this->config, $this->logger, $this->mapper);
$s->run($backend, new AllUsersIterator($backend), function ($uid) {
});
$s->run($backend, new AllUsersIterator($backend));
}

public function testSyncHomeLogsWhenBackendDiffersFromExisting() {
Expand Down Expand Up @@ -294,7 +292,7 @@ public function testSyncUserName() {
->method('getUserValue')
->with('user1', 'core', 'username', null)
->willReturn(null);

$this->config->expects($this->once())
->method('setUserValue')
->with('user1', 'core', 'username', 'userName1');
Expand Down

0 comments on commit af2e241

Please sign in to comment.