Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoeland Jago Douma <roeland@famdouma.nl>2019-02-12 11:26:46 +0300
committerRoeland Jago Douma <roeland@famdouma.nl>2019-02-25 09:24:50 +0300
commite819e97829407093fd704e70f9cb5964e3f24969 (patch)
tree347d8e3a8f776d4a46d3fc5954ddab1d23f02346 /core/Controller
parent5df6400e287f5ba1c23dcd4749386d8bd2967d35 (diff)
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>
Diffstat (limited to 'core/Controller')
-rw-r--r--core/Controller/ClientFlowLoginV2Controller.php299
1 files changed, 299 insertions, 0 deletions
diff --git a/core/Controller/ClientFlowLoginV2Controller.php b/core/Controller/ClientFlowLoginV2Controller.php
new file mode 100644
index 00000000000..cb73b3241a0
--- /dev/null
+++ b/core/Controller/ClientFlowLoginV2Controller.php
@@ -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;
+ }
+}