diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index ca4c723fba3a..4a64eacb247a 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -22,14 +22,12 @@ namespace OC\Authentication\Token; -use JsonSerializable; use OCP\AppFramework\Db\Entity; /** * @method void setId(int $id) * @method void setUid(string $uid); * @method void setPassword(string $password) - * @method string getPassword() * @method void setName(string $name) * @method string getName() * @method void setToken(string $token) @@ -39,7 +37,7 @@ * @method void setLastActivity(int $lastActivity) * @method int getLastActivity() */ -class DefaultToken extends Entity implements IToken, JsonSerializable { +class DefaultToken extends Entity implements IToken { /** * @var string user UID diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 9f1735712705..970c2242dbe8 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -111,4 +111,17 @@ public function getTokenByUser(IUser $user) { return $entities; } + /** + * @param IUser $user + * @param int $id + */ + public function deleteById(IUser $user, $id) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))); + $qb->execute(); + } + } diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 3527f4155a9f..0f7c54dab572 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -150,6 +150,16 @@ public function invalidateToken($token) { $this->mapper->invalidate($this->hashToken($token)); } + /** + * Invalidate (delete) the given token + * + * @param IUser $user + * @param int $id + */ + public function invalidateTokenById(IUser $user, $id) { + $this->mapper->deleteById($user, $id); + } + /** * Invalidate (delete) old session tokens */ diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index b8648dda5b75..e4e4581e738a 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -47,7 +47,7 @@ public function generateToken($token, $uid, $password, $name, $type = IToken::TE * @return IToken */ public function getToken($tokenId) ; - + /** * @param string $token * @throws InvalidTokenException @@ -62,6 +62,14 @@ public function validateToken($token); */ public function invalidateToken($token); + /** + * Invalidate (delete) the given token + * + * @param IUser $user + * @param int $id + */ + public function invalidateTokenById(IUser $user, $id); + /** * Update token activity timestamp * diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 2a01ea75ea95..b741cd4ac22c 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -22,7 +22,9 @@ namespace OC\Authentication\Token; -interface IToken { +use JsonSerializable; + +interface IToken extends JsonSerializable { const TEMPORARY_TOKEN = 0; const PERMANENT_TOKEN = 1; @@ -30,7 +32,7 @@ interface IToken { /** * Get the token ID * - * @return string + * @return int */ public function getId(); diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index 71868b7688d2..75311920d2a1 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -60,7 +60,8 @@ class AuthSettingsController extends Controller { * @param ISecureRandom $random * @param string $uid */ - public function __construct($appName, IRequest $request, IProvider $tokenProvider, IUserManager $userManager, ISession $session, ISecureRandom $random, $uid) { + public function __construct($appName, IRequest $request, IProvider $tokenProvider, IUserManager $userManager, + ISession $session, ISecureRandom $random, $uid) { parent::__construct($appName, $request); $this->tokenProvider = $tokenProvider; $this->userManager = $userManager; @@ -131,4 +132,20 @@ private function generateRandomDeviceToken() { return implode('-', $groups); } + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @return JSONResponse + */ + public function destroy($id) { + $user = $this->userManager->get($this->uid); + if (is_null($user)) { + return []; + } + + $this->tokenProvider->invalidateTokenById($user, $id); + return []; + } + } diff --git a/settings/css/settings.css b/settings/css/settings.css index 418c5f955172..7dd6977e4411 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -114,12 +114,17 @@ table.nostyle td { padding: 0.2em 0; } #sessions table td, #devices table th, #devices table td { - padding: 10px; + padding: 10px; } #sessions .token-list td, #devices .token-list td { - border-top: 1px solid #DDD; + border-top: 1px solid #DDD; +} +#sessions .token-list td a.icon-delete, +#devices .token-list td a.icon-delete { + display: block; + opacity: 0.6; } #device-new-token { diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 8ca38d80d84f..31955ffa7e52 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -26,9 +26,10 @@ OC.Settings = OC.Settings || {}; var TEMPLATE_TOKEN = - '' + '' + '{{name}}' + '{{lastActivity}}' + + '' + ''; var SubView = Backbone.View.extend({ @@ -77,11 +78,15 @@ var tokenTypes = [0, 1]; var _this = this; _.each(tokenTypes, function(type) { + var el = type === 0 ? '#sessions' : '#devices'; _this._views.push(new SubView({ - el: type === 0 ? '#sessions' : '#devices', + el: el, type: type, collection: _this.collection })); + + var $el = $(el); + $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this)); }); this._form = $('#device-token-form'); @@ -149,6 +154,27 @@ this._addingToken = state; this._addTokenBtn.toggleClass('icon-loading-small', state); }, + _onDeleteToken: function(event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + var id = $row.data('id'); + + var token = this.collection.get(id); + if (_.isUndefined(token)) { + // Ignore event + return; + } + + var destroyingToken = token.destroy(); + + var _this = this; + $.when(destroyingToken).fail(function() { + OC.Notification.showTemporary(t('core', 'Error while deleting the token')); + }); + $.when(destroyingToken).always(function() { + _this.render(); + }); + }, _toggleFormResult: function(showForm) { this._form.toggleClass('hidden', !showForm); this._result.toggleClass('hidden', showForm); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 4f8d564f549b..5a090877dfdd 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -145,8 +145,8 @@ - - + + @@ -161,9 +161,9 @@
BrowserMost recent activityt('Browser'));?>t('Most recent activity'));?>
- - - + + + diff --git a/tests/lib/authentication/token/defaulttokenmappertest.php b/tests/lib/authentication/token/defaulttokenmappertest.php index e17149a5c1b8..9179e23bfb2e 100644 --- a/tests/lib/authentication/token/defaulttokenmappertest.php +++ b/tests/lib/authentication/token/defaulttokenmappertest.php @@ -159,4 +159,31 @@ public function testGetTokenByUserNotFound() { $this->assertCount(0, $this->mapper->getTokenByUser($user)); } + public function testDeleteById() { + $user = $this->getMock('\OCP\IUser'); + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('id') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'))); + $result = $qb->execute(); + $id = $result->fetch()['id']; + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user1')); + + $this->mapper->deleteById($user, $id); + $this->assertEquals(2, $this->getNumberOfTokens()); + } + + public function testDeleteByIdWrongUser() { + $user = $this->getMock('\OCP\IUser'); + $id = 33; + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user10000')); + + $this->mapper->deleteById($user, $id); + $this->assertEquals(3, $this->getNumberOfTokens()); + } + } diff --git a/tests/lib/authentication/token/defaulttokenprovidertest.php b/tests/lib/authentication/token/defaulttokenprovidertest.php index eeb249cfa8a1..8af5e1e933a7 100644 --- a/tests/lib/authentication/token/defaulttokenprovidertest.php +++ b/tests/lib/authentication/token/defaulttokenprovidertest.php @@ -170,6 +170,17 @@ public function testInvalidateToken() { $this->tokenProvider->invalidateToken('token7'); } + public function testInvaildateTokenById() { + $id = 123; + $user = $this->getMock('\OCP\IUser'); + + $this->mapper->expects($this->once()) + ->method('deleteById') + ->with($user, $id); + + $this->tokenProvider->invalidateTokenById($user, $id); + } + public function testInvalidateOldTokens() { $defaultSessionLifetime = 60 * 60 * 24; $this->config->expects($this->once()) diff --git a/tests/settings/controller/AuthSettingsControllerTest.php b/tests/settings/controller/AuthSettingsControllerTest.php index 3b46a2caa2b8..49491c8ff521 100644 --- a/tests/settings/controller/AuthSettingsControllerTest.php +++ b/tests/settings/controller/AuthSettingsControllerTest.php @@ -138,4 +138,19 @@ public function testCreateInvalidToken() { $this->assertEquals($expected, $this->controller->create($name)); } + public function testDestroy() { + $id = 123; + $user = $this->getMock('\OCP\IUser'); + + $this->userManager->expects($this->once()) + ->method('get') + ->with($this->uid) + ->will($this->returnValue($user)); + $this->tokenProvider->expects($this->once()) + ->method('invalidateTokenById') + ->with($user, $id); + + $this->assertEquals([], $this->controller->destroy($id)); + } + }
NameMost recent activityt('Name'));?>t('Most recent activity'));?>