summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php2
-rw-r--r--lib/private/Authentication/Token/IProvider.php3
-rw-r--r--settings/Application.php2
-rw-r--r--settings/Controller/AuthSettingsController.php71
-rw-r--r--settings/css/settings.css22
-rw-r--r--settings/js/authtoken_view.js95
-rw-r--r--settings/templates/personal.php11
-rw-r--r--tests/settings/controller/AuthSettingsControllerTest.php77
8 files changed, 259 insertions, 24 deletions
diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
index 6c69d852d7b..3527f4155a9 100644
--- a/lib/private/Authentication/Token/DefaultTokenProvider.php
+++ b/lib/private/Authentication/Token/DefaultTokenProvider.php
@@ -134,6 +134,7 @@ class DefaultTokenProvider implements IProvider {
/**
* @param IToken $savedToken
* @param string $tokenId session token
+ * @throws InvalidTokenException
* @return string
*/
public function getPassword(IToken $savedToken, $tokenId) {
@@ -203,6 +204,7 @@ class DefaultTokenProvider implements IProvider {
*
* @param string $password
* @param string $token
+ * @throws InvalidTokenException
* @return string the decrypted key
*/
private function decryptPassword($password, $token) {
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index a5c5faa5639..b8648dda5b7 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -35,7 +35,7 @@ interface IProvider {
* @param string $password
* @param string $name
* @param int $type token type
- * @return DefaultToken
+ * @return IToken
*/
public function generateToken($token, $uid, $password, $name, $type = IToken::TEMPORARY_TOKEN);
@@ -85,6 +85,7 @@ interface IProvider {
*
* @param IToken $token
* @param string $tokenId
+ * @throws InvalidTokenException
* @return string
*/
public function getPassword(IToken $token, $tokenId);
diff --git a/settings/Application.php b/settings/Application.php
index 7069fc9c35d..728c2bf9de4 100644
--- a/settings/Application.php
+++ b/settings/Application.php
@@ -104,6 +104,8 @@ class Application extends App {
$c->query('Request'),
$c->query('ServerContainer')->query('OC\Authentication\Token\IProvider'),
$c->query('UserManager'),
+ $c->query('ServerContainer')->getSession(),
+ $c->query('ServerContainer')->getSecureRandom(),
$c->query('UserId')
);
});
diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php
index 1d874193d36..71868b7688d 100644
--- a/settings/Controller/AuthSettingsController.php
+++ b/settings/Controller/AuthSettingsController.php
@@ -22,41 +22,56 @@
namespace OC\Settings\Controller;
+use OC\AppFramework\Http;
+use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
+use OCP\ISession;
use OCP\IUserManager;
+use OCP\Security\ISecureRandom;
+use OCP\Session\Exceptions\SessionNotAvailableException;
class AuthSettingsController extends Controller {
/** @var IProvider */
private $tokenProvider;
- /**
- * @var IUserManager
- */
+ /** @var IUserManager */
private $userManager;
+ /** @var ISession */
+ private $session;
+
/** @var string */
private $uid;
+ /** @var ISecureRandom */
+ private $random;
+
/**
* @param string $appName
* @param IRequest $request
* @param IProvider $tokenProvider
* @param IUserManager $userManager
+ * @param ISession $session
+ * @param ISecureRandom $random
* @param string $uid
*/
- public function __construct($appName, IRequest $request, IProvider $tokenProvider, IUserManager $userManager, $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;
$this->uid = $uid;
+ $this->session = $session;
+ $this->random = $random;
}
/**
* @NoAdminRequired
+ * @NoSubadminRequired
*
* @return JSONResponse
*/
@@ -68,4 +83,52 @@ class AuthSettingsController extends Controller {
return $this->tokenProvider->getTokenByUser($user);
}
+ /**
+ * @NoAdminRequired
+ * @NoSubadminRequired
+ *
+ * @return JSONResponse
+ */
+ public function create($name) {
+ try {
+ $sessionId = $this->session->getId();
+ } catch (SessionNotAvailableException $ex) {
+ $resp = new JSONResponse();
+ $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+ return $resp;
+ }
+
+ try {
+ $sessionToken = $this->tokenProvider->getToken($sessionId);
+ $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
+ } catch (InvalidTokenException $ex) {
+ $resp = new JSONResponse();
+ $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+ return $resp;
+ }
+
+ $token = $this->generateRandomDeviceToken();
+ $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $password, $name, IToken::PERMANENT_TOKEN);
+
+ return [
+ 'token' => $token,
+ 'deviceToken' => $deviceToken
+ ];
+ }
+
+ /**
+ * Return a 20 digit device password
+ *
+ * Example: ABCDE-FGHIJ-KLMNO-PQRST
+ *
+ * @return string
+ */
+ private function generateRandomDeviceToken() {
+ $groups = [];
+ for ($i = 0; $i < 4; $i++) {
+ $groups[] = $this->random->generate(5, implode('', range('A', 'Z')));
+ }
+ return implode('-', $groups);
+ }
+
}
diff --git a/settings/css/settings.css b/settings/css/settings.css
index be61265935e..418c5f95517 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -100,10 +100,6 @@ input#identity {
table.nostyle label { margin-right: 2em; }
table.nostyle td { padding: 0.2em 0; }
-#sessions,
-#devices {
- min-height: 180px;
-}
#sessions table,
#devices table {
width: 100%;
@@ -114,6 +110,24 @@ table.nostyle td { padding: 0.2em 0; }
#devices table th {
font-weight: 800;
}
+#sessions table th,
+#sessions table td,
+#devices table th,
+#devices table td {
+ padding: 10px;
+}
+
+#sessions .token-list td,
+#devices .token-list td {
+ border-top: 1px solid #DDD;
+}
+
+#device-new-token {
+ padding: 10px;
+ font-family: monospace;
+ font-size: 1.4em;
+ background-color: lightyellow;
+}
/* USERS */
#newgroup-init a span { margin-left: 20px; }
diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js
index 0ca16821233..8ca38d80d84 100644
--- a/settings/js/authtoken_view.js
+++ b/settings/js/authtoken_view.js
@@ -1,4 +1,4 @@
-/* global Backbone, Handlebars */
+/* global Backbone, Handlebars, moment */
/**
* @author Christoph Wurst <christoph@owncloud.com>
@@ -20,16 +20,16 @@
*
*/
-(function(OC, _, Backbone, $, Handlebars) {
+(function(OC, _, Backbone, $, Handlebars, moment) {
'use strict';
OC.Settings = OC.Settings || {};
var TEMPLATE_TOKEN =
- '<tr>'
- + '<td>{{name}}</td>'
- + '<td>{{lastActivity}}</td>'
- + '<tr>';
+ '<tr>'
+ + '<td>{{name}}</td>'
+ + '<td>{{lastActivity}}</td>'
+ + '<tr>';
var SubView = Backbone.View.extend({
collection: null,
@@ -46,48 +46,115 @@
var tokens = this.collection.filter(function(token) {
return parseInt(token.get('type')) === _this.type;
});
- list.removeClass('icon-loading');
list.html('');
tokens.forEach(function(token) {
- var html = _this.template(token.toJSON());
+ var viewData = token.toJSON();
+ viewData.lastActivity = moment(viewData.lastActivity, 'X').
+ format('LLL');
+ var html = _this.template(viewData);
list.append(html);
});
},
+ toggleLoading: function(state) {
+ this.$el.find('.token-list').toggleClass('icon-loading', state);
+ }
});
var AuthTokenView = Backbone.View.extend({
collection: null,
- views
- : [],
+ _views: [],
+ _form: undefined,
+ _tokenName: undefined,
+ _addTokenBtn: undefined,
+ _result: undefined,
+ _newToken: undefined,
+ _hideTokenBtn: undefined,
+ _addingToken: false,
initialize: function(options) {
this.collection = options.collection;
var tokenTypes = [0, 1];
var _this = this;
_.each(tokenTypes, function(type) {
- _this.views.push(new SubView({
+ _this._views.push(new SubView({
el: type === 0 ? '#sessions' : '#devices',
type: type,
collection: _this.collection
}));
});
+
+ this._form = $('#device-token-form');
+ this._tokenName = $('#device-token-name');
+ this._addTokenBtn = $('#device-add-token');
+ this._addTokenBtn.click(_.bind(this._addDeviceToken, this));
+
+ this._result = $('#device-token-result');
+ this._newToken = $('#device-new-token');
+ this._hideTokenBtn = $('#device-token-hide');
+ this._hideTokenBtn.click(_.bind(this._hideToken, this));
},
render: function() {
- _.each(this.views, function(view) {
+ _.each(this._views, function(view) {
view.render();
+ view.toggleLoading(false);
});
},
reload: function() {
+ var _this = this;
+
+ _.each(this._views, function(view) {
+ view.toggleLoading(true);
+ });
+
var loadingTokens = this.collection.fetch();
- var _this = this;
$.when(loadingTokens).done(function() {
_this.render();
});
+ $.when(loadingTokens).fail(function() {
+ OC.Notification.showTemporary(t('core', 'Error while loading browser sessions and device tokens'));
+ });
+ },
+ _addDeviceToken: function() {
+ var _this = this;
+ this._toggleAddingToken(true);
+
+ var deviceName = this._tokenName.val();
+ var creatingToken = $.ajax(OC.generateUrl('/settings/personal/authtokens'), {
+ method: 'POST',
+ data: {
+ name: deviceName
+ }
+ });
+
+ $.when(creatingToken).done(function(resp) {
+ _this.collection.add(resp.deviceToken);
+ _this.render();
+ _this._newToken.text(resp.token);
+ _this._toggleFormResult(false);
+ _this._tokenName.val('');
+ });
+ $.when(creatingToken).fail(function() {
+ OC.Notification.showTemporary(t('core', 'Error while creating device token'));
+ });
+ $.when(creatingToken).always(function() {
+ _this._toggleAddingToken(false);
+ });
+ },
+ _hideToken: function() {
+ this._toggleFormResult(true);
+ },
+ _toggleAddingToken: function(state) {
+ this._addingToken = state;
+ this._addTokenBtn.toggleClass('icon-loading-small', state);
+ },
+ _toggleFormResult: function(showForm) {
+ this._form.toggleClass('hidden', !showForm);
+ this._result.toggleClass('hidden', showForm);
}
});
OC.Settings.AuthTokenView = AuthTokenView;
-})(OC, _, Backbone, $, Handlebars);
+})(OC, _, Backbone, $, Handlebars, moment);
diff --git a/settings/templates/personal.php b/settings/templates/personal.php
index a7e86b50a59..4f8d564f549 100644
--- a/settings/templates/personal.php
+++ b/settings/templates/personal.php
@@ -147,6 +147,7 @@ if($_['passwordChangeSupported']) {
<tr>
<th>Browser</th>
<th>Most recent activity</th>
+ <th></th>
</tr>
</thead>
<tbody class="token-list icon-loading">
@@ -162,11 +163,21 @@ if($_['passwordChangeSupported']) {
<tr>
<th>Name</th>
<th>Most recent activity</th>
+ <th><a class="icon-delete"></a></th>
</tr>
</thead>
<tbody class="token-list icon-loading">
</tbody>
</table>
+ <p><?php p($l->t('A device password is a passcode that gives an app or device permissions to access your ownCloud account.'));?></p>
+ <div id="device-token-form">
+ <input id="device-token-name" type="text" placeholder="Device name">
+ <button id="device-add-token" class="button">Create new device password</button>
+ </div>
+ <div id="device-token-result" class="hidden">
+ <span id="device-new-token"></span>
+ <button id="device-token-hide" class="button">Done</button>
+ </div>
</div>
<form id="language" class="section">
diff --git a/tests/settings/controller/AuthSettingsControllerTest.php b/tests/settings/controller/AuthSettingsControllerTest.php
index d236f9f5ebc..3b46a2caa2b 100644
--- a/tests/settings/controller/AuthSettingsControllerTest.php
+++ b/tests/settings/controller/AuthSettingsControllerTest.php
@@ -22,7 +22,12 @@
namespace Test\Settings\Controller;
+use OC\AppFramework\Http;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\IToken;
use OC\Settings\Controller\AuthSettingsController;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Session\Exceptions\SessionNotAvailableException;
use Test\TestCase;
class AuthSettingsControllerTest extends TestCase {
@@ -32,6 +37,8 @@ class AuthSettingsControllerTest extends TestCase {
private $request;
private $tokenProvider;
private $userManager;
+ private $session;
+ private $secureRandom;
private $uid;
protected function setUp() {
@@ -40,10 +47,12 @@ class AuthSettingsControllerTest extends TestCase {
$this->request = $this->getMock('\OCP\IRequest');
$this->tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
$this->userManager = $this->getMock('\OCP\IUserManager');
+ $this->session = $this->getMock('\OCP\ISession');
+ $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom');
$this->uid = 'jane';
$this->user = $this->getMock('\OCP\IUser');
- $this->controller = new AuthSettingsController('core', $this->request, $this->tokenProvider, $this->userManager, $this->uid);
+ $this->controller = new AuthSettingsController('core', $this->request, $this->tokenProvider, $this->userManager, $this->session, $this->secureRandom, $this->uid);
}
public function testIndex() {
@@ -63,4 +72,70 @@ class AuthSettingsControllerTest extends TestCase {
$this->assertEquals($result, $this->controller->index());
}
+ public function testCreate() {
+ $name = 'Nexus 4';
+ $sessionToken = $this->getMock('\OC\Authentication\Token\IToken');
+ $deviceToken = $this->getMock('\OC\Authentication\Token\IToken');
+ $password = '123456';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue('sessionid'));
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sessionid')
+ ->will($this->returnValue($sessionToken));
+ $this->tokenProvider->expects($this->once())
+ ->method('getPassword')
+ ->with($sessionToken, 'sessionid')
+ ->will($this->returnValue($password));
+
+ $this->secureRandom->expects($this->exactly(4))
+ ->method('generate')
+ ->with(5, implode('', range('A', 'Z')))
+ ->will($this->returnValue('XXXXX'));
+ $newToken = 'XXXXX-XXXXX-XXXXX-XXXXX';
+
+ $this->tokenProvider->expects($this->once())
+ ->method('generateToken')
+ ->with($newToken, $this->uid, $password, $name, IToken::PERMANENT_TOKEN)
+ ->will($this->returnValue($deviceToken));
+
+ $expected = [
+ 'token' => $newToken,
+ 'deviceToken' => $deviceToken,
+ ];
+ $this->assertEquals($expected, $this->controller->create($name));
+ }
+
+ public function testCreateSessionNotAvailable() {
+ $name = 'personal phone';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->will($this->throwException(new SessionNotAvailableException()));
+
+ $expected = new JSONResponse();
+ $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+
+ $this->assertEquals($expected, $this->controller->create($name));
+ }
+
+ public function testCreateInvalidToken() {
+ $name = 'Company IPhone';
+
+ $this->session->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue('sessionid'));
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('sessionid')
+ ->will($this->throwException(new InvalidTokenException()));
+
+ $expected = new JSONResponse();
+ $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
+
+ $this->assertEquals($expected, $this->controller->create($name));
+ }
+
}