diff options
Diffstat (limited to 'lib/private')
21 files changed, 512 insertions, 40 deletions
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 0485d178b49..2c745973ed2 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -320,14 +320,18 @@ class Request implements \ArrayAccess, \Countable, IRequest { // There's a few headers that seem to end up in the top-level // server array. - switch($name) { + switch ($name) { case 'CONTENT_TYPE' : case 'CONTENT_LENGTH' : if (isset($this->server[$name])) { return $this->server[$name]; } break; - + case 'REMOTE_ADDR' : + if (isset($this->server[$name])) { + return $this->server[$name]; + } + break; } return ''; @@ -595,6 +599,44 @@ class Request implements \ArrayAccess, \Countable, IRequest { } /** + * Checks if given $remoteAddress matches given $trustedProxy. + * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if + * $remoteAddress is an IPv4 address within that IP range. + * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result + * will be returned. + * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise + */ + protected function matchesTrustedProxy($trustedProxy, $remoteAddress) { + $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/'; + + if (preg_match($cidrre, $trustedProxy, $match)) { + $net = $match[1]; + $shiftbits = min(32, max(0, 32 - intval($match[2]))); + $netnum = ip2long($net) >> $shiftbits; + $ipnum = ip2long($remoteAddress) >> $shiftbits; + + return $ipnum === $netnum; + } + + return $trustedProxy === $remoteAddress; + } + + /** + * Checks if given $remoteAddress matches any entry in the given array $trustedProxies. + * For details regarding what "match" means, refer to `matchesTrustedProxy`. + * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise + */ + protected function isTrustedProxy($trustedProxies, $remoteAddress) { + foreach ($trustedProxies as $tp) { + if ($this->matchesTrustedProxy($tp, $remoteAddress)) { + return true; + } + } + + return false; + } + + /** * Returns the remote address, if the connection came from a trusted proxy * and `forwarded_for_headers` has been configured then the IP address * specified in this header will be returned instead. @@ -605,7 +647,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : ''; $trustedProxies = $this->config->getSystemValue('trusted_proxies', []); - if(\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies)) { + if(\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) { $forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [ 'HTTP_X_FORWARDED_FOR' // only have one default, so we cannot ship an insecure product out of the box diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index 463e7cd93c9..7c1c4595e9a 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -39,6 +39,8 @@ class PasswordConfirmationMiddleware extends Middleware { private $userSession; /** @var ITimeFactory */ private $timeFactory; + /** @var array */ + private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; /** * PasswordConfirmationMiddleware constructor. @@ -73,7 +75,7 @@ class PasswordConfirmationMiddleware extends Middleware { $lastConfirm = (int) $this->session->get('last-password-confirm'); // we can't check the password against a SAML backend, so skip password confirmation in this case - if ($backendClassName !== 'user_saml' && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay + if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay throw new NotConfirmedException(); } } diff --git a/lib/private/Authentication/Exceptions/ExpiredTokenException.php b/lib/private/Authentication/Exceptions/ExpiredTokenException.php index a45ca5b6955..d5b2e2cbca7 100644 --- a/lib/private/Authentication/Exceptions/ExpiredTokenException.php +++ b/lib/private/Authentication/Exceptions/ExpiredTokenException.php @@ -21,9 +21,9 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ -namespace OC\Authentication\Token; +namespace OC\Authentication\Exceptions; -use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IToken; class ExpiredTokenException extends InvalidTokenException { /** @var IToken */ diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index a27a875a27f..98609a3f14b 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -29,6 +29,7 @@ declare(strict_types=1); namespace OC\Authentication\Token; use Exception; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OCP\AppFramework\Db\DoesNotExistException; diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index 7ee76b7b384..21223cecdf7 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OC\Authentication\Token; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 98a48f41523..3174599221d 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 OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 33c0b1d59eb..9f596ac4568 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace OC\Authentication\Token; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OCP\AppFramework\Db\DoesNotExistException; diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index 101d6845ec3..6faa5d5d125 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -84,11 +84,17 @@ class MailPlugin implements ISearchPlugin { foreach ($addressBookContacts as $contact) { if (isset($contact['EMAIL'])) { $emailAddresses = $contact['EMAIL']; - if (!is_array($emailAddresses)) { + if (\is_string($emailAddresses)) { $emailAddresses = [$emailAddresses]; } - foreach ($emailAddresses as $emailAddress) { + foreach ($emailAddresses as $type => $emailAddress) { $displayName = $emailAddress; + $emailAddressType = null; + if (\is_array($emailAddress)) { + $emailAddressData = $emailAddress; + $emailAddress = $emailAddressData['value']; + $emailAddressType = $emailAddressData['type']; + } if (isset($contact['FN'])) { $displayName = $contact['FN'] . ' (' . $emailAddress . ')'; } @@ -121,6 +127,8 @@ class MailPlugin implements ISearchPlugin { if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $singleResult = [[ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -142,6 +150,8 @@ class MailPlugin implements ISearchPlugin { if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $userResults['wide'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -160,6 +170,9 @@ class MailPlugin implements ISearchPlugin { } $result['exact'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $emailAddress, @@ -168,6 +181,9 @@ class MailPlugin implements ISearchPlugin { } else { $result['wide'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $emailAddress, @@ -194,6 +210,7 @@ class MailPlugin implements ISearchPlugin { if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) { $result['exact'][] = [ 'label' => $search, + 'uuid' => $search, 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $search, diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index e0f5298f83b..d877346b155 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -30,6 +30,8 @@ use OCP\Collaboration\Collaborators\SearchResultType; use OCP\Contacts\IManager; use OCP\Federation\ICloudIdManager; use OCP\IConfig; +use OCP\IUserManager; +use OCP\IUserSession; use OCP\Share; class RemotePlugin implements ISearchPlugin { @@ -41,12 +43,20 @@ class RemotePlugin implements ISearchPlugin { private $cloudIdManager; /** @var IConfig */ private $config; + /** @var IUserManager */ + private $userManager; + /** @var string */ + private $userId = ''; - public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) { $this->contactsManager = $contactsManager; $this->cloudIdManager = $cloudIdManager; $this->config = $config; - + $this->userManager = $userManager; + $user = $userSession->getUser(); + if ($user !== null) { + $this->userId = $user->getUID(); + } $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; } @@ -63,23 +73,47 @@ class RemotePlugin implements ISearchPlugin { } if (isset($contact['CLOUD'])) { $cloudIds = $contact['CLOUD']; - if (!is_array($cloudIds)) { + if (is_string($cloudIds)) { $cloudIds = [$cloudIds]; } $lowerSearch = strtolower($search); foreach ($cloudIds as $cloudId) { + $cloudIdType = ''; + if (\is_array($cloudId)) { + $cloudIdData = $cloudId; + $cloudId = $cloudIdData['value']; + $cloudIdType = $cloudIdData['type']; + } try { - list(, $serverUrl) = $this->splitUserRemote($cloudId); + list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId); } catch (\InvalidArgumentException $e) { continue; } + $localUser = $this->userManager->get($remoteUser); + /** + * Add local share if remote cloud id matches a local user ones + */ + if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId() ) { + $result['wide'][] = [ + 'label' => $contact['FN'], + 'uuid' => $contact['UID'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $remoteUser + ] + ]; + } + if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { if (strtolower($cloudId) === $lowerSearch) { $searchResult->markExactIdMatch($resultType); } $result['exact'][] = [ 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, 'value' => [ 'shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => $cloudId, @@ -89,6 +123,9 @@ class RemotePlugin implements ISearchPlugin { } else { $result['wide'][] = [ 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, 'value' => [ 'shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => $cloudId, @@ -106,14 +143,24 @@ class RemotePlugin implements ISearchPlugin { $result['wide'] = array_slice($result['wide'], $offset, $limit); } + /** + * Add generic share with remote item for valid cloud ids that are not users of the local instance + */ if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { - $result['exact'][] = [ - 'label' => $search, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $search, - ], - ]; + try { + list($remoteUser, $serverUrl) = $this->splitUserRemote($search); + $localUser = $this->userManager->get($remoteUser); + if ($localUser === null || $search !== $localUser->getCloudId()) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $search, + ], + ]; + } + } catch (\InvalidArgumentException $e) { + } } $searchResult->addResultSet($resultType, $result['wide'], $result['exact']); diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 53f73f0f95d..575af56ceb5 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -390,4 +390,8 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { public function getChecksum() { return $this->data['checksum']; } + + public function getExtension(): string { + return pathinfo($this->getName(), PATHINFO_EXTENSION); + } } diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index 7c411620ca0..a3eabbcc446 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -142,4 +142,8 @@ class File extends Node implements \OCP\Files\File { public function getChecksum() { return $this->getFileInfo()->getChecksum(); } + + public function getExtension(): string { + return $this->getFileInfo()->getExtension(); + } } diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index faa57ecb0b4..389a1a9f0ff 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -344,6 +344,10 @@ class LazyRoot implements IRootFolder { return $this->__call(__FUNCTION__, func_get_args()); } + public function getExtension(): string { + return $this->__call(__FUNCTION__, func_get_args()); + } + /** * @inheritDoc */ diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index d2232624b9b..590f1080617 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -354,6 +354,10 @@ class Node implements \OCP\Files\Node { public function getChecksum() { } + public function getExtension(): string { + return $this->getFileInfo()->getExtension(); + } + /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @throws \OCP\Lock\LockedException diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php index 860fce57277..4cbe9189593 100644 --- a/lib/private/Files/Storage/Wrapper/Quota.php +++ b/lib/private/Files/Storage/Wrapper/Quota.php @@ -83,7 +83,7 @@ class Quota extends Wrapper { * @return int */ public function free_space($path) { - if ($this->quota < 0 || strpos($path, 'cache') === 0) { + if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) { return $this->storage->free_space($path); } else { $used = $this->getSize($this->sizeRoot); diff --git a/lib/private/FullTextSearch/FullTextSearchManager.php b/lib/private/FullTextSearch/FullTextSearchManager.php new file mode 100644 index 00000000000..6529ef2506a --- /dev/null +++ b/lib/private/FullTextSearch/FullTextSearchManager.php @@ -0,0 +1,227 @@ +<?php +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2018, Maxence Lange <maxence@artificial-owl.com> + * @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\FullTextSearch; + + +use OCP\FullTextSearch\Exceptions\FullTextSearchAppNotAvailableException; +use OCP\FullTextSearch\IFullTextSearchManager; +use OCP\FullTextSearch\Model\IIndex; +use OCP\FullTextSearch\Model\ISearchResult; +use OCP\FullTextSearch\Service\IIndexService; +use OCP\FullTextSearch\Service\IProviderService; +use OCP\FullTextSearch\Service\ISearchService; + + +/** + * Class FullTextSearchManager + * + * @package OC\FullTextSearch + */ +class FullTextSearchManager implements IFullTextSearchManager { + + + /** @var IProviderService */ + private $providerService; + + /** @var IIndexService */ + private $indexService; + + /** @var ISearchService */ + private $searchService; + + + /** + * @since 15.0.0 + * + * @param IProviderService $providerService + */ + public function registerProviderService(IProviderService $providerService) { + $this->providerService = $providerService; + } + + /** + * @since 15.0.0 + * + * @param IIndexService $indexService + */ + public function registerIndexService(IIndexService $indexService) { + $this->indexService = $indexService; + } + + /** + * @since 15.0.0 + * + * @param ISearchService $searchService + */ + public function registerSearchService(ISearchService $searchService) { + $this->searchService = $searchService; + } + + + /** + * @return IProviderService + * @throws FullTextSearchAppNotAvailableException + */ + private function getProviderService(): IProviderService { + if ($this->providerService === null) { + throw new FullTextSearchAppNotAvailableException('No IProviderService registered'); + } + + return $this->providerService; + } + + + /** + * @return IIndexService + * @throws FullTextSearchAppNotAvailableException + */ + private function getIndexService(): IIndexService { + if ($this->indexService === null) { + throw new FullTextSearchAppNotAvailableException('No IIndexService registered'); + } + + return $this->indexService; + } + + + /** + * @return ISearchService + * @throws FullTextSearchAppNotAvailableException + */ + private function getSearchService(): ISearchService { + if ($this->searchService === null) { + throw new FullTextSearchAppNotAvailableException('No ISearchService registered'); + } + + return $this->searchService; + } + + + /** + * @throws FullTextSearchAppNotAvailableException + */ + public function addJavascriptAPI() { + $this->getProviderService()->addJavascriptAPI(); + } + + + /** + * @param string $providerId + * + * @return bool + * @throws FullTextSearchAppNotAvailableException + */ + public function isProviderIndexed(string $providerId): bool { + return $this->getProviderService()->isProviderIndexed($providerId); + } + + + /** + * @param string $providerId + * @param string $documentId + * @return IIndex + * @throws FullTextSearchAppNotAvailableException + */ + public function getIndex(string $providerId, string $documentId): IIndex { + return $this->getIndexService()->getIndex($providerId, $documentId); + } + + /** + * @param string $providerId + * @param string $documentId + * @param string $userId + * @param int $status + * + * @see IIndex for available value for $status. + * + * @return IIndex + * @throws FullTextSearchAppNotAvailableException + */ + public function createIndex(string $providerId, string $documentId, string $userId, int $status = 0): IIndex { + return $this->getIndexService()->getIndex($providerId, $documentId); + } + + + /** + * @param string $providerId + * @param string $documentId + * @param int $status + * @param bool $reset + * + * @see IIndex for available value for $status. + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexStatus(string $providerId, string $documentId, int $status, bool $reset = false) { + $this->getIndexService()->updateIndexStatus($providerId, $documentId, $status, $reset); + } + + /** + * @param string $providerId + * @param array $documentIds + * @param int $status + * @param bool $reset + * + * @see IIndex for available value for $status. + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexesStatus(string $providerId, array $documentIds, int $status, bool $reset = false) { + $this->getIndexService()->updateIndexStatus($providerId, $documentIds, $status, $reset); + } + + + /** + * @param IIndex[] $indexes + * + * @throws FullTextSearchAppNotAvailableException + */ + public function updateIndexes(array $indexes) { + $this->getIndexService()->updateIndexes($indexes); + } + + + /** + * @param array $request + * @param string $userId + * + * @return ISearchResult[] + * @throws FullTextSearchAppNotAvailableException + */ + public function search(array $request, string $userId = ''): array { + $searchRequest = $this->getSearchService()->generateSearchRequest($request); + + return $this->getSearchService()->search($userId, $searchRequest); + } + + +} + diff --git a/lib/private/Server.php b/lib/private/Server.php index 32d7705919c..ceecd059df2 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -82,6 +82,7 @@ use OC\Files\Node\LazyRoot; use OC\Files\Node\Root; use OC\Files\Storage\StorageFactory; use OC\Files\View; +use OC\FullTextSearch\FullTextSearchManager; use OC\Http\Client\ClientService; use OC\IntegrityCheck\Checker; use OC\IntegrityCheck\Helpers\AppLocator; @@ -138,6 +139,7 @@ use OCP\Federation\ICloudIdManager; use OCP\Authentication\LoginCredentials\IStore; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorageFactory; +use OCP\FullTextSearch\IFullTextSearchManager; use OCP\GlobalScale\IConfig; use OCP\ICacheFactory; use OCP\IDBConnection; @@ -758,7 +760,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('TrustedDomainHelper', function ($c) { return new TrustedDomainHelper($this->getConfig()); }); - $this->registerService('Throttler', function (Server $c) { + $this->registerService(Throttler::class, function (Server $c) { return new Throttler( $c->getDatabaseConnection(), new TimeFactory(), @@ -766,6 +768,7 @@ class Server extends ServerContainer implements IServerContainer { $c->getConfig() ); }); + $this->registerAlias('Throttler', Throttler::class); $this->registerService('IntegrityCodeChecker', function (Server $c) { // IConfig and IAppManager requires a working database. This code // might however be called when ownCloud is not yet setup. @@ -1183,6 +1186,7 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(IDashboardManager::class, Dashboard\DashboardManager::class); + $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class); $this->connectDispatcher(); } diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 9c5d78a5958..3dcca0facbc 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -296,6 +296,7 @@ class DefaultShareProvider implements IShareProvider { ->set('token', $qb->createNamedParameter($share->getToken())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT) ->execute(); } @@ -953,6 +954,7 @@ class DefaultShareProvider implements IShareProvider { } $share->setProviderId($this->identifier()); + $share->setHideDownload((int)$data['hide_download'] === 1); return $share; } diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 71c0453d9e5..e218360f87b 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -30,6 +30,7 @@ use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IUserManager; use OCP\Share\Exceptions\IllegalIDChangeException; +use OCP\Share\IShare; class Share implements \OCP\Share\IShare { @@ -85,6 +86,9 @@ class Share implements \OCP\Share\IShare { /** @var ICacheEntry|null */ private $nodeCacheEntry; + /** @var bool */ + private $hideDownload = false; + public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { $this->rootFolder = $rootFolder; $this->userManager = $userManager; @@ -514,4 +518,13 @@ class Share implements \OCP\Share\IShare { public function getNodeCacheEntry() { return $this->nodeCacheEntry; } + + public function setHideDownload(bool $hide): IShare { + $this->hideDownload = $hide; + return $this; + } + + public function getHideDownload(): bool { + return $this->hideDownload; + } } diff --git a/lib/private/Template/IconsCacher.php b/lib/private/Template/IconsCacher.php index f3660442dc5..c91bf13ad53 100644 --- a/lib/private/Template/IconsCacher.php +++ b/lib/private/Template/IconsCacher.php @@ -47,15 +47,18 @@ class IconsCacher { protected $urlGenerator; /** @var string */ - private $iconVarRE = '/--(icon-[a-zA-Z0-9-]+):\s?url\(["\']([a-zA-Z0-9-_\~\/\.\?\=]+)[^;]+;/m'; + private $iconVarRE = '/--(icon-[a-zA-Z0-9-]+):\s?url\(["\']?([a-zA-Z0-9-_\~\/\.\?\&\=\:\;\+\,]+)[^;]+;/m'; /** @var string */ private $fileName = 'icons-vars.css'; + private $iconList = 'icons-list.template'; + /** * @param ILogger $logger * @param Factory $appDataFactory * @param IURLGenerator $urlGenerator + * @throws \OCP\Files\NotPermittedException */ public function __construct(ILogger $logger, Factory $appDataFactory, @@ -71,7 +74,7 @@ class IconsCacher { } } - private function getIconsFromCss(string $css): array{ + private function getIconsFromCss(string $css): array { preg_match_all($this->iconVarRE, $css, $matches, PREG_SET_ORDER); $icons = []; foreach ($matches as $icon) { @@ -80,42 +83,112 @@ class IconsCacher { return $icons; } + /** - * Parse and cache css - * * @param string $css + * @return string + * @throws NotFoundException + * @throws \OCP\Files\NotPermittedException */ - public function setIconsCss(string $css) { + public function setIconsCss(string $css): string { - $cachedFile = $this->getCachedCSS(); + $cachedFile = $this->getCachedList(); if (!$cachedFile) { $currentData = ''; + $cachedFile = $this->folder->newFile($this->iconList); } else { $currentData = $cachedFile->getContent(); } - // remove :root - $currentData = str_replace([':root {', '}'], '', $currentData); + $cachedVarsCssFile = $this->getCachedCSS(); + if (!$cachedVarsCssFile) { + $cachedVarsCssFile = $this->folder->newFile($this->fileName); + } $icons = $this->getIconsFromCss($currentData . $css); $data = ''; + $list = ''; foreach ($icons as $icon => $url) { - $data .= "--$icon: url('$url');"; + $list .= "--$icon: url('$url');"; + list($location,$color) = $this->parseUrl($url); + $svg = false; + if ($location !== '') { + $svg = file_get_contents($location); + } + if ($svg === false) { + $this->logger->debug('Failed to get icon file ' . $location); + $data .= "--$icon: url('$url');"; + continue; + } + $encode = base64_encode($this->colorizeSvg($svg, $color)); + $data .= '--' . $icon . ': url(data:image/svg+xml;base64,' . $encode . ');'; + } + + if (\strlen($data) > 0 && \strlen($list) > 0) { + $data = ":root {\n$data\n}"; + $cachedVarsCssFile->putContent($data); + $list = ":root {\n$list\n}"; + $cachedFile->putContent($list); } - if (strlen($data) > 0) { - if (!$cachedFile) { - $cachedFile = $this->folder->newFile($this->fileName); + return preg_replace($this->iconVarRE, '', $css); + } + + /** + * @param $url + * @return array + */ + private function parseUrl($url): array { + $location = ''; + $color = ''; + $base = $this->getRoutePrefix() . '/svg/'; + $cleanUrl = \substr($url, \strlen($base)); + if (\strpos($url, $base . 'core') === 0) { + $cleanUrl = \substr($cleanUrl, \strlen('core')); + if (\preg_match('/\/([a-zA-Z0-9-_\~\/\.\=\:\;\+\,]+)\?color=([0-9a-fA-F]{3,6})/', $cleanUrl, $matches)) { + list(,$cleanUrl,$color) = $matches; + $location = \OC::$SERVERROOT . '/core/img/' . $cleanUrl . '.svg'; + } + } elseif (\strpos($url, $base) === 0) { + if(\preg_match('/([A-z0-9\_\-]+)\/([a-zA-Z0-9-_\~\/\.\=\:\;\+\,]+)\?color=([0-9a-fA-F]{3,6})/', $cleanUrl, $matches)) { + list(,$app,$cleanUrl, $color) = $matches; + $location = \OC_App::getAppPath($app) . '/img/' . $cleanUrl . '.svg'; + if ($app === 'settings') { + $location = \OC::$SERVERROOT . '/settings/img/' . $cleanUrl . '.svg'; + } } - $data = ":root { - $data - }"; - $cachedFile->putContent($data); } + return [ + $location, + $color + ]; + } - return preg_replace($this->iconVarRE, '', $css); + /** + * @param $svg + * @param $color + * @return string + */ + public function colorizeSvg($svg, $color): string { + // add fill (fill is not present on black elements) + $fillRe = '/<((circle|rect|path)((?!fill)[a-z0-9 =".\-#():;])+)\/>/mi'; + $svg = preg_replace($fillRe, '<$1 fill="#' . $color . '"/>', $svg); + + // replace any fill or stroke colors + $svg = preg_replace('/stroke="#([a-z0-9]{3,6})"/mi', 'stroke="#' . $color . '"', $svg); + $svg = preg_replace('/fill="#([a-z0-9]{3,6})"/mi', 'fill="#' . $color . '"', $svg); + return $svg; + } + + private function getRoutePrefix() { + $frontControllerActive = (\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); + $prefix = \OC::$WEBROOT . '/index.php'; + if ($frontControllerActive) { + $prefix = \OC::$WEBROOT; + } + return $prefix; } /** @@ -130,6 +203,18 @@ class IconsCacher { } } + /** + * Get icon-vars list template + * @return ISimpleFile|boolean + */ + public function getCachedList() { + try { + return $this->folder->getFile($this->iconList); + } catch (NotFoundException $e) { + return false; + } + } + public function injectCss() { // Only inject once foreach (\OC_Util::$headers as $header) { diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index b691a8a64cb..ad9ff0b6757 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -70,6 +70,9 @@ class JSConfigHelper { /** @var CapabilitiesManager */ private $capabilitiesManager; + /** @var array user back-ends excluded from password verification */ + private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; + /** * @param IL10N $l * @param Defaults $defaults @@ -158,7 +161,7 @@ class JSConfigHelper { $array = [ "oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false', "oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false', - "backendAllowsPasswordConfirmation" => $userBackend === 'user_saml'? 'false' : 'true', + "backendAllowsPasswordConfirmation" => !isset($this->excludedUserBackEnds[$userBackend]) ? 'true' : 'false', "oc_dataURL" => is_string($dataLocation) ? "\"".$dataLocation."\"" : 'false', "oc_webroot" => "\"".\OC::$WEBROOT."\"", "oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index a9c638dca93..674f38e2401 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -38,6 +38,7 @@ namespace OC\User; use OC; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Exceptions\PasswordLoginForbiddenException; @@ -401,7 +402,13 @@ class Session implements IUserSession, Emitter { $this->manager->emit('\OC\User', 'preLogin', array($user, $password)); } - $isTokenPassword = $this->isTokenPassword($password); + try { + $isTokenPassword = $this->isTokenPassword($password); + } catch (ExpiredTokenException $e) { + // Just return on an expired token no need to check further or record a failed login + return false; + } + if (!$isTokenPassword && $this->isTokenAuthEnforced()) { throw new PasswordLoginForbiddenException(); } @@ -474,11 +481,14 @@ class Session implements IUserSession, Emitter { * * @param string $password * @return boolean + * @throws ExpiredTokenException */ public function isTokenPassword($password) { try { $this->tokenProvider->getToken($password); return true; + } catch (ExpiredTokenException $e) { + throw $e; } catch (InvalidTokenException $ex) { return false; } |