Add basic implementation for OAuth 2.0 Authorization Code Flow

Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
This commit is contained in:
Lukas Reschke 2017-05-04 23:46:59 +02:00
parent 879e11e7d1
commit 5f71805c35
No known key found for this signature in database
GPG Key ID: B9F6980CF6E759B1
17 changed files with 838 additions and 23 deletions

View File

@ -210,6 +210,19 @@ class Auth extends AbstractBasic {
*/
private function auth(RequestInterface $request, ResponseInterface $response) {
$forcedLogout = false;
$authHeader = $request->getHeader('Authorization');
if (strpos($authHeader, 'Bearer ') !== false) {
if($this->userSession->tryTokenLogin($this->request)) {
$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
$user = $this->userSession->getUser()->getUID();
\OC_Util::setupFS($user);
$this->currentUser = $user;
$this->session->close();
return [true, $this->principalPrefix . $user];
}
}
if(!$this->request->passesCSRFCheck() &&
$this->requiresCSRFCheck()) {
// In case of a fail with POST we need to recheck the credentials

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<table>
<name>*dbprefix*oauth2_clients</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<notnull>true</notnull>
<autoincrement>true</autoincrement>
<unsigned>true</unsigned>
<primary>true</primary>
</field>
<field>
<name>name</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>redirect_uri</name>
<type>text</type>
<notnull>true</notnull>
<length>2000</length>
</field>
<field>
<name>client_identifier</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>secret</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
</declaration>
</table>
<table>
<name>*dbprefix*oauth2_access_tokens</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<notnull>true</notnull>
<autoincrement>true</autoincrement>
<unsigned>true</unsigned>
<primary>true</primary>
</field>
<field>
<name>token_id</name>
<type>integer</type>
<notnull>true</notnull>
</field>
<field>
<name>client_id</name>
<type>integer</type>
<notnull>true</notnull>
</field>
<field>
<name>hashed_code</name>
<type>text</type>
<notnull>true</notnull>
<length>128</length>
</field>
<field>
<name>encrypted_token</name>
<type>text</type>
<notnull>true</notnull>
<length>255</length>
</field>
</declaration>
</table>
</database>

View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<info>
<id>oauth2</id>
<name>OAuth 2.0</name>
<description>The OAuth2 app allows administrators to configure the built-in authentication workflow to also allow OAuth2 compatible authentication from other web applications.</description>
<licence>AGPL</licence>
<author>Lukas Reschke</author>
<namespace>OAuth2</namespace>
<version>1.0.3</version>
<default_enable/>
<types>
<authentication/>
</types>
<settings>
<admin>OCA\OAuth2\Settings\Admin</admin>
</settings>
</info>

View File

@ -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/>.
*
*/
return [
'routes' => [
[
'name' => 'Settings#addClient',
'url' => '/settings',
'verb' => 'POST',
],
[
'name' => 'Settings#deleteClient',
'url' => '/clients/{id}/delete',
'verb' => 'POST'
],
[
'name' => 'LoginRedirector#authorize',
'url' => '/authorize',
'verb' => 'GET',
],
[
'name' => 'OauthApi#getToken',
'url' => '/api/v1/token',
// TODO: POST!
'verb' => 'GET'
],
],
];

View File

@ -0,0 +1,78 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Controller;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
class LoginRedirectorController extends Controller {
/** @var IURLGenerator */
private $urlGenerator;
/** @var ClientMapper */
private $clientMapper;
/**
* @param string $appName
* @param IRequest $request
* @param IURLGenerator $urlGenerator
* @param ClientMapper $clientMapper
*/
public function __construct($appName,
IRequest $request,
IURLGenerator $urlGenerator,
ClientMapper $clientMapper) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->clientMapper = $clientMapper;
}
/**
* @PublicPage
* @NoCSRFRequired
*
* @param string $client_id
* @param string $redirect_uri
* @param string $state
* @return RedirectResponse
*/
public function authorize($client_id,
$redirect_uri,
$state) {
$client = $this->clientMapper->getByIdentifier($client_id);
if($client->getRedirectUri() !== $redirect_uri) {
throw new \Exception('Redirect URI does not match');
}
$targetUrl = $this->urlGenerator->linkToRouteAbsolute(
'core.ClientFlowLogin.showAuthPickerPage',
[
'clientIdentifier' => $client->getClientIdentifier(),
'oauthState' => $state,
]
);
return new RedirectResponse($targetUrl);
}
}

View File

@ -0,0 +1,88 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Controller;
use OC\Authentication\Token\DefaultTokenMapper;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
class OauthApiController extends Controller {
/** @var AccessTokenMapper */
private $accessTokenMapper;
/** @var ICrypto */
private $crypto;
/** @var DefaultTokenMapper */
private $defaultTokenMapper;
/** @var ISecureRandom */
private $secureRandom;
/**
* @param string $appName
* @param IRequest $request
* @param ICrypto $crypto
* @param AccessTokenMapper $accessTokenMapper
* @param DefaultTokenMapper $defaultTokenMapper
* @param ISecureRandom $secureRandom
*/
public function __construct($appName,
IRequest $request,
ICrypto $crypto,
AccessTokenMapper $accessTokenMapper,
DefaultTokenMapper $defaultTokenMapper,
ISecureRandom $secureRandom) {
parent::__construct($appName, $request);
$this->crypto = $crypto;
$this->accessTokenMapper = $accessTokenMapper;
$this->defaultTokenMapper = $defaultTokenMapper;
$this->secureRandom = $secureRandom;
}
/**
* @PublicPage
* @NoCSRFRequired
*
* @param string $code
* @return JSONResponse
*/
public function getToken($code) {
$accessToken = $this->accessTokenMapper->getByCode($code);
$decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
$newCode = $this->secureRandom->generate(128);
$accessToken->setHashedCode(hash('sha512', $newCode));
$accessToken->setEncryptedToken($this->crypto->encrypt($decryptedToken, $newCode));
$this->accessTokenMapper->update($accessToken);
return new JSONResponse(
[
'access_token' => $decryptedToken,
'token_type' => 'token',
'expires_in' => 3600,
'refresh_token' => $newCode,
'user_id' => ($this->defaultTokenMapper->getTokenById($accessToken->getTokenId()))->getUID(),
]
);
}
}

View File

@ -0,0 +1,86 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Controller;
use OCA\OAuth2\Db\Client;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\Security\ISecureRandom;
class SettingsController extends Controller {
/** @var IURLGenerator */
private $urlGenerator;
/** @var ClientMapper */
private $clientMapper;
/** @var ISecureRandom */
private $secureRandom;
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
/**
* @param string $appName
* @param IRequest $request
* @param IURLGenerator $urlGenerator
* @param ClientMapper $clientMapper
* @param ISecureRandom $secureRandom
*/
public function __construct($appName,
IRequest $request,
IURLGenerator $urlGenerator,
ClientMapper $clientMapper,
ISecureRandom $secureRandom) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->secureRandom = $secureRandom;
$this->clientMapper = $clientMapper;
}
/**
* @param string $name
* @param string $redirectUri
* @return RedirectResponse
*/
public function addClient($name,
$redirectUri) {
$client = new Client();
$client->setName($name);
$client->setRedirectUri($redirectUri);
$client->setSecret($this->secureRandom->generate(64, self::validChars));
$client->setClientIdentifier($this->secureRandom->generate(64, self::validChars));
$this->clientMapper->insert($client);
return new RedirectResponse($this->urlGenerator->getAbsoluteURL('/index.php/settings/admin/security'));
}
/**
* @param int $id
* @return RedirectResponse
*/
public function deleteClient($id) {
$client = new Client();
$client->setId($id);
$this->clientMapper->delete($client);
return new RedirectResponse($this->urlGenerator->getAbsoluteURL('/index.php/settings/admin/security'));
}
}

View File

@ -0,0 +1,53 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method int getTokenId()
* @method void setTokenId(int $identifier)
* @method int getClientId()
* @method void setClientId(int $identifier)
* @method string getEncryptedToken()
* @method void setEncryptedToken(string $token)
* @method string getHashedCode()
* @method void setHashedCode(string $token)
*/
class AccessToken extends Entity {
/** @var int */
protected $tokenId;
/** @var int */
protected $clientId;
/** @var string */
protected $hashedCode;
/** @var string */
protected $encryptedToken;
public function __construct() {
$this->addType('id', 'int');
$this->addType('token_id', 'int');
$this->addType('client_id', 'int');
$this->addType('hashed_code', 'string');
$this->addType('encrypted_token', 'string');
}
}

View File

@ -0,0 +1,49 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Db;
use OCP\AppFramework\Db\Mapper;
use OCP\IDBConnection;
class AccessTokenMapper extends Mapper {
/**
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'oauth2_access_tokens');
}
/**
* @param string $code
* @return AccessToken
*/
public function getByCode($code) {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName)
->where($qb->expr()->eq('hashed_code', $qb->createParameter('hashedCode')));
return $this->findEntity($qb->getSQL(), [hash('sha512', $code)]);
}
}

View File

@ -0,0 +1,53 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method string getClientIdentifier()
* @method void setClientIdentifier(string $identifier)
* @method string getSecret()
* @method void setSecret(string $secret)
* @method string getRedirectUri()
* @method void setRedirectUri(string $redirectUri)
* @method string getName()
* @method void setName(string $name)
*/
class Client extends Entity {
/** @var string */
protected $name;
/** @var string */
protected $redirectUri;
/** @var string */
protected $clientIdentifier;
/** @var string */
protected $secret;
public function __construct() {
$this->addType('id', 'int');
$this->addType('name', 'string');
$this->addType('redirect_uri', 'string');
$this->addType('client_identifier', 'string');
$this->addType('secret', 'string');
}
}

View File

@ -0,0 +1,61 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Db;
use OCP\AppFramework\Db\Mapper;
use OCP\IDBConnection;
class ClientMapper extends Mapper {
/**
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'oauth2_clients');
}
/**
* @param string $clientIdentifier
* @return Client
*/
public function getByIdentifier($clientIdentifier) {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName)
->where($qb->expr()->eq('client_identifier', $qb->createParameter('clientId')));
return $this->findEntity($qb->getSQL(), [$clientIdentifier]);
}
/**
* @return Client[]
*/
public function getClients() {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')
->from($this->tableName);
return $this->findEntities($qb->getSQL());
}
}

View File

@ -0,0 +1,67 @@
<?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/>.
*
*/
namespace OCA\OAuth2\Settings;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\Settings\ISettings;
class Admin implements ISettings {
/** @var ClientMapper */
private $clientMapper;
/**
* @param ClientMapper $clientMapper
*/
public function __construct(ClientMapper $clientMapper) {
$this->clientMapper = $clientMapper;
}
/**
* @return TemplateResponse
*/
public function getForm() {
return new TemplateResponse(
'oauth2',
'admin',
[
'clients' => $this->clientMapper->getClients(),
],
''
);
}
/**
* {@inheritdoc}
*/
public function getSection() {
return 'security';
}
/**
* {@inheritdoc}
*/
public function getPriority() {
return 0;
}
}

View File

@ -0,0 +1,70 @@
<?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/>.
*
*/
$urlGenerator = \OC::$server->getURLGenerator();
$themingDefaults = \OC::$server->getThemingDefaults();
/** @var array $_ */
/** @var \OCA\OAuth2\Db\Client[] $clients */
$clients = $_['clients'];
?>
<div id="oauth2" class="section">
<h2><?php p($l->t('OAuth 2.0 clients')); ?></h2>
<p class="settings-hint"><?php p($l->t('OAuth 2.0 allows external services to request access to your %s.', [$themingDefaults->getName()])); ?></p>
<table class="grid">
<thead>
<tr>
<th id="headerName" scope="col"><?php p($l->t('Name')); ?></th>
<th id="headerRedirectUri" scope="col"><?php p($l->t('Redirection URI')); ?></th>
<th id="headerClientIdentifier" scope="col"><?php p($l->t('Client Identifier')); ?></th>
<th id="headerSecret" scope="col"><?php p($l->t('Secret')); ?></th>
<th id="headerRemove">&nbsp;</th>
</tr>
</thead>
<tbody>
<?php foreach ($clients as $client) { ?>
<tr>
<td><?php p($client->getName()); ?></td>
<td><?php p($client->getRedirectUri()); ?></td>
<td><code><?php p($client->getClientIdentifier()); ?></code></td>
<td><code><?php p($client->getSecret()); ?></code></td>
<td>
<form id="form-inline" class="delete" action="<?php p($urlGenerator->linkToRoute('oauth2.Settings.deleteClient', ['id' => $client->getId()])); ?>" method="POST">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="submit" class="button icon-delete" value="">
</form>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<br/>
<h3><?php p($l->t('Add client')); ?></h3>
<form action="<?php p($urlGenerator->linkToRoute('oauth2.Settings.addClient')); ?>" method="POST">
<input type="text" id="name" name="name" placeholder="<?php p($l->t('Name')); ?>">
<input type="url" id="redirectUri" name="redirectUri" placeholder="<?php p($l->t('Redirection URI')); ?>">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="submit" class="button" value="<?php p($l->t('Add')); ?>">
</form>
</div>

View File

@ -25,6 +25,9 @@ use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken;
use OCA\OAuth2\Db\AccessToken;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
@ -35,6 +38,7 @@ use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\Session\Exceptions\SessionNotAvailableException;
@ -53,6 +57,12 @@ class ClientFlowLoginController extends Controller {
private $random;
/** @var IURLGenerator */
private $urlGenerator;
/** @var ClientMapper */
private $clientMapper;
/** @var AccessTokenMapper */
private $accessTokenMapper;
/** @var ICrypto */
private $crypto;
const stateName = 'client.flow.state.token';
@ -66,6 +76,9 @@ class ClientFlowLoginController extends Controller {
* @param IProvider $tokenProvider
* @param ISecureRandom $random
* @param IURLGenerator $urlGenerator
* @param ClientMapper $clientMapper
* @param AccessTokenMapper $accessTokenMapper
* @param ICrypto $crypto
*/
public function __construct($appName,
IRequest $request,
@ -75,7 +88,10 @@ class ClientFlowLoginController extends Controller {
ISession $session,
IProvider $tokenProvider,
ISecureRandom $random,
IURLGenerator $urlGenerator) {
IURLGenerator $urlGenerator,
ClientMapper $clientMapper,
AccessTokenMapper $accessTokenMapper,
ICrypto $crypto) {
parent::__construct($appName, $request);
$this->userSession = $userSession;
$this->l10n = $l10n;
@ -84,6 +100,9 @@ class ClientFlowLoginController extends Controller {
$this->tokenProvider = $tokenProvider;
$this->random = $random;
$this->urlGenerator = $urlGenerator;
$this->clientMapper = $clientMapper;
$this->accessTokenMapper = $accessTokenMapper;
$this->crypto = $crypto;
}
/**
@ -126,31 +145,31 @@ class ClientFlowLoginController extends Controller {
* @NoCSRFRequired
* @UseSession
*
* @param string $clientIdentifier
*
* @return TemplateResponse
*/
public function showAuthPickerPage() {
if($this->userSession->isLoggedIn()) {
return new TemplateResponse(
$this->appName,
'403',
[
'file' => $this->l10n->t('Auth flow can only be started unauthenticated.'),
],
'guest'
);
}
public function showAuthPickerPage($clientIdentifier = '',
$oauthState = '') {
$stateToken = $this->random->generate(
64,
ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
);
$this->session->set(self::stateName, $stateToken);
$clientName = $this->getClientName();
if($clientIdentifier !== '') {
$client = $this->clientMapper->getByIdentifier($clientIdentifier);
$clientName = $client->getName();
}
return new TemplateResponse(
$this->appName,
'loginflow/authpicker',
[
'client' => $this->getClientName(),
'client' => $clientName,
'clientIdentifier' => $clientIdentifier,
'oauthState' => $oauthState,
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
@ -166,9 +185,13 @@ class ClientFlowLoginController extends Controller {
* @UseSession
*
* @param string $stateToken
* @param string $clientIdentifier
* @param string $oauthState
* @return TemplateResponse
*/
public function redirectPage($stateToken = '') {
public function redirectPage($stateToken = '',
$clientIdentifier = '',
$oauthState = '') {
if(!$this->isValidToken($stateToken)) {
return $this->stateTokenForbiddenResponse();
}
@ -179,6 +202,8 @@ class ClientFlowLoginController extends Controller {
[
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'clientIdentifier' => $clientIdentifier,
'oauthState' => $oauthState,
],
'empty'
);
@ -189,9 +214,15 @@ class ClientFlowLoginController extends Controller {
* @UseSession
*
* @param string $stateToken
* @param string $clientIdentifier
* @param string $state
* @param string $oauthState
* @return Http\RedirectResponse|Response
*/
public function generateAppPassword($stateToken) {
public function generateAppPassword($stateToken,
$clientIdentifier = '',
$state = '',
$oauthState = '') {
if(!$this->isValidToken($stateToken)) {
$this->session->remove(self::stateName);
return $this->stateTokenForbiddenResponse();
@ -222,9 +253,10 @@ class ClientFlowLoginController extends Controller {
}
$token = $this->random->generate(72);
$this->tokenProvider->generateToken(
$uid = $this->userSession->getUser()->getUID();
$generatedToken = $this->tokenProvider->generateToken(
$token,
$this->userSession->getUser()->getUID(),
$uid,
$loginName,
$password,
$this->getClientName(),
@ -232,7 +264,27 @@ class ClientFlowLoginController extends Controller {
IToken::DO_NOT_REMEMBER
);
return new Http\RedirectResponse('nc://login/server:' . $this->request->getServerHost() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token));
}
if($clientIdentifier !== '') {
$client = $this->clientMapper->getByIdentifier($clientIdentifier);
$code = $this->random->generate(128);
$accessToken = new AccessToken();
$accessToken->setClientId($client->getId());
$accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
$accessToken->setHashedCode(hash('sha512', $code));
$accessToken->setTokenId($generatedToken->getId());
$this->accessTokenMapper->insert($accessToken);
$redirectUri = sprintf(
'%s?state=%s&code=%s',
$client->getRedirectUri(),
urlencode($oauthState),
urlencode($code)
);
} else {
$redirectUri = 'nc://login/server:' . $this->request->getServerHost() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
}
return new Http\RedirectResponse($redirectUri);
}
}

View File

@ -35,7 +35,7 @@ $urlGenerator = $_['urlGenerator'];
<br/>
<p id="redirect-link">
<a href="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.redirectPage', ['stateToken' => $_['stateToken']])) ?>">
<a href="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.redirectPage', ['stateToken' => $_['stateToken'], 'clientIdentifier' => $_['clientIdentifier'], 'oauthState' => $_['oauthState']])) ?>">
<input type="submit" class="login primary icon-confirm-white" value="<?php p('Grant access') ?>">
</a>
</p>

View File

@ -31,7 +31,9 @@ $urlGenerator = $_['urlGenerator'];
</div>
<form method="POST" action="<?php p($urlGenerator->linkToRouteAbsolute('core.ClientFlowLogin.generateAppPassword')) ?>">
<input type="hidden" name="clientIdentifier" value="<?php p($_['clientIdentifier']) ?>" />
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
<input type="hidden" name="oauthState" value="<?php p($_['oauthState']) ?>" />
<input id="submit-redirect-form" type="submit" class="hidden "/>
</form>

View File

@ -725,7 +725,7 @@ class Session implements IUserSession, Emitter {
*/
public function tryTokenLogin(IRequest $request) {
$authHeader = $request->getHeader('Authorization');
if (strpos($authHeader, 'token ') === false) {
if (strpos($authHeader, 'Bearer ') === false) {
// No auth header, let's try session id
try {
$token = $this->session->getId();
@ -733,7 +733,7 @@ class Session implements IUserSession, Emitter {
return false;
}
} else {
$token = substr($authHeader, 6);
$token = substr($authHeader, 7);
}
if (!$this->loginWithToken($token)) {