migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Daniel Kesselberg (mail@danielkesselberg.de)
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @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;
interface INamedToken extends IToken {
/**
* Set token name
*
* @param string $name
* @return void
*/
public function setName(string $name): void;
}
@@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Marcel Waldvogel <marcel.waldvogel@uni-konstanz.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.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\PasswordlessTokenException;
use OCP\Authentication\Exceptions\ExpiredTokenException;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\Authentication\Token\IToken as OCPIToken;
interface IProvider {
/**
* Create and persist a new token
*
* @param string $token
* @param string $uid
* @param string $loginName
* @param string|null $password
* @param string $name Name will be trimmed to 120 chars when longer
* @param int $type token type
* @param int $remember whether the session token should be used for remember-me
* @return OCPIToken
* @throws \RuntimeException when OpenSSL reports a problem
*/
public function generateToken(string $token,
string $uid,
string $loginName,
?string $password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken;
/**
* Get a token by token id
*
* @param string $tokenId
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
* @return OCPIToken
*/
public function getToken(string $tokenId): OCPIToken;
/**
* Get a token by token id
*
* @param int $tokenId
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
* @return OCPIToken
*/
public function getTokenById(int $tokenId): OCPIToken;
/**
* Duplicate an existing session token
*
* @param string $oldSessionId
* @param string $sessionId
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
* @return OCPIToken The new token
*/
public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken;
/**
* Invalidate (delete) the given session token
*
* @param string $token
*/
public function invalidateToken(string $token);
/**
* Invalidate (delete) the given token
*
* @param string $uid
* @param int $id
*/
public function invalidateTokenById(string $uid, int $id);
/**
* Invalidate (delete) old session tokens
*/
public function invalidateOldTokens();
/**
* Invalidate (delete) tokens last used before a given date
*/
public function invalidateLastUsedBefore(string $uid, int $before): void;
/**
* Save the updated token
*
* @param OCPIToken $token
*/
public function updateToken(OCPIToken $token);
/**
* Update token activity timestamp
*
* @param OCPIToken $token
*/
public function updateTokenActivity(OCPIToken $token);
/**
* 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 string $uid
* @return OCPIToken[]
*/
public function getTokenByUser(string $uid): array;
/**
* Get the (unencrypted) password of the given token
*
* @param OCPIToken $savedToken
* @param string $tokenId
* @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string
*/
public function getPassword(OCPIToken $savedToken, string $tokenId): string;
/**
* Encrypt and set the password of the given token
*
* @param OCPIToken $token
* @param string $tokenId
* @param string $password
* @throws InvalidTokenException
*/
public function setPassword(OCPIToken $token, string $tokenId, string $password);
/**
* Rotate the token. Useful for for example oauth tokens
*
* @param OCPIToken $token
* @param string $oldTokenId
* @param string $newTokenId
* @return OCPIToken
* @throws \RuntimeException when OpenSSL reports a problem
*/
public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken;
/**
* Marks a token as having an invalid password.
*
* @param OCPIToken $token
* @param string $tokenId
*/
public function markPasswordInvalid(OCPIToken $token, string $tokenId);
/**
* Update all the passwords of $uid if required
*
* @param string $uid
* @param string $password
*/
public function updatePasswords(string $uid, string $password);
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Robin Appelman <robin@icewind.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 OCP\Authentication\Token\IToken as OCPIToken;
/**
* @deprecated 28.0.0 use {@see \OCP\Authentication\Token\IToken} instead
*/
interface IToken extends OCPIToken {
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @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;
interface IWipeableToken extends IToken {
/**
* Mark the token for remote wipe
*/
public function wipe(): void;
}
@@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Morris Jobke <hey@morrisjobke.de>
* @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 Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\Authentication\Exceptions\ExpiredTokenException;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\Authentication\Token\IProvider as OCPIProvider;
use OCP\Authentication\Token\IToken as OCPIToken;
class Manager implements IProvider, OCPIProvider {
/** @var PublicKeyTokenProvider */
private $publicKeyTokenProvider;
public function __construct(PublicKeyTokenProvider $publicKeyTokenProvider) {
$this->publicKeyTokenProvider = $publicKeyTokenProvider;
}
/**
* Create and persist a new token
*
* @param string $token
* @param string $uid
* @param string $loginName
* @param string|null $password
* @param string $name Name will be trimmed to 120 chars when longer
* @param int $type token type
* @param int $remember whether the session token should be used for remember-me
* @return OCPIToken
*/
public function generateToken(string $token,
string $uid,
string $loginName,
$password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
try {
return $this->publicKeyTokenProvider->generateToken(
$token,
$uid,
$loginName,
$password,
$name,
$type,
$remember
);
} catch (UniqueConstraintViolationException $e) {
// It's rare, but if two requests of the same session (e.g. env-based SAML)
// try to create the session token they might end up here at the same time
// because we use the session ID as token and the db token is created anew
// with every request.
//
// If the UIDs match, then this should be fine.
$existing = $this->getToken($token);
if ($existing->getUID() !== $uid) {
throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e);
}
return $existing;
}
}
/**
* Save the updated token
*
* @param OCPIToken $token
* @throws InvalidTokenException
*/
public function updateToken(OCPIToken $token) {
$provider = $this->getProvider($token);
$provider->updateToken($token);
}
/**
* Update token activity timestamp
*
* @throws InvalidTokenException
* @param OCPIToken $token
*/
public function updateTokenActivity(OCPIToken $token) {
$provider = $this->getProvider($token);
$provider->updateTokenActivity($token);
}
/**
* @param string $uid
* @return OCPIToken[]
*/
public function getTokenByUser(string $uid): array {
return $this->publicKeyTokenProvider->getTokenByUser($uid);
}
/**
* Get a token by token
*
* @param string $tokenId
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
* @return OCPIToken
*/
public function getToken(string $tokenId): OCPIToken {
try {
return $this->publicKeyTokenProvider->getToken($tokenId);
} catch (WipeTokenException $e) {
throw $e;
} catch (ExpiredTokenException $e) {
throw $e;
} catch (InvalidTokenException $e) {
throw $e;
}
}
/**
* Get a token by token id
*
* @param int $tokenId
* @throws InvalidTokenException
* @return OCPIToken
*/
public function getTokenById(int $tokenId): OCPIToken {
try {
return $this->publicKeyTokenProvider->getTokenById($tokenId);
} catch (ExpiredTokenException $e) {
throw $e;
} catch (WipeTokenException $e) {
throw $e;
} catch (InvalidTokenException $e) {
throw $e;
}
}
/**
* @param string $oldSessionId
* @param string $sessionId
* @throws InvalidTokenException
* @return OCPIToken
*/
public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken {
try {
return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
} catch (ExpiredTokenException $e) {
throw $e;
} catch (InvalidTokenException $e) {
throw $e;
}
}
/**
* @param OCPIToken $savedToken
* @param string $tokenId session token
* @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string
*/
public function getPassword(OCPIToken $savedToken, string $tokenId): string {
$provider = $this->getProvider($savedToken);
return $provider->getPassword($savedToken, $tokenId);
}
public function setPassword(OCPIToken $token, string $tokenId, string $password) {
$provider = $this->getProvider($token);
$provider->setPassword($token, $tokenId, $password);
}
public function invalidateToken(string $token) {
$this->publicKeyTokenProvider->invalidateToken($token);
}
public function invalidateTokenById(string $uid, int $id) {
$this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
}
public function invalidateOldTokens() {
$this->publicKeyTokenProvider->invalidateOldTokens();
}
public function invalidateLastUsedBefore(string $uid, int $before): void {
$this->publicKeyTokenProvider->invalidateLastUsedBefore($uid, $before);
}
/**
* @param OCPIToken $token
* @param string $oldTokenId
* @param string $newTokenId
* @return OCPIToken
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
*/
public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken {
if ($token instanceof PublicKeyToken) {
return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
}
/** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */
throw new OcInvalidTokenException();
}
/**
* @param OCPIToken $token
* @return IProvider
* @throws InvalidTokenException
*/
private function getProvider(OCPIToken $token): IProvider {
if ($token instanceof PublicKeyToken) {
return $this->publicKeyTokenProvider;
}
/** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */
throw new OcInvalidTokenException();
}
public function markPasswordInvalid(OCPIToken $token, string $tokenId) {
$this->getProvider($token)->markPasswordInvalid($token, $tokenId);
}
public function updatePasswords(string $uid, string $password) {
$this->publicKeyTokenProvider->updatePasswords($uid, $password);
}
public function invalidateTokensOfUser(string $uid, ?string $clientName) {
$tokens = $this->getTokenByUser($uid);
foreach ($tokens as $token) {
if ($clientName === null || ($token->getName() === $clientName)) {
$this->invalidateTokenById($uid, $token->getId());
}
}
}
}
@@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @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 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)
* @method bool getPasswordInvalid()
* @method string getPasswordHash()
* @method setPasswordHash(string $hash)
*/
class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
public const VERSION = 2;
/** @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 hashed user password */
protected $passwordHash;
/** @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;
/** @var bool */
protected $passwordInvalid;
public function __construct() {
$this->addType('uid', 'string');
$this->addType('loginName', 'string');
$this->addType('password', 'string');
$this->addType('passwordHash', '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->addType('passwordInvalid', 'bool');
}
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
*/
public function getPassword(): ?string {
return parent::getPassword();
}
public function jsonSerialize(): array {
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
*/
public function setLastCheck(int $time): void {
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(array|string|null $scope): void {
if (is_array($scope)) {
parent::setScope(json_encode($scope));
} else {
parent::setScope((string)$scope);
}
}
public function getName(): string {
return parent::getName();
}
public function setName(string $name): void {
parent::setName($name);
}
public function getRemember(): int {
return parent::getRemember();
}
public function setToken(string $token): void {
parent::setToken($token);
}
public function setPassword(string $password = null): void {
parent::setPassword($password);
}
public function setExpires($expires): void {
parent::setExpires($expires);
}
/**
* @return int|null
*/
public function getExpires() {
return parent::getExpires();
}
public function setPasswordInvalid(bool $invalid) {
parent::setPasswordInvalid($invalid);
}
public function wipe(): void {
parent::setType(IToken::WIPE_TOKEN);
}
}
@@ -0,0 +1,268 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @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;
/**
* @template-extends QBMapper<PublicKeyToken>
*/
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($this->tableName)
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, 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($this->tableName)
->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(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
->execute();
}
public function invalidateLastUsedBefore(string $uid, int $before): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->lt('last_activity', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
return $qb->executeStatement();
}
/**
* 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($this->tableName)
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, 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($this->tableName)
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, 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 string $uid
* @return PublicKeyToken[]
*/
public function getTokenByUser(string $uid): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->tableName)
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, 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;
}
public function deleteById(string $uid, int $id) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, 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($this->tableName)
->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
$qb->execute();
}
public function deleteTempToken(PublicKeyToken $except) {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->eq('uid', $qb->createNamedParameter($except->getUID())))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN)))
->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($except->getId())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
$qb->execute();
}
public function hasExpiredTokens(string $uid): bool {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->tableName)
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('password_invalid', $qb->createNamedParameter(true), IQueryBuilder::PARAM_BOOL))
->setMaxResults(1);
$cursor = $qb->execute();
$data = $cursor->fetchAll();
$cursor->closeCursor();
return count($data) === 1;
}
/**
* Update the last activity timestamp
*
* In highly concurrent setups it can happen that two parallel processes
* trigger the update at (nearly) the same time. In that special case it's
* not necessary to hit the database with two actual updates. Therefore the
* target last activity is included in the WHERE clause with a few seconds
* of tolerance.
*
* Example:
* - process 1 (P1) reads the token at timestamp 1500
* - process 1 (P2) reads the token at timestamp 1501
* - activity update interval is 100
*
* This means
*
* - P1 will see a last_activity smaller than the current time and update
* the token row
* - If P2 reads after P1 had written, it will see 1600 as last activity
* and the comparison on last_activity won't be truthy. This means no rows
* need to be updated a second time
* - If P2 reads before P1 had written, it will see 1501 as last activity,
* but the comparison on last_activity will still not be truthy and the
* token row is not updated a second time
*
* @param IToken $token
* @param int $now
*/
public function updateActivity(IToken $token, int $now): void {
$qb = $this->db->getQueryBuilder();
$update = $qb->update($this->getTableName())
->set('last_activity', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
$qb->expr()->lt('last_activity', $qb->createNamedParameter($now - 15, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
);
$update->executeStatement();
}
public function updateHashesForUser(string $userId, string $passwordHash): void {
$qb = $this->db->getQueryBuilder();
$update = $qb->update($this->getTableName())
->set('password_hash', $qb->createNamedParameter($passwordHash))
->where(
$qb->expr()->eq('uid', $qb->createNamedParameter($userId))
);
$update->executeStatement();
}
public function getFirstTokenForUser(string $userId): ?PublicKeyToken {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId)))
->setMaxResults(1)
->orderBy('id');
$result = $qb->executeQuery();
$data = $result->fetch();
$result->closeCursor();
if ($data === false) {
return null;
}
return PublicKeyToken::fromRow($data);
}
}
@@ -0,0 +1,555 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @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 OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\WipeTokenException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\Token\IToken as OCPIToken;
use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
use OCP\Security\ICrypto;
use OCP\Security\IHasher;
use Psr\Log\LoggerInterface;
class PublicKeyTokenProvider implements IProvider {
public const TOKEN_MIN_LENGTH = 22;
use TTransactional;
/** @var PublicKeyTokenMapper */
private $mapper;
/** @var ICrypto */
private $crypto;
/** @var IConfig */
private $config;
private IDBConnection $db;
/** @var LoggerInterface */
private $logger;
/** @var ITimeFactory */
private $time;
/** @var CappedMemoryCache */
private $cache;
private IHasher $hasher;
public function __construct(PublicKeyTokenMapper $mapper,
ICrypto $crypto,
IConfig $config,
IDBConnection $db,
LoggerInterface $logger,
ITimeFactory $time,
IHasher $hasher) {
$this->mapper = $mapper;
$this->crypto = $crypto;
$this->config = $config;
$this->db = $db;
$this->logger = $logger;
$this->time = $time;
$this->cache = new CappedMemoryCache();
$this->hasher = $hasher;
}
/**
* {@inheritDoc}
*/
public function generateToken(string $token,
string $uid,
string $loginName,
?string $password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
if (strlen($token) < self::TOKEN_MIN_LENGTH) {
$exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given');
$this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]);
throw $exception;
}
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
// We need to check against one old token to see if there is a password
// hash that we can reuse for detecting outdated passwords
$randomOldToken = $this->mapper->getFirstTokenForUser($uid);
$oldTokenMatches = $randomOldToken && $randomOldToken->getPasswordHash() && $password !== null && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash());
$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
if ($oldTokenMatches) {
$dbToken->setPasswordHash($randomOldToken->getPasswordHash());
}
$this->mapper->insert($dbToken);
if (!$oldTokenMatches && $password !== null) {
$this->updatePasswords($uid, $password);
}
// Add the token to the cache
$this->cache[$dbToken->getToken()] = $dbToken;
return $dbToken;
}
public function getToken(string $tokenId): OCPIToken {
/**
* Token length: 72
* @see \OC\Core\Controller\ClientFlowLoginController::generateAppPassword
* @see \OC\Core\Controller\AppPasswordController::getAppPassword
* @see \OC\Core\Command\User\AddAppPassword::execute
* @see \OC\Core\Service\LoginFlowV2Service::flowDone
* @see \OCA\Talk\MatterbridgeManager::generatePassword
* @see \OCA\Preferred_Providers\Controller\PasswordController::generateAppPassword
* @see \OCA\GlobalSiteSelector\TokenHandler::generateAppPassword
*
* Token length: 22-256 - https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length
* @see \OC\User\Session::createSessionToken
*
* Token length: 29
* @see \OCA\Settings\Controller\AuthSettingsController::generateRandomDeviceToken
* @see \OCA\Registration\Service\RegistrationService::generateAppPassword
*/
if (strlen($tokenId) < self::TOKEN_MIN_LENGTH) {
throw new InvalidTokenException('Token is too short for a generated token, should be the password during basic auth');
}
$tokenHash = $this->hashToken($tokenId);
if (isset($this->cache[$tokenHash])) {
if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
$ex = $this->cache[$tokenHash];
throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
}
$token = $this->cache[$tokenHash];
} else {
try {
$token = $this->mapper->getToken($tokenHash);
$this->cache[$token->getToken()] = $token;
} catch (DoesNotExistException $ex) {
try {
$token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId));
$this->cache[$token->getToken()] = $token;
$this->rotate($token, $tokenId, $tokenId);
} catch (DoesNotExistException $ex2) {
$this->cache[$tokenHash] = $ex2;
throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
}
}
}
if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
throw new ExpiredTokenException($token);
}
if ($token->getType() === OCPIToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
if ($token->getPasswordInvalid() === true) {
//The password is invalid we should throw an TokenPasswordExpiredException
throw new TokenPasswordExpiredException($token);
}
return $token;
}
public function getTokenById(int $tokenId): OCPIToken {
try {
$token = $this->mapper->getTokenById($tokenId);
} catch (DoesNotExistException $ex) {
throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
}
if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
throw new ExpiredTokenException($token);
}
if ($token->getType() === OCPIToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
if ($token->getPasswordInvalid() === true) {
//The password is invalid we should throw an TokenPasswordExpiredException
throw new TokenPasswordExpiredException($token);
}
return $token;
}
public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken {
$this->cache->clear();
return $this->atomic(function () use ($oldSessionId, $sessionId) {
$token = $this->getToken($oldSessionId);
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
$password = null;
if (!is_null($token->getPassword())) {
$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
$password = $this->decryptPassword($token->getPassword(), $privateKey);
}
$newToken = $this->generateToken(
$sessionId,
$token->getUID(),
$token->getLoginName(),
$password,
$token->getName(),
OCPIToken::TEMPORARY_TOKEN,
$token->getRemember()
);
$this->mapper->delete($token);
return $newToken;
}, $this->db);
}
public function invalidateToken(string $token) {
$this->cache->clear();
$this->mapper->invalidate($this->hashToken($token));
$this->mapper->invalidate($this->hashTokenWithEmptySecret($token));
}
public function invalidateTokenById(string $uid, int $id) {
$this->cache->clear();
$this->mapper->deleteById($uid, $id);
}
public function invalidateOldTokens() {
$this->cache->clear();
$olderThan = $this->time->getTime() - $this->config->getSystemValueInt('session_lifetime', 60 * 60 * 24);
$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
$this->mapper->invalidateOld($olderThan, OCPIToken::DO_NOT_REMEMBER);
$rememberThreshold = $this->time->getTime() - $this->config->getSystemValueInt('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, OCPIToken::REMEMBER);
}
public function invalidateLastUsedBefore(string $uid, int $before): void {
$this->cache->clear();
$this->mapper->invalidateLastUsedBefore($uid, $before);
}
public function updateToken(OCPIToken $token) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
$this->mapper->update($token);
}
public function updateTokenActivity(OCPIToken $token) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
$activityInterval = min(max($activityInterval, 0), 300);
/** @var PublicKeyToken $token */
$now = $this->time->getTime();
if ($token->getLastActivity() < ($now - $activityInterval)) {
$token->setLastActivity($now);
$this->mapper->updateActivity($token, $now);
}
}
public function getTokenByUser(string $uid): array {
return $this->mapper->getTokenByUser($uid);
}
public function getPassword(OCPIToken $savedToken, string $tokenId): string {
if (!($savedToken instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
if ($savedToken->getPassword() === null) {
throw new PasswordlessTokenException();
}
// Decrypt private key with tokenId
$privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
// Decrypt password with private key
return $this->decryptPassword($savedToken->getPassword(), $privateKey);
}
public function setPassword(OCPIToken $token, string $tokenId, string $password) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
$this->atomic(function () use ($password, $token) {
// When changing passwords all temp tokens are deleted
$this->mapper->deleteTempToken($token);
// Update the password for all tokens
$tokens = $this->mapper->getTokenByUser($token->getUID());
$hashedPassword = $this->hashPassword($password);
foreach ($tokens as $t) {
$publicKey = $t->getPublicKey();
$t->setPassword($this->encryptPassword($password, $publicKey));
$t->setPasswordHash($hashedPassword);
$this->updateToken($t);
}
}, $this->db);
}
private function hashPassword(string $password): string {
return $this->hasher->hash(sha1($password) . $password);
}
public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
// 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->getSystemValueString('secret');
return $this->crypto->encrypt($plaintext, $token . $secret);
}
/**
* @throws InvalidTokenException
*/
private function decrypt(string $cipherText, string $token): string {
$secret = $this->config->getSystemValueString('secret');
try {
return $this->crypto->decrypt($cipherText, $token . $secret);
} catch (\Exception $ex) {
// Retry with empty secret as a fallback for instances where the secret might not have been set by accident
try {
return $this->crypto->decrypt($cipherText, $token);
} catch (\Exception $ex2) {
// Delete the invalid token
$this->invalidateToken($token);
throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex2);
}
}
}
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;
}
private function hashToken(string $token): string {
$secret = $this->config->getSystemValueString('secret');
return hash('sha512', $token . $secret);
}
/**
* @deprecated Fallback for instances where the secret might not have been set by accident
*/
private function hashTokenWithEmptySecret(string $token): string {
return hash('sha512', $token);
}
/**
* @throws \RuntimeException when OpenSSL reports a problem
*/
private function newToken(string $token,
string $uid,
string $loginName,
$password,
string $name,
int $type,
int $remember): PublicKeyToken {
$dbToken = new PublicKeyToken();
$dbToken->setUid($uid);
$dbToken->setLoginName($loginName);
$config = array_merge([
'digest_alg' => 'sha512',
'private_key_bits' => $password !== null && strlen($password) > 250 ? 4096 : 2048,
], $this->config->getSystemValue('openssl', []));
// Generate new key
$res = openssl_pkey_new($config);
if ($res === false) {
$this->logOpensslError();
throw new \RuntimeException('OpenSSL reported a problem');
}
if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
$this->logOpensslError();
throw new \RuntimeException('OpenSSL reported a problem');
}
// 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) && $this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
throw new \RuntimeException('Trying to save a password with more than 469 characters is not supported. If you want to use big passwords, disable the auth.storeCryptedPassword option in config.php');
}
$dbToken->setPassword($this->encryptPassword($password, $publicKey));
$dbToken->setPasswordHash($this->hashPassword($password));
}
$dbToken->setName($name);
$dbToken->setToken($this->hashToken($token));
$dbToken->setType($type);
$dbToken->setRemember($remember);
$dbToken->setLastActivity($this->time->getTime());
$dbToken->setLastCheck($this->time->getTime());
$dbToken->setVersion(PublicKeyToken::VERSION);
return $dbToken;
}
public function markPasswordInvalid(OCPIToken $token, string $tokenId) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
$token->setPasswordInvalid(true);
$this->mapper->update($token);
}
public function updatePasswords(string $uid, string $password) {
$this->cache->clear();
// prevent setting an empty pw as result of pw-less-login
if ($password === '' || !$this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
return;
}
$this->atomic(function () use ($password, $uid) {
// Update the password for all tokens
$tokens = $this->mapper->getTokenByUser($uid);
$newPasswordHash = null;
/**
* - true: The password hash could not be verified anymore
* and the token needs to be updated with the newly encrypted password
* - false: The hash could still be verified
* - missing: The hash needs to be verified
*/
$hashNeedsUpdate = [];
foreach ($tokens as $t) {
if (!isset($hashNeedsUpdate[$t->getPasswordHash()])) {
if ($t->getPasswordHash() === null) {
$hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
} elseif (!$this->hasher->verify(sha1($password) . $password, $t->getPasswordHash())) {
$hashNeedsUpdate[$t->getPasswordHash() ?: ''] = true;
} else {
$hashNeedsUpdate[$t->getPasswordHash() ?: ''] = false;
}
}
$needsUpdating = $hashNeedsUpdate[$t->getPasswordHash() ?: ''] ?? true;
if ($needsUpdating) {
if ($newPasswordHash === null) {
$newPasswordHash = $this->hashPassword($password);
}
$publicKey = $t->getPublicKey();
$t->setPassword($this->encryptPassword($password, $publicKey));
$t->setPasswordHash($newPasswordHash);
$t->setPasswordInvalid(false);
$this->updateToken($t);
}
}
// If password hashes are different we update them all to be equal so
// that the next execution only needs to verify once
if (count($hashNeedsUpdate) > 1) {
$newPasswordHash = $this->hashPassword($password);
$this->mapper->updateHashesForUser($uid, $newPasswordHash);
}
}, $this->db);
}
private function logOpensslError() {
$errors = [];
while ($error = openssl_error_string()) {
$errors[] = $error;
}
$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
}
}
@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @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 OC\Authentication\Events\RemoteWipeFinished;
use OC\Authentication\Events\RemoteWipeStarted;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUser;
use Psr\Log\LoggerInterface;
use function array_filter;
class RemoteWipe {
/** @var IProvider */
private $tokenProvider;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var LoggerInterface */
private $logger;
public function __construct(IProvider $tokenProvider,
IEventDispatcher $eventDispatcher,
LoggerInterface $logger) {
$this->tokenProvider = $tokenProvider;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
}
/**
* @param IToken $token
* @return bool
*
* @throws InvalidTokenException
* @throws WipeTokenException
*/
public function markTokenForWipe(IToken $token): bool {
if (!$token instanceof IWipeableToken) {
return false;
}
$token->wipe();
$this->tokenProvider->updateToken($token);
return true;
}
/**
* @param IUser $user
*
* @return bool true if any tokens have been marked for remote wipe
*/
public function markAllTokensForWipe(IUser $user): bool {
$tokens = $this->tokenProvider->getTokenByUser($user->getUID());
/** @var IWipeableToken[] $wipeable */
$wipeable = array_filter($tokens, function (IToken $token) {
return $token instanceof IWipeableToken;
});
if (empty($wipeable)) {
return false;
}
foreach ($wipeable as $token) {
$token->wipe();
$this->tokenProvider->updateToken($token);
}
return true;
}
/**
* @param string $token
*
* @return bool whether wiping was started
* @throws InvalidTokenException
*
*/
public function start(string $token): bool {
try {
$this->tokenProvider->getToken($token);
// We expect a WipedTokenException here. If we reach this point this
// is an ordinary token
return false;
} catch (WipeTokenException $e) {
// Expected -> continue below
}
$dbToken = $e->getToken();
$this->logger->info("user " . $dbToken->getUID() . " started a remote wipe");
$this->eventDispatcher->dispatch(RemoteWipeStarted::class, new RemoteWipeStarted($dbToken));
return true;
}
/**
* @param string $token
*
* @return bool whether wiping could be finished
* @throws InvalidTokenException
*/
public function finish(string $token): bool {
try {
$this->tokenProvider->getToken($token);
// We expect a WipedTokenException here. If we reach this point this
// is an ordinary token
return false;
} catch (WipeTokenException $e) {
// Expected -> continue below
}
$dbToken = $e->getToken();
$this->tokenProvider->invalidateToken($token);
$this->logger->info("user " . $dbToken->getUID() . " finished a remote wipe");
$this->eventDispatcher->dispatch(RemoteWipeFinished::class, new RemoteWipeFinished($dbToken));
return true;
}
}
@@ -0,0 +1,41 @@
<?php
/**
* @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
*
* @author Thomas Citharel <nextcloud@tcit.fr>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Authentication\Token;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
class TokenCleanupJob extends TimedJob {
private IProvider $provider;
public function __construct(ITimeFactory $time, IProvider $provider) {
parent::__construct($time);
$this->provider = $provider;
// Run once a day at off-peak time
$this->setInterval(24 * 60 * 60);
$this->setTimeSensitivity(self::TIME_INSENSITIVE);
}
protected function run($argument) {
$this->provider->invalidateOldTokens();
}
}