diff options
Diffstat (limited to 'lib/private')
25 files changed, 653 insertions, 77 deletions
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 587178e8ede..9d210cd8419 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -853,6 +853,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @return string Server host */ public function getInsecureServerHost(): string { + if ($this->fromTrustedProxy() && $this->getOverwriteHost() !== null) { + return $this->getOverwriteHost(); + } + $host = 'localhost'; if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_HOST'])) { if (strpos($this->server['HTTP_X_FORWARDED_HOST'], ',') !== false) { @@ -868,6 +872,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $host = $this->server['SERVER_NAME']; } } + return $host; } diff --git a/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php b/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php new file mode 100644 index 00000000000..6719037b4a0 --- /dev/null +++ b/lib/private/Authentication/Exceptions/TokenPasswordExpiredException.php @@ -0,0 +1,29 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Authentication\Exceptions; + +class TokenPasswordExpiredException extends ExpiredTokenException { + +} diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index ea94efce54d..0642d4426c4 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace OC\Authentication\Token; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; @@ -60,15 +61,29 @@ class Manager implements IProvider { string $name, int $type = IToken::TEMPORARY_TOKEN, int $remember = IToken::DO_NOT_REMEMBER): IToken { - return $this->publicKeyTokenProvider->generateToken( - $token, - $uid, - $loginName, - $password, - $name, - $type, - $remember - ); + 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; + } } /** diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 624e2c0cadc..19987bec253 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -25,6 +25,7 @@ namespace OC\Authentication\Token; use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\TokenPasswordExpiredException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Exceptions\WipeTokenException; use OC\Cache\CappedMemoryCache; @@ -108,6 +109,11 @@ class PublicKeyTokenProvider implements IProvider { throw new WipeTokenException($token); } + if ($token->getPasswordInvalid() === true) { + //The password is invalid we should throw an TokenPasswordExpiredException + throw new TokenPasswordExpiredException($token); + } + return $token; } @@ -126,6 +132,11 @@ class PublicKeyTokenProvider implements IProvider { throw new WipeTokenException($token); } + if ($token->getPasswordInvalid() === true) { + //The password is invalid we should throw an TokenPasswordExpiredException + throw new TokenPasswordExpiredException($token); + } + return $token; } diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index 971b7025564..9de269224f5 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -71,7 +71,7 @@ class UserPlugin implements ISearchPlugin { foreach ($userGroups as $userGroup) { $usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $limit, $offset); foreach ($usersTmp as $uid => $userDisplayName) { - $users[$uid] = $userDisplayName; + $users[(string) $uid] = $userDisplayName; } } } else { @@ -80,7 +80,7 @@ class UserPlugin implements ISearchPlugin { foreach ($usersTmp as $user) { if ($user->isEnabled()) { // Don't keep deactivated users - $users[$user->getUID()] = $user->getDisplayName(); + $users[(string) $user->getUID()] = $user->getDisplayName(); } } } @@ -94,6 +94,7 @@ class UserPlugin implements ISearchPlugin { $foundUserById = false; $lowerSearch = strtolower($search); foreach ($users as $uid => $userDisplayName) { + $uid = (string) $uid; if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) { if (strtolower($uid) === $lowerSearch) { $foundUserById = true; diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index f9c6f4155a3..65e1d84bfb1 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -38,6 +38,7 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception\ConstraintViolationException; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\TransactionIsolationLevel; use OC\DB\QueryBuilder\QueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -148,7 +149,7 @@ class Connection extends ReconnectWrapper implements IDBConnection { $this->adapter = new $params['adapter']($this); $this->tablePrefix = $params['tablePrefix']; - parent::setTransactionIsolation(parent::TRANSACTION_READ_COMMITTED); + $this->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); } /** diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php new file mode 100644 index 00000000000..26adad9e572 --- /dev/null +++ b/lib/private/DirectEditing/Manager.php @@ -0,0 +1,236 @@ +<?php +/** + * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @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\DirectEditing; + +use Doctrine\DBAL\FetchMode; +use OCP\AppFramework\Http\NotFoundResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DirectEditing\ACreateFromTemplate; +use OCP\DirectEditing\IEditor; +use \OCP\DirectEditing\IManager; +use OCP\DirectEditing\IToken; +use OCP\DirectEditing\RegisterDirectEditorEvent; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IDBConnection; +use OCP\IUserSession; +use OCP\Security\ISecureRandom; +use OCP\Share\IShare; + +class Manager implements IManager { + + private const TOKEN_CLEANUP_TIME = 12 * 60 * 60 ; + + public const TABLE_TOKENS = 'direct_edit'; + + /** @var IEditor[] */ + private $editors = []; + + /** @var IDBConnection */ + private $connection; + /** + * @var ISecureRandom + */ + private $random; + private $userId; + private $rootFolder; + + public function __construct( + ISecureRandom $random, + IDBConnection $connection, + IUserSession $userSession, + IRootFolder $rootFolder + ) { + $this->random = $random; + $this->connection = $connection; + $this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null; + $this->rootFolder = $rootFolder; + } + + public function registerDirectEditor(IEditor $directEditor): void { + $this->editors[$directEditor->getId()] = $directEditor; + } + + public function getEditors(): array { + return $this->editors; + } + + public function getTemplates(string $editor, string $type): array { + if (!array_key_exists($editor, $this->editors)) { + throw new \RuntimeException('No matching editor found'); + } + $templates = []; + foreach ($this->editors[$editor]->getCreators() as $creator) { + if ($creator instanceof ACreateFromTemplate && $creator->getId() === $type) { + $templates = $creator->getTemplates(); + } + } + $return = []; + $return['templates'] = $templates; + return $return; + } + + public function create(string $path, string $editorId, string $creatorId, $templateId = null): string { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $file = $userFolder->newFile($path); + $editor = $this->getEditor($editorId); + $creators = $editor->getCreators(); + foreach ($creators as $creator) { + if ($creator->getId() === $creatorId) { + $creator->create($file, $creatorId, $templateId); + return $this->createToken($editorId, $file); + } + } + throw new \RuntimeException('No creator found'); + } + + public function open(int $fileId, string $editorId = null): string { + $file = $this->rootFolder->getUserFolder($this->userId)->getById($fileId); + if (count($file) === 0 || !($file[0] instanceof File) || $file === null) { + throw new NotFoundException(); + } + /** @var File $file */ + $file = $file[0]; + + if ($editorId === null) { + $editorId = $this->findEditorForFile($file); + } + + return $this->createToken($editorId, $file); + } + + private function findEditorForFile(File $file) { + foreach ($this->editors as $editor) { + if (in_array($file->getMimeType(), $editor->getMimetypes())) { + return $editor->getId(); + } + } + throw new \RuntimeException('No default editor found for files mimetype'); + } + + public function edit(string $token): Response { + try { + /** @var IEditor $editor */ + $tokenObject = $this->getToken($token); + if ($tokenObject->hasBeenAccessed()) { + throw new \RuntimeException('Token has already been used and can only be used for followup requests'); + } + $editor = $this->getEditor($tokenObject->getEditor()); + $this->accessToken($token); + + } catch (\Throwable $throwable) { + $this->invalidateToken($token); + return new NotFoundResponse(); + } + return $editor->open($tokenObject); + } + + public function editSecure(File $file, string $editorId): TemplateResponse { + // TODO: Implementation in follow up + } + + private function getEditor($editorId): IEditor { + if (!array_key_exists($editorId, $this->editors)) { + throw new \RuntimeException('No editor found'); + } + return $this->editors[$editorId]; + } + + public function getToken(string $token): IToken { + $query = $this->connection->getQueryBuilder(); + $query->select('*')->from(self::TABLE_TOKENS) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + if ($tokenRow = $result->fetch(FetchMode::ASSOCIATIVE)) { + return new Token($this, $tokenRow); + } + throw new \RuntimeException('Failed to validate the token'); + } + + public function cleanup(): int { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TABLE_TOKENS) + ->where($query->expr()->lt('timestamp', $query->createNamedParameter(time() - self::TOKEN_CLEANUP_TIME))); + return $query->execute(); + } + + public function refreshToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->update(self::TABLE_TOKENS) + ->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + + public function invalidateToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TABLE_TOKENS) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + public function accessToken(string $token): bool { + $query = $this->connection->getQueryBuilder(); + $query->update(self::TABLE_TOKENS) + ->set('accessed', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)) + ->set('timestamp', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)) + ->where($query->expr()->eq('token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR))); + $result = $query->execute(); + return $result !== 0; + } + + public function invokeTokenScope($userId): void { + \OC_User::setIncognitoMode(true); + \OC_User::setUserId($userId); + } + + public function createToken($editorId, File $file, IShare $share = null): string { + $token = $this->random->generate(64, ISecureRandom::CHAR_HUMAN_READABLE); + $query = $this->connection->getQueryBuilder(); + $query->insert(self::TABLE_TOKENS) + ->values([ + 'token' => $query->createNamedParameter($token), + 'editor_id' => $query->createNamedParameter($editorId), + 'file_id' => $query->createNamedParameter($file->getId()), + 'user_id' => $query->createNamedParameter($this->userId), + 'share_id' => $query->createNamedParameter($share !== null ? $share->getId(): null), + 'timestamp' => $query->createNamedParameter(time()) + ]); + $query->execute(); + return $token; + } + + public function getFileForToken($userId, $fileId) { + $userFolder = $this->rootFolder->getUserFolder($userId); + return $userFolder->getById($fileId)[0]; + } + +} diff --git a/lib/private/DirectEditing/Token.php b/lib/private/DirectEditing/Token.php new file mode 100644 index 00000000000..148621a2cf3 --- /dev/null +++ b/lib/private/DirectEditing/Token.php @@ -0,0 +1,76 @@ +<?php +/** + * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @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\DirectEditing; + + +use OCP\DirectEditing\IToken; +use OCP\Files\File; + +class Token implements IToken { + + /** @var Manager */ + private $manager; + private $data; + + public function __construct(Manager $manager, $data) { + $this->manager = $manager; + $this->data = $data; + } + + public function extend(): void { + $this->manager->refreshToken($this->data['token']); + } + + public function invalidate(): void { + $this->manager->invalidateToken($this->data['token']); + } + + public function getFile(): File { + if ($this->data['share_id'] !== null) { + return $this->manager->getShareForToken($this->data['share_id']); + } + return $this->manager->getFileForToken($this->data['user_id'], $this->data['file_id']); + } + + public function getToken(): string { + return $this->data['token']; + } + + public function useTokenScope(): void { + $this->manager->invokeTokenScope($this->data['user_id']); + } + + public function hasBeenAccessed(): bool { + return (bool) $this->data['accessed']; + } + + public function getEditor(): string { + return $this->data['editor_id']; + } + + public function getUser(): string { + return $this->data['user_id']; + } + +} diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index b504c7a29da..e4669f70709 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -28,6 +28,7 @@ namespace OC\Files\Node; use OCP\Files\GenericFileException; use OCP\Files\NotPermittedException; +use OCP\Lock\LockedException; class File extends Node implements \OCP\Files\File { /** @@ -42,7 +43,8 @@ class File extends Node implements \OCP\Files\File { /** * @return string - * @throws \OCP\Files\NotPermittedException + * @throws NotPermittedException + * @throws LockedException */ public function getContent() { if ($this->checkPermissions(\OCP\Constants::PERMISSION_READ)) { @@ -57,8 +59,9 @@ class File extends Node implements \OCP\Files\File { /** * @param string|resource $data - * @throws \OCP\Files\NotPermittedException + * @throws NotPermittedException * @throws \OCP\Files\GenericFileException + * @throws LockedException */ public function putContent($data) { if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { @@ -76,7 +79,8 @@ class File extends Node implements \OCP\Files\File { /** * @param string $mode * @return resource - * @throws \OCP\Files\NotPermittedException + * @throws NotPermittedException + * @throws LockedException */ public function fopen($mode) { $preHooks = array(); @@ -113,6 +117,11 @@ class File extends Node implements \OCP\Files\File { } } + /** + * @throws NotPermittedException + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException + */ public function delete() { if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { $this->sendHooks(array('preDelete')); diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index b792b584077..417bb4980ee 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -24,10 +24,10 @@ namespace OC\Files\Node; use OC\Files\Filesystem; use OC\Files\View; +use OCP\EventDispatcher\GenericEvent; use OCP\Files\FileInfo; use OCP\Util; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\GenericEvent; class HookConnector { /** diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 95d16cf5c99..7f50524f28d 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -33,6 +33,7 @@ use OCP\Files\FileInfo; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Lock\LockedException; use Symfony\Component\EventDispatcher\GenericEvent; // FIXME: this class really should be abstract @@ -75,6 +76,7 @@ class Node implements \OCP\Files\Node { * * @param string $path path * @return string non-existing node class + * @throws \Exception */ protected function createNonExistingNode($path) { throw new \Exception('Must be implemented by subclasses'); @@ -117,6 +119,8 @@ class Node implements \OCP\Files\Node { /** * @param int $permissions * @return bool + * @throws InvalidPathException + * @throws NotFoundException */ protected function checkPermissions($permissions) { return ($this->getPermissions() & $permissions) === $permissions; @@ -127,7 +131,9 @@ class Node implements \OCP\Files\Node { /** * @param int $mtime - * @throws \OCP\Files\NotPermittedException + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException */ public function touch($mtime = null) { if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { @@ -366,7 +372,7 @@ class Node implements \OCP\Files\Node { /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException + * @throws LockedException */ public function lock($type) { $this->view->lockFile($this->path, $type); @@ -374,7 +380,7 @@ class Node implements \OCP\Files\Node { /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException + * @throws LockedException */ public function changeLock($type) { $this->view->changeLock($this->path, $type); @@ -382,7 +388,7 @@ class Node implements \OCP\Files\Node { /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException + * @throws LockedException */ public function unlock($type) { $this->view->unlockFile($this->path, $type); @@ -390,8 +396,10 @@ class Node implements \OCP\Files\Node { /** * @param string $targetPath - * @throws \OCP\Files\NotPermittedException if copy not allowed or failed * @return \OC\Files\Node\Node + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException if copy not allowed or failed */ public function copy($targetPath) { $targetPath = $this->normalizePath($targetPath); @@ -414,8 +422,11 @@ class Node implements \OCP\Files\Node { /** * @param string $targetPath - * @throws \OCP\Files\NotPermittedException if move not allowed or failed * @return \OC\Files\Node\Node + * @throws InvalidPathException + * @throws NotFoundException + * @throws NotPermittedException if move not allowed or failed + * @throws LockedException */ public function move($targetPath) { $targetPath = $this->normalizePath($targetPath); diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index 69e6f309c31..ca96e6b7434 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -34,7 +34,6 @@ namespace OC\Files\Storage; use Exception; -use GuzzleHttp\Exception\RequestException; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\IteratorDirectory; use OC\Files\Filesystem; @@ -45,6 +44,8 @@ use OCP\Files\FileInfo; use OCP\Files\ForbiddenException; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; +use OCP\Http\Client\IClientService; +use OCP\ICertificateManager; use OCP\ILogger; use OCP\Util; use Psr\Http\Message\ResponseInterface; @@ -79,8 +80,10 @@ class DAV extends Common { protected $client; /** @var ArrayCache */ protected $statCache; - /** @var \OCP\Http\Client\IClientService */ + /** @var IClientService */ protected $httpClientService; + /** @var ICertificateManager */ + protected $certManager; /** * @param array $params @@ -111,13 +114,9 @@ class DAV extends Common { } if ($this->secure === true) { // inject mock for testing - $certManager = \OC::$server->getCertificateManager(); - if (is_null($certManager)) { //no user - $certManager = \OC::$server->getCertificateManager(null); - } - $certPath = $certManager->getAbsoluteBundlePath(); - if (file_exists($certPath)) { - $this->certPath = $certPath; + $this->certManager = \OC::$server->getCertificateManager(); + if (is_null($this->certManager)) { //no user + $this->certManager = \OC::$server->getCertificateManager(null); } } $this->root = $params['root'] ?? '/'; @@ -150,8 +149,15 @@ class DAV extends Common { $this->client = new Client($settings); $this->client->setThrowExceptions(true); - if ($this->secure === true && $this->certPath) { - $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); + + if($this->secure === true) { + $certPath = $this->certManager->getAbsoluteBundlePath(); + if (file_exists($certPath)) { + $this->certPath = $certPath; + } + if ($this->certPath) { + $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); + } } } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index e9a9e8e9885..aade0cdaf25 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -424,12 +424,26 @@ class Local extends \OC\Files\Storage\Common { public function getETag($path) { if ($this->is_file($path)) { $stat = $this->stat($path); - return md5( - $stat['mtime'] . - $stat['ino'] . - $stat['dev'] . - $stat['size'] - ); + + if ($stat === false) { + return md5(''); + } + + $toHash = ''; + if (isset($stat['mtime'])) { + $toHash .= $stat['mtime']; + } + if (isset($stat['ino'])) { + $toHash .= $stat['ino']; + } + if (isset($stat['dev'])) { + $toHash .= $stat['dev']; + } + if (isset($stat['size'])) { + $toHash .= $stat['size']; + } + + return md5($toHash); } else { return parent::getETag($path); } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 4f4c4b90f2c..ed962bde1a4 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -589,6 +589,7 @@ class View { /** * @param string $path * @return mixed + * @throws LockedException */ public function file_get_contents($path) { return $this->basicOperation('file_get_contents', $path, array('read')); @@ -640,7 +641,7 @@ class View { * @param string $path * @param string|resource $data * @return bool|mixed - * @throws \Exception + * @throws LockedException */ public function file_put_contents($path, $data) { if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier @@ -739,6 +740,7 @@ class View { * @param string $path2 target path * * @return bool|mixed + * @throws LockedException */ public function rename($path1, $path2) { $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); @@ -962,6 +964,7 @@ class View { * @param string $path * @param string $mode 'r' or 'w' * @return resource + * @throws LockedException */ public function fopen($path, $mode) { $mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support @@ -1117,7 +1120,7 @@ class View { * @param array $hooks (optional) * @param mixed $extraParam (optional) * @return mixed - * @throws \Exception + * @throws LockedException * * This method takes requests for basic filesystem functions (e.g. reading & writing * files), processes hooks and proxies, sanitises paths, and finally passes them on to @@ -1919,7 +1922,7 @@ class View { * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage * * @return bool False if the path is excluded from locking, true otherwise - * @throws \OCP\Lock\LockedException if the path is already locked + * @throws LockedException if the path is already locked */ private function lockPath($path, $type, $lockMountPoint = false) { $absolutePath = $this->getAbsolutePath($path); @@ -1939,9 +1942,9 @@ class View { $this->lockingProvider ); } - } catch (\OCP\Lock\LockedException $e) { + } catch (LockedException $e) { // rethrow with the a human-readable path - throw new \OCP\Lock\LockedException( + throw new LockedException( $this->getPathRelativeToFiles($absolutePath), $e ); @@ -1959,7 +1962,7 @@ class View { * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage * * @return bool False if the path is excluded from locking, true otherwise - * @throws \OCP\Lock\LockedException if the path is already locked + * @throws LockedException if the path is already locked */ public function changeLock($path, $type, $lockMountPoint = false) { $path = Filesystem::normalizePath($path); @@ -1980,15 +1983,15 @@ class View { $this->lockingProvider ); } - } catch (\OCP\Lock\LockedException $e) { + } catch (LockedException $e) { try { // rethrow with the a human-readable path - throw new \OCP\Lock\LockedException( + throw new LockedException( $this->getPathRelativeToFiles($absolutePath), $e ); } catch (\InvalidArgumentException $e) { - throw new \OCP\Lock\LockedException( + throw new LockedException( $absolutePath, $e ); @@ -2007,6 +2010,7 @@ class View { * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage * * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException */ private function unlockPath($path, $type, $lockMountPoint = false) { $absolutePath = $this->getAbsolutePath($path); @@ -2038,6 +2042,7 @@ class View { * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage * * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException */ public function lockFile($path, $type, $lockMountPoint = false) { $absolutePath = $this->getAbsolutePath($path); @@ -2064,6 +2069,7 @@ class View { * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage * * @return bool False if the path is excluded from locking, true otherwise + * @throws LockedException */ public function unlockFile($path, $type, $lockMountPoint = false) { $absolutePath = $this->getAbsolutePath($path); diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index 0e51b4ec2cc..dc4a7fbd11c 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -392,7 +392,7 @@ class Manager extends PublicEmitter implements IGroupManager { $matchingUsers = []; foreach ($groupUsers as $groupUser) { - $matchingUsers[$groupUser->getUID()] = $groupUser->getDisplayName(); + $matchingUsers[(string) $groupUser->getUID()] = $groupUser->getDisplayName(); } return $matchingUsers; } diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index 8cfdb57b225..a3b855aea26 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -92,7 +92,9 @@ class ExceptionSerializer { ]; private function editTrace(array &$sensitiveValues, array $traceLine): array { - $sensitiveValues = array_merge($sensitiveValues, $traceLine['args']); + if (isset($traceLine['args'])) { + $sensitiveValues = array_merge($sensitiveValues, $traceLine['args']); + } $traceLine['args'] = ['*** sensitive parameters replaced ***']; return $traceLine; } diff --git a/lib/private/Server.php b/lib/private/Server.php index b4af17ba288..fa2a521b6b1 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -199,6 +199,8 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class); $this->registerAlias('ContactsManager', \OCP\Contacts\IManager::class); + $this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class); + $this->registerAlias(IActionFactory::class, ActionFactory::class); @@ -315,9 +317,6 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias('LazyRootFolder', \OCP\Files\IRootFolder::class); - $this->registerService(\OC\User\Manager::class, function (Server $c) { - return new \OC\User\Manager($c->getConfig(), $c->getEventDispatcher()); - }); $this->registerAlias('UserManager', \OC\User\Manager::class); $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class); diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index a5ffa9e0f1e..1205cb81525 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -348,7 +348,7 @@ class Manager implements IManager { } $sections = [ - 0 => [new Section('personal-info', $this->l->t('Personal info'), 0, $this->url->imagePath('core', 'actions/info.svg'))], + 0 => [new Section('personal-info', $this->l->t('Personal info'), 0, $this->url->imagePath('core', 'actions/user.svg'))], 5 => [new Section('security', $this->l->t('Security'), 0, $this->url->imagePath('settings', 'password.svg'))], 15 => [new Section('sync-clients', $this->l->t('Mobile & desktop'), 0, $this->url->imagePath('core', 'clients/phone.svg'))], ]; diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 266c70846c6..4ebd5618189 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -49,6 +49,7 @@ use OC\Authentication\Token\DefaultTokenCleanupJob; use OC\Authentication\Token\DefaultTokenProvider; use OC\Log\Rotate; use OC\Preview\BackgroundCleanupJob; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Defaults; use OCP\IGroup; use OCP\IL10N; @@ -421,6 +422,9 @@ class Setup { $userSession->login($username, $password); $userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password); + $session = $userSession->getSession(); + $session->set('last-password-confirm', \OC::$server->query(ITimeFactory::class)->getTime()); + // Set email for admin if (!empty($options['adminemail'])) { $config->setUserValue($user->getUID(), 'settings', 'email', $options['adminemail']); diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index c25a7b03d5c..152e5d55394 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -144,9 +144,19 @@ class DefaultShareProvider implements IShareProvider { //Set the UID of the user we share with $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith())); $qb->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING)); + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { //Set the GID of the group we share with $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith())); + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { //set label for public link $qb->setValue('label', $qb->createNamedParameter($share->getLabel())); diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index d9809fd128a..db9c704871d 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -55,6 +55,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Mail\IMailer; +use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; use OCP\Share; @@ -191,8 +192,7 @@ class Manager implements IManager { // Let others verify the password try { - $event = new GenericEvent($password); - $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event); + $this->eventDispatcher->dispatch(new ValidatePasswordPolicyEvent($password)); } catch (HintException $e) { throw new \Exception($e->getHint()); } @@ -360,6 +360,77 @@ class Manager implements IManager { * @throws \InvalidArgumentException * @throws \Exception */ + protected function validateExpirationDateInternal(\OCP\Share\IShare $share) { + $expirationDate = $share->getExpirationDate(); + + if ($expirationDate !== null) { + //Make sure the expiration date is a date + $expirationDate->setTime(0, 0, 0); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + if ($date >= $expirationDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new GenericShareException($message, $message, 404); + } + } + + // If expiredate is empty set a default one if there is a default + $fullId = null; + try { + $fullId = $share->getFullId(); + } catch (\UnexpectedValueException $e) { + // This is a new share + } + + if ($fullId === null && $expirationDate === null && $this->shareApiInternalDefaultExpireDate()) { + $expirationDate = new \DateTime(); + $expirationDate->setTime(0,0,0); + $expirationDate->add(new \DateInterval('P'.$this->shareApiInternalDefaultExpireDays().'D')); + } + + // If we enforce the expiration date check that is does not exceed + if ($this->shareApiInternalDefaultExpireDateEnforced()) { + if ($expirationDate === null) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiInternalDefaultExpireDays() . 'D')); + if ($date < $expirationDate) { + $message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiInternalDefaultExpireDays()]); + throw new GenericShareException($message, $message, 404); + } + } + + $accepted = true; + $message = ''; + \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [ + 'expirationDate' => &$expirationDate, + 'accepted' => &$accepted, + 'message' => &$message, + 'passwordSet' => $share->getPassword() !== null, + ]); + + if (!$accepted) { + throw new \Exception($message); + } + + $share->setExpirationDate($expirationDate); + + return $share; + } + + /** + * Validate if the expiration date fits the system settings + * + * @param \OCP\Share\IShare $share The share to validate the expiration date of + * @return \OCP\Share\IShare The modified share object + * @throws GenericShareException + * @throws \InvalidArgumentException + * @throws \Exception + */ protected function validateExpirationDate(\OCP\Share\IShare $share) { $expirationDate = $share->getExpirationDate(); @@ -636,8 +707,16 @@ class Manager implements IManager { //Verify share type if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { $this->userCreateChecks($share); + + //Verify the expiration date + $share = $this->validateExpirationDateInternal($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { $this->groupCreateChecks($share); + + //Verify the expiration date + $share = $this->validateExpirationDateInternal($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { $this->linkCreateChecks($share); $this->setLinkParent($share); @@ -653,7 +732,7 @@ class Manager implements IManager { ); //Verify the expiration date - $this->validateExpirationDate($share); + $share = $this->validateExpirationDate($share); //Verify the password $this->verifyPassword($share->getPassword()); @@ -850,8 +929,20 @@ class Manager implements IManager { if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { $this->userCreateChecks($share); + + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { + //Verify the expiration date + $this->validateExpirationDate($share); + $expirationDateUpdated = true; + } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { $this->groupCreateChecks($share); + + if ($share->getExpirationDate() != $originalShare->getExpirationDate()) { + //Verify the expiration date + $this->validateExpirationDate($share); + $expirationDateUpdated = true; + } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { $this->linkCreateChecks($share); @@ -1575,7 +1666,7 @@ class Manager implements IManager { } /** - * Is default expire date enabled + * Is default link expire date enabled * * @return bool */ @@ -1584,7 +1675,7 @@ class Manager implements IManager { } /** - * Is default expire date enforced + * Is default link expire date enforced *` * @return bool */ @@ -1593,9 +1684,9 @@ class Manager implements IManager { $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; } + /** - * Number of default expire days - *shareApiLinkAllowPublicUpload + * Number of default link expire days * @return int */ public function shareApiLinkDefaultExpireDays() { @@ -1603,6 +1694,34 @@ class Manager implements IManager { } /** + * Is default internal expire date enabled + * + * @return bool + */ + public function shareApiInternalDefaultExpireDate(): bool { + return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes'; + } + + /** + * Is default expire date enforced + *` + * @return bool + */ + public function shareApiInternalDefaultExpireDateEnforced(): bool { + return $this->shareApiInternalDefaultExpireDate() && + $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes'; + } + + + /** + * Number of default expire days + * @return int + */ + public function shareApiInternalDefaultExpireDays(): int { + return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'); + } + + /** * Allow public upload on link shares * * @return bool diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index df5eda9121b..065bf2545e0 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -149,6 +149,13 @@ class JSConfigHelper { } $outgoingServer2serverShareEnabled = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; + $defaultInternalExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes'; + $defaultInternalExpireDate = $defaultInternalExpireDateEnforced = null; + if ($defaultInternalExpireDateEnabled) { + $defaultInternalExpireDate = (int) $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'); + $defaultInternalExpireDateEnforced = $this->config->getAppValue('core', 'shareapi_internal_enforce_expire_date', 'no') === 'yes'; + } + $countOfDataLocation = 0; $dataLocation = str_replace(\OC::$SERVERROOT .'/', '', $this->config->getSystemValue('datadirectory', ''), $countOfDataLocation); if($countOfDataLocation !== 1 || !$this->groupManager->isAdmin($uid)) { @@ -255,7 +262,10 @@ class JSConfigHelper { 'resharingAllowed' => \OC\Share\Share::isResharingAllowed(), 'remoteShareAllowed' => $outgoingServer2serverShareEnabled, 'federatedCloudShareDoc' => $this->urlGenerator->linkToDocs('user-sharing-federated'), - 'allowGroupSharing' => \OC::$server->getShareManager()->allowGroupSharing() + 'allowGroupSharing' => \OC::$server->getShareManager()->allowGroupSharing(), + 'defaultInternalExpireDateEnabled' => $defaultInternalExpireDateEnabled, + 'defaultInternalExpireDate' => $defaultInternalExpireDate, + 'defaultInternalExpireDateEnforced' => $defaultInternalExpireDateEnforced, ] ]), "_theme" => json_encode([ diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index 167690f3a6e..1fb69031f03 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -94,8 +94,9 @@ class URLGenerator implements IURLGenerator { public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string { $route = \OC::$server->getRouter()->generate('ocs.'.$routeName, $arguments, false); - if (strpos($route, '/index.php') === 0) { - $route = substr($route, 10); + $indexPhpPos = strpos($route, '/index.php/'); + if ($indexPhpPos !== false) { + $route = substr($route, $indexPhpPos + 10); } $route = substr($route, 7); diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index 23dbe8c2334..a4c35deb2b8 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -58,7 +58,9 @@ declare(strict_types=1); namespace OC\User; use OC\Cache\CappedMemoryCache; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; +use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\User\Backend\ABackend; use OCP\User\Backend\ICheckPasswordBackend; use OCP\User\Backend\ICountUsersBackend; @@ -68,7 +70,6 @@ use OCP\User\Backend\IGetHomeBackend; use OCP\User\Backend\IGetRealUIDBackend; use OCP\User\Backend\ISetDisplayNameBackend; use OCP\User\Backend\ISetPasswordBackend; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; /** @@ -86,7 +87,7 @@ class Database extends ABackend /** @var CappedMemoryCache */ private $cache; - /** @var EventDispatcherInterface */ + /** @var IEventDispatcher */ private $eventDispatcher; /** @var IDBConnection */ @@ -98,13 +99,13 @@ class Database extends ABackend /** * \OC\User\Database constructor. * - * @param EventDispatcherInterface $eventDispatcher + * @param IEventDispatcher $eventDispatcher * @param string $table */ public function __construct($eventDispatcher = null, $table = 'users') { $this->cache = new CappedMemoryCache(); $this->table = $table; - $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->getEventDispatcher(); + $this->eventDispatcher = $eventDispatcher ? $eventDispatcher : \OC::$server->query(IEventDispatcher::class); } /** @@ -130,8 +131,7 @@ class Database extends ABackend $this->fixDI(); if (!$this->userExists($uid)) { - $event = new GenericEvent($password); - $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event); + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); $qb = $this->dbConn->getQueryBuilder(); $qb->insert($this->table) @@ -199,8 +199,7 @@ class Database extends ABackend $this->fixDI(); if ($this->userExists($uid)) { - $event = new GenericEvent($password); - $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event); + $this->eventDispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password)); $hasher = \OC::$server->getHasher(); $hashedPassword = $hasher->hash($password); diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index da2d354b7ef..29cae3da79b 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -33,12 +33,15 @@ namespace OC\User; use OC\Hooks\PublicEmitter; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IGroup; use OCP\IUser; use OCP\IUserBackend; use OCP\IUserManager; use OCP\User\Backend\IGetRealUIDBackend; +use OCP\User\Events\CreateUserEvent; +use OCP\User\Events\UserCreatedEvent; use OCP\UserInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -72,17 +75,24 @@ class Manager extends PublicEmitter implements IUserManager { /** @var IConfig */ private $config; + /** @var EventDispatcherInterface */ private $dispatcher; - public function __construct(IConfig $config, EventDispatcherInterface $dispatcher) { + /** @var IEventDispatcher */ + private $eventDispatcher; + + public function __construct(IConfig $config, + EventDispatcherInterface $oldDispatcher, + IEventDispatcher $eventDispatcher) { $this->config = $config; - $this->dispatcher = $dispatcher; + $this->dispatcher = $oldDispatcher; $cachedUsers = &$this->cachedUsers; $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) { /** @var \OC\User\User $user */ unset($cachedUsers[$user->getUID()]); }); + $this->eventDispatcher = $eventDispatcher; } /** @@ -349,6 +359,7 @@ class Manager extends PublicEmitter implements IUserManager { } $this->emit('\OC\User', 'preCreateUser', [$uid, $password]); + $this->eventDispatcher->dispatchTyped(new CreateUserEvent($uid, $password)); $state = $backend->createUser($uid, $password); if($state === false) { throw new \InvalidArgumentException($l->t('Could not create user')); @@ -356,6 +367,7 @@ class Manager extends PublicEmitter implements IUserManager { $user = $this->getUserObject($uid, $backend); if ($user instanceof IUser) { $this->emit('\OC\User', 'postCreateUser', [$user, $password]); + $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); } return $user; } @@ -460,11 +472,11 @@ class Manager extends PublicEmitter implements IUserManager { ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled'))) ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR)); - + $result = $queryBuilder->execute(); $count = $result->fetchColumn(); $result->closeCursor(); - + if ($count !== false) { $count = (int)$count; } else { @@ -494,7 +506,7 @@ class Manager extends PublicEmitter implements IUserManager { $result = $queryBuilder->execute(); $count = $result->fetchColumn(); $result->closeCursor(); - + if ($count !== false) { $count = (int)$count; } else { |