Login flow V2
This adds the new login flow. The desktop client will open up a browser and poll a returned endpoint at regular intervals to check if the flow is done. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
parent
5df6400e28
commit
e819e97829
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\BackgroundJobs;
|
||||
|
||||
use OC\Core\Db\LoginFlowV2Mapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
|
||||
class CleanupLoginFlowV2 extends TimedJob {
|
||||
|
||||
/** @var LoginFlowV2Mapper */
|
||||
private $loginFlowV2Mapper;
|
||||
|
||||
public function __construct(ITimeFactory $time, LoginFlowV2Mapper $loginFlowV2Mapper) {
|
||||
parent::__construct($time);
|
||||
$this->loginFlowV2Mapper = $loginFlowV2Mapper;
|
||||
|
||||
$this->setInterval(3600);
|
||||
}
|
||||
|
||||
protected function run($argument) {
|
||||
$this->loginFlowV2Mapper->cleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Controller;
|
||||
|
||||
use OC\Core\Db\LoginFlowV2;
|
||||
use OC\Core\Exception\LoginFlowV2NotFoundException;
|
||||
use OC\Core\Service\LoginFlowV2Service;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\StandaloneTemplateResponse;
|
||||
use OCP\Defaults;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Security\ISecureRandom;
|
||||
|
||||
class ClientFlowLoginV2Controller extends Controller {
|
||||
|
||||
private const tokenName = 'client.flow.v2.login.token';
|
||||
private const stateName = 'client.flow.v2.state.token';
|
||||
|
||||
/** @var LoginFlowV2Service */
|
||||
private $loginFlowV2Service;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
/** @var ISession */
|
||||
private $session;
|
||||
/** @var ISecureRandom */
|
||||
private $random;
|
||||
/** @var Defaults */
|
||||
private $defaults;
|
||||
/** @var string */
|
||||
private $userId;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
LoginFlowV2Service $loginFlowV2Service,
|
||||
IURLGenerator $urlGenerator,
|
||||
ISession $session,
|
||||
ISecureRandom $random,
|
||||
Defaults $defaults,
|
||||
?string $userId,
|
||||
IL10N $l10n) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->loginFlowV2Service = $loginFlowV2Service;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->session = $session;
|
||||
$this->random = $random;
|
||||
$this->defaults = $defaults;
|
||||
$this->userId = $userId;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
*/
|
||||
public function poll(string $token): JSONResponse {
|
||||
try {
|
||||
$creds = $this->loginFlowV2Service->poll($token);
|
||||
} catch (LoginFlowV2NotFoundException $e) {
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
return new JSONResponse($creds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
* @UseSession
|
||||
*/
|
||||
public function landing(string $token): Response {
|
||||
if (!$this->loginFlowV2Service->startLoginFlow($token)) {
|
||||
return $this->loginTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
$this->session->set(self::tokenName, $token);
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
* @UseSession
|
||||
*/
|
||||
public function showAuthPickerPage(): StandaloneTemplateResponse {
|
||||
try {
|
||||
$flow = $this->getFlowByLoginToken();
|
||||
} catch (LoginFlowV2NotFoundException $e) {
|
||||
return $this->loginTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
$stateToken = $this->random->generate(
|
||||
64,
|
||||
ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
|
||||
);
|
||||
$this->session->set(self::stateName, $stateToken);
|
||||
|
||||
return new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'loginflowv2/authpicker',
|
||||
[
|
||||
'client' => $flow->getClientName(),
|
||||
'instanceName' => $this->defaults->getName(),
|
||||
'urlGenerator' => $this->urlGenerator,
|
||||
'stateToken' => $stateToken,
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @UseSession
|
||||
* @NoCSRFRequired
|
||||
* @NoSameSiteCookieRequired
|
||||
*/
|
||||
public function grantPage(string $stateToken): StandaloneTemplateResponse {
|
||||
if(!$this->isValidStateToken($stateToken)) {
|
||||
return $this->stateTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
try {
|
||||
$flow = $this->getFlowByLoginToken();
|
||||
} catch (LoginFlowV2NotFoundException $e) {
|
||||
return $this->loginTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
return new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'loginflowv2/grant',
|
||||
[
|
||||
'client' => $flow->getClientName(),
|
||||
'instanceName' => $this->defaults->getName(),
|
||||
'urlGenerator' => $this->urlGenerator,
|
||||
'stateToken' => $stateToken,
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @UseSession
|
||||
*/
|
||||
public function generateAppPassword(string $stateToken): Response {
|
||||
if(!$this->isValidStateToken($stateToken)) {
|
||||
return $this->stateTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
try {
|
||||
$this->getFlowByLoginToken();
|
||||
} catch (LoginFlowV2NotFoundException $e) {
|
||||
return $this->loginTokenForbiddenResponse();
|
||||
}
|
||||
|
||||
$loginToken = $this->session->get(self::tokenName);
|
||||
|
||||
// Clear session variables
|
||||
$this->session->remove(self::tokenName);
|
||||
$this->session->remove(self::stateName);
|
||||
$sessionId = $this->session->getId();
|
||||
|
||||
$result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
|
||||
|
||||
if ($result) {
|
||||
return new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'loginflowv2/done',
|
||||
[],
|
||||
'guest'
|
||||
);
|
||||
}
|
||||
|
||||
$response = new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'403',
|
||||
[
|
||||
'message' => $this->l10n->t('Could not complete login'),
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoCSRFRequired
|
||||
* @PublicPage
|
||||
*/
|
||||
public function init(): JSONResponse {
|
||||
// Get client user agent
|
||||
$userAgent = $this->request->getHeader('USER_AGENT');
|
||||
|
||||
$tokens = $this->loginFlowV2Service->createTokens($userAgent);
|
||||
|
||||
$data = [
|
||||
'poll' => [
|
||||
'token' => $tokens->getPollToken(),
|
||||
'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
|
||||
],
|
||||
'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
|
||||
];
|
||||
|
||||
return new JSONResponse($data);
|
||||
}
|
||||
|
||||
private function isValidStateToken(string $stateToken): bool {
|
||||
$currentToken = $this->session->get(self::stateName);
|
||||
if(!is_string($stateToken) || !is_string($currentToken)) {
|
||||
return false;
|
||||
}
|
||||
return hash_equals($currentToken, $stateToken);
|
||||
}
|
||||
|
||||
private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
|
||||
$response = new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'403',
|
||||
[
|
||||
'message' => $this->l10n->t('State token does not match'),
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LoginFlowV2
|
||||
* @throws LoginFlowV2NotFoundException
|
||||
*/
|
||||
private function getFlowByLoginToken(): LoginFlowV2 {
|
||||
$currentToken = $this->session->get(self::tokenName);
|
||||
if(!is_string($currentToken)) {
|
||||
throw new LoginFlowV2NotFoundException('Login token not set in session');
|
||||
}
|
||||
|
||||
return $this->loginFlowV2Service->getByLoginToken($currentToken);
|
||||
}
|
||||
|
||||
private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
|
||||
$response = new StandaloneTemplateResponse(
|
||||
$this->appName,
|
||||
'403',
|
||||
[
|
||||
'message' => $this->l10n->t('Your login token is invalid or has expired'),
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
$response->setStatus(Http::STATUS_FORBIDDEN);
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getServerPath(): string {
|
||||
$serverPostfix = '';
|
||||
|
||||
if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
|
||||
$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
|
||||
} else if (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
|
||||
$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
|
||||
}
|
||||
|
||||
$protocol = $this->request->getServerProtocol();
|
||||
return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Data;
|
||||
|
||||
class LoginFlowV2Credentials implements \JsonSerializable {
|
||||
/** @var string */
|
||||
private $server;
|
||||
/** @var string */
|
||||
private $loginName;
|
||||
/** @var string */
|
||||
private $appPassword;
|
||||
|
||||
public function __construct(string $server, string $loginName, string $appPassword) {
|
||||
$this->server = $server;
|
||||
$this->loginName = $loginName;
|
||||
$this->appPassword = $appPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServer(): string {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginName(): string {
|
||||
return $this->loginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAppPassword(): string {
|
||||
return $this->appPassword;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'server' => $this->server,
|
||||
'loginName' => $this->loginName,
|
||||
'appPassword' => $this->appPassword,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Data;
|
||||
|
||||
class LoginFlowV2Tokens {
|
||||
|
||||
/** @var string */
|
||||
private $loginToken;
|
||||
/** @var string */
|
||||
private $pollToken;
|
||||
|
||||
public function __construct(string $loginToken, string $pollToken) {
|
||||
$this->loginToken = $loginToken;
|
||||
$this->pollToken = $pollToken;
|
||||
}
|
||||
|
||||
public function getPollToken(): string {
|
||||
return $this->pollToken;
|
||||
|
||||
}
|
||||
|
||||
public function getLoginToken(): string {
|
||||
return $this->loginToken;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
/**
|
||||
* @method int getTimestamp()
|
||||
* @method void setTimestamp(int $timestamp)
|
||||
* @method int getStarted()
|
||||
* @method void setStarted(int $started)
|
||||
* @method string getPollToken()
|
||||
* @method void setPollToken(string $token)
|
||||
* @method string getLoginToken()
|
||||
* @method void setLoginToken(string $token)
|
||||
* @method string getPublicKey()
|
||||
* @method void setPublicKey(string $key)
|
||||
* @method string getPrivateKey()
|
||||
* @method void setPrivateKey(string $key)
|
||||
* @method string getClientName()
|
||||
* @method void setClientName(string $clientName)
|
||||
* @method string getLoginName()
|
||||
* @method void setLoginName(string $loginName)
|
||||
* @method string getServer()
|
||||
* @method void setServer(string $server)
|
||||
* @method string getAppPassword()
|
||||
* @method void setAppPassword(string $appPassword)
|
||||
*/
|
||||
class LoginFlowV2 extends Entity {
|
||||
/** @var int */
|
||||
protected $timestamp;
|
||||
/** @var int */
|
||||
protected $started;
|
||||
/** @var string */
|
||||
protected $pollToken;
|
||||
/** @var string */
|
||||
protected $loginToken;
|
||||
/** @var string */
|
||||
protected $publicKey;
|
||||
/** @var string */
|
||||
protected $privateKey;
|
||||
/** @var string */
|
||||
protected $clientName;
|
||||
/** @var string */
|
||||
protected $loginName;
|
||||
/** @var string */
|
||||
protected $server;
|
||||
/** @var string */
|
||||
protected $appPassword;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('timestamp', 'int');
|
||||
$this->addType('started', 'int');
|
||||
$this->addType('pollToken', 'string');
|
||||
$this->addType('loginToken', 'string');
|
||||
$this->addType('publicKey', 'string');
|
||||
$this->addType('privateKey', 'string');
|
||||
$this->addType('clientName', 'string');
|
||||
$this->addType('loginName', 'string');
|
||||
$this->addType('server', 'string');
|
||||
$this->addType('appPassword', 'string');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class LoginFlowV2Mapper extends QBMapper {
|
||||
private const lifetime = 1200;
|
||||
|
||||
/** @var ITimeFactory */
|
||||
private $timeFactory;
|
||||
|
||||
public function __construct(IDBConnection $db, ITimeFactory $timeFactory) {
|
||||
parent::__construct($db, 'login_flow_v2', LoginFlowV2::class);
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pollToken
|
||||
* @return LoginFlowV2
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function getByPollToken(string $pollToken): LoginFlowV2 {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('poll_token', $qb->createNamedParameter($pollToken))
|
||||
);
|
||||
|
||||
$entity = $this->findEntity($qb);
|
||||
return $this->validateTimestamp($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $loginToken
|
||||
* @return LoginFlowV2
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function getByLoginToken(string $loginToken): LoginFlowV2 {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->eq('login_token', $qb->createNamedParameter($loginToken))
|
||||
);
|
||||
|
||||
$entity = $this->findEntity($qb);
|
||||
return $this->validateTimestamp($entity);
|
||||
}
|
||||
|
||||
public function cleanup(): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete($this->getTableName())
|
||||
->where(
|
||||
$qb->expr()->lt('timestamp', $qb->createNamedParameter($this->timeFactory->getTime() - self::lifetime))
|
||||
);
|
||||
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoginFlowV2 $flowV2
|
||||
* @return LoginFlowV2
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
private function validateTimestamp(LoginFlowV2 $flowV2): LoginFlowV2 {
|
||||
if ($flowV2->getTimestamp() < ($this->timeFactory->getTime() - self::lifetime)) {
|
||||
$this->delete($flowV2);
|
||||
throw new DoesNotExistException('Token expired');
|
||||
}
|
||||
|
||||
return $flowV2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Exception;
|
||||
|
||||
class LoginFlowV2NotFoundException extends \Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Migrations;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version16000Date20190212081545 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$table = $schema->createTable('login_flow_v2');
|
||||
$table->addColumn('id', Type::BIGINT, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('timestamp', Type::BIGINT, [
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('started', Type::SMALLINT, [
|
||||
'notnull' => true,
|
||||
'length' => 1,
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('poll_token', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('login_token', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('public_key', Type::TEXT, [
|
||||
'notnull' => true,
|
||||
'length' => 32768,
|
||||
]);
|
||||
$table->addColumn('private_key', Type::TEXT, [
|
||||
'notnull' => true,
|
||||
'length' => 32768,
|
||||
]);
|
||||
$table->addColumn('client_name', Type::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('login_name', Type::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('server', Type::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('app_password', Type::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 1024,
|
||||
]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['poll_token']);
|
||||
$table->addUniqueIndex(['login_token']);
|
||||
$table->addIndex(['timestamp']);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Service;
|
||||
|
||||
use OC\Authentication\Exceptions\InvalidTokenException;
|
||||
use OC\Authentication\Exceptions\PasswordlessTokenException;
|
||||
use OC\Authentication\Token\IProvider;
|
||||
use OC\Authentication\Token\IToken;
|
||||
use OC\Core\Data\LoginFlowV2Credentials;
|
||||
use OC\Core\Data\LoginFlowV2Tokens;
|
||||
use OC\Core\Db\LoginFlowV2;
|
||||
use OC\Core\Db\LoginFlowV2Mapper;
|
||||
use OC\Core\Exception\LoginFlowV2NotFoundException;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\ILogger;
|
||||
use OCP\Security\ICrypto;
|
||||
use OCP\Security\ISecureRandom;
|
||||
|
||||
class LoginFlowV2Service {
|
||||
|
||||
/** @var LoginFlowV2Mapper */
|
||||
private $mapper;
|
||||
/** @var ISecureRandom */
|
||||
private $random;
|
||||
/** @var ITimeFactory */
|
||||
private $time;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var ICrypto */
|
||||
private $crypto;
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
/** @var IProvider */
|
||||
private $tokenProvider;
|
||||
|
||||
public function __construct(LoginFlowV2Mapper $mapper,
|
||||
ISecureRandom $random,
|
||||
ITimeFactory $time,
|
||||
IConfig $config,
|
||||
ICrypto $crypto,
|
||||
ILogger $logger,
|
||||
IProvider $tokenProvider) {
|
||||
$this->mapper = $mapper;
|
||||
$this->random = $random;
|
||||
$this->time = $time;
|
||||
$this->config = $config;
|
||||
$this->crypto = $crypto;
|
||||
$this->logger = $logger;
|
||||
$this->tokenProvider = $tokenProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pollToken
|
||||
* @return LoginFlowV2Credentials
|
||||
* @throws LoginFlowV2NotFoundException
|
||||
*/
|
||||
public function poll(string $pollToken): LoginFlowV2Credentials {
|
||||
try {
|
||||
$data = $this->mapper->getByPollToken($this->hashToken($pollToken));
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new LoginFlowV2NotFoundException('Invalid token');
|
||||
}
|
||||
|
||||
$loginName = $data->getLoginName();
|
||||
$server = $data->getServer();
|
||||
$appPassword = $data->getAppPassword();
|
||||
|
||||
if ($loginName === null || $server === null || $appPassword === null) {
|
||||
throw new LoginFlowV2NotFoundException('Token not yet ready');
|
||||
}
|
||||
|
||||
// Remove the data from the DB
|
||||
$this->mapper->delete($data);
|
||||
|
||||
try {
|
||||
// Decrypt the apptoken
|
||||
$privateKey = $this->crypto->decrypt($data->getPrivateKey(), $pollToken);
|
||||
$appPassword = $this->decryptPassword($data->getAppPassword(), $privateKey);
|
||||
} catch (\Exception $e) {
|
||||
throw new LoginFlowV2NotFoundException('Apptoken could not be decrypted');
|
||||
}
|
||||
|
||||
return new LoginFlowV2Credentials($server, $loginName, $appPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $loginToken
|
||||
* @return LoginFlowV2
|
||||
* @throws LoginFlowV2NotFoundException
|
||||
*/
|
||||
public function getByLoginToken(string $loginToken): LoginFlowV2 {
|
||||
try {
|
||||
return $this->mapper->getByLoginToken($loginToken);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new LoginFlowV2NotFoundException('Login token invalid');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $loginToken
|
||||
* @return bool returns true if the start was successfull. False if not.
|
||||
*/
|
||||
public function startLoginFlow(string $loginToken): bool {
|
||||
try {
|
||||
$data = $this->mapper->getByLoginToken($loginToken);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->getStarted() !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data->setStarted(1);
|
||||
$this->mapper->update($data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $loginToken
|
||||
* @param string $sessionId
|
||||
* @param string $server
|
||||
* @param string $userId
|
||||
* @return bool true if the flow was successfully completed false otherwise
|
||||
*/
|
||||
public function flowDone(string $loginToken, string $sessionId, string $server, string $userId): bool {
|
||||
try {
|
||||
$data = $this->mapper->getByLoginToken($loginToken);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$sessionToken = $this->tokenProvider->getToken($sessionId);
|
||||
$loginName = $sessionToken->getLoginName();
|
||||
try {
|
||||
$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
|
||||
} catch (PasswordlessTokenException $ex) {
|
||||
$password = null;
|
||||
}
|
||||
} catch (InvalidTokenException $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$appPassword = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
|
||||
$this->tokenProvider->generateToken(
|
||||
$appPassword,
|
||||
$userId,
|
||||
$loginName,
|
||||
$password,
|
||||
$data->getClientName(),
|
||||
IToken::PERMANENT_TOKEN,
|
||||
IToken::DO_NOT_REMEMBER
|
||||
);
|
||||
|
||||
$data->setLoginName($loginName);
|
||||
$data->setServer($server);
|
||||
|
||||
// Properly encrypt
|
||||
$data->setAppPassword($this->encryptPassword($appPassword, $data->getPublicKey()));
|
||||
|
||||
$this->mapper->update($data);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createTokens(string $userAgent): LoginFlowV2Tokens {
|
||||
$flow = new LoginFlowV2();
|
||||
$pollToken = $this->random->generate(128, ISecureRandom::CHAR_DIGITS.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER);
|
||||
$loginToken = $this->random->generate(128, ISecureRandom::CHAR_DIGITS.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER);
|
||||
$flow->setPollToken($this->hashToken($pollToken));
|
||||
$flow->setLoginToken($loginToken);
|
||||
$flow->setStarted(0);
|
||||
$flow->setTimestamp($this->time->getTime());
|
||||
$flow->setClientName($userAgent);
|
||||
|
||||
[$publicKey, $privateKey] = $this->getKeyPair();
|
||||
$privateKey = $this->crypto->encrypt($privateKey, $pollToken);
|
||||
|
||||
$flow->setPublicKey($publicKey);
|
||||
$flow->setPrivateKey($privateKey);
|
||||
|
||||
$this->mapper->insert($flow);
|
||||
|
||||
return new LoginFlowV2Tokens($loginToken, $pollToken);
|
||||
}
|
||||
|
||||
private function hashToken(string $token): string {
|
||||
$secret = $this->config->getSystemValue('secret');
|
||||
return hash('sha512', $token . $secret);
|
||||
}
|
||||
|
||||
private function getKeyPair(): array {
|
||||
$config = array_merge([
|
||||
'digest_alg' => 'sha512',
|
||||
'private_key_bits' => 2048,
|
||||
], $this->config->getSystemValue('openssl', []));
|
||||
|
||||
// Generate new key
|
||||
$res = openssl_pkey_new($config);
|
||||
if ($res === false) {
|
||||
$this->logOpensslError();
|
||||
throw new \RuntimeException('Could not initialize keys');
|
||||
}
|
||||
|
||||
openssl_pkey_export($res, $privateKey);
|
||||
|
||||
// Extract the public key from $res to $pubKey
|
||||
$publicKey = openssl_pkey_get_details($res);
|
||||
$publicKey = $publicKey['key'];
|
||||
|
||||
return [$publicKey, $privateKey];
|
||||
}
|
||||
|
||||
private function logOpensslError(): void {
|
||||
$errors = [];
|
||||
while ($error = openssl_error_string()) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
|
||||
}
|
||||
|
||||
private function encryptPassword(string $password, string $publicKey): string {
|
||||
openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
|
||||
$encryptedPassword = base64_encode($encryptedPassword);
|
||||
|
||||
return $encryptedPassword;
|
||||
}
|
||||
|
||||
private function decryptPassword(string $encryptedPassword, string $privateKey): string {
|
||||
$encryptedPassword = base64_decode($encryptedPassword);
|
||||
openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
|
||||
|
||||
return $password;
|
||||
}
|
||||
}
|
|
@ -52,10 +52,18 @@ $application->registerRoutes($this, [
|
|||
['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],
|
||||
['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
|
||||
['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
|
||||
// Original login flow used by all clients
|
||||
['name' => 'ClientFlowLogin#showAuthPickerPage', 'url' => '/login/flow', 'verb' => 'GET'],
|
||||
['name' => 'ClientFlowLogin#generateAppPassword', 'url' => '/login/flow', 'verb' => 'POST'],
|
||||
['name' => 'ClientFlowLogin#grantPage', 'url' => '/login/flow/grant', 'verb' => 'GET'],
|
||||
['name' => 'ClientFlowLogin#apptokenRedirect', 'url' => '/login/flow/apptoken', 'verb' => 'POST'],
|
||||
// NG login flow used by desktop client in case of Kerberos/fancy 2fa (smart cards for example)
|
||||
['name' => 'ClientFlowLoginV2#poll', 'url' => '/login/v2/poll', 'verb' => 'POST'],
|
||||
['name' => 'ClientFlowLoginV2#showAuthPickerPage', 'url' => '/login/v2/flow', 'verb' => 'GET'],
|
||||
['name' => 'ClientFlowLoginV2#landing', 'url' => '/login/v2/flow/{token}', 'verb' => 'GET'],
|
||||
['name' => 'ClientFlowLoginV2#grantPage', 'url' => '/login/v2/grant', 'verb' => 'GET'],
|
||||
['name' => 'ClientFlowLoginV2#generateAppPassword', 'url' => '/login/v2/grant', 'verb' => 'POST'],
|
||||
['name' => 'ClientFlowLoginV2#init', 'url' => '/login/v2', 'verb' => 'POST'],
|
||||
['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
|
||||
['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
|
||||
['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Connect to your account')) ?></h2>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('Please log in before granting %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<a href="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.grantPage', ['stateToken' => $_['stateToken']])) ?>">
|
||||
<input type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Log in')) ?>">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Account connected')) ?></h2>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('Your client should now be connected! You can close this window.')) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
</div>
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
style('core', 'login/authpicker');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
|
||||
<div class="picker-window">
|
||||
<h2><?php p($l->t('Account access')) ?></h2>
|
||||
<p class="info">
|
||||
<?php print_unescaped($l->t('You are about to grant %1$s access to your %2$s account.', [
|
||||
'<strong>' . \OCP\Util::sanitizeHTML($_['client']) . '</strong>',
|
||||
\OCP\Util::sanitizeHTML($_['instanceName'])
|
||||
])) ?>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form method="POST" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.generateAppPassword')) ?>">
|
||||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<div id="submit-wrapper">
|
||||
<input type="submit" id="submit" class="login primary" title="" value="<?php p($l->t('Grant access')); ?>" />
|
||||
<div class="submit-icon icon-confirm-white"></div>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
|
@ -565,6 +565,7 @@ return array(
|
|||
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
|
||||
'OC\\Core\\Application' => $baseDir . '/core/Application.php',
|
||||
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
|
||||
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => $baseDir . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
|
||||
'OC\\Core\\Command\\App\\CheckCode' => $baseDir . '/core/Command/App/CheckCode.php',
|
||||
'OC\\Core\\Command\\App\\Disable' => $baseDir . '/core/Command/App/Disable.php',
|
||||
'OC\\Core\\Command\\App\\Enable' => $baseDir . '/core/Command/App/Enable.php',
|
||||
|
@ -654,6 +655,7 @@ return array(
|
|||
'OC\\Core\\Controller\\AvatarController' => $baseDir . '/core/Controller/AvatarController.php',
|
||||
'OC\\Core\\Controller\\CSRFTokenController' => $baseDir . '/core/Controller/CSRFTokenController.php',
|
||||
'OC\\Core\\Controller\\ClientFlowLoginController' => $baseDir . '/core/Controller/ClientFlowLoginController.php',
|
||||
'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => $baseDir . '/core/Controller/ClientFlowLoginV2Controller.php',
|
||||
'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php',
|
||||
'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php',
|
||||
'OC\\Core\\Controller\\GuestAvatarController' => $baseDir . '/core/Controller/GuestAvatarController.php',
|
||||
|
@ -671,6 +673,11 @@ return array(
|
|||
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
|
||||
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
|
||||
'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php',
|
||||
'OC\\Core\\Data\\LoginFlowV2Credentials' => $baseDir . '/core/Data/LoginFlowV2Credentials.php',
|
||||
'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php',
|
||||
'OC\\Core\\Db\\LoginFlowV2' => $baseDir . '/core/Db/LoginFlowV2.php',
|
||||
'OC\\Core\\Db\\LoginFlowV2Mapper' => $baseDir . '/core/Db/LoginFlowV2Mapper.php',
|
||||
'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => $baseDir . '/core/Exception/LoginFlowV2NotFoundException.php',
|
||||
'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php',
|
||||
'OC\\Core\\Migrations\\Version13000Date20170705121758' => $baseDir . '/core/Migrations/Version13000Date20170705121758.php',
|
||||
'OC\\Core\\Migrations\\Version13000Date20170718121200' => $baseDir . '/core/Migrations/Version13000Date20170718121200.php',
|
||||
|
@ -688,6 +695,8 @@ return array(
|
|||
'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php',
|
||||
'OC\\Core\\Migrations\\Version15000Date20181015062942' => $baseDir . '/core/Migrations/Version15000Date20181015062942.php',
|
||||
'OC\\Core\\Migrations\\Version15000Date20181029084625' => $baseDir . '/core/Migrations/Version15000Date20181029084625.php',
|
||||
'OC\\Core\\Migrations\\Version16000Date20190212081545' => $baseDir . '/core/Migrations/Version16000Date20190212081545.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
|
||||
'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php',
|
||||
'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php',
|
||||
|
@ -985,6 +994,7 @@ return array(
|
|||
'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => $baseDir . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php',
|
||||
'OC\\Repair\\NC14\\RepairPendingCronJobs' => $baseDir . '/lib/private/Repair/NC14/RepairPendingCronJobs.php',
|
||||
'OC\\Repair\\NC15\\SetVcardDatabaseUID' => $baseDir . '/lib/private/Repair/NC15/SetVcardDatabaseUID.php',
|
||||
'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => $baseDir . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php',
|
||||
'OC\\Repair\\NC16\\CleanupCardDAVPhotoCache' => $baseDir . '/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php',
|
||||
'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php',
|
||||
'OC\\Repair\\Owncloud\\DropAccountTermsTable' => $baseDir . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php',
|
||||
|
|
|
@ -595,6 +595,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
|
||||
'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php',
|
||||
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
|
||||
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
|
||||
'OC\\Core\\Command\\App\\CheckCode' => __DIR__ . '/../../..' . '/core/Command/App/CheckCode.php',
|
||||
'OC\\Core\\Command\\App\\Disable' => __DIR__ . '/../../..' . '/core/Command/App/Disable.php',
|
||||
'OC\\Core\\Command\\App\\Enable' => __DIR__ . '/../../..' . '/core/Command/App/Enable.php',
|
||||
|
@ -684,6 +685,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Core\\Controller\\AvatarController' => __DIR__ . '/../../..' . '/core/Controller/AvatarController.php',
|
||||
'OC\\Core\\Controller\\CSRFTokenController' => __DIR__ . '/../../..' . '/core/Controller/CSRFTokenController.php',
|
||||
'OC\\Core\\Controller\\ClientFlowLoginController' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginController.php',
|
||||
'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginV2Controller.php',
|
||||
'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php',
|
||||
'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php',
|
||||
'OC\\Core\\Controller\\GuestAvatarController' => __DIR__ . '/../../..' . '/core/Controller/GuestAvatarController.php',
|
||||
|
@ -701,6 +703,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
|
||||
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
|
||||
'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php',
|
||||
'OC\\Core\\Data\\LoginFlowV2Credentials' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Credentials.php',
|
||||
'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php',
|
||||
'OC\\Core\\Db\\LoginFlowV2' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2.php',
|
||||
'OC\\Core\\Db\\LoginFlowV2Mapper' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2Mapper.php',
|
||||
'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => __DIR__ . '/../../..' . '/core/Exception/LoginFlowV2NotFoundException.php',
|
||||
'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php',
|
||||
'OC\\Core\\Migrations\\Version13000Date20170705121758' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170705121758.php',
|
||||
'OC\\Core\\Migrations\\Version13000Date20170718121200' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170718121200.php',
|
||||
|
@ -718,6 +725,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php',
|
||||
'OC\\Core\\Migrations\\Version15000Date20181015062942' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181015062942.php',
|
||||
'OC\\Core\\Migrations\\Version15000Date20181029084625' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181029084625.php',
|
||||
'OC\\Core\\Migrations\\Version16000Date20190212081545' => __DIR__ . '/../../..' . '/core/Migrations/Version16000Date20190212081545.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
|
||||
'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php',
|
||||
'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php',
|
||||
|
@ -1015,6 +1024,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Repair\\NC14\\AddPreviewBackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php',
|
||||
'OC\\Repair\\NC14\\RepairPendingCronJobs' => __DIR__ . '/../../..' . '/lib/private/Repair/NC14/RepairPendingCronJobs.php',
|
||||
'OC\\Repair\\NC15\\SetVcardDatabaseUID' => __DIR__ . '/../../..' . '/lib/private/Repair/NC15/SetVcardDatabaseUID.php',
|
||||
'OC\\Repair\\NC16\\AddClenupLoginFlowV2BackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/AddClenupLoginFlowV2BackgroundJob.php',
|
||||
'OC\\Repair\\NC16\\CleanupCardDAVPhotoCache' => __DIR__ . '/../../..' . '/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php',
|
||||
'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php',
|
||||
'OC\\Repair\\Owncloud\\DropAccountTermsTable' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php',
|
||||
|
|
|
@ -43,6 +43,7 @@ use OC\Repair\NC13\RepairInvalidPaths;
|
|||
use OC\Repair\NC14\AddPreviewBackgroundCleanupJob;
|
||||
use OC\Repair\NC14\RepairPendingCronJobs;
|
||||
use OC\Repair\NC15\SetVcardDatabaseUID;
|
||||
use OC\Repair\NC16\AddClenupLoginFlowV2BackgroundJob;
|
||||
use OC\Repair\NC16\CleanupCardDAVPhotoCache;
|
||||
use OC\Repair\OldGroupMembershipShares;
|
||||
use OC\Repair\Owncloud\DropAccountTermsTable;
|
||||
|
@ -150,6 +151,7 @@ class Repair implements IOutput {
|
|||
new RepairPendingCronJobs(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
|
||||
new SetVcardDatabaseUID(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getLogger()),
|
||||
new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->getLogger()),
|
||||
new AddClenupLoginFlowV2BackgroundJob(\OC::$server->getJobList()),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Repair\NC16;
|
||||
|
||||
use OC\Core\BackgroundJobs\CleanupLoginFlowV2;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
|
||||
class AddClenupLoginFlowV2BackgroundJob implements IRepairStep {
|
||||
|
||||
/** @var IJobList */
|
||||
private $jobList;
|
||||
|
||||
public function __construct(IJobList $jobList) {
|
||||
$this->jobList = $jobList;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'Add background job to cleanup login flow v2 tokens';
|
||||
}
|
||||
|
||||
public function run(IOutput $output) {
|
||||
$this->jobList->add(CleanupLoginFlowV2::class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Core\Controller;
|
||||
|
||||
use OC\Core\Controller\ClientFlowLoginV2Controller;
|
||||
use OC\Core\Data\LoginFlowV2Credentials;
|
||||
use OC\Core\Db\LoginFlowV2;
|
||||
use OC\Core\Exception\LoginFlowV2NotFoundException;
|
||||
use OC\Core\Service\LoginFlowV2Service;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Defaults;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
class ClientFlowLoginV2ControllerTest extends TestCase {
|
||||
|
||||
/** @var IRequest|MockObject */
|
||||
private $request;
|
||||
/** @var LoginFlowV2Service|MockObject */
|
||||
private $loginFlowV2Service;
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
/** @var ISession|MockObject */
|
||||
private $session;
|
||||
/** @var ISecureRandom|MockObject */
|
||||
private $random;
|
||||
/** @var Defaults|MockObject */
|
||||
private $defaults;
|
||||
/** @var IL10N|MockObject */
|
||||
private $l;
|
||||
/** @var ClientFlowLoginV2Controller */
|
||||
private $controller;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->loginFlowV2Service = $this->createMock(LoginFlowV2Service::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->session = $this->createMock(ISession::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$this->defaults = $this->createMock(Defaults::class);
|
||||
$this->l = $this->createMock(IL10N::class);
|
||||
$this->controller = new ClientFlowLoginV2Controller(
|
||||
'core',
|
||||
$this->request,
|
||||
$this->loginFlowV2Service,
|
||||
$this->urlGenerator,
|
||||
$this->session,
|
||||
$this->random,
|
||||
$this->defaults,
|
||||
'user',
|
||||
$this->l
|
||||
);
|
||||
}
|
||||
|
||||
public function testPollInvalid() {
|
||||
$this->loginFlowV2Service->method('poll')
|
||||
->with('token')
|
||||
->willThrowException(new LoginFlowV2NotFoundException());
|
||||
|
||||
$result = $this->controller->poll('token');
|
||||
|
||||
$this->assertSame([], $result->getData());
|
||||
$this->assertSame(Http::STATUS_NOT_FOUND, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testPollValid() {
|
||||
$creds = new LoginFlowV2Credentials('server', 'login', 'pass');
|
||||
$this->loginFlowV2Service->method('poll')
|
||||
->with('token')
|
||||
->willReturn($creds);
|
||||
|
||||
$result = $this->controller->poll('token');
|
||||
|
||||
$this->assertSame($creds, $result->getData());
|
||||
$this->assertSame(Http::STATUS_OK, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testLandingInvalid() {
|
||||
$this->session->expects($this->never())
|
||||
->method($this->anything());
|
||||
|
||||
$this->loginFlowV2Service->method('startLoginFlow')
|
||||
->with('token')
|
||||
->willReturn(false);
|
||||
|
||||
$result = $this->controller->landing('token');
|
||||
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
$this->assertInstanceOf(Http\StandaloneTemplateResponse::class, $result);
|
||||
}
|
||||
|
||||
public function testLandingValid() {
|
||||
$this->session->expects($this->once())
|
||||
->method('set')
|
||||
->with('client.flow.v2.login.token', 'token');
|
||||
|
||||
$this->loginFlowV2Service->method('startLoginFlow')
|
||||
->with('token')
|
||||
->willReturn(true);
|
||||
|
||||
$this->urlGenerator->method('linkToRouteAbsolute')
|
||||
->with('core.ClientFlowLoginV2.showAuthPickerPage')
|
||||
->willReturn('https://server/path');
|
||||
|
||||
$result = $this->controller->landing('token');
|
||||
|
||||
$this->assertInstanceOf(Http\RedirectResponse::class, $result);
|
||||
$this->assertSame(Http::STATUS_SEE_OTHER, $result->getStatus());
|
||||
$this->assertSame('https://server/path', $result->getRedirectURL());
|
||||
}
|
||||
|
||||
public function testShowAuthPickerNoLoginToken() {
|
||||
$this->session->method('get')
|
||||
->willReturn(null);
|
||||
|
||||
$result = $this->controller->showAuthPickerPage();
|
||||
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testShowAuthPickerInvalidLoginToken() {
|
||||
$this->session->method('get')
|
||||
->with('client.flow.v2.login.token')
|
||||
->willReturn('loginToken');
|
||||
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willThrowException(new LoginFlowV2NotFoundException());
|
||||
|
||||
$result = $this->controller->showAuthPickerPage();
|
||||
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testShowAuthPickerValidLoginToken() {
|
||||
$this->session->method('get')
|
||||
->with('client.flow.v2.login.token')
|
||||
->willReturn('loginToken');
|
||||
|
||||
$flow = new LoginFlowV2();
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willReturn($flow);
|
||||
|
||||
$this->random->method('generate')
|
||||
->with(64, ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS)
|
||||
->willReturn('random');
|
||||
$this->session->expects($this->once())
|
||||
->method('set')
|
||||
->with('client.flow.v2.state.token', 'random');
|
||||
|
||||
$this->controller->showAuthPickerPage();
|
||||
}
|
||||
|
||||
public function testGrantPageInvalidStateToken() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
return null;
|
||||
}));
|
||||
|
||||
$result = $this->controller->grantPage('stateToken');
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testGrantPageInvalidLoginToken() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if ($name === 'client.flow.v2.state.token') {
|
||||
return 'stateToken';
|
||||
}
|
||||
if ($name === 'client.flow.v2.login.token') {
|
||||
return 'loginToken';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willThrowException(new LoginFlowV2NotFoundException());
|
||||
|
||||
$result = $this->controller->grantPage('stateToken');
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testGrantPageValid() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if ($name === 'client.flow.v2.state.token') {
|
||||
return 'stateToken';
|
||||
}
|
||||
if ($name === 'client.flow.v2.login.token') {
|
||||
return 'loginToken';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$flow = new LoginFlowV2();
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willReturn($flow);
|
||||
|
||||
$result = $this->controller->grantPage('stateToken');
|
||||
$this->assertSame(Http::STATUS_OK, $result->getStatus());
|
||||
}
|
||||
|
||||
|
||||
public function testGenerateAppPasswordInvalidStateToken() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
return null;
|
||||
}));
|
||||
|
||||
$result = $this->controller->generateAppPassword('stateToken');
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testGenerateAppPassworInvalidLoginToken() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if ($name === 'client.flow.v2.state.token') {
|
||||
return 'stateToken';
|
||||
}
|
||||
if ($name === 'client.flow.v2.login.token') {
|
||||
return 'loginToken';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willThrowException(new LoginFlowV2NotFoundException());
|
||||
|
||||
$result = $this->controller->generateAppPassword('stateToken');
|
||||
$this->assertSame(Http::STATUS_FORBIDDEN, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testGenerateAppPassworValid() {
|
||||
$this->session->method('get')
|
||||
->will($this->returnCallback(function($name) {
|
||||
if ($name === 'client.flow.v2.state.token') {
|
||||
return 'stateToken';
|
||||
}
|
||||
if ($name === 'client.flow.v2.login.token') {
|
||||
return 'loginToken';
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
|
||||
$flow = new LoginFlowV2();
|
||||
$this->loginFlowV2Service->method('getByLoginToken')
|
||||
->with('loginToken')
|
||||
->willReturn($flow);
|
||||
|
||||
$clearedState = false;
|
||||
$clearedLogin = false;
|
||||
$this->session->method('remove')
|
||||
->will($this->returnCallback(function ($name) use (&$clearedLogin, &$clearedState) {
|
||||
if ($name === 'client.flow.v2.state.token') {
|
||||
$clearedState = true;
|
||||
}
|
||||
if ($name === 'client.flow.v2.login.token') {
|
||||
$clearedLogin = true;
|
||||
}
|
||||
}));
|
||||
|
||||
$this->session->method('getId')
|
||||
->willReturn('sessionId');
|
||||
|
||||
$this->loginFlowV2Service->expects($this->once())
|
||||
->method('flowDone')
|
||||
->with(
|
||||
'loginToken',
|
||||
'sessionId',
|
||||
'https://server',
|
||||
'user'
|
||||
)->willReturn(true);
|
||||
|
||||
$this->request->method('getServerProtocol')
|
||||
->willReturn('https');
|
||||
$this->request->method('getRequestUri')
|
||||
->willReturn('/login/v2');
|
||||
$this->request->method('getServerHost')
|
||||
->willReturn('server');
|
||||
|
||||
$result = $this->controller->generateAppPassword('stateToken');
|
||||
$this->assertSame(Http::STATUS_OK, $result->getStatus());
|
||||
|
||||
$this->assertTrue($clearedLogin);
|
||||
$this->assertTrue($clearedState);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
|
||||
// when updating major/minor version number.
|
||||
|
||||
$OC_Version = array(16, 0, 0, 0);
|
||||
$OC_Version = array(16, 0, 0, 1);
|
||||
|
||||
// The human readable string
|
||||
$OC_VersionString = '16.0.0 alpha';
|
||||
|
|
Loading…
Reference in New Issue