Skip to content
Snippets Groups Projects
Unverified Commit 02e0af12 authored by Roeland Jago Douma's avatar Roeland Jago Douma
Browse files

Initial PKT implementation

parent 8eec3a9c
No related branches found
No related tags found
No related merge requests found
...@@ -422,6 +422,9 @@ return array( ...@@ -422,6 +422,9 @@ return array(
'OC\\Authentication\\Token\\IProvider' => $baseDir . '/lib/private/Authentication/Token/IProvider.php', 'OC\\Authentication\\Token\\IProvider' => $baseDir . '/lib/private/Authentication/Token/IProvider.php',
'OC\\Authentication\\Token\\IToken' => $baseDir . '/lib/private/Authentication/Token/IToken.php', 'OC\\Authentication\\Token\\IToken' => $baseDir . '/lib/private/Authentication/Token/IToken.php',
'OC\\Authentication\\Token\\Manager' => $baseDir . '/lib/private/Authentication/Token/Manager.php', 'OC\\Authentication\\Token\\Manager' => $baseDir . '/lib/private/Authentication/Token/Manager.php',
'OC\\Authentication\\Token\\PublicKeyToken' => $baseDir . '/lib/private/Authentication/Token/PublicKeyToken.php',
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php',
'OC\\Authentication\\Token\\PublicKeyTokenProvider' => $baseDir . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php', 'OC\\Authentication\\TwoFactorAuth\\Manager' => $baseDir . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
'OC\\Avatar' => $baseDir . '/lib/private/Avatar.php', 'OC\\Avatar' => $baseDir . '/lib/private/Avatar.php',
'OC\\AvatarManager' => $baseDir . '/lib/private/AvatarManager.php', 'OC\\AvatarManager' => $baseDir . '/lib/private/AvatarManager.php',
......
...@@ -452,6 +452,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c ...@@ -452,6 +452,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IProvider.php', 'OC\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IProvider.php',
'OC\\Authentication\\Token\\IToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IToken.php', 'OC\\Authentication\\Token\\IToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/IToken.php',
'OC\\Authentication\\Token\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/Manager.php', 'OC\\Authentication\\Token\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/Manager.php',
'OC\\Authentication\\Token\\PublicKeyToken' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyToken.php',
'OC\\Authentication\\Token\\PublicKeyTokenMapper' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenMapper.php',
'OC\\Authentication\\Token\\PublicKeyTokenProvider' => __DIR__ . '/../../..' . '/lib/private/Authentication/Token/PublicKeyTokenProvider.php',
'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php', 'OC\\Authentication\\TwoFactorAuth\\Manager' => __DIR__ . '/../../..' . '/lib/private/Authentication/TwoFactorAuth/Manager.php',
'OC\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar.php', 'OC\\Avatar' => __DIR__ . '/../../..' . '/lib/private/Avatar.php',
'OC\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/AvatarManager.php', 'OC\\AvatarManager' => __DIR__ . '/../../..' . '/lib/private/AvatarManager.php',
......
...@@ -37,6 +37,7 @@ use OCP\AppFramework\Db\Entity; ...@@ -37,6 +37,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setRemember(int $remember) * @method void setRemember(int $remember)
* @method void setLastActivity(int $lastactivity) * @method void setLastActivity(int $lastactivity)
* @method int getLastActivity() * @method int getLastActivity()
* @method void setVersion(int $version)
*/ */
class DefaultToken extends Entity implements IToken { class DefaultToken extends Entity implements IToken {
...@@ -73,6 +74,9 @@ class DefaultToken extends Entity implements IToken { ...@@ -73,6 +74,9 @@ class DefaultToken extends Entity implements IToken {
/** @var int */ /** @var int */
protected $expires; protected $expires;
/** @var int */
protected $version;
public function __construct() { public function __construct() {
$this->addType('uid', 'string'); $this->addType('uid', 'string');
$this->addType('loginName', 'string'); $this->addType('loginName', 'string');
...@@ -85,6 +89,9 @@ class DefaultToken extends Entity implements IToken { ...@@ -85,6 +89,9 @@ class DefaultToken extends Entity implements IToken {
$this->addType('lastCheck', 'int'); $this->addType('lastCheck', 'int');
$this->addType('scope', 'string'); $this->addType('scope', 'string');
$this->addType('expires', 'int'); $this->addType('expires', 'int');
$this->addType('version', 'int');
$this->setVersion(1);
} }
public function getId(): int { public function getId(): int {
......
...@@ -50,8 +50,8 @@ class DefaultTokenMapper extends QBMapper { ...@@ -50,8 +50,8 @@ class DefaultTokenMapper extends QBMapper {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('authtoken') $qb->delete('authtoken')
->where($qb->expr()->eq('token', $qb->createParameter('token'))) ->where($qb->expr()->eq('token', $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR)))
->setParameter('token', $token) ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
->execute(); ->execute();
} }
...@@ -66,6 +66,7 @@ class DefaultTokenMapper extends QBMapper { ...@@ -66,6 +66,7 @@ class DefaultTokenMapper extends QBMapper {
->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT))) ->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
->execute(); ->execute();
} }
...@@ -79,9 +80,10 @@ class DefaultTokenMapper extends QBMapper { ...@@ -79,9 +80,10 @@ class DefaultTokenMapper extends QBMapper {
public function getToken(string $token): DefaultToken { public function getToken(string $token): DefaultToken {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$result = $qb->select('*') $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version')
->from('authtoken') ->from('authtoken')
->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) ->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
->execute(); ->execute();
$data = $result->fetch(); $data = $result->fetch();
...@@ -102,9 +104,10 @@ class DefaultTokenMapper extends QBMapper { ...@@ -102,9 +104,10 @@ class DefaultTokenMapper extends QBMapper {
public function getTokenById(int $id): DefaultToken { public function getTokenById(int $id): DefaultToken {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$result = $qb->select('*') $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version')
->from('authtoken') ->from('authtoken')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
->execute(); ->execute();
$data = $result->fetch(); $data = $result->fetch();
...@@ -127,9 +130,10 @@ class DefaultTokenMapper extends QBMapper { ...@@ -127,9 +130,10 @@ class DefaultTokenMapper extends QBMapper {
public function getTokenByUser(IUser $user): array { public function getTokenByUser(IUser $user): array {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('id', 'uid', 'login_name', 'password', 'name', 'token', 'type', 'remember', 'last_activity', 'last_check', 'scope', 'expires', 'version')
->from('authtoken') ->from('authtoken')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
->setMaxResults(1000); ->setMaxResults(1000);
$result = $qb->execute(); $result = $qb->execute();
$data = $result->fetchAll(); $data = $result->fetchAll();
...@@ -151,7 +155,8 @@ class DefaultTokenMapper extends QBMapper { ...@@ -151,7 +155,8 @@ class DefaultTokenMapper extends QBMapper {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('authtoken') $qb->delete('authtoken')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))); ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
$qb->execute(); $qb->execute();
} }
...@@ -163,7 +168,8 @@ class DefaultTokenMapper extends QBMapper { ...@@ -163,7 +168,8 @@ class DefaultTokenMapper extends QBMapper {
public function deleteByName(string $name) { public function deleteByName(string $name) {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('authtoken') $qb->delete('authtoken')
->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR)); ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
$qb->execute(); $qb->execute();
} }
......
...@@ -76,9 +76,9 @@ class Manager implements IProvider { ...@@ -76,9 +76,9 @@ class Manager implements IProvider {
public function updateToken(IToken $token) { public function updateToken(IToken $token) {
if ($token instanceof DefaultToken) { if ($token instanceof DefaultToken) {
$this->defaultTokenProvider->updateToken($token); $this->defaultTokenProvider->updateToken($token);
} else {
throw new InvalidTokenException();
} }
throw new InvalidTokenException();
} }
/** /**
...@@ -90,9 +90,9 @@ class Manager implements IProvider { ...@@ -90,9 +90,9 @@ class Manager implements IProvider {
public function updateTokenActivity(IToken $token) { public function updateTokenActivity(IToken $token) {
if ($token instanceof DefaultToken) { if ($token instanceof DefaultToken) {
$this->defaultTokenProvider->updateTokenActivity($token); $this->defaultTokenProvider->updateTokenActivity($token);
} else {
throw new InvalidTokenException();
} }
throw new InvalidTokenException();
} }
/** /**
......
<?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\Authentication\Token;
use OCP\AppFramework\Db\Entity;
/**
* @method void setId(int $id)
* @method void setUid(string $uid);
* @method void setLoginName(string $loginname)
* @method void setName(string $name)
* @method string getToken()
* @method void setType(int $type)
* @method int getType()
* @method void setRemember(int $remember)
* @method void setLastActivity(int $lastactivity)
* @method int getLastActivity()
* @method string getPrivateKey()
* @method void setPrivateKey(string $key)
* @method string getPublicKey()
* @method void setPublicKey(string $key)
* @method void setVersion(int $version)
*/
class PublicKeyToken extends Entity implements IToken {
/** @var string user UID */
protected $uid;
/** @var string login name used for generating the token */
protected $loginName;
/** @var string encrypted user password */
protected $password;
/** @var string token name (e.g. browser/OS) */
protected $name;
/** @var string */
protected $token;
/** @var int */
protected $type;
/** @var int */
protected $remember;
/** @var int */
protected $lastActivity;
/** @var int */
protected $lastCheck;
/** @var string */
protected $scope;
/** @var int */
protected $expires;
/** @var string */
protected $privateKey;
/** @var string */
protected $publicKey;
/** @var int */
protected $version;
public function __construct() {
$this->addType('uid', 'string');
$this->addType('loginName', 'string');
$this->addType('password', 'string');
$this->addType('name', 'string');
$this->addType('token', 'string');
$this->addType('type', 'int');
$this->addType('remember', 'int');
$this->addType('lastActivity', 'int');
$this->addType('lastCheck', 'int');
$this->addType('scope', 'string');
$this->addType('expires', 'int');
$this->addType('publicKey', 'string');
$this->addType('privateKey', 'string');
$this->addType('version', 'int');
$this->setVersion(2);
}
public function getId(): int {
return $this->id;
}
public function getUID(): string {
return $this->uid;
}
/**
* Get the login name used when generating the token
*
* @return string
*/
public function getLoginName(): string {
return parent::getLoginName();
}
/**
* Get the (encrypted) login password
*
* @return string|null
*/
public function getPassword() {
return parent::getPassword();
}
public function jsonSerialize() {
return [
'id' => $this->id,
'name' => $this->name,
'lastActivity' => $this->lastActivity,
'type' => $this->type,
'scope' => $this->getScopeAsArray()
];
}
/**
* Get the timestamp of the last password check
*
* @return int
*/
public function getLastCheck(): int {
return parent::getLastCheck();
}
/**
* Get the timestamp of the last password check
*
* @param int $time
*/
public function setLastCheck(int $time) {
parent::setLastCheck($time);
}
public function getScope(): string {
$scope = parent::getScope();
if ($scope === null) {
return '';
}
return $scope;
}
public function getScopeAsArray(): array {
$scope = json_decode($this->getScope(), true);
if (!$scope) {
return [
'filesystem'=> true
];
}
return $scope;
}
public function setScope($scope) {
if (\is_array($scope)) {
parent::setScope(json_encode($scope));
} else {
parent::setScope((string)$scope);
}
}
public function getName(): string {
return parent::getName();
}
public function getRemember(): int {
return parent::getRemember();
}
public function setToken(string $token) {
parent::setToken($token);
}
public function setPassword(string $password = null) {
parent::setPassword($password);
}
public function setExpires($expires) {
parent::setExpires($expires);
}
/**
* @return int|null
*/
public function getExpires() {
return parent::getExpires();
}
}
<?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\Authentication\Token;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
class PublicKeyTokenMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'authtoken');
}
/**
* Invalidate (delete) a given token
*
* @param string $token
*/
public function invalidate(string $token) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->delete('authtoken')
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->execute();
}
/**
* @param int $olderThan
* @param int $remember
*/
public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->delete('authtoken')
->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->execute();
}
/**
* Get the user UID for the given token
*
* @throws DoesNotExistException
*/
public function getToken(string $token): PublicKeyToken {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$result = $qb->select('*')
->from('authtoken')
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->execute();
$data = $result->fetch();
$result->closeCursor();
if ($data === false) {
throw new DoesNotExistException('token does not exist');
}
return PublicKeyToken::fromRow($data);
}
/**
* Get the token for $id
*
* @throws DoesNotExistException
*/
public function getTokenById(int $id): PublicKeyToken {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$result = $qb->select('*')
->from('authtoken')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->execute();
$data = $result->fetch();
$result->closeCursor();
if ($data === false) {
throw new DoesNotExistException('token does not exist');
}
return PublicKeyToken::fromRow($data);
}
/**
* Get all tokens of a user
*
* The provider may limit the number of result rows in case of an abuse
* where a high number of (session) tokens is generated
*
* @param IUser $user
* @return DefaultToken[]
*/
public function getTokenByUser(IUser $user): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('authtoken')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
->setMaxResults(1000);
$result = $qb->execute();
$data = $result->fetchAll();
$result->closeCursor();
$entities = array_map(function ($row) {
return PublicKeyToken::fromRow($row);
}, $data);
return $entities;
}
/**
* @param IUser $user
* @param int $id
*/
public function deleteById(IUser $user, int $id) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->delete('authtoken')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)));
$qb->execute();
}
/**
* delete all auth token which belong to a specific client if the client was deleted
*
* @param string $name
*/
public function deleteByName(string $name) {
$qb = $this->db->getQueryBuilder();
$qb->delete('authtoken')
->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)));
$qb->execute();
}
}
<?php
declare(strict_types=1);
/**
* @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
use OCP\Security\ICrypto;
class PublicKeyTokenProvider implements IProvider {
/** @var PublicKeyTokenMapper */
private $mapper;
/** @var ICrypto */
private $crypto;
/** @var IConfig */
private $config;
/** @var ILogger $logger */
private $logger;
/** @var ITimeFactory $time */
private $time;
public function __construct(PublicKeyTokenMapper $mapper,
ICrypto $crypto,
IConfig $config,
ILogger $logger,
ITimeFactory $time) {
$this->mapper = $mapper;
$this->crypto = $crypto;
$this->config = $config;
$this->logger = $logger;
$this->time = $time;
}
public function generateToken(string $token,
string $uid,
string $loginName,
$password,
string $name,
int $type = IToken::TEMPORARY_TOKEN,
int $remember = IToken::DO_NOT_REMEMBER): IToken {
$dbToken = new PublicKeyToken();
$dbToken->setUid($uid);
$dbToken->setLoginName($loginName);
$config = [
'digest_alg' => 'sha512',
'private_key_bits' => 2048,
];
// Generate new key
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privateKey);
// Extract the public key from $res to $pubKey
$publicKey = openssl_pkey_get_details($res);
$publicKey = $publicKey['key'];
$dbToken->setPublicKey($publicKey);
$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
if (!is_null($password)) {
$dbToken->setPassword($this->encryptPassword($password, $publicKey));
}
$dbToken->setName($name);
$dbToken->setToken($this->hashToken($token));
$dbToken->setType($type);
$dbToken->setRemember($remember);
$dbToken->setLastActivity($this->time->getTime());
$dbToken->setLastCheck($this->time->getTime());
$this->mapper->insert($dbToken);
return $dbToken;
}
public function getToken(string $tokenId): IToken {
try {
$token = $this->mapper->getToken($this->hashToken($tokenId));
} catch (DoesNotExistException $ex) {
throw new InvalidTokenException();
}
if ($token->getExpires() !== null && $token->getExpires() < $this->time->getTime()) {
throw new ExpiredTokenException($token);
}
return $token;
}
public function getTokenById(int $tokenId): IToken {
try {
$token = $this->mapper->getTokenById($tokenId);
} catch (DoesNotExistException $ex) {
throw new InvalidTokenException();
}
if ($token->getExpires() !== null && $token->getExpires() < $this->time->getTime()) {
throw new ExpiredTokenException($token);
}
return $token;
}
public function renewSessionToken(string $oldSessionId, string $sessionId) {
$token = $this->getToken($oldSessionId);
$password = null;
if (!is_null($token->getPassword())) {
$password = $this->decryptPassword($token->getPassword(), $oldSessionId);
}
$this->generateToken(
$sessionId,
$token->getUID(),
$token->getLoginName(),
$password,
$token->getName(),
IToken::TEMPORARY_TOKEN,
$token->getRemember()
);
$this->mapper->delete($token);
}
public function invalidateToken(string $token) {
$this->mapper->invalidate($this->hashToken($token));
}
public function invalidateTokenById(IUser $user, int $id) {
$this->mapper->deleteById($user, $id);
}
public function invalidateOldTokens() {
$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
}
public function updateToken(IToken $token) {
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException();
}
$this->mapper->update($token);
}
public function updateTokenActivity(IToken $token) {
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException();
}
/** @var DefaultToken $token */
$now = $this->time->getTime();
if ($token->getLastActivity() < ($now - 60)) {
// Update token only once per minute
$token->setLastActivity($now);
$this->mapper->update($token);
}
}
public function getTokenByUser(IUser $user): array {
return $this->mapper->getTokenByUser($user);
}
public function getPassword(IToken $token, string $tokenId): string {
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException();
}
// Decrypt private key with tokenId
$privateKey = $this->decrypt($token->getPrivateKey(), $tokenId);
// Decrypt password with private key
return $this->decryptPassword($token->getPassword(), $privateKey);
}
public function setPassword(IToken $token, string $tokenId, string $password) {
// Kill all temp tokens except the current token
// Update pass for all permanent tokens by rencrypting
}
public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException();
}
// Decrypt private key with oldTokenId
$privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
// Encrypt with the new token
$token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
$token->setToken($this->hashToken($newTokenId));
$this->updateToken($token);
return $token;
}
private function encrypt(string $plaintext, string $token): string {
$secret = $this->config->getSystemValue('secret');
return $this->crypto->encrypt($plaintext, $token . $secret);
}
/**
* @throws InvalidTokenException
*/
private function decrypt(string $cipherText, string $token): string {
$secret = $this->config->getSystemValue('secret');
try {
return $this->crypto->decrypt($cipherText, $token . $secret);
} catch (\Exception $ex) {
// Delete the invalid token
$this->invalidateToken($token);
throw new InvalidTokenException();
}
}
private function encryptPassword(string $password, string $publicKey): string {
openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
return $encryptedPassword;
}
private function decryptPassword(string $encryptedPassword, string $privateKey): string {
openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
return $password;
}
private function hashToken(string $token): string {
$secret = $this->config->getSystemValue('secret');
return hash('sha512', $token . $secret);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment