summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Müller <DeepDiver1975@users.noreply.github.com>2016-05-11 13:52:45 +0200
committerThomas Müller <DeepDiver1975@users.noreply.github.com>2016-05-11 13:52:45 +0200
commitefa545f8f0697896a538587bee644fc7c3699185 (patch)
tree8a3d24a3af825ca93583df5e639b8865deacefaf
parentf39e163d4a6ee63444bfb6a797e12a482bd0a49f (diff)
parent0486d750aade407739977911cc1aab40e65dc460 (diff)
downloadnextcloud-server-efa545f8f0697896a538587bee644fc7c3699185.tar.gz
nextcloud-server-efa545f8f0697896a538587bee644fc7c3699185.zip
Merge pull request #24189 from owncloud/pluggable-auth
Pluggable auth
-rw-r--r--apps/dav/lib/connector/sabre/auth.php9
-rw-r--r--apps/dav/tests/unit/connector/sabre/auth.php13
-rw-r--r--build/integration/features/auth.feature78
-rw-r--r--build/integration/features/bootstrap/Auth.php117
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php5
-rw-r--r--core/Application.php23
-rw-r--r--core/Controller/LoginController.php88
-rw-r--r--core/Controller/TokenController.php90
-rw-r--r--core/routes.php4
-rw-r--r--core/templates/login.php2
-rw-r--r--db_structure.xml86
-rw-r--r--lib/base.php193
-rw-r--r--lib/private/Authentication/Exceptions/InvalidTokenException.php29
-rw-r--r--lib/private/Authentication/Token/DefaultToken.php81
-rw-r--r--lib/private/Authentication/Token/DefaultTokenCleanupJob.php36
-rw-r--r--lib/private/Authentication/Token/DefaultTokenMapper.php86
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php205
-rw-r--r--lib/private/Authentication/Token/IProvider.php42
-rw-r--r--lib/private/Authentication/Token/IToken.php46
-rw-r--r--lib/private/Server.php30
-rw-r--r--lib/private/Setup.php15
-rw-r--r--lib/private/Updater.php2
-rw-r--r--lib/private/User/Session.php310
-rw-r--r--lib/private/legacy/api.php44
-rw-r--r--lib/private/legacy/user.php55
-rw-r--r--lib/private/legacy/util.php3
-rw-r--r--tests/core/controller/LoginControllerTest.php74
-rw-r--r--tests/core/controller/TokenControllerTest.php94
-rw-r--r--tests/lib/authentication/token/defaulttokencleanupjobtest.php57
-rw-r--r--tests/lib/authentication/token/defaulttokenmappertest.php144
-rw-r--r--tests/lib/authentication/token/defaulttokenprovidertest.php199
-rw-r--r--tests/lib/user/session.php222
-rw-r--r--version.php2
33 files changed, 2058 insertions, 426 deletions
diff --git a/apps/dav/lib/connector/sabre/auth.php b/apps/dav/lib/connector/sabre/auth.php
index b63efa3a1ba..b8047e779f5 100644
--- a/apps/dav/lib/connector/sabre/auth.php
+++ b/apps/dav/lib/connector/sabre/auth.php
@@ -33,7 +33,7 @@ use Exception;
use OC\AppFramework\Http\Request;
use OCP\IRequest;
use OCP\ISession;
-use OCP\IUserSession;
+use OC\User\Session;
use Sabre\DAV\Auth\Backend\AbstractBasic;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Exception\ServiceUnavailable;
@@ -45,7 +45,7 @@ class Auth extends AbstractBasic {
/** @var ISession */
private $session;
- /** @var IUserSession */
+ /** @var Session */
private $userSession;
/** @var IRequest */
private $request;
@@ -54,12 +54,12 @@ class Auth extends AbstractBasic {
/**
* @param ISession $session
- * @param IUserSession $userSession
+ * @param Session $userSession
* @param IRequest $request
* @param string $principalPrefix
*/
public function __construct(ISession $session,
- IUserSession $userSession,
+ Session $userSession,
IRequest $request,
$principalPrefix = 'principals/users/') {
$this->session = $session;
@@ -104,6 +104,7 @@ class Auth extends AbstractBasic {
} else {
\OC_Util::setUpFS(); //login hooks may need early access to the filesystem
if($this->userSession->login($username, $password)) {
+ $this->userSession->createSessionToken($this->request, $username, $password);
\OC_Util::setUpFS($this->userSession->getUser()->getUID());
$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
$this->session->close();
diff --git a/apps/dav/tests/unit/connector/sabre/auth.php b/apps/dav/tests/unit/connector/sabre/auth.php
index b81a5e003b5..a0168e435e2 100644
--- a/apps/dav/tests/unit/connector/sabre/auth.php
+++ b/apps/dav/tests/unit/connector/sabre/auth.php
@@ -28,7 +28,7 @@ use OCP\IRequest;
use OCP\IUser;
use Test\TestCase;
use OCP\ISession;
-use OCP\IUserSession;
+use OC\User\Session;
/**
* Class Auth
@@ -41,7 +41,7 @@ class Auth extends TestCase {
private $session;
/** @var \OCA\DAV\Connector\Sabre\Auth */
private $auth;
- /** @var IUserSession */
+ /** @var Session */
private $userSession;
/** @var IRequest */
private $request;
@@ -50,7 +50,7 @@ class Auth extends TestCase {
parent::setUp();
$this->session = $this->getMockBuilder('\OCP\ISession')
->disableOriginalConstructor()->getMock();
- $this->userSession = $this->getMockBuilder('\OCP\IUserSession')
+ $this->userSession = $this->getMockBuilder('\OC\User\Session')
->disableOriginalConstructor()->getMock();
$this->request = $this->getMockBuilder('\OCP\IRequest')
->disableOriginalConstructor()->getMock();
@@ -170,6 +170,10 @@ class Auth extends TestCase {
->method('login')
->with('MyTestUser', 'MyTestPassword')
->will($this->returnValue(true));
+ $this->userSession
+ ->expects($this->once())
+ ->method('createSessionToken')
+ ->with($this->request, 'MyTestUser', 'MyTestPassword');
$this->session
->expects($this->once())
->method('set')
@@ -559,6 +563,9 @@ class Auth extends TestCase {
->method('login')
->with('username', 'password')
->will($this->returnValue(true));
+ $this->userSession
+ ->expects($this->once())
+ ->method('createSessionToken');
$user = $this->getMockBuilder('\OCP\IUser')
->disableOriginalConstructor()
->getMock();
diff --git a/build/integration/features/auth.feature b/build/integration/features/auth.feature
new file mode 100644
index 00000000000..43aa618bd00
--- /dev/null
+++ b/build/integration/features/auth.feature
@@ -0,0 +1,78 @@
+Feature: auth
+
+ Background:
+ Given user "user0" exists
+ Given a new client token is used
+
+
+ # FILES APP
+
+ Scenario: access files app anonymously
+ When requesting "/index.php/apps/files" with "GET"
+ Then the HTTP status code should be "401"
+
+ Scenario: access files app with basic auth
+ When requesting "/index.php/apps/files" with "GET" using basic auth
+ Then the HTTP status code should be "200"
+
+ Scenario: access files app with basic token auth
+ When requesting "/index.php/apps/files" with "GET" using basic token auth
+ Then the HTTP status code should be "200"
+
+ Scenario: access files app with a client token
+ When requesting "/index.php/apps/files" with "GET" using a client token
+ Then the HTTP status code should be "200"
+
+ Scenario: access files app with browser session
+ Given a new browser session is started
+ When requesting "/index.php/apps/files" with "GET" using browser session
+ Then the HTTP status code should be "200"
+
+
+ # WebDAV
+
+ Scenario: using WebDAV anonymously
+ When requesting "/remote.php/webdav" with "PROPFIND"
+ Then the HTTP status code should be "401"
+
+ Scenario: using WebDAV with basic auth
+ When requesting "/remote.php/webdav" with "PROPFIND" using basic auth
+ Then the HTTP status code should be "207"
+
+ Scenario: using WebDAV with token auth
+ When requesting "/remote.php/webdav" with "PROPFIND" using basic token auth
+ Then the HTTP status code should be "207"
+
+ # DAV token auth is not possible yet
+ #Scenario: using WebDAV with a client token
+ # When requesting "/remote.php/webdav" with "PROPFIND" using a client token
+ # Then the HTTP status code should be "207"
+
+ Scenario: using WebDAV with browser session
+ Given a new browser session is started
+ When requesting "/remote.php/webdav" with "PROPFIND" using browser session
+ Then the HTTP status code should be "207"
+
+
+ # OCS
+
+ Scenario: using OCS anonymously
+ When requesting "/ocs/v1.php/apps/files_sharing/api/v1/remote_shares" with "GET"
+ Then the OCS status code should be "997"
+
+ Scenario: using OCS with basic auth
+ When requesting "/ocs/v1.php/apps/files_sharing/api/v1/remote_shares" with "GET" using basic auth
+ Then the OCS status code should be "100"
+
+ Scenario: using OCS with token auth
+ When requesting "/ocs/v1.php/apps/files_sharing/api/v1/remote_shares" with "GET" using basic token auth
+ Then the OCS status code should be "100"
+
+ Scenario: using OCS with client token
+ When requesting "/ocs/v1.php/apps/files_sharing/api/v1/remote_shares" with "GET" using a client token
+ Then the OCS status code should be "100"
+
+ Scenario: using OCS with browser session
+ Given a new browser session is started
+ When requesting "/ocs/v1.php/apps/files_sharing/api/v1/remote_shares" with "GET" using browser session
+ Then the OCS status code should be "100" \ No newline at end of file
diff --git a/build/integration/features/bootstrap/Auth.php b/build/integration/features/bootstrap/Auth.php
new file mode 100644
index 00000000000..5f36b199e06
--- /dev/null
+++ b/build/integration/features/bootstrap/Auth.php
@@ -0,0 +1,117 @@
+<?php
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\ClientException;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait Auth {
+
+ private $clientToken;
+
+ /** @BeforeScenario */
+ public function tearUpScenario() {
+ $this->client = new Client();
+ $this->responseXml = '';
+ }
+
+ /**
+ * @When requesting :url with :method
+ */
+ public function requestingWith($url, $method) {
+ $this->sendRequest($url, $method);
+ }
+
+ private function sendRequest($url, $method, $authHeader = null, $useCookies = false) {
+ $fullUrl = substr($this->baseUrl, 0, -5) . $url;
+ try {
+ if ($useCookies) {
+ $request = $this->client->createRequest($method, $fullUrl, [
+ 'cookies' => $this->cookieJar,
+ ]);
+ } else {
+ $request = $this->client->createRequest($method, $fullUrl);
+ }
+ if ($authHeader) {
+ $request->setHeader('Authorization', $authHeader);
+ }
+ $request->setHeader('OCS_APIREQUEST', 'true');
+ $request->setHeader('requesttoken', $this->requestToken);
+ $this->response = $this->client->send($request);
+ } catch (ClientException $ex) {
+ $this->response = $ex->getResponse();
+ }
+ }
+
+ /**
+ * @Given a new client token is used
+ */
+ public function aNewClientTokenIsUsed() {
+ $client = new Client();
+ $resp = $client->post(substr($this->baseUrl, 0, -5) . '/token/generate', [
+ 'json' => [
+ 'user' => 'user0',
+ 'password' => '123456',
+ ]
+ ]);
+ $this->clientToken = json_decode($resp->getBody()->getContents())->token;
+ }
+
+ /**
+ * @When requesting :url with :method using basic auth
+ */
+ public function requestingWithBasicAuth($url, $method) {
+ $this->sendRequest($url, $method, 'basic ' . base64_encode('user0:123456'));
+ }
+
+ /**
+ * @When requesting :url with :method using basic token auth
+ */
+ public function requestingWithBasicTokenAuth($url, $method) {
+ $this->sendRequest($url, $method, 'basic ' . base64_encode('user0:' . $this->clientToken));
+ }
+
+ /**
+ * @When requesting :url with :method using a client token
+ */
+ public function requestingWithUsingAClientToken($url, $method) {
+ $this->sendRequest($url, $method, 'token ' . $this->clientToken);
+ }
+
+ /**
+ * @When requesting :url with :method using browser session
+ */
+ public function requestingWithBrowserSession($url, $method) {
+ $this->sendRequest($url, $method, null, true);
+ }
+
+ /**
+ * @Given a new browser session is started
+ */
+ public function aNewBrowserSessionIsStarted() {
+ $loginUrl = substr($this->baseUrl, 0, -5) . '/login';
+ // Request a new session and extract CSRF token
+ $client = new Client();
+ $response = $client->get(
+ $loginUrl, [
+ 'cookies' => $this->cookieJar,
+ ]
+ );
+ $this->extracRequestTokenFromResponse($response);
+
+ // Login and extract new token
+ $client = new Client();
+ $response = $client->post(
+ $loginUrl, [
+ 'body' => [
+ 'user' => 'user0',
+ 'password' => '123456',
+ 'requesttoken' => $this->requestToken,
+ ],
+ 'cookies' => $this->cookieJar,
+ ]
+ );
+ $this->extracRequestTokenFromResponse($response);
+ }
+
+}
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index 31be33165e6..b8fb516fada 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -6,6 +6,9 @@ use GuzzleHttp\Message\ResponseInterface;
require __DIR__ . '/../../vendor/autoload.php';
trait BasicStructure {
+
+ use Auth;
+
/** @var string */
private $currentUser = '';
@@ -176,7 +179,7 @@ trait BasicStructure {
* @param string $user
*/
public function loggingInUsingWebAs($user) {
- $loginUrl = substr($this->baseUrl, 0, -5);
+ $loginUrl = substr($this->baseUrl, 0, -5) . '/login';
// Request a new session and extract CSRF token
$client = new Client();
$response = $client->get(
diff --git a/core/Application.php b/core/Application.php
index 0a54386a2ce..a835dc7fbb2 100644
--- a/core/Application.php
+++ b/core/Application.php
@@ -1,6 +1,7 @@
<?php
/**
* @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Christoph Wurst <christoph@owncloud.com>
* @author Lukas Reschke <lukas@owncloud.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <rullzer@owncloud.com>
@@ -28,12 +29,14 @@ namespace OC\Core;
use OC\AppFramework\Utility\SimpleContainer;
use OC\AppFramework\Utility\TimeFactory;
+use OC\Core\Controller\AvatarController;
use OC\Core\Controller\LoginController;
-use \OCP\AppFramework\App;
use OC\Core\Controller\LostController;
+use OC\Core\Controller\TokenController;
use OC\Core\Controller\UserController;
-use OC\Core\Controller\AvatarController;
-use \OCP\Util;
+use OC_Defaults;
+use OCP\AppFramework\App;
+use OCP\Util;
/**
* Class Application
@@ -101,6 +104,15 @@ class Application extends App {
$c->query('URLGenerator')
);
});
+ $container->registerService('TokenController', function(SimpleContainer $c) {
+ return new TokenController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('UserManager'),
+ $c->query('OC\Authentication\Token\DefaultTokenProvider'),
+ $c->query('SecureRandom')
+ );
+ });
/**
* Core class wrappers
@@ -132,6 +144,9 @@ class Application extends App {
$container->registerService('UserSession', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getUserSession();
});
+ $container->registerService('Session', function(SimpleContainer $c) {
+ return $c->query('ServerContainer')->getSession();
+ });
$container->registerService('Cache', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getCache();
});
@@ -139,7 +154,7 @@ class Application extends App {
return $c->query('ServerContainer')->getUserFolder();
});
$container->registerService('Defaults', function() {
- return new \OC_Defaults;
+ return new OC_Defaults;
});
$container->registerService('Mailer', function(SimpleContainer $c) {
return $c->query('ServerContainer')->getMailer();
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index 796706d364a..6985e2be87e 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -1,5 +1,7 @@
<?php
+
/**
+ * @author Christoph Wurst <christoph@owncloud.com>
* @author Lukas Reschke <lukas@owncloud.com>
*
* @copyright Copyright (c) 2016, ownCloud, Inc.
@@ -21,7 +23,10 @@
namespace OC\Core\Controller;
-use OC\Setup;
+use OC;
+use OC\User\Session;
+use OC_App;
+use OC_Util;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
@@ -31,17 +36,21 @@ use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
-use OCP\IUserSession;
class LoginController extends Controller {
+
/** @var IUserManager */
private $userManager;
+
/** @var IConfig */
private $config;
+
/** @var ISession */
private $session;
- /** @var IUserSession */
+
+ /** @var Session */
private $userSession;
+
/** @var IURLGenerator */
private $urlGenerator;
@@ -51,16 +60,11 @@ class LoginController extends Controller {
* @param IUserManager $userManager
* @param IConfig $config
* @param ISession $session
- * @param IUserSession $userSession
+ * @param Session $userSession
* @param IURLGenerator $urlGenerator
*/
- function __construct($appName,
- IRequest $request,
- IUserManager $userManager,
- IConfig $config,
- ISession $session,
- IUserSession $userSession,
- IURLGenerator $urlGenerator) {
+ function __construct($appName, IRequest $request, IUserManager $userManager, IConfig $config, ISession $session,
+ Session $userSession, IURLGenerator $urlGenerator) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
$this->config = $config;
@@ -94,20 +98,18 @@ class LoginController extends Controller {
* @param string $redirect_url
* @param string $remember_login
*
- * @return TemplateResponse
+ * @return TemplateResponse|RedirectResponse
*/
- public function showLoginForm($user,
- $redirect_url,
- $remember_login) {
- if($this->userSession->isLoggedIn()) {
- return new RedirectResponse(\OC_Util::getDefaultPageUrl());
+ public function showLoginForm($user, $redirect_url, $remember_login) {
+ if ($this->userSession->isLoggedIn()) {
+ return new RedirectResponse(OC_Util::getDefaultPageUrl());
}
$parameters = array();
$loginMessages = $this->session->get('loginMessages');
$errors = [];
$messages = [];
- if(is_array($loginMessages)) {
+ if (is_array($loginMessages)) {
list($errors, $messages) = $loginMessages;
}
$this->session->remove('loginMessages');
@@ -137,8 +139,8 @@ class LoginController extends Controller {
}
}
- $parameters['alt_login'] = \OC_App::getAlternativeLogIns();
- $parameters['rememberLoginAllowed'] = \OC_Util::rememberLoginAllowed();
+ $parameters['alt_login'] = OC_App::getAlternativeLogIns();
+ $parameters['rememberLoginAllowed'] = OC_Util::rememberLoginAllowed();
$parameters['rememberLoginState'] = !empty($remember_login) ? $remember_login : 0;
if (!is_null($user) && $user !== '') {
@@ -150,11 +152,49 @@ class LoginController extends Controller {
}
return new TemplateResponse(
- $this->appName,
- 'login',
- $parameters,
- 'guest'
+ $this->appName, 'login', $parameters, 'guest'
);
}
+ /**
+ * @PublicPage
+ * @UseSession
+ *
+ * @param string $user
+ * @param string $password
+ * @param string $redirect_url
+ * @return RedirectResponse
+ */
+ public function tryLogin($user, $password, $redirect_url) {
+ // TODO: Add all the insane error handling
+ $loginResult = $this->userManager->checkPassword($user, $password);
+ if ($loginResult === false) {
+ $users = $this->userManager->getByEmail($user);
+ // we only allow login by email if unique
+ if (count($users) === 1) {
+ $user = $users[0]->getUID();
+ $loginResult = $this->userManager->checkPassword($user, $password);
+ }
+ }
+ if ($loginResult === false) {
+ $this->session->set('loginMessages', [
+ [],
+ ['invalidpassword']
+ ]);
+ // Read current user and append if possible
+ $args = !is_null($user) ? ['user' => $user] : [];
+ return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args));
+ }
+ $this->userSession->createSessionToken($this->request, $loginResult->getUID(), $password);
+ if (!is_null($redirect_url) && $this->userSession->isLoggedIn()) {
+ $location = $this->urlGenerator->getAbsoluteURL(urldecode($redirect_url));
+ // Deny the redirect if the URL contains a @
+ // This prevents unvalidated redirects like ?redirect_url=:user@domain.com
+ if (strpos($location, '@') === false) {
+ return new RedirectResponse($location);
+ }
+ }
+ return new RedirectResponse($this->urlGenerator->linkTo('files', 'index'));
+ }
+
}
diff --git a/core/Controller/TokenController.php b/core/Controller/TokenController.php
new file mode 100644
index 00000000000..6606a3c8345
--- /dev/null
+++ b/core/Controller/TokenController.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Core\Controller;
+
+use OC\AppFramework\Http;
+use OC\Authentication\Token\DefaultTokenProvider;
+use OC\Authentication\Token\IToken;
+use OC\User\Manager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+use OCP\Security\ISecureRandom;
+
+class TokenController extends Controller {
+
+ /** @var Manager */
+ private $userManager;
+
+ /** @var DefaultTokenProvider */
+ private $tokenProvider;
+
+ /** @var ISecureRandom */
+ private $secureRandom;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param Manager $userManager
+ * @param DefaultTokenProvider $tokenProvider
+ * @param ISecureRandom $secureRandom
+ */
+ public function __construct($appName, IRequest $request, Manager $userManager, DefaultTokenProvider $tokenProvider,
+ ISecureRandom $secureRandom) {
+ parent::__construct($appName, $request);
+ $this->userManager = $userManager;
+ $this->tokenProvider = $tokenProvider;
+ $this->secureRandom = $secureRandom;
+ }
+
+ /**
+ * Generate a new access token clients can authenticate with
+ *
+ * @PublicPage
+ * @NoCSRFRequired
+ *
+ * @param string $user
+ * @param string $password
+ * @param string $name the name of the client
+ * @return JSONResponse
+ */
+ public function generateToken($user, $password, $name = 'unknown client') {
+ if (is_null($user) || is_null($password)) {
+ $response = new Response();
+ $response->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
+ return $response;
+ }
+ if ($this->userManager->checkPassword($user, $password) === false) {
+ $response = new Response();
+ $response->setStatus(Http::STATUS_UNAUTHORIZED);
+ return $response;
+ }
+ $token = $this->secureRandom->generate(128);
+ $this->tokenProvider->generateToken($token, $user, $password, $name, IToken::PERMANENT_TOKEN);
+ return [
+ 'token' => $token,
+ ];
+ }
+
+}
diff --git a/core/routes.php b/core/routes.php
index a9c800af4e8..70909352000 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -42,9 +42,11 @@ $application->registerRoutes($this, [
['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'],
['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'],
['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'],
+ ['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'],
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
- ]
+ ['name' => 'token#generateToken', 'url' => '/token/generate', 'verb' => 'POST'],
+ ],
]);
// Post installation check
diff --git a/core/templates/login.php b/core/templates/login.php
index 86c186928cc..45814fc71d9 100644
--- a/core/templates/login.php
+++ b/core/templates/login.php
@@ -9,7 +9,7 @@ script('core', [
?>
<!--[if IE 8]><style>input[type="checkbox"]{padding:0;}</style><![endif]-->
-<form method="post" name="login" action="<?php p(OC::$WEBROOT) ?>/">
+<form method="post" name="login">
<fieldset>
<?php if (!empty($_['redirect_url'])) {
print_unescaped('<input type="hidden" name="redirect_url" value="' . \OCP\Util::sanitizeHTML($_['redirect_url']) . '">');
diff --git a/db_structure.xml b/db_structure.xml
index 99541a4f901..7b4a3b53294 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -1032,6 +1032,92 @@
</table>
<table>
+ <name>*dbprefix*authtoken</name>
+
+ <declaration>
+
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+
+ <!-- Foreign Key users::uid -->
+ <field>
+ <name>uid</name>
+ <type>text</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+
+ <field>
+ <name>password</name>
+ <type>clob</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>4000</length>
+ </field>
+
+ <field>
+ <name>name</name>
+ <type>text</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>100</length>
+ </field>
+
+ <field>
+ <name>token</name>
+ <type>text</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>200</length>
+ </field>
+
+ <field>
+ <name>type</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>2</length>
+ </field>
+
+ <field>
+ <name>last_activity</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+
+ <index>
+ <name>authtoken_token_index</name>
+ <unique>true</unique>
+ <field>
+ <name>token</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+
+ <index>
+ <name>authtoken_last_activity_index</name>
+ <field>
+ <name>last_activity</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+
+ </declaration>
+ </table>
+
+ <table>
<!--
List of tags (category) + a unique tag id (id) per user (uid) and type.
diff --git a/lib/base.php b/lib/base.php
index 0ea8a117e98..16ce0973a95 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -856,7 +856,7 @@ class OC {
} else {
// For guests: Load only filesystem and logging
OC_App::loadApps(array('filesystem', 'logging'));
- \OC_User::tryBasicAuthLogin();
+ self::handleLogin($request);
}
}
@@ -878,17 +878,6 @@ class OC {
}
}
- // Handle redirect URL for logged in users
- if (isset($_REQUEST['redirect_url']) && OC_User::isLoggedIn()) {
- $location = \OC::$server->getURLGenerator()->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
-
- // Deny the redirect if the URL contains a @
- // This prevents unvalidated redirects like ?redirect_url=:user@domain.com
- if (strpos($location, '@') === false) {
- header('Location: ' . $location);
- return;
- }
- }
// Handle WebDAV
if ($_SERVER['REQUEST_METHOD'] == 'PROPFIND') {
// not allowed any more to prevent people
@@ -904,12 +893,33 @@ class OC {
OC_App::loadApps();
OC_User::setupBackends();
OC_Util::setupFS();
+ // FIXME
// Redirect to default application
OC_Util::redirectToDefaultPage();
} else {
// Not handled and not logged in
- self::handleLogin();
+ header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute('core.login.showLoginForm'));
+ }
+ }
+
+ /**
+ * Check login: apache auth, auth token, basic auth
+ *
+ * @param OCP\IRequest $request
+ * @return boolean
+ */
+ private static function handleLogin(OCP\IRequest $request) {
+ $userSession = self::$server->getUserSession();
+ if (OC_User::handleApacheAuth()) {
+ return true;
}
+ if ($userSession->tryTokenLogin($request)) {
+ return true;
+ }
+ if ($userSession->tryBasicAuthLogin($request)) {
+ return true;
+ }
+ return false;
}
protected static function handleAuthHeaders() {
@@ -932,163 +942,6 @@ class OC {
}
}
}
-
- protected static function handleLogin() {
- OC_App::loadApps(array('prelogin'));
- $error = array();
- $messages = [];
-
- try {
- // auth possible via apache module?
- if (OC::tryApacheAuth()) {
- $error[] = 'apacheauthfailed';
- } // remember was checked after last login
- elseif (OC::tryRememberLogin()) {
- $error[] = 'invalidcookie';
- } // logon via web form
- elseif (OC::tryFormLogin()) {
- $error[] = 'invalidpassword';
- }
- } catch (\OC\User\LoginException $e) {
- $messages[] = $e->getMessage();
- } catch (\Exception $ex) {
- \OCP\Util::logException('handleLogin', $ex);
- // do not disclose information. show generic error
- $error[] = 'internalexception';
- }
-
- if(!\OC::$server->getUserSession()->isLoggedIn()) {
- $loginMessages = array(array_unique($error), $messages);
- \OC::$server->getSession()->set('loginMessages', $loginMessages);
- // Read current user and append if possible
- $args = [];
- if(isset($_POST['user'])) {
- $args['user'] = $_POST['user'];
- }
-
- $redirectionTarget = \OC::$server->getURLGenerator()->linkToRoute('core.login.showLoginForm', $args);
- header('Location: ' . $redirectionTarget);
- }
- }
-
- /**
- * Remove outdated and therefore invalid tokens for a user
- * @param string $user
- */
- protected static function cleanupLoginTokens($user) {
- $config = \OC::$server->getConfig();
- $cutoff = time() - $config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
- $tokens = $config->getUserKeys($user, 'login_token');
- foreach ($tokens as $token) {
- $time = $config->getUserValue($user, 'login_token', $token);
- if ($time < $cutoff) {
- $config->deleteUserValue($user, 'login_token', $token);
- }
- }
- }
-
- /**
- * Try to login a user via HTTP authentication
- * @return bool|void
- */
- protected static function tryApacheAuth() {
- $return = OC_User::handleApacheAuth();
-
- // if return is true we are logged in -> redirect to the default page
- if ($return === true) {
- $_REQUEST['redirect_url'] = \OC::$server->getRequest()->getRequestUri();
- OC_Util::redirectToDefaultPage();
- exit;
- }
-
- // in case $return is null apache based auth is not enabled
- return is_null($return) ? false : true;
- }
-
- /**
- * Try to login a user using the remember me cookie.
- * @return bool Whether the provided cookie was valid
- */
- protected static function tryRememberLogin() {
- if (!isset($_COOKIE["oc_remember_login"])
- || !isset($_COOKIE["oc_token"])
- || !isset($_COOKIE["oc_username"])
- || !$_COOKIE["oc_remember_login"]
- || !OC_Util::rememberLoginAllowed()
- ) {
- return false;
- }
-
- if (\OC::$server->getConfig()->getSystemValue('debug', false)) {
- \OCP\Util::writeLog('core', 'Trying to login from cookie', \OCP\Util::DEBUG);
- }
-
- if(OC_User::userExists($_COOKIE['oc_username'])) {
- self::cleanupLoginTokens($_COOKIE['oc_username']);
- // verify whether the supplied "remember me" token was valid
- $granted = OC_User::loginWithCookie(
- $_COOKIE['oc_username'], $_COOKIE['oc_token']);
- if($granted === true) {
- OC_Util::redirectToDefaultPage();
- // doesn't return
- }
- \OCP\Util::writeLog('core', 'Authentication cookie rejected for user ' .
- $_COOKIE['oc_username'], \OCP\Util::WARN);
- // if you reach this point you have changed your password
- // or you are an attacker
- // we can not delete tokens here because users may reach
- // this point multiple times after a password change
- }
-
- OC_User::unsetMagicInCookie();
- return true;
- }
-
- /**
- * Tries to login a user using the form based authentication
- * @return bool|void
- */
- protected static function tryFormLogin() {
- if (!isset($_POST["user"]) || !isset($_POST['password'])) {
- return false;
- }
-
- if(!(\OC::$server->getRequest()->passesCSRFCheck())) {
- return false;
- }
- OC_App::loadApps();
-
- //setup extra user backends
- OC_User::setupBackends();
-
- if (OC_User::login((string)$_POST["user"], (string)$_POST["password"])) {
- $userId = OC_User::getUser();
-
- // setting up the time zone
- if (isset($_POST['timezone-offset'])) {
- self::$server->getSession()->set('timezone', (string)$_POST['timezone-offset']);
- self::$server->getConfig()->setUserValue($userId, 'core', 'timezone', (string)$_POST['timezone']);
- }
-
- self::cleanupLoginTokens($userId);
- if (!empty($_POST["remember_login"])) {
- $config = self::$server->getConfig();
- if ($config->getSystemValue('debug', false)) {
- self::$server->getLogger()->debug('Setting remember login to cookie', array('app' => 'core'));
- }
- $token = \OC::$server->getSecureRandom()->generate(32);
- $config->setUserValue($userId, 'login_token', $token, time());
- OC_User::setMagicInCookie($userId, $token);
- } else {
- OC_User::unsetMagicInCookie();
- }
- OC_Util::redirectToDefaultPage();
- exit();
- }
- return true;
- }
-
}
-
OC::init();
diff --git a/lib/private/Authentication/Exceptions/InvalidTokenException.php b/lib/private/Authentication/Exceptions/InvalidTokenException.php
new file mode 100644
index 00000000000..3e52d3b78f0
--- /dev/null
+++ b/lib/private/Authentication/Exceptions/InvalidTokenException.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Exceptions;
+
+use Exception;
+
+class InvalidTokenException extends Exception {
+
+}
diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php
new file mode 100644
index 00000000000..25caf675a43
--- /dev/null
+++ b/lib/private/Authentication/Token/DefaultToken.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+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)
+ * @method string getToken()
+ * @method void setType(string $type)
+ * @method int getType()
+ * @method void setLastActivity(int $lastActivity)
+ * @method int getLastActivity()
+ */
+class DefaultToken extends Entity implements IToken {
+
+ /**
+ * @var string user UID
+ */
+ protected $uid;
+
+ /**
+ * @var string encrypted user password
+ */
+ protected $password;
+
+ /**
+ * @var string token name (e.g. browser/OS)
+ */
+ protected $name;
+
+ /**
+ * @var string
+ */
+ protected $token;
+
+ /**
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * @var int
+ */
+ protected $lastActivity;
+
+ public function getId() {
+ return $this->id;
+ }
+
+ public function getUID() {
+ return $this->uid;
+ }
+
+}
diff --git a/lib/private/Authentication/Token/DefaultTokenCleanupJob.php b/lib/private/Authentication/Token/DefaultTokenCleanupJob.php
new file mode 100644
index 00000000000..4d1290eb623
--- /dev/null
+++ b/lib/private/Authentication/Token/DefaultTokenCleanupJob.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+use OC;
+use OC\BackgroundJob\Job;
+
+class DefaultTokenCleanupJob extends Job {
+
+ protected function run($argument) {
+ /* @var $provider DefaultTokenProvider */
+ $provider = OC::$server->query('OC\Authentication\Token\DefaultTokenProvider');
+ $provider->invalidateOldTokens();
+ }
+
+}
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
new file mode 100644
index 00000000000..18adbe48d78
--- /dev/null
+++ b/lib/private/Authentication/Token/DefaultTokenMapper.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Mapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class DefaultTokenMapper extends Mapper {
+
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'authtoken');
+ }
+
+ /**
+ * Invalidate (delete) a given token
+ *
+ * @param string $token
+ */
+ public function invalidate($token) {
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('authtoken')
+ ->andWhere($qb->expr()->eq('token', $qb->createParameter('token')))
+ ->setParameter('token', $token)
+ ->execute();
+ }
+
+ /**
+ * @param int $olderThan
+ */
+ public function invalidateOld($olderThan) {
+ /* @var $qb IQueryBuilder */
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete('authtoken')
+ ->where($qb->expr()->lt('last_activity', $qb->createParameter('last_activity')))
+ ->andWhere($qb->expr()->eq('type', $qb->createParameter('type')))
+ ->setParameter('last_activity', $olderThan, IQueryBuilder::PARAM_INT)
+ ->setParameter('type', IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)
+ ->execute();
+ }
+
+ /**
+ * Get the user UID for the given token
+ *
+ * @param string $token
+ * @throws DoesNotExistException
+ * @return DefaultToken
+ */
+ public function getToken($token) {
+ /* @var $qb IQueryBuilder */
+ $qb = $this->db->getQueryBuilder();
+ $result = $qb->select('id', 'uid', 'password', 'name', 'type', 'token', 'last_activity')
+ ->from('authtoken')
+ ->where($qb->expr()->eq('token', $qb->createParameter('token')))
+ ->setParameter('token', $token)
+ ->execute();
+
+ $data = $result->fetch();
+ if ($data === false) {
+ throw new DoesNotExistException('token does not exist');
+ }
+ return DefaultToken::fromRow($data);
+ }
+
+}
diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
new file mode 100644
index 00000000000..a6641277cf9
--- /dev/null
+++ b/lib/private/Authentication/Token/DefaultTokenProvider.php
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+use Exception;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\Security\ICrypto;
+
+class DefaultTokenProvider implements IProvider {
+
+ /** @var DefaultTokenMapper */
+ private $mapper;
+
+ /** @var ICrypto */
+ private $crypto;
+
+ /** @var IConfig */
+ private $config;
+
+ /** @var ILogger $logger */
+ private $logger;
+
+ /** @var ITimeFactory $time */
+ private $time;
+
+ /**
+ * @param DefaultTokenMapper $mapper
+ * @param ICrypto $crypto
+ * @param IConfig $config
+ * @param ILogger $logger
+ * @param ITimeFactory $time
+ */
+ public function __construct(DefaultTokenMapper $mapper, ICrypto $crypto, IConfig $config, ILogger $logger, ITimeFactory $time) {
+ $this->mapper = $mapper;
+ $this->crypto = $crypto;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->time = $time;
+ }
+
+ /**
+ * Create and persist a new token
+ *
+ * @param string $token
+ * @param string $uid
+ * @param string $password
+ * @param string $name
+ * @param int $type token type
+ * @return DefaultToken
+ */
+ public function generateToken($token, $uid, $password, $name, $type = IToken::TEMPORARY_TOKEN) {
+ $dbToken = new DefaultToken();
+ $dbToken->setUid($uid);
+ $dbToken->setPassword($this->encryptPassword($password, $token));
+ $dbToken->setName($name);
+ $dbToken->setToken($this->hashToken($token));
+ $dbToken->setType($type);
+ $dbToken->setLastActivity($this->time->getTime());
+
+ $this->mapper->insert($dbToken);
+
+ return $dbToken;
+ }
+
+ /**
+ * Update token activity timestamp
+ *
+ * @throws InvalidTokenException
+ * @param IToken $token
+ */
+ public function updateToken(IToken $token) {
+ if (!($token instanceof DefaultToken)) {
+ throw new InvalidTokenException();
+ }
+ /** @var DefaultToken $token */
+ $token->setLastActivity($this->time->getTime());
+
+ $this->mapper->update($token);
+ }
+
+ /**
+ * @param string $token
+ * @throws InvalidTokenException
+ * @return DefaultToken
+ */
+ public function getToken($token) {
+ try {
+ return $this->mapper->getToken($this->hashToken($token));
+ } catch (DoesNotExistException $ex) {
+ throw new InvalidTokenException();
+ }
+ }
+
+ /**
+ * @param DefaultToken $savedToken
+ * @param string $token session token
+ * @return string
+ */
+ public function getPassword(DefaultToken $savedToken, $token) {
+ return $this->decryptPassword($savedToken->getPassword(), $token);
+ }
+
+ /**
+ * Invalidate (delete) the given session token
+ *
+ * @param string $token
+ */
+ public function invalidateToken($token) {
+ $this->mapper->invalidate($this->hashToken($token));
+ }
+
+ /**
+ * Invalidate (delete) old session tokens
+ */
+ public function invalidateOldTokens() {
+ $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
+ $this->logger->info('Invalidating tokens older than ' . date('c', $olderThan));
+ $this->mapper->invalidateOld($olderThan);
+ }
+
+ /**
+ * @param string $token
+ * @throws InvalidTokenException
+ * @return DefaultToken user UID
+ */
+ public function validateToken($token) {
+ $this->logger->debug('validating default token <' . $token . '>');
+ try {
+ $dbToken = $this->mapper->getToken($this->hashToken($token));
+ $this->logger->debug('valid token for ' . $dbToken->getUID());
+ return $dbToken;
+ } catch (DoesNotExistException $ex) {
+ $this->logger->warning('invalid token');
+ throw new InvalidTokenException();
+ }
+ }
+
+ /**
+ * @param string $token
+ * @return string
+ */
+ private function hashToken($token) {
+ $secret = $this->config->getSystemValue('secret');
+ return hash('sha512', $token . $secret);
+ }
+
+ /**
+ * Encrypt the given password
+ *
+ * The token is used as key
+ *
+ * @param string $password
+ * @param string $token
+ * @return string encrypted password
+ */
+ private function encryptPassword($password, $token) {
+ $secret = $this->config->getSystemValue('secret');
+ return $this->crypto->encrypt($password, $token . $secret);
+ }
+
+ /**
+ * Decrypt the given password
+ *
+ * The token is used as key
+ *
+ * @param string $password
+ * @param string $token
+ * @return string the decrypted key
+ */
+ private function decryptPassword($password, $token) {
+ $secret = $this->config->getSystemValue('secret');
+ try {
+ return $this->crypto->decrypt($password, $token . $secret);
+ } catch (Exception $ex) {
+ // Delete the invalid token
+ $this->invalidateToken($token);
+ throw new InvalidTokenException();
+ }
+ }
+
+}
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
new file mode 100644
index 00000000000..f8a3262ca8b
--- /dev/null
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+use OC\Authentication\Exceptions\InvalidTokenException;
+
+interface IProvider {
+
+ /**
+ * @param string $token
+ * @throws InvalidTokenException
+ * @return IToken
+ */
+ public function validateToken($token);
+
+ /**
+ * Update token activity timestamp
+ *
+ * @param IToken $token
+ */
+ public function updateToken(IToken $token);
+}
diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php
new file mode 100644
index 00000000000..9b2bd18f83b
--- /dev/null
+++ b/lib/private/Authentication/Token/IToken.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Authentication\Token;
+
+/**
+ * @since 9.1.0
+ */
+interface IToken {
+
+ const TEMPORARY_TOKEN = 0;
+ const PERMANENT_TOKEN = 1;
+
+ /**
+ * Get the token ID
+ *
+ * @return string
+ */
+ public function getId();
+
+ /**
+ * Get the user UID
+ *
+ * @return string
+ */
+ public function getUID();
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index bbe6b88876f..cbab1f09ebd 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -5,6 +5,7 @@
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Bernhard Reiter <ockham@raz.or.at>
* @author Björn Schießle <schiessle@owncloud.com>
+ * @author Christoph Wurst <christoph@owncloud.com>
* @author Christopher Schäpers <kondou@ts.unde.re>
* @author Joas Schilling <nickvergessen@owncloud.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
@@ -208,12 +209,35 @@ class Server extends ServerContainer implements IServerContainer {
});
return $groupManager;
});
+ $this->registerService('OC\Authentication\Token\DefaultTokenMapper', function (Server $c) {
+ $dbConnection = $c->getDatabaseConnection();
+ return new Authentication\Token\DefaultTokenMapper($dbConnection);
+ });
+ $this->registerService('OC\Authentication\Token\DefaultTokenProvider', function (Server $c) {
+ $mapper = $c->query('OC\Authentication\Token\DefaultTokenMapper');
+ $crypto = $c->getCrypto();
+ $config = $c->getConfig();
+ $logger = $c->getLogger();
+ $timeFactory = new TimeFactory();
+ return new \OC\Authentication\Token\DefaultTokenProvider($mapper, $crypto, $config, $logger, $timeFactory);
+ });
$this->registerService('UserSession', function (Server $c) {
$manager = $c->getUserManager();
-
$session = new \OC\Session\Memory('');
-
- $userSession = new \OC\User\Session($manager, $session);
+ $timeFactory = new TimeFactory();
+ // Token providers might require a working database. This code
+ // might however be called when ownCloud is not yet setup.
+ if (\OC::$server->getSystemConfig()->getValue('installed', false)) {
+ $defaultTokenProvider = $c->query('OC\Authentication\Token\DefaultTokenProvider');
+ $tokenProviders = [
+ $defaultTokenProvider,
+ ];
+ } else {
+ $defaultTokenProvider = null;
+ $tokenProviders = [];
+ }
+
+ $userSession = new \OC\User\Session($manager, $session, $timeFactory, $defaultTokenProvider, $tokenProviders);
$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
\OC_Hook::emit('OC_User', 'pre_createUser', array('run' => true, 'uid' => $uid, 'password' => $password));
});
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index 23c66f98b7c..67d714188ac 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -364,7 +364,14 @@ class Setup {
$group =\OC::$server->getGroupManager()->createGroup('admin');
$group->addUser($user);
- \OC_User::login($username, $password);
+
+ // Create a session token for the newly created user
+ // The token provider requires a working db, so it's not injected on setup
+ /* @var $userSession User\Session */
+ $userSession = \OC::$server->getUserSession();
+ $defaultTokenProvider = \OC::$server->query('OC\Authentication\Token\DefaultTokenProvider');
+ $userSession->setTokenProvider($defaultTokenProvider);
+ $userSession->createSessionToken($request, $username, $password);
//guess what this does
Installer::installShippedApps();
@@ -382,6 +389,8 @@ class Setup {
$config->setSystemValue('logtimezone', date_default_timezone_get());
}
+ self::installBackgroundJobs();
+
//and we are done
$config->setSystemValue('installed', true);
}
@@ -389,6 +398,10 @@ class Setup {
return $error;
}
+ public static function installBackgroundJobs() {
+ \OC::$server->getJobList()->add('\OC\Authentication\Token\DefaultTokenCleanupJob');
+ }
+
/**
* @return string Absolute path to htaccess
*/
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 7ca3cd09362..dbcaccaad26 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -216,6 +216,8 @@ class Updater extends BasicEmitter {
try {
Setup::updateHtaccess();
Setup::protectDataDirectory();
+ // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378
+ Setup::installBackgroundJobs();
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index c7f8a6920de..c9f42d7e414 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -1,7 +1,9 @@
<?php
+
/**
* @author Arthur Schiwon <blizzz@owncloud.com>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Christoph Wurst <christoph@owncloud.com>
* @author Joas Schilling <nickvergessen@owncloud.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@owncloud.com>
@@ -31,10 +33,22 @@
namespace OC\User;
+use OC;
+use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\DefaultTokenProvider;
+use OC\Authentication\Token\IProvider;
+use OC\Authentication\Token\IToken;
use OC\Hooks\Emitter;
+use OC_User;
+use OC_Util;
+use OCA\DAV\Connector\Sabre\Auth;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IRequest;
use OCP\ISession;
+use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
+use OCP\Session\Exceptions\SessionNotAvailableException;
/**
* Class Session
@@ -55,22 +69,57 @@ use OCP\IUserSession;
* @package OC\User
*/
class Session implements IUserSession, Emitter {
- /** @var \OC\User\Manager $manager */
+ /*
+ * @var Manager $manager
+ */
+
private $manager;
- /** @var \OC\Session\Session $session */
+ /*
+ * @var ISession $session
+ */
private $session;
- /** @var \OC\User\User $activeUser */
+ /*
+ * @var ITimeFactory
+ */
+ private $timeFacory;
+
+ /**
+ * @var DefaultTokenProvider
+ */
+ private $tokenProvider;
+
+ /**
+ * @var IProvider[]
+ */
+ private $tokenProviders;
+
+ /**
+ * @var User $activeUser
+ */
protected $activeUser;
/**
* @param IUserManager $manager
* @param ISession $session
+ * @param ITimeFactory $timeFacory
+ * @param IProvider $tokenProvider
+ * @param IProvider[] $tokenProviders
*/
- public function __construct(IUserManager $manager, ISession $session) {
+ public function __construct(IUserManager $manager, ISession $session, ITimeFactory $timeFacory, $tokenProvider, array $tokenProviders = []) {
$this->manager = $manager;
$this->session = $session;
+ $this->timeFacory = $timeFacory;
+ $this->tokenProvider = $tokenProvider;
+ $this->tokenProviders = $tokenProviders;
+ }
+
+ /**
+ * @param DefaultTokenProvider $provider
+ */
+ public function setTokenProvider(DefaultTokenProvider $provider) {
+ $this->tokenProvider = $provider;
}
/**
@@ -94,7 +143,7 @@ class Session implements IUserSession, Emitter {
/**
* get the manager object
*
- * @return \OC\User\Manager
+ * @return Manager
*/
public function getManager() {
return $this->manager;
@@ -125,7 +174,7 @@ class Session implements IUserSession, Emitter {
/**
* set the currently active user
*
- * @param \OC\User\User|null $user
+ * @param User|null $user
*/
public function setUser($user) {
if (is_null($user)) {
@@ -139,25 +188,65 @@ class Session implements IUserSession, Emitter {
/**
* get the current active user
*
- * @return \OCP\IUser|null Current user, otherwise null
+ * @return IUser|null Current user, otherwise null
*/
public function getUser() {
// FIXME: This is a quick'n dirty work-around for the incognito mode as
// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
- if (\OC_User::isIncognitoMode()) {
+ if (OC_User::isIncognitoMode()) {
return null;
}
- if ($this->activeUser) {
- return $this->activeUser;
- } else {
+ if (is_null($this->activeUser)) {
$uid = $this->session->get('user_id');
- if ($uid !== null) {
- $this->activeUser = $this->manager->get($uid);
- return $this->activeUser;
- } else {
+ if (is_null($uid)) {
+ return null;
+ }
+ $this->activeUser = $this->manager->get($uid);
+ if (is_null($this->activeUser)) {
return null;
}
+ $this->validateSession($this->activeUser);
}
+ return $this->activeUser;
+ }
+
+ protected function validateSession(IUser $user) {
+ try {
+ $sessionId = $this->session->getId();
+ } catch (SessionNotAvailableException $ex) {
+ return;
+ }
+ try {
+ $token = $this->tokenProvider->getToken($sessionId);
+ } catch (InvalidTokenException $ex) {
+ // Session was invalidated
+ $this->logout();
+ return;
+ }
+
+ // Check whether login credentials are still valid
+ // This check is performed each 5 minutes
+ $lastCheck = $this->session->get('last_login_check') ? : 0;
+ $now = $this->timeFacory->getTime();
+ if ($lastCheck < ($now - 60 * 5)) {
+ try {
+ $pwd = $this->tokenProvider->getPassword($token, $sessionId);
+ } catch (InvalidTokenException $ex) {
+ // An invalid token password was used -> log user out
+ $this->logout();
+ return;
+ }
+
+ if ($this->manager->checkPassword($user->getUID(), $pwd) === false) {
+ // Password has changed -> log user out
+ $this->logout();
+ return;
+ }
+ $this->session->set('last_login_check', $now);
+ }
+
+ // Session is valid, so the token can be refreshed
+ $this->updateToken($this->tokenProvider, $token);
}
/**
@@ -218,6 +307,11 @@ class Session implements IUserSession, Emitter {
$this->session->regenerateId();
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
$user = $this->manager->checkPassword($uid, $password);
+ if ($user === false) {
+ if ($this->validateToken($password)) {
+ $user = $this->getUser();
+ }
+ }
if ($user !== false) {
if (!is_null($user)) {
if ($user->isEnabled()) {
@@ -225,6 +319,7 @@ class Session implements IUserSession, Emitter {
$this->setLoginName($uid);
$this->manager->emit('\OC\User', 'postLogin', array($user, $password));
if ($this->isLoggedIn()) {
+ $this->prepareUserLogin();
return true;
} else {
// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
@@ -241,6 +336,142 @@ class Session implements IUserSession, Emitter {
return false;
}
+ protected function prepareUserLogin() {
+ // TODO: mock/inject/use non-static
+ // Refresh the token
+ \OC::$server->getCsrfTokenManager()->refreshToken();
+ //we need to pass the user name, which may differ from login name
+ $user = $this->getUser()->getUID();
+ OC_Util::setupFS($user);
+ //trigger creation of user home and /files folder
+ \OC::$server->getUserFolder($user);
+ }
+
+ /**
+ * Tries to login the user with HTTP Basic Authentication
+ *
+ * @param IRequest $request
+ * @return boolean if the login was successful
+ */
+ public function tryBasicAuthLogin(IRequest $request) {
+ if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
+ $result = $this->login($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW']);
+ if ($result === true) {
+ /**
+ * Add DAV authenticated. This should in an ideal world not be
+ * necessary but the iOS App reads cookies from anywhere instead
+ * only the DAV endpoint.
+ * This makes sure that the cookies will be valid for the whole scope
+ * @see https://github.com/owncloud/core/issues/22893
+ */
+ $this->session->set(
+ Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
+ );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private function loginWithToken($uid) {
+ // TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
+ $user = $this->manager->get($uid);
+ if (is_null($user)) {
+ // user does not exist
+ return false;
+ }
+
+ //login
+ $this->setUser($user);
+ // TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user));
+ return true;
+ }
+
+ /**
+ * Create a new session token for the given user credentials
+ *
+ * @param IRequest $request
+ * @param string $uid user UID
+ * @param string $password
+ * @return boolean
+ */
+ public function createSessionToken(IRequest $request, $uid, $password) {
+ if (is_null($this->manager->get($uid))) {
+ // User does not exist
+ return false;
+ }
+ $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
+ $loggedIn = $this->login($uid, $password);
+ if ($loggedIn) {
+ try {
+ $sessionId = $this->session->getId();
+ $this->tokenProvider->generateToken($sessionId, $uid, $password, $name);
+ } catch (SessionNotAvailableException $ex) {
+
+ }
+ }
+ return $loggedIn;
+ }
+
+ /**
+ * @param string $token
+ * @return boolean
+ */
+ private function validateToken($token) {
+ foreach ($this->tokenProviders as $provider) {
+ try {
+ $token = $provider->validateToken($token);
+ if (!is_null($token)) {
+ $result = $this->loginWithToken($token->getUID());
+ if ($result) {
+ // Login success
+ $this->updateToken($provider, $token);
+ return true;
+ }
+ }
+ } catch (InvalidTokenException $ex) {
+
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param IProvider $provider
+ * @param IToken $token
+ */
+ private function updateToken(IProvider $provider, IToken $token) {
+ // To save unnecessary DB queries, this is only done once a minute
+ $lastTokenUpdate = $this->session->get('last_token_update') ? : 0;
+ $now = $this->timeFacory->getTime();
+ if ($lastTokenUpdate < ($now - 60)) {
+ $provider->updateToken($token);
+ $this->session->set('last_token_update', $now);
+ }
+ }
+
+ /**
+ * Tries to login the user with auth token header
+ *
+ * @todo check remember me cookie
+ * @return boolean
+ */
+ public function tryTokenLogin(IRequest $request) {
+ $authHeader = $request->getHeader('Authorization');
+ if (strpos($authHeader, 'token ') === false) {
+ // No auth header, let's try session id
+ try {
+ $sessionId = $this->session->getId();
+ return $this->validateToken($sessionId);
+ } catch (SessionNotAvailableException $ex) {
+ return false;
+ }
+ } else {
+ $token = substr($authHeader, 6);
+ return $this->validateToken($token);
+ }
+ }
+
/**
* perform login using the magic cookie (remember login)
*
@@ -258,15 +489,15 @@ class Session implements IUserSession, Emitter {
}
// get stored tokens
- $tokens = \OC::$server->getConfig()->getUserKeys($uid, 'login_token');
+ $tokens = OC::$server->getConfig()->getUserKeys($uid, 'login_token');
// test cookies token against stored tokens
if (!in_array($currentToken, $tokens, true)) {
return false;
}
// replace successfully used token with a new one
- \OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
- $newToken = \OC::$server->getSecureRandom()->generate(32);
- \OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time());
+ OC::$server->getConfig()->deleteUserValue($uid, 'login_token', $currentToken);
+ $newToken = OC::$server->getSecureRandom()->generate(32);
+ OC::$server->getConfig()->setUserValue($uid, 'login_token', $newToken, time());
$this->setMagicInCookie($user->getUID(), $newToken);
//login
@@ -280,6 +511,14 @@ class Session implements IUserSession, Emitter {
*/
public function logout() {
$this->manager->emit('\OC\User', 'logout');
+ $user = $this->getUser();
+ if (!is_null($user)) {
+ try {
+ $this->tokenProvider->invalidateToken($this->session->getId());
+ } catch (SessionNotAvailableException $ex) {
+
+ }
+ }
$this->setUser(null);
$this->setLoginName(null);
$this->unsetMagicInCookie();
@@ -293,11 +532,11 @@ class Session implements IUserSession, Emitter {
* @param string $token
*/
public function setMagicInCookie($username, $token) {
- $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https';
- $expires = time() + \OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
- setcookie("oc_username", $username, $expires, \OC::$WEBROOT, '', $secureCookie, true);
- setcookie("oc_token", $token, $expires, \OC::$WEBROOT, '', $secureCookie, true);
- setcookie("oc_remember_login", "1", $expires, \OC::$WEBROOT, '', $secureCookie, true);
+ $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
+ $expires = time() + OC::$server->getConfig()->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
+ setcookie('oc_username', $username, $expires, OC::$WEBROOT, '', $secureCookie, true);
+ setcookie('oc_token', $token, $expires, OC::$WEBROOT, '', $secureCookie, true);
+ setcookie('oc_remember_login', '1', $expires, OC::$WEBROOT, '', $secureCookie, true);
}
/**
@@ -305,18 +544,19 @@ class Session implements IUserSession, Emitter {
*/
public function unsetMagicInCookie() {
//TODO: DI for cookies and IRequest
- $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https';
-
- unset($_COOKIE["oc_username"]); //TODO: DI
- unset($_COOKIE["oc_token"]);
- unset($_COOKIE["oc_remember_login"]);
- setcookie('oc_username', '', time() - 3600, \OC::$WEBROOT, '',$secureCookie, true);
- setcookie('oc_token', '', time() - 3600, \OC::$WEBROOT, '', $secureCookie, true);
- setcookie('oc_remember_login', '', time() - 3600, \OC::$WEBROOT, '', $secureCookie, true);
+ $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
+
+ unset($_COOKIE['oc_username']); //TODO: DI
+ unset($_COOKIE['oc_token']);
+ unset($_COOKIE['oc_remember_login']);
+ setcookie('oc_username', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
+ setcookie('oc_token', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
+ setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT, '', $secureCookie, true);
// old cookies might be stored under /webroot/ instead of /webroot
// and Firefox doesn't like it!
- setcookie('oc_username', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true);
- setcookie('oc_token', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true);
- setcookie('oc_remember_login', '', time() - 3600, \OC::$WEBROOT . '/', '', $secureCookie, true);
+ setcookie('oc_username', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
+ setcookie('oc_token', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
+ setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
}
+
}
diff --git a/lib/private/legacy/api.php b/lib/private/legacy/api.php
index 702b9df1927..60300c88b57 100644
--- a/lib/private/legacy/api.php
+++ b/lib/private/legacy/api.php
@@ -337,7 +337,7 @@ class OC_API {
}
// reuse existing login
- $loggedIn = OC_User::isLoggedIn();
+ $loggedIn = \OC::$server->getUserSession()->isLoggedIn();
if ($loggedIn === true) {
$ocsApiRequest = isset($_SERVER['HTTP_OCS_APIREQUEST']) ? $_SERVER['HTTP_OCS_APIREQUEST'] === 'true' : false;
if ($ocsApiRequest) {
@@ -353,35 +353,25 @@ class OC_API {
// basic auth - because OC_User::login will create a new session we shall only try to login
// if user and pass are set
- if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) ) {
- $authUser = $_SERVER['PHP_AUTH_USER'];
- $authPw = $_SERVER['PHP_AUTH_PW'];
- try {
- $return = OC_User::login($authUser, $authPw);
- } catch (\OC\User\LoginException $e) {
- return false;
+ $userSession = \OC::$server->getUserSession();
+ $request = \OC::$server->getRequest();
+ try {
+ $loginSuccess = $userSession->tryTokenLogin($request);
+ if (!$loginSuccess) {
+ $loginSuccess = $userSession->tryBasicAuthLogin($request);
}
- if ($return === true) {
- self::$logoutRequired = true;
-
- // initialize the user's filesystem
- \OC_Util::setUpFS(\OC_User::getUser());
- self::$isLoggedIn = true;
+ } catch (\OC\User\LoginException $e) {
+ return false;
+ }
+
+ if ($loginSuccess === true) {
+ self::$logoutRequired = true;
- /**
- * Add DAV authenticated. This should in an ideal world not be
- * necessary but the iOS App reads cookies from anywhere instead
- * only the DAV endpoint.
- * This makes sure that the cookies will be valid for the whole scope
- * @see https://github.com/owncloud/core/issues/22893
- */
- \OC::$server->getSession()->set(
- \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED,
- \OC::$server->getUserSession()->getUser()->getUID()
- );
+ // initialize the user's filesystem
+ \OC_Util::setUpFS(\OC_User::getUser());
+ self::$isLoggedIn = true;
- return \OC_User::getUser();
- }
+ return \OC_User::getUser();
}
return false;
diff --git a/lib/private/legacy/user.php b/lib/private/legacy/user.php
index 7855b5e7059..499e916994a 100644
--- a/lib/private/legacy/user.php
+++ b/lib/private/legacy/user.php
@@ -6,6 +6,7 @@
* @author Bart Visscher <bartv@thisnet.nl>
* @author Bartek Przybylski <bart.p.pl@gmail.com>
* @author Björn Schießle <schiessle@owncloud.com>
+ * @author Christoph Wurst <christoph@owncloud.com>
* @author Florian Preinstorfer <nblock@archlinux.us>
* @author Georg Ehrke <georg@owncloud.com>
* @author Jakob Sack <mail@jakobsack.de>
@@ -67,7 +68,7 @@ class OC_User {
private static $_setupedBackends = array();
- // bool, stores if a user want to access a resource anonymously, e.g if he opens a public link
+ // bool, stores if a user want to access a resource anonymously, e.g if they open a public link
private static $incognitoMode = false;
/**
@@ -148,37 +149,7 @@ class OC_User {
}
/**
- * Try to login a user
- *
- * @param string $loginName The login name of the user to log in
- * @param string $password The password of the user
- * @return boolean|null
- *
- * Log in a user and regenerate a new session - if the password is ok
- */
- public static function login($loginName, $password) {
- $result = self::getUserSession()->login($loginName, $password);
- if (!$result) {
- $users = \OC::$server->getUserManager()->getByEmail($loginName);
- // we only allow login by email if unique
- if (count($users) === 1) {
- $result = self::getUserSession()->login($users[0]->getUID(), $password);
- }
- }
- if ($result) {
- // Refresh the token
- \OC::$server->getCsrfTokenManager()->refreshToken();
- //we need to pass the user name, which may differ from login name
- $user = self::getUserSession()->getUser()->getUID();
- OC_Util::setupFS($user);
- //trigger creation of user home and /files folder
- \OC::$server->getUserFolder($user);
- }
- return $result;
- }
-
- /**
* Try to login a user using the magic cookie (remember login)
*
* @deprecated use \OCP\IUserSession::loginWithCookie()
@@ -284,28 +255,6 @@ class OC_User {
}
/**
- * Tries to login the user with HTTP Basic Authentication
- */
- public static function tryBasicAuthLogin() {
- if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
- $result = \OC_User::login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
- if($result === true) {
- /**
- * Add DAV authenticated. This should in an ideal world not be
- * necessary but the iOS App reads cookies from anywhere instead
- * only the DAV endpoint.
- * This makes sure that the cookies will be valid for the whole scope
- * @see https://github.com/owncloud/core/issues/22893
- */
- \OC::$server->getSession()->set(
- \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED,
- \OC::$server->getUserSession()->getUser()->getUID()
- );
- }
- }
- }
-
- /**
* Check if the user is logged in, considers also the HTTP basic credentials
*
* @deprecated use \OC::$server->getUserSession()->isLoggedIn()
diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php
index b3432470f03..4f7a8668dfc 100644
--- a/lib/private/legacy/util.php
+++ b/lib/private/legacy/util.php
@@ -957,8 +957,7 @@ class OC_Util {
public static function checkLoggedIn() {
// Check if we are a user
if (!OC_User::isLoggedIn()) {
- header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
- 'core.login.showLoginForm',
+ header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php',
[
'redirect_url' => \OC::$server->getRequest()->getRequestUri()
]
diff --git a/tests/core/controller/LoginControllerTest.php b/tests/core/controller/LoginControllerTest.php
index f9a6080892b..0b5a143f4e3 100644
--- a/tests/core/controller/LoginControllerTest.php
+++ b/tests/core/controller/LoginControllerTest.php
@@ -53,7 +53,9 @@ class LoginControllerTest extends TestCase {
$this->userManager = $this->getMock('\\OCP\\IUserManager');
$this->config = $this->getMock('\\OCP\\IConfig');
$this->session = $this->getMock('\\OCP\\ISession');
- $this->userSession = $this->getMock('\\OCP\\IUserSession');
+ $this->userSession = $this->getMockBuilder('\\OC\\User\\Session')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->urlGenerator = $this->getMock('\\OCP\\IURLGenerator');
$this->loginController = new LoginController(
@@ -264,4 +266,74 @@ class LoginControllerTest extends TestCase {
);
$this->assertEquals($expectedResponse, $this->loginController->showLoginForm('0', '', ''));
}
+
+ public function testLoginWithInvalidCredentials() {
+ $user = $this->getMock('\OCP\IUser');
+ $password = 'secret';
+ $loginPageUrl = 'some url';
+
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->will($this->returnValue(false));
+ $this->urlGenerator->expects($this->once())
+ ->method('linkToRoute')
+ ->with('core.login.showLoginForm')
+ ->will($this->returnValue($loginPageUrl));
+
+ $this->userSession->expects($this->never())
+ ->method('createSessionToken');
+
+ $expected = new \OCP\AppFramework\Http\RedirectResponse($loginPageUrl);
+ $this->assertEquals($expected, $this->loginController->tryLogin($user, $password, ''));
+ }
+
+ public function testLoginWithValidCredentials() {
+ $user = $this->getMock('\OCP\IUser');
+ $password = 'secret';
+ $indexPageUrl = 'some url';
+
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->will($this->returnValue($user));
+ $this->userSession->expects($this->once())
+ ->method('createSessionToken')
+ ->with($this->request, $user->getUID(), $password);
+ $this->urlGenerator->expects($this->once())
+ ->method('linkTo')
+ ->with('files', 'index')
+ ->will($this->returnValue($indexPageUrl));
+
+ $expected = new \OCP\AppFramework\Http\RedirectResponse($indexPageUrl);
+ $this->assertEquals($expected, $this->loginController->tryLogin($user, $password, null));
+ }
+
+ public function testLoginWithValidCredentialsAndRedirectUrl() {
+ $user = $this->getMock('\OCP\IUser');
+ $user->expects($this->any())
+ ->method('getUID')
+ ->will($this->returnValue('jane'));
+ $password = 'secret';
+ $originalUrl = 'another%20url';
+ $redirectUrl = 'http://localhost/another url';
+
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->with('jane', $password)
+ ->will($this->returnValue($user));
+ $this->userSession->expects($this->once())
+ ->method('createSessionToken')
+ ->with($this->request, $user->getUID(), $password);
+ $this->userSession->expects($this->once())
+ ->method('isLoggedIn')
+ ->with()
+ ->will($this->returnValue(true));
+ $this->urlGenerator->expects($this->once())
+ ->method('getAbsoluteURL')
+ ->with(urldecode($originalUrl))
+ ->will($this->returnValue($redirectUrl));
+
+ $expected = new \OCP\AppFramework\Http\RedirectResponse(urldecode($redirectUrl));
+ $this->assertEquals($expected, $this->loginController->tryLogin($user->getUID(), $password, $originalUrl));
+ }
+
}
diff --git a/tests/core/controller/TokenControllerTest.php b/tests/core/controller/TokenControllerTest.php
new file mode 100644
index 00000000000..4635f96f48f
--- /dev/null
+++ b/tests/core/controller/TokenControllerTest.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace OC\Core\Controller;
+
+use OC\AppFramework\Http;
+use OCP\AppFramework\Http\Response;
+use Test\TestCase;
+
+class TokenControllerTest extends TestCase {
+
+ /** \OC\Core\Controller\TokenController */
+ private $tokenController;
+ private $request;
+ private $userManager;
+ private $tokenProvider;
+ private $secureRandom;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->request = $this->getMock('\OCP\IRequest');
+ $this->userManager = $this->getMockBuilder('\OC\User\Manager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->tokenProvider = $this->getMockBuilder('\OC\Authentication\Token\DefaultTokenProvider')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom');
+
+ $this->tokenController = new TokenController('core', $this->request, $this->userManager, $this->tokenProvider,
+ $this->secureRandom);
+ }
+
+ public function testWithoutCredentials() {
+ $expected = new Response();
+ $expected->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
+
+ $actual = $this->tokenController->generateToken(null, null);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testWithInvalidCredentials() {
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->with('john', 'passme')
+ ->will($this->returnValue(false));
+ $expected = new Response();
+ $expected->setStatus(Http::STATUS_UNAUTHORIZED);
+
+ $actual = $this->tokenController->generateToken('john', 'passme');
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testWithValidCredentials() {
+ $this->userManager->expects($this->once())
+ ->method('checkPassword')
+ ->with('john', '123456')
+ ->will($this->returnValue(true));
+ $this->secureRandom->expects($this->once())
+ ->method('generate')
+ ->with(128)
+ ->will($this->returnValue('verysecurerandomtoken'));
+ $expected = [
+ 'token' => 'verysecurerandomtoken'
+ ];
+
+ $actual = $this->tokenController->generateToken('john', '123456');
+
+ $this->assertEquals($expected, $actual);
+ }
+
+}
diff --git a/tests/lib/authentication/token/defaulttokencleanupjobtest.php b/tests/lib/authentication/token/defaulttokencleanupjobtest.php
new file mode 100644
index 00000000000..c9082c08b30
--- /dev/null
+++ b/tests/lib/authentication/token/defaulttokencleanupjobtest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Token\DefaultTokenCleanupJob;
+use Test\TestCase;
+
+class DefaultTokenCleanupJobTest extends TestCase {
+
+ /** @var DefaultTokenCleanupJob */
+ private $job;
+ private $tokenProvider;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->tokenProvider = $this->getMockBuilder('\OC\Authentication\Token\DefaultTokenProvider')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->overwriteService('\OC\Authentication\Token\DefaultTokenProvider', $this->tokenProvider);
+ $this->job = new DefaultTokenCleanupJob();
+ }
+
+ protected function tearDown() {
+ parent::tearDown();
+
+ $this->restoreService('\OC\Authentication\Token\DefaultTokenProvider');
+ }
+
+ public function testRun() {
+ $this->tokenProvider->expects($this->once())
+ ->method('invalidateOldTokens')
+ ->with();
+ $this->invokePrivate($this->job, 'run', [null]);
+ }
+
+}
diff --git a/tests/lib/authentication/token/defaulttokenmappertest.php b/tests/lib/authentication/token/defaulttokenmappertest.php
new file mode 100644
index 00000000000..9a21e143fb4
--- /dev/null
+++ b/tests/lib/authentication/token/defaulttokenmappertest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace Test\Authentication\Token;
+
+use OC;
+use OC\Authentication\Token\DefaultToken;
+use OC\Authentication\Token\DefaultTokenMapper;
+use OC\Authentication\Token\IToken;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use Test\TestCase;
+
+/**
+ * Class DefaultTokenMapperTest
+ *
+ * @group DB
+ * @package Test\Authentication
+ */
+class DefaultTokenMapperTest extends TestCase {
+
+ /** @var DefaultTokenMapper */
+ private $mapper;
+ private $dbConnection;
+ private $time;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->dbConnection = OC::$server->getDatabaseConnection();
+ $this->time = time();
+ $this->resetDatabase();
+
+ $this->mapper = new DefaultTokenMapper($this->dbConnection);
+ }
+
+ private function resetDatabase() {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->delete('authtoken')->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user1'),
+ 'password' => $qb->createNamedParameter('a75c7116460c082912d8f6860a850904|3nz5qbG1nNSLLi6V|c55365a0e54cfdfac4a175bcf11a7612aea74492277bba6e5d96a24497fa9272488787cb2f3ad34d8b9b8060934fce02f008d371df3ff3848f4aa61944851ff0'),
+ 'name' => $qb->createNamedParameter('Firefox on Linux'),
+ 'token' => $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user2'),
+ 'password' => $qb->createNamedParameter('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'),
+ 'name' => $qb->createNamedParameter('Firefox on Android'),
+ 'token' => $qb->createNamedParameter('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 3, IQueryBuilder::PARAM_INT), // Three days ago
+ ])->execute();
+ $qb->insert('authtoken')->values([
+ 'uid' => $qb->createNamedParameter('user1'),
+ 'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'),
+ 'name' => $qb->createNamedParameter('Iceweasel on Linux'),
+ 'token' => $qb->createNamedParameter('47af8697ba590fb82579b5f1b3b6e8066773a62100abbe0db09a289a62f5d980dc300fa3d98b01d7228468d1ab05c1aa14c8d14bd5b6eee9cdf1ac14864680c3'),
+ 'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+ 'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ ])->execute();
+ }
+
+ private function getNumberOfTokens() {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $result = $qb->select($qb->createFunction('count(*) as `count`'))
+ ->from('authtoken')
+ ->execute()
+ ->fetch();
+ return (int) $result['count'];
+ }
+
+ public function testInvalidate() {
+ $token = '9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206';
+
+ $this->mapper->invalidate($token);
+
+ $this->assertSame(2, $this->getNumberOfTokens());
+ }
+
+ public function testInvalidateInvalid() {
+ $token = 'youwontfindthisoneinthedatabase';
+
+ $this->mapper->invalidate($token);
+
+ $this->assertSame(3, $this->getNumberOfTokens());
+ }
+
+ public function testInvalidateOld() {
+ $olderThan = $this->time - 60 * 60; // One hour
+
+ $this->mapper->invalidateOld($olderThan);
+
+ $this->assertSame(2, $this->getNumberOfTokens());
+ }
+
+ public function testGetToken() {
+ $token = '1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b';
+ $token = new DefaultToken();
+ $token->setUid('user2');
+ $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f');
+ $token->setName('Firefox on Android');
+ $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b');
+ $token->setType(IToken::TEMPORARY_TOKEN);
+ $token->setLastActivity($this->time - 60 * 60 * 24 * 3);
+
+ $dbToken = $this->mapper->getToken($token->getToken());
+
+ $token->setId($dbToken->getId()); // We don't know the ID
+ $token->resetUpdatedFields();
+
+ $this->assertEquals($token, $dbToken);
+ }
+
+ /**
+ * @expectedException \OCP\AppFramework\Db\DoesNotExistException
+ */
+ public function testGetInvalidToken() {
+ $token = 'thisisaninvalidtokenthatisnotinthedatabase';
+
+ $this->mapper->getToken($token);
+ }
+
+}
diff --git a/tests/lib/authentication/token/defaulttokenprovidertest.php b/tests/lib/authentication/token/defaulttokenprovidertest.php
new file mode 100644
index 00000000000..5ee33d0ec11
--- /dev/null
+++ b/tests/lib/authentication/token/defaulttokenprovidertest.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @author Christoph Wurst <christoph@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @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/>
+ *
+ */
+
+namespace Test\Authentication\Token;
+
+use OC\Authentication\Token\DefaultToken;
+use OC\Authentication\Token\DefaultTokenProvider;
+use OC\Authentication\Token\IToken;
+use OCP\AppFramework\Db\DoesNotExistException;
+use Test\TestCase;
+
+class DefaultTokenProviderTest extends TestCase {
+
+ /** @var DefaultTokenProvider */
+ private $tokenProvider;
+ private $mapper;
+ private $crypto;
+ private $config;
+ private $logger;
+ private $timeFactory;
+ private $time;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->mapper = $this->getMockBuilder('\OC\Authentication\Token\DefaultTokenMapper')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->crypto = $this->getMock('\OCP\Security\ICrypto');
+ $this->config = $this->getMock('\OCP\IConfig');
+ $this->logger = $this->getMock('\OCP\ILogger');
+ $this->timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
+ $this->time = 1313131;
+ $this->timeFactory->expects($this->any())
+ ->method('getTime')
+ ->will($this->returnValue($this->time));
+
+ $this->tokenProvider = new DefaultTokenProvider($this->mapper, $this->crypto, $this->config, $this->logger,
+ $this->timeFactory);
+ }
+
+ public function testGenerateToken() {
+ $token = 'token';
+ $uid = 'user';
+ $password = 'passme';
+ $name = 'Some browser';
+ $type = IToken::PERMANENT_TOKEN;
+
+ $toInsert = new DefaultToken();
+ $toInsert->setUid($uid);
+ $toInsert->setPassword('encryptedpassword');
+ $toInsert->setName($name);
+ $toInsert->setToken(hash('sha512', $token . '1f4h9s'));
+ $toInsert->setType($type);
+ $toInsert->setLastActivity($this->time);
+
+ $this->config->expects($this->any())
+ ->method('getSystemValue')
+ ->with('secret')
+ ->will($this->returnValue('1f4h9s'));
+ $this->crypto->expects($this->once())
+ ->method('encrypt')
+ ->with($password, $token . '1f4h9s')
+ ->will($this->returnValue('encryptedpassword'));
+ $this->mapper->expects($this->once())
+ ->method('insert')
+ ->with($this->equalTo($toInsert));
+
+ $actual = $this->tokenProvider->generateToken($token, $uid, $password, $name, $type);
+
+ $this->assertEquals($toInsert, $actual);
+ }
+
+ public function testUpdateToken() {
+ $tk = new DefaultToken();
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($tk);
+
+ $this->tokenProvider->updateToken($tk);
+
+ $this->assertEquals($this->time, $tk->getLastActivity());
+ }
+
+ public function testGetPassword() {
+ $token = 'token1234';
+ $tk = new DefaultToken();
+ $tk->setPassword('someencryptedvalue');
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('secret')
+ ->will($this->returnValue('1f4h9s'));
+ $this->crypto->expects($this->once())
+ ->method('decrypt')
+ ->with('someencryptedvalue', $token . '1f4h9s')
+ ->will($this->returnValue('passme'));
+
+ $actual = $this->tokenProvider->getPassword($tk, $token);
+
+ $this->assertEquals('passme', $actual);
+ }
+
+ /**
+ * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
+ */
+ public function testGetPasswordDeletesInvalidToken() {
+ $token = 'token1234';
+ $tk = new DefaultToken();
+ $tk->setPassword('someencryptedvalue');
+ /* @var $tokenProvider DefaultTokenProvider */
+ $tokenProvider = $this->getMockBuilder('\OC\Authentication\Token\DefaultTokenProvider')
+ ->setMethods([
+ 'invalidateToken'
+ ])
+ ->setConstructorArgs([$this->mapper, $this->crypto, $this->config, $this->logger,
+ $this->timeFactory])
+ ->getMock();
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('secret')
+ ->will($this->returnValue('1f4h9s'));
+ $this->crypto->expects($this->once())
+ ->method('decrypt')
+ ->with('someencryptedvalue', $token . '1f4h9s')
+ ->will($this->throwException(new \Exception('some crypto error occurred')));
+ $tokenProvider->expects($this->once())
+ ->method('invalidateToken')
+ ->with($token);
+
+ $tokenProvider->getPassword($tk, $token);
+ }
+
+ public function testInvalidateToken() {
+ $this->mapper->expects($this->once())
+ ->method('invalidate')
+ ->with(hash('sha512', 'token7'));
+
+ $this->tokenProvider->invalidateToken('token7');
+ }
+
+ public function testInvalidateOldTokens() {
+ $defaultSessionLifetime = 60 * 60 * 24;
+ $this->config->expects($this->once())
+ ->method('getSystemValue')
+ ->with('session_lifetime', $defaultSessionLifetime)
+ ->will($this->returnValue(150));
+ $this->mapper->expects($this->once())
+ ->method('invalidateOld')
+ ->with($this->time - 150);
+
+ $this->tokenProvider->invalidateOldTokens();
+ }
+
+ public function testValidateToken() {
+ $token = 'sometoken';
+ $dbToken = new DefaultToken();
+ $this->mapper->expects($this->once())
+ ->method('getToken')
+ ->with(hash('sha512', $token))
+ ->will($this->returnValue($dbToken));
+
+ $actual = $this->tokenProvider->validateToken($token);
+
+ $this->assertEquals($dbToken, $actual);
+ }
+
+ /**
+ * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
+ */
+ public function testValidateInvalidToken() {
+ $token = 'sometoken';
+ $this->mapper->expects($this->once())
+ ->method('getToken')
+ ->with(hash('sha512', $token))
+ ->will($this->throwException(new DoesNotExistException('')));
+
+ $this->tokenProvider->validateToken($token);
+ }
+
+}
diff --git a/tests/lib/user/session.php b/tests/lib/user/session.php
index 5a8ea57cb86..c6ddeb416fb 100644
--- a/tests/lib/user/session.php
+++ b/tests/lib/user/session.php
@@ -11,107 +11,138 @@ namespace Test\User;
use OC\Session\Memory;
use OC\User\User;
-use OCP\ISession;
-use OCP\IUserManager;
-use OCP\UserInterface;
-use Test\TestCase;
/**
* @group DB
* @package Test\User
*/
-class Session extends TestCase {
- public function testGetUser() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
- $session = $this->getMock('\OC\Session\Memory', array(), array(''));
- $session->expects($this->once())
- ->method('get')
- ->with('user_id')
- ->will($this->returnValue('foo'));
+class Session extends \Test\TestCase {
- /** @var UserInterface | \PHPUnit_Framework_MockObject_MockObject $backend */
- $backend = $this->getMock('\Test\Util\User\Dummy');
- $backend->expects($this->once())
- ->method('userExists')
- ->with('foo')
- ->will($this->returnValue(true));
+ /** @var \OCP\AppFramework\Utility\ITimeFactory */
+ private $timeFactory;
- $manager = new \OC\User\Manager();
- $manager->registerBackend($backend);
+ /** @var \OC\Authentication\Token\DefaultTokenProvider */
+ protected $defaultProvider;
- $userSession = new \OC\User\Session($manager, $session);
- $user = $userSession->getUser();
- $this->assertEquals('foo', $user->getUID());
+ protected function setUp() {
+ parent::setUp();
+
+ $this->timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
+ $this->timeFactory->expects($this->any())
+ ->method('getTime')
+ ->will($this->returnValue(10000));
+ $this->defaultProvider = $this->getMockBuilder('\OC\Authentication\Token\DefaultTokenProvider')
+ ->disableOriginalConstructor()
+ ->getMock();
}
- public function testIsLoggedIn() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
+ public function testGetUser() {
+ $token = new \OC\Authentication\Token\DefaultToken();
+
+ $expectedUser = new User('foo', null);
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
- $session->expects($this->once())
+ $session->expects($this->at(0))
->method('get')
->with('user_id')
- ->will($this->returnValue('foo'));
+ ->will($this->returnValue($expectedUser->getUID()));
+ $sessionId = 'abcdef12345';
- /** @var UserInterface | \PHPUnit_Framework_MockObject_MockObject $backend */
- $backend = $this->getMock('\Test\Util\User\Dummy');
- $backend->expects($this->once())
- ->method('userExists')
- ->with('foo')
+ $manager = $this->getMockBuilder('\OC\User\Manager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $session->expects($this->once())
+ ->method('getId')
+ ->will($this->returnValue($sessionId));
+ $this->defaultProvider->expects($this->once())
+ ->method('getToken')
+ ->will($this->returnValue($token));
+ $session->expects($this->at(2))
+ ->method('get')
+ ->with('last_login_check')
+ ->will($this->returnValue(null)); // No check has been run yet
+ $this->defaultProvider->expects($this->once())
+ ->method('getPassword')
+ ->with($token, $sessionId)
+ ->will($this->returnValue('password123'));
+ $manager->expects($this->once())
+ ->method('checkPassword')
+ ->with($expectedUser->getUID(), 'password123')
->will($this->returnValue(true));
+ $session->expects($this->at(3))
+ ->method('set')
+ ->with('last_login_check', 10000);
+
+ $session->expects($this->at(4))
+ ->method('get')
+ ->with('last_token_update')
+ ->will($this->returnValue(null)); // No check run so far
+ $this->defaultProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($token);
+ $session->expects($this->at(5))
+ ->method('set')
+ ->with('last_token_update', $this->equalTo(10000));
+
+ $manager->expects($this->any())
+ ->method('get')
+ ->with($expectedUser->getUID())
+ ->will($this->returnValue($expectedUser));
- $manager = new \OC\User\Manager();
- $manager->registerBackend($backend);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
+ $user = $userSession->getUser();
+ $this->assertSame($expectedUser, $user);
+ }
- $userSession = new \OC\User\Session($manager, $session);
- $isLoggedIn = $userSession->isLoggedIn();
- $this->assertTrue($isLoggedIn);
+ public function isLoggedInData() {
+ return [
+ [true],
+ [false],
+ ];
}
- public function testNotLoggedIn() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
+ /**
+ * @dataProvider isLoggedInData
+ */
+ public function testIsLoggedIn($isLoggedIn) {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
- $session->expects($this->once())
- ->method('get')
- ->with('user_id')
- ->will($this->returnValue(null));
-
- /** @var UserInterface | \PHPUnit_Framework_MockObject_MockObject $backend */
- $backend = $this->getMock('\Test\Util\User\Dummy');
- $backend->expects($this->never())
- ->method('userExists');
- $manager = new \OC\User\Manager();
- $manager->registerBackend($backend);
+ $manager = $this->getMockBuilder('\OC\User\Manager')
+ ->disableOriginalConstructor()
+ ->getMock();
- $userSession = new \OC\User\Session($manager, $session);
- $isLoggedIn = $userSession->isLoggedIn();
- $this->assertFalse($isLoggedIn);
+ $userSession = $this->getMockBuilder('\OC\User\Session')
+ ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]])
+ ->setMethods([
+ 'getUser'
+ ])
+ ->getMock();
+ $user = new User('sepp', null);
+ $userSession->expects($this->once())
+ ->method('getUser')
+ ->will($this->returnValue($isLoggedIn ? $user : null));
+ $this->assertEquals($isLoggedIn, $userSession->isLoggedIn());
}
public function testSetUser() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->once())
->method('set')
->with('user_id', 'foo');
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager');
$backend = $this->getMock('\Test\Util\User\Dummy');
- /** @var User | \PHPUnit_Framework_MockObject_MockObject $user */
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
$user->expects($this->once())
->method('getUID')
->will($this->returnValue('foo'));
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$userSession->setUser($user);
}
public function testLoginValidPasswordEnabled() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->once())
->method('regenerateId');
@@ -127,8 +158,7 @@ class Session extends TestCase {
return false;
break;
}
- },
- 'foo'));
+ }, 'foo'));
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -140,13 +170,12 @@ class Session extends TestCase {
unset($managerMethods[$i]);
}
}
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
$backend = $this->getMock('\Test\Util\User\Dummy');
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
- $user->expects($this->exactly(2))
+ $user->expects($this->any())
->method('isEnabled')
->will($this->returnValue(true));
$user->expects($this->any())
@@ -160,22 +189,27 @@ class Session extends TestCase {
->with('foo', 'bar')
->will($this->returnValue($user));
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = $this->getMockBuilder('\OC\User\Session')
+ ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]])
+ ->setMethods([
+ 'prepareUserLogin'
+ ])
+ ->getMock();
+ $userSession->expects($this->once())
+ ->method('prepareUserLogin');
$userSession->login('foo', 'bar');
$this->assertEquals($user, $userSession->getUser());
}
/**
* @expectedException \OC\User\LoginException
- * @expectedExceptionMessage User disabled
*/
public function testLoginValidPasswordDisabled() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->never())
->method('set');
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -187,13 +221,12 @@ class Session extends TestCase {
unset($managerMethods[$i]);
}
}
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
$backend = $this->getMock('\Test\Util\User\Dummy');
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
- $user->expects($this->once())
+ $user->expects($this->any())
->method('isEnabled')
->will($this->returnValue(false));
$user->expects($this->never())
@@ -204,17 +237,16 @@ class Session extends TestCase {
->with('foo', 'bar')
->will($this->returnValue($user));
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$userSession->login('foo', 'bar');
}
public function testLoginInvalidPassword() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->never())
->method('set');
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -226,7 +258,6 @@ class Session extends TestCase {
unset($managerMethods[$i]);
}
}
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
$backend = $this->getMock('\Test\Util\User\Dummy');
@@ -242,32 +273,31 @@ class Session extends TestCase {
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$userSession->login('foo', 'bar');
}
public function testLoginNonExisting() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->never())
->method('set');
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager');
+ $backend = $this->getMock('\Test\Util\User\Dummy');
+
$manager->expects($this->once())
->method('checkPassword')
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$userSession->login('foo', 'bar');
}
public function testRememberLoginValidToken() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->exactly(1))
->method('set')
@@ -278,10 +308,9 @@ class Session extends TestCase {
default:
return false;
}
- },
- 'foo'));
+ }, 'foo'));
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -314,13 +343,12 @@ class Session extends TestCase {
$token = 'goodToken';
\OC::$server->getConfig()->setUserValue('foo', 'login_token', $token, time());
- /** @var \OC\User\Session $userSession */
$userSession = $this->getMock(
'\OC\User\Session',
//override, otherwise tests will fail because of setcookie()
array('setMagicInCookie'),
//there are passed as parameters to the constructor
- array($manager, $session));
+ array($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]));
$granted = $userSession->loginWithCookie('foo', $token);
@@ -328,12 +356,11 @@ class Session extends TestCase {
}
public function testRememberLoginInvalidToken() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->never())
->method('set');
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -345,7 +372,6 @@ class Session extends TestCase {
unset($managerMethods[$i]);
}
}
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
$backend = $this->getMock('\Test\Util\User\Dummy');
@@ -367,19 +393,18 @@ class Session extends TestCase {
$token = 'goodToken';
\OC::$server->getConfig()->setUserValue('foo', 'login_token', $token, time());
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$granted = $userSession->loginWithCookie('foo', 'badToken');
$this->assertSame($granted, false);
}
public function testRememberLoginInvalidUser() {
- /** @var ISession | \PHPUnit_Framework_MockObject_MockObject $session */
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->never())
->method('set');
$session->expects($this->once())
- ->method('regenerateId');
+ ->method('regenerateId');
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -391,7 +416,6 @@ class Session extends TestCase {
unset($managerMethods[$i]);
}
}
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
$backend = $this->getMock('\Test\Util\User\Dummy');
@@ -412,7 +436,7 @@ class Session extends TestCase {
$token = 'goodToken';
\OC::$server->getConfig()->setUserValue('foo', 'login_token', $token, time());
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]);
$granted = $userSession->loginWithCookie('foo', $token);
$this->assertSame($granted, false);
@@ -424,7 +448,6 @@ class Session extends TestCase {
'bar' => new User('bar', null)
);
- /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject $manager */
$manager = $this->getMockBuilder('\OC\User\Manager')
->disableOriginalConstructor()
->getMock();
@@ -432,12 +455,20 @@ class Session extends TestCase {
$manager->expects($this->any())
->method('get')
->will($this->returnCallback(function ($uid) use ($users) {
- return $users[$uid];
- }));
+ return $users[$uid];
+ }));
$session = new Memory('');
$session->set('user_id', 'foo');
- $userSession = new \OC\User\Session($manager, $session);
+ $userSession = $this->getMockBuilder('\OC\User\Session')
+ ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->defaultProvider, [$this->defaultProvider]])
+ ->setMethods([
+ 'validateSession'
+ ])
+ ->getMock();
+ $userSession->expects($this->any())
+ ->method('validateSession');
+
$this->assertEquals($users['foo'], $userSession->getUser());
$session2 = new Memory('');
@@ -445,4 +476,5 @@ class Session extends TestCase {
$userSession->setSession($session2);
$this->assertEquals($users['bar'], $userSession->getUser());
}
+
}
diff --git a/version.php b/version.php
index a3840349811..81e0bc1b067 100644
--- a/version.php
+++ b/version.php
@@ -26,7 +26,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = array(9, 1, 0, 0);
+$OC_Version = array(9, 1, 0, 1);
// The human readable string
$OC_VersionString = '9.1.0 pre alpha';