Make it possible to wipe all tokens/devices of a user
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
parent
1c261675ad
commit
d058ef2b6c
|
@ -50,6 +50,7 @@ return [
|
|||
['root' => '/cloud', 'name' => 'Users#getCurrentUser', 'url' => '/user', 'verb' => 'GET'],
|
||||
['root' => '/cloud', 'name' => 'Users#getEditableFields', 'url' => '/user/fields', 'verb' => 'GET'],
|
||||
['root' => '/cloud', 'name' => 'Users#editUser', 'url' => '/users/{userId}', 'verb' => 'PUT'],
|
||||
['root' => '/cloud', 'name' => 'Users#wipeUserDevices', 'url' => '/users/{userId}/wipe', 'verb' => 'POST'],
|
||||
['root' => '/cloud', 'name' => 'Users#deleteUser', 'url' => '/users/{userId}', 'verb' => 'DELETE'],
|
||||
['root' => '/cloud', 'name' => 'Users#enableUser', 'url' => '/users/{userId}/enable', 'verb' => 'PUT'],
|
||||
['root' => '/cloud', 'name' => 'Users#disableUser', 'url' => '/users/{userId}/disable', 'verb' => 'PUT'],
|
||||
|
|
|
@ -34,6 +34,7 @@ declare(strict_types=1);
|
|||
namespace OCA\Provisioning_API\Controller;
|
||||
|
||||
use OC\Accounts\AccountManager;
|
||||
use OC\Authentication\Token\RemoteWipe;
|
||||
use OC\HintException;
|
||||
use OC\Settings\Mailer\NewUserMailHelper;
|
||||
use OCA\Provisioning_API\FederatedFileSharingFactory;
|
||||
|
@ -46,6 +47,7 @@ use OCP\IGroup;
|
|||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
|
@ -65,6 +67,8 @@ class UsersController extends AUserData {
|
|||
private $federatedFileSharingFactory;
|
||||
/** @var ISecureRandom */
|
||||
private $secureRandom;
|
||||
/** @var RemoteWipe */
|
||||
private $remoteWipe;
|
||||
|
||||
/**
|
||||
* @param string $appName
|
||||
|
@ -93,7 +97,8 @@ class UsersController extends AUserData {
|
|||
IFactory $l10nFactory,
|
||||
NewUserMailHelper $newUserMailHelper,
|
||||
FederatedFileSharingFactory $federatedFileSharingFactory,
|
||||
ISecureRandom $secureRandom) {
|
||||
ISecureRandom $secureRandom,
|
||||
RemoteWipe $remoteWipe) {
|
||||
parent::__construct($appName,
|
||||
$request,
|
||||
$userManager,
|
||||
|
@ -108,6 +113,7 @@ class UsersController extends AUserData {
|
|||
$this->newUserMailHelper = $newUserMailHelper;
|
||||
$this->federatedFileSharingFactory = $federatedFileSharingFactory;
|
||||
$this->secureRandom = $secureRandom;
|
||||
$this->remoteWipe = $remoteWipe;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -587,6 +593,37 @@ class UsersController extends AUserData {
|
|||
return new DataResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @PasswordConfirmationRequired
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param string $userId
|
||||
*
|
||||
* @return DataResponse
|
||||
*
|
||||
* @throws OCSException
|
||||
*/
|
||||
public function wipeUserDevices(string $userId): DataResponse {
|
||||
/** @var IUser $currentLoggedInUser */
|
||||
$currentLoggedInUser = $this->userSession->getUser();
|
||||
|
||||
$targetUser = $this->userManager->get($userId);
|
||||
|
||||
if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
|
||||
throw new OCSException('', 101);
|
||||
}
|
||||
|
||||
// If not permitted
|
||||
$subAdminManager = $this->groupManager->getSubAdmin();
|
||||
if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
|
||||
throw new OCSException('', \OCP\API::RESPOND_UNAUTHORISED);
|
||||
}
|
||||
|
||||
$this->remoteWipe->markAllTokensForWipe($targetUser);
|
||||
|
||||
return new DataResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @PasswordConfirmationRequired
|
||||
* @NoAdminRequired
|
||||
|
|
|
@ -25,19 +25,15 @@ declare(strict_types=1);
|
|||
|
||||
namespace OC\Authentication\Token;
|
||||
|
||||
use BadMethodCallException;
|
||||
use function array_filter;
|
||||
use OC\Authentication\Events\RemoteWipeFinished;
|
||||
use OC\Authentication\Events\RemoteWipeStarted;
|
||||
use OC\Authentication\Exceptions\ExpiredTokenException;
|
||||
use OC\Authentication\Exceptions\InvalidTokenException;
|
||||
use OC\Authentication\Exceptions\WipeTokenException;
|
||||
use OCP\Activity\IEvent;
|
||||
use OCP\Activity\IManager as IActivityManager;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\Notification\IManager as INotificationManager;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class RemoteWipe {
|
||||
|
||||
|
@ -58,6 +54,15 @@ class RemoteWipe {
|
|||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidTokenException
|
||||
* @throws WipeTokenException
|
||||
* @throws ExpiredTokenException
|
||||
*/
|
||||
public function markTokenForWipe(int $id): bool {
|
||||
$token = $this->tokenProvider->getTokenById($id);
|
||||
|
||||
|
@ -71,6 +76,31 @@ class RemoteWipe {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IUser $user
|
||||
*
|
||||
* @return bool true if any tokens have been marked for remote wipe
|
||||
*/
|
||||
public function markAllTokensForWipe(IUser $user): bool {
|
||||
$tokens = $this->tokenProvider->getTokenByUser($user->getUID());
|
||||
|
||||
/** @var IWipeableToken[] $wipeable */
|
||||
$wipeable = array_filter($tokens, function (IToken $token) {
|
||||
return $token instanceof IWipeableToken;
|
||||
});
|
||||
|
||||
if (empty($wipeable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($wipeable as $token) {
|
||||
$token->wipe();
|
||||
$this->tokenProvider->updateToken($token);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
*
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -23,10 +23,10 @@
|
|||
<template>
|
||||
<!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
|
||||
<div class="row" v-if="Object.keys(user).length ===1" :data-id="user.id">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable}">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||
<img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
||||
v-if="!loading.delete && !loading.disable">
|
||||
v-if="!loading.delete && !loading.disable && !loading.wipe">
|
||||
</div>
|
||||
<div class="name">{{user.id}}</div>
|
||||
<div class="obfuscated">{{t('settings','You do not have permissions to see the details of this user')}}</div>
|
||||
|
@ -34,10 +34,10 @@
|
|||
|
||||
<!-- User full data -->
|
||||
<div class="row" v-else :class="{'disabled': loading.delete || loading.disable}" :data-id="user.id">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable}">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||
<img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
||||
v-if="!loading.delete && !loading.disable">
|
||||
v-if="!loading.delete && !loading.disable && !loading.wipe">
|
||||
</div>
|
||||
<!-- dirty hack to ellipsis on two lines -->
|
||||
<div class="name">{{user.id}}</div>
|
||||
|
@ -165,22 +165,31 @@ export default {
|
|||
quota: false,
|
||||
delete: false,
|
||||
disable: false,
|
||||
languages: false
|
||||
languages: false,
|
||||
wipe: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/* USER POPOVERMENU ACTIONS */
|
||||
userActions() {
|
||||
let actions = [{
|
||||
icon: 'icon-delete',
|
||||
text: t('settings','Delete user'),
|
||||
action: this.deleteUser
|
||||
},{
|
||||
icon: this.user.enabled ? 'icon-close' : 'icon-add',
|
||||
text: this.user.enabled ? t('settings','Disable user') : t('settings','Enable user'),
|
||||
action: this.enableDisableUser
|
||||
}];
|
||||
let actions = [
|
||||
{
|
||||
icon: 'icon-delete',
|
||||
text: t('settings', 'Delete user'),
|
||||
action: this.deleteUser,
|
||||
},
|
||||
{
|
||||
icon: 'icon-delete',
|
||||
text: t('settings', 'Wipe all devices'),
|
||||
action: this.wipeUserDevices,
|
||||
},
|
||||
{
|
||||
icon: this.user.enabled ? 'icon-close' : 'icon-add',
|
||||
text: this.user.enabled ? t('settings', 'Disable user') : t('settings', 'Enable user'),
|
||||
action: this.enableDisableUser,
|
||||
},
|
||||
];
|
||||
if (this.user.email !== null && this.user.email !== '') {
|
||||
actions.push({
|
||||
icon: 'icon-mail',
|
||||
|
@ -308,6 +317,17 @@ export default {
|
|||
return names.slice(2,).join(', ');
|
||||
},
|
||||
|
||||
wipeUserDevices() {
|
||||
this.loading.wipe = true;
|
||||
this.loading.all = true;
|
||||
let userid = this.user.id;
|
||||
return this.$store.dispatch('wipeUserDevices', userid)
|
||||
.then(() => {
|
||||
this.loading.wipe = false
|
||||
this.loading.all = false
|
||||
});
|
||||
},
|
||||
|
||||
deleteUser() {
|
||||
this.loading.delete = true;
|
||||
this.loading.all = true;
|
||||
|
|
|
@ -397,6 +397,20 @@ const actions = {
|
|||
}).catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark all user devices for remote wipe
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {string} userid User id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
wipeUserDevices(context, userid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/users/${userid}/wipe`, 2))
|
||||
.catch((error) => {throw error;});
|
||||
}).catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user
|
||||
*
|
||||
|
|
|
@ -33,6 +33,7 @@ use OC\Authentication\Token\IWipeableToken;
|
|||
use OC\Authentication\Token\RemoteWipe;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
|
@ -93,6 +94,43 @@ class RemoteWipeTest extends TestCase {
|
|||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testMarkAllTokensForWipeNoWipeableToken(): void {
|
||||
/** @var IUser|MockObject $user */
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user123');
|
||||
$token1 = $this->createMock(IToken::class);
|
||||
$token2 = $this->createMock(IToken::class);
|
||||
$this->tokenProvider->expects($this->once())
|
||||
->method('getTokenByUser')
|
||||
->with('user123')
|
||||
->willReturn([$token1, $token2]);
|
||||
|
||||
$result = $this->remoteWipe->markAllTokensForWipe($user);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testMarkAllTokensForWipe(): void {
|
||||
/** @var IUser|MockObject $user */
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user123');
|
||||
$token1 = $this->createMock(IToken::class);
|
||||
$token2 = $this->createMock(IWipeableToken::class);
|
||||
$this->tokenProvider->expects($this->once())
|
||||
->method('getTokenByUser')
|
||||
->with('user123')
|
||||
->willReturn([$token1, $token2]);
|
||||
$token2->expects($this->once())
|
||||
->method('wipe');
|
||||
$this->tokenProvider->expects($this->once())
|
||||
->method('updateToken')
|
||||
->with($token2);
|
||||
|
||||
$result = $this->remoteWipe->markAllTokensForWipe($user);
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testStartWipingNotAWipeToken() {
|
||||
$token = $this->createMock(IToken::class);
|
||||
$this->tokenProvider->expects($this->once())
|
||||
|
|
Loading…
Reference in New Issue