Remote wipe support

This allows a user to mark a token for remote wipe.
Clients that support this can then wipe the device properly.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
Roeland Jago Douma 2019-04-03 16:00:46 +02:00
parent ae7f89fd9f
commit f03eb7ec3c
No known key found for this signature in database
GPG key ID: F941078878347C0C
34 changed files with 696 additions and 31 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -28,6 +28,7 @@
namespace OC\Core;
use OC\Authentication\Notifications\Notifier as AuthenticationNotifier;
use OC\Core\Notification\RemoveLinkSharesNotifier;
use OC\DB\MissingIndexInformation;
use OC\DB\SchemaWrapper;
@ -60,12 +61,20 @@ class Application extends App {
return new RemoveLinkSharesNotifier(
$server->getL10NFactory()
);
}, function() use ($server) {
}, function() {
return [
'id' => 'core',
'name' => 'core',
];
});
$notificationManager->registerNotifier(function() use ($server) {
return $server->query(AuthenticationNotifier::class);
}, function() {
return [
'id' => 'auth',
'name' => 'authentication notifier',
];
});
$eventDispatcher->addListener(IDBConnection::CHECK_MISSING_INDEXES_EVENT,
function(GenericEvent $event) use ($container) {

View file

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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/>.
*
*/
namespace OC\Core\Controller;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\RemoteWipe;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
class WipeController extends Controller {
/** @var RemoteWipe */
private $remoteWipe;
public function __construct(string $appName,
IRequest $request,
RemoteWipe $remoteWipe) {
parent::__construct($appName, $request);
$this->remoteWipe = $remoteWipe;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @AnonRateThrottle(limit=10, period=300)
*
* @param string $token
*
* @return JSONResponse
*/
public function checkWipe(string $token): JSONResponse {
try {
if ($this->remoteWipe->start($token)) {
return new JSONResponse([
'wipe' => true
]);
}
return new JSONResponse([], Http::STATUS_NOT_FOUND);
} catch (InvalidTokenException $e) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @AnonRateThrottle(limit=10, period=300)
*
* @param string $token
*
* @return JSONResponse
*/
public function wipeDone(string $token): JSONResponse {
try {
if ($this->remoteWipe->finish($token)) {
return new JSONResponse([]);
}
return new JSONResponse([], Http::STATUS_NOT_FOUND);
} catch (InvalidTokenException $e) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
}
}

View file

@ -81,6 +81,8 @@ $application->registerRoutes($this, [
['name' => 'contactsMenu#findOne', 'url' => '/contactsmenu/findOne', 'verb' => 'POST'],
['name' => 'WalledGarden#get', 'url' => '/204', 'verb' => 'GET'],
['name' => 'Search#search', 'url' => '/core/search', 'verb' => 'GET'],
['name' => 'Wipe#checkWipe', 'url' => '/core/wipe/check', 'verb' => 'POST'],
['name' => 'Wipe#wipeDone', 'url' => '/core/wipe/success', 'verb' => 'POST'],
// Legacy routes that need to be globally available while they are handled by an app
['name' => 'viewcontroller#showFile', 'url' => '/f/{fileid}', 'verb' => 'GET', 'app' => 'files'],

View file

@ -512,6 +512,7 @@ return array(
'OC\\Authentication\\Exceptions\\PasswordlessTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/PasswordlessTokenException.php',
'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => $baseDir . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => $baseDir . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
'OC\\Authentication\\Exceptions\\WipeTokenException' => $baseDir . '/lib/private/Authentication/Exceptions/WipeTokenException.php',
'OC\\Authentication\\LoginCredentials\\Credentials' => $baseDir . '/lib/private/Authentication/LoginCredentials/Credentials.php',
'OC\\Authentication\\LoginCredentials\\Store' => $baseDir . '/lib/private/Authentication/LoginCredentials/Store.php',
'OC\\Authentication\\Login\\ALoginCommand' => $baseDir . '/lib/private/Authentication/Login/ALoginCommand.php',
@ -530,6 +531,7 @@ return array(
'OC\\Authentication\\Login\\UidLoginCommand' => $baseDir . '/lib/private/Authentication/Login/UidLoginCommand.php',
'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => $baseDir . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php',
'OC\\Authentication\\Login\\UserDisabledCheckCommand' => $baseDir . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php',
'OC\\Authentication\\Notifications\\Notifier' => $baseDir . '/lib/private/Authentication/Notifications/Notifier.php',
'OC\\Authentication\\Token\\DefaultToken' => $baseDir . '/lib/private/Authentication/Token/DefaultToken.php',
'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php',
'OC\\Authentication\\Token\\DefaultTokenMapper' => $baseDir . '/lib/private/Authentication/Token/DefaultTokenMapper.php',
@ -537,10 +539,12 @@ return array(
'OC\\Authentication\\Token\\INamedToken' => $baseDir . '/lib/private/Authentication/Token/INamedToken.php',
'OC\\Authentication\\Token\\IProvider' => $baseDir . '/lib/private/Authentication/Token/IProvider.php',
'OC\\Authentication\\Token\\IToken' => $baseDir . '/lib/private/Authentication/Token/IToken.php',
'OC\\Authentication\\Token\\IWipeableToken' => $baseDir . '/lib/private/Authentication/Token/IWipeableToken.php',
'OC\\Authentication\\Token\\Manager' => $baseDir . '/lib/private/Authentication/Token/Manager.php',
'OC\\Authentication\\Token\\PublicKeyToken' => $baseDir . '/lib/private/Authentication/Token/PublicKeyToken.php',
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php',
'OC\\Authentication\\Token\\PublicKeyTokenProvider' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php',
'OC\\Authentication\\Token\\RemoteWipe' => $baseDir . '/lib/private/Authentication/Token/RemoteWipe.php',
'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php',
'OC\\Authentication\\TwoFactorAuth\\EnforcementState' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
@ -711,6 +715,7 @@ return array(
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => $baseDir . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => $baseDir . '/core/Data/LoginFlowV2Credentials.php',
'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php',
'OC\\Core\\Db\\LoginFlowV2' => $baseDir . '/core/Db/LoginFlowV2.php',

View file

@ -542,6 +542,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Authentication\\Exceptions\\PasswordlessTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/PasswordlessTokenException.php',
'OC\\Authentication\\Exceptions\\TwoFactorAuthRequiredException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/TwoFactorAuthRequiredException.php',
'OC\\Authentication\\Exceptions\\UserAlreadyLoggedInException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/UserAlreadyLoggedInException.php',
'OC\\Authentication\\Exceptions\\WipeTokenException' => __DIR__ . '/../../..' . '/lib/private/Authentication/Exceptions/WipeTokenException.php',
'OC\\Authentication\\LoginCredentials\\Credentials' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Credentials.php',
'OC\\Authentication\\LoginCredentials\\Store' => __DIR__ . '/../../..' . '/lib/private/Authentication/LoginCredentials/Store.php',
'OC\\Authentication\\Login\\ALoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/ALoginCommand.php',
@ -560,6 +561,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Authentication\\Login\\UidLoginCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UidLoginCommand.php',
'OC\\Authentication\\Login\\UpdateLastPasswordConfirmCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UpdateLastPasswordConfirmCommand.php',
'OC\\Authentication\\Login\\UserDisabledCheckCommand' => __DIR__ . '/../../..' . '/lib/private/Authentication/Login/UserDisabledCheckCommand.php',
'OC\\Authentication\\Notifications\\Notifier' => __DIR__ . '/../../..' . '/lib/private/Authentication/Notifications/Notifier.php',
'OC\\Authentication\\Token\\DefaultToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultToken.php',
'OC\\Authentication\\Token\\DefaultTokenCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenCleanupJob.php',
'OC\\Authentication\\Token\\DefaultTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/DefaultTokenMapper.php',
@ -567,10 +569,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Authentication\\Token\\INamedToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/INamedToken.php',
'OC\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IProvider.php',
'OC\\Authentication\\Token\\IToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IToken.php',
'OC\\Authentication\\Token\\IWipeableToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IWipeableToken.php',
'OC\\Authentication\\Token\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/Manager.php',
'OC\\Authentication\\Token\\PublicKeyToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyToken.php',
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php',
'OC\\Authentication\\Token\\PublicKeyTokenProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php',
'OC\\Authentication\\Token\\RemoteWipe' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/RemoteWipe.php',
'OC\\Authentication\\TwoFactorAuth\\Db\\ProviderUserAssignmentDao' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php',
'OC\\Authentication\\TwoFactorAuth\\EnforcementState' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/EnforcementState.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
@ -741,6 +745,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => __DIR__ . '/../../..' . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Credentials.php',
'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php',
'OC\\Core\\Db\\LoginFlowV2' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2.php',

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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/>.
*
*/
namespace OC\Authentication\Exceptions;
use OC\Authentication\Token\IToken;
class WipeTokenException extends InvalidTokenException {
/** @var IToken */
private $token;
public function __construct(IToken $token) {
parent::__construct();
$this->token = $token;
}
public function getToken(): IToken {
return $this->token;
}
}

View file

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
namespace OC\Authentication\Notifications;
use InvalidArgumentException;
use OCP\L10N\IFactory as IL10nFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
class Notifier implements INotifier {
/** @var IL10nFactory */
private $factory;
public function __construct(IL10nFactory $l10nFactory) {
$this->factory = $l10nFactory;
}
/**
* @inheritDoc
*/
public function prepare(INotification $notification, $languageCode) {
if ($notification->getApp() !== 'auth') {
// Not my app => throw
throw new InvalidArgumentException();
}
// Read the language from the notification
$l = $this->factory->get('lib', $languageCode);
switch ($notification->getSubject()) {
case 'remote_wipe_start':
$notification->setParsedSubject(
$l->t('Remote wipe started')
)->setParsedMessage(
$l->t('A remote wipe was started on device %s', $notification->getSubjectParameters())
);
return $notification;
case 'remote_wipe_finish':
$notification->setParsedSubject(
$l->t('Remote wipe finished')
)->setParsedMessage(
$l->t('The remote wipe on %s has finished', $notification->getSubjectParameters())
);
return $notification;
default:
// Unknown subject => Unknown notification => throw
throw new InvalidArgumentException();
}
}
}

View file

@ -29,6 +29,7 @@ namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
interface IProvider {
@ -59,6 +60,7 @@ interface IProvider {
* @param string $tokenId
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
* @return IToken
*/
public function getToken(string $tokenId): IToken;
@ -69,6 +71,7 @@ interface IProvider {
* @param int $tokenId
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
* @return IToken
*/
public function getTokenById(int $tokenId): IToken;

View file

@ -30,6 +30,7 @@ interface IToken extends JsonSerializable {
const TEMPORARY_TOKEN = 0;
const PERMANENT_TOKEN = 1;
const WIPE_TOKEN = 2;
const DO_NOT_REMEMBER = 0;
const REMEMBER = 1;

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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/>.
*
*/
namespace OC\Authentication\Token;
interface IWipeableToken {
public function wipe(): void;
}

View file

@ -26,6 +26,7 @@ namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
class Manager implements IProvider {
@ -113,6 +114,8 @@ class Manager implements IProvider {
public function getToken(string $tokenId): IToken {
try {
return $this->publicKeyTokenProvider->getToken($tokenId);
} catch (WipeTokenException $e) {
throw $e;
} catch (ExpiredTokenException $e) {
throw $e;
} catch(InvalidTokenException $e) {
@ -143,6 +146,8 @@ class Manager implements IProvider {
return $this->publicKeyTokenProvider->getTokenById($tokenId);
} catch (ExpiredTokenException $e) {
throw $e;
} catch (WipeTokenException $e) {
throw $e;
} catch (InvalidTokenException $e) {
return $this->defaultTokenProvider->getTokenById($tokenId);
}

View file

@ -44,7 +44,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setVersion(int $version)
* @method bool getPasswordInvalid()
*/
class PublicKeyToken extends Entity implements INamedToken {
class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
const VERSION = 2;
@ -226,4 +226,8 @@ class PublicKeyToken extends Entity implements INamedToken {
public function setPasswordInvalid(bool $invalid) {
parent::setPasswordInvalid($invalid);
}
public function wipe(): void {
parent::setType(IToken::WIPE_TOKEN);
}
}

View file

@ -26,6 +26,7 @@ namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
@ -85,6 +86,10 @@ class PublicKeyTokenProvider implements IProvider {
throw new ExpiredTokenException($token);
}
if ($token->getType() === IToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
return $token;
}
@ -99,6 +104,10 @@ class PublicKeyTokenProvider implements IProvider {
throw new ExpiredTokenException($token);
}
if ($token->getType() === IToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
return $token;
}

View file

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @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/>.
*/
namespace OC\Authentication\Token;
use BadMethodCallException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
use OCP\Activity\IManager as IActivityManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ILogger;
use OCP\Notification\IManager as INotificationManager;
class RemoteWipe {
/** @var IProvider */
private $tokenProvider;
/** @var IActivityManager */
private $activityManager;
/** @var INotificationManager */
private $notificationManager;
/** @var ITimeFactory */
private $timeFactory;
/** @var ILogger */
private $logger;
public function __construct(IProvider $tokenProvider,
IActivityManager $activityManager,
INotificationManager $notificationManager,
ITimeFactory $timeFactory,
ILogger $logger) {
$this->tokenProvider = $tokenProvider;
$this->activityManager = $activityManager;
$this->notificationManager = $notificationManager;
$this->timeFactory = $timeFactory;
$this->logger = $logger;
}
/**
* @param string $token
*
* @return bool whether wiping was started
* @throws InvalidTokenException
*
*/
public function start(string $token): bool {
try {
$this->tokenProvider->getToken($token);
// We expect a WipedTokenException here. If we reach this point this
// is an ordinary token
return false;
} catch (WipeTokenException $e) {
// Expected -> continue below
}
$dbToken = $e->getToken();
$this->logger->info("user " . $dbToken->getUID() . " started a remote wipe");
$this->sendNotification('remote_wipe_start', $e->getToken());
$this->publishActivity('remote_wipe_start', $e->getToken());
return true;
}
/**
* @param string $token
*
* @return bool whether wiping could be finished
* @throws InvalidTokenException
*/
public function finish(string $token): bool {
try {
$this->tokenProvider->getToken($token);
// We expect a WipedTokenException here. If we reach this point this
// is an ordinary token
return false;
} catch (WipeTokenException $e) {
// Expected -> continue below
}
$dbToken = $e->getToken();
$this->tokenProvider->invalidateToken($token);
$this->logger->info("user " . $dbToken->getUID() . " finished a remote wipe");
$this->sendNotification('remote_wipe_finish', $e->getToken());
$this->publishActivity('remote_wipe_finish', $e->getToken());
return true;
}
private function publishActivity(string $event, IToken $token): void {
$activity = $this->activityManager->generateEvent();
$activity->setApp('core')
->setType('security')
->setAuthor($token->getUID())
->setAffectedUser($token->getUID())
->setSubject($event, [
'name' => $token->getName(),
]);
try {
$this->activityManager->publish($activity);
} catch (BadMethodCallException $e) {
$this->logger->warning('could not publish activity', ['app' => 'core']);
$this->logger->logException($e, ['app' => 'core']);
}
}
private function sendNotification(string $event, IToken $token): void {
$notification = $this->notificationManager->createNotification();
$notification->setApp('auth')
->setUser($token->getUID())
->setDateTime($this->timeFactory->getDateTime())
->setObject('token', $token->getId())
->setSubject($event, [
'name' => $token->getName(),
]);
$this->notificationManager->notify($notification);
}
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
*
@ -78,6 +81,28 @@ class SecurityProvider implements IProvider {
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/password.svg')));
}
break;
case 'remote_wipe_start':
$params = $event->getSubjectParameters();
$event->setParsedSubject($l->t('Remote wipe was started on %1$s', [
$params['name'],
]));
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.png')));
} else {
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg')));
}
break;
case 'remote_wipe_finish':
$params = $event->getSubjectParameters();
$event->setParsedSubject($l->t('Remote wipe has finished on %1$s', [
$params['name'],
]));
if ($this->activityManager->getRequirePNG()) {
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.png')));
} else {
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/delete.svg')));
}
break;
default:
throw new InvalidArgumentException();
}

View file

@ -28,16 +28,17 @@
namespace OC\Settings\Controller;
use BadMethodCallException;
use OC\AppFramework\Http;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
use OC\Authentication\Token\INamedToken;
use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken;
use OC\Authentication\Token\IWipeableToken;
use OC\Settings\Activity\Provider;
use OCP\Activity\IManager;
use OC\Authentication\Token\PublicKeyToken;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\ILogger;
use OCP\IRequest;
@ -168,6 +169,9 @@ class AuthSettingsController extends Controller {
public function destroy($id) {
try {
$token = $this->findTokenByIdAndUser($id);
} catch (WipeTokenException $e) {
//continue as we can destroy tokens in wipe
$token = $e->getToken();
} catch (InvalidTokenException $e) {
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
@ -246,4 +250,27 @@ class AuthSettingsController extends Controller {
}
return $token;
}
/**
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
*
* @param int $id
* @return JSONResponse
* @throws InvalidTokenException
* @throws \OC\Authentication\Exceptions\ExpiredTokenException
*/
public function wipe(int $id): JSONResponse {
$token = $this->tokenProvider->getTokenById($id);
if (!($token instanceof IWipeableToken)) {
return new JSONResponse([], Http::STATUS_BAD_REQUEST);
}
$token->wipe();
$this->tokenProvider->updateToken($token);
return new JSONResponse([]);
}
}

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -42,6 +42,8 @@ $application->registerRoutes($this, [
'AuthSettings' => ['url' => '/settings/personal/authtokens'],
],
'routes' => [
['name' => 'AuthSettings#wipe', 'url' => '/settings/personal/authtokens/wipe/{id}', 'verb' => 'POST'],
['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],

View file

@ -20,7 +20,8 @@
-->
<template>
<tr :data-id="token.id">
<tr :data-id="token.id"
:class="{wiping}">
<td class="client">
<div :class="iconName.icon"></div>
</td>
@ -33,6 +34,8 @@
@blur="cancelRename"
@keyup.esc="cancelRename">
<span v-else>{{iconName.name}}</span>
<span v-if="wiping"
class="wiping-warning">({{ t('settings', 'Marked for remote wipe') }})</span>
</td>
<td>
<span class="last-activity" v-tooltip="lastActivity">{{lastActivityRelative}}</span>
@ -142,13 +145,25 @@
text: t('settings', 'Rename'),
});
}
if (this.token.canDelete) {
if (this.token.canDelete && this.token.type !== 2) {
// TODO: add text/longtext with some description
actions.push({
icon: 'icon-delete',
action: () => this.$emit('delete', this.token),
text: t('settings', 'Revoke'),
});
actions.push({
icon: 'icon-delete',
action: this.wipe,
text: t('settings', 'Wipe device'),
});
} else if (this.token.canDelete && this.token.type === 2) {
actions.push({
icon: 'icon-delete',
action: () => this.$emit('delete', this.token),
text: t('settings', 'Revoke'),
longtext: t('settings', 'Revoking this token might prevent the wiping of your device if it hasn\'t started the wipe yet.'),
});
}
return actions;
@ -197,6 +212,9 @@
name,
};
},
wiping() {
return this.token.type === 2;
}
},
data () {
return {
@ -224,11 +242,20 @@
this.renaming = false;
this.$emit('rename', this.token, this.newName);
},
wipe () {
this.actionOpen = false;
this.$emit('wipe', this.token);
}
}
}
</script>
<style lang="scss" scoped>
.wiping {
background-color: var(--color-background-darker);
}
td {
border-top: 1px solid var(--color-border);
max-width: 200px;
@ -254,6 +281,9 @@
margin: 0;
}
}
&.token-name .wiping-warning {
color: var(--color-text-lighter);
}
&.more {
@extend %icon;

View file

@ -35,7 +35,8 @@
:token="token"
@toggleScope="toggleScope"
@rename="rename"
@delete="onDelete"/>
@delete="onDelete"
@wipe="onWipe" />
</tbody>
</table>
</template>
@ -75,6 +76,10 @@
onDelete (token) {
// Just pass it on
this.$emit('delete', token);
},
onWipe(token) {
// Just pass it on
this.$emit('wipe', token);
}
}
}

View file

@ -26,13 +26,15 @@
<AuthTokenList :tokens="tokens"
@toggleScope="toggleTokenScope"
@rename="rename"
@delete="deleteToken"/>
@delete="deleteToken"
@wipe="wipeToken" />
<AuthTokenSetupDialogue :add="addNewToken" />
</div>
</template>
<script>
import Axios from 'nextcloud-axios';
import confirmPassword from 'nextcloud-password-confirmation';
import AuthTokenList from './AuthTokenList';
import AuthTokenSetupDialogue from './AuthTokenSetupDialogue';
@ -132,6 +134,23 @@
// Restore
this.tokens.push(token);
})
},
wipeToken(token) {
console.debug('wiping app token', token);
confirmPassword()
.then(() => Axios.post(this.baseUrl + '/wipe/' + token.id))
.then(tap(() => {
console.debug('app token marked for wipe')
// Update the type
// TODO: refactor the server-side code to return the updated token
token.type = 2;
}))
.catch(err => {
console.error.bind('could not wipe app token', err);
OC.Notification.showTemporary(t('core', 'Error while wiping the device with the token'));
})
}
}
}

View file

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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/>.
*
*/
namespace Tests\Core\Controller;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\RemoteWipe;
use OC\Core\Controller\WipeController;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class WipeControllerTest extends TestCase {
/** @var RemoteWipe|MockObject */
private $remoteWipe;
/** @var WipeController */
private $controller;
public function setUp() {
parent::setUp();
$this->remoteWipe = $this->createMock(RemoteWipe::class);
$this->controller = new WipeController(
'core',
$this->createMock(IRequest::class),
$this->remoteWipe);
}
public function dataTest() {
return [
// valid token, could perform operation, valid result
[ true, true, true],
[ true, false, false],
[false, true, false],
[false, false, false],
];
}
/**
* @param bool $valid
* @param bool $couldPerform
* @param bool $result
*
* @dataProvider dataTest
*/
public function testCheckWipe(bool $valid, bool $couldPerform, bool $result) {
if (!$valid) {
$this->remoteWipe->method('start')
->with('mytoken')
->willThrowException(new InvalidTokenException());
} else {
$this->remoteWipe->method('start')
->with('mytoken')
->willReturn($couldPerform);
}
$result = $this->controller->checkWipe('mytoken');
if (!$valid || !$couldPerform) {
$this->assertSame(Http::STATUS_NOT_FOUND, $result->getStatus());
$this->assertSame([], $result->getData());
} else {
$this->assertSame(Http::STATUS_OK, $result->getStatus());
$this->assertSame(['wipe' => true], $result->getData());
}
}
/**
* @param bool $valid
* @param bool $couldPerform
* @param bool $result
*
* @dataProvider dataTest
*/
public function testWipeDone(bool $valid, bool $couldPerform, bool $result) {
if (!$valid) {
$this->remoteWipe->method('finish')
->with('mytoken')
->willThrowException(new InvalidTokenException());
} else {
$this->remoteWipe->method('finish')
->with('mytoken')
->willReturn($couldPerform);
}
$result = $this->controller->wipeDone('mytoken');
if (!$valid || !$couldPerform) {
$this->assertSame(Http::STATUS_NOT_FOUND, $result->getStatus());
$this->assertSame([], $result->getData());
} else {
$this->assertSame(Http::STATUS_OK, $result->getStatus());
$this->assertSame([], $result->getData());
}
}
}