diff options
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/App/AppStore/Fetcher/Fetcher.php | 3 | ||||
-rw-r--r-- | lib/private/AppFramework/DependencyInjection/DIContainer.php | 14 | ||||
-rw-r--r-- | lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php | 81 | ||||
-rw-r--r-- | lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php | 17 | ||||
-rw-r--r-- | lib/private/Files/ObjectStore/S3ConnectionTrait.php | 17 | ||||
-rw-r--r-- | lib/private/Files/ObjectStore/S3Signature.php | 204 | ||||
-rw-r--r-- | lib/private/Template/CSSResourceLocator.php | 4 | ||||
-rw-r--r-- | lib/private/Template/JSConfigHelper.php | 3 | ||||
-rw-r--r-- | lib/private/Template/SCSSCacher.php | 31 | ||||
-rw-r--r-- | lib/private/TemplateLayout.php | 9 | ||||
-rw-r--r-- | lib/private/User/Manager.php | 10 |
11 files changed, 354 insertions, 39 deletions
diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php index cd33d228c4b..8bf9ca15349 100644 --- a/lib/private/App/AppStore/Fetcher/Fetcher.php +++ b/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -36,6 +36,7 @@ use OCP\Files\NotFoundException; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\ILogger; +use OCP\Util; abstract class Fetcher { const INVALIDATE_AFTER_SECONDS = 300; @@ -170,7 +171,7 @@ abstract class Fetcher { $file->putContent(json_encode($responseJson)); return json_decode($file->getContent(), true)['data']; } catch (ConnectException $e) { - $this->logger->info('Could not connect to appstore', ['app' => 'appstoreFetcher']); + $this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => Util::INFO, 'message' => 'Could not connect to appstore']); return []; } catch (\Exception $e) { return []; diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 0b6291d46de..612728d1356 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -54,6 +54,7 @@ use OCP\AppFramework\Http\IOutput; use OCP\AppFramework\IApi; use OCP\AppFramework\IAppContainer; use OCP\AppFramework\QueryException; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\GlobalScale\IConfig; @@ -227,7 +228,6 @@ class DIContainer extends SimpleContainer implements IAppContainer { $server->getNavigationManager(), $server->getURLGenerator(), $server->getLogger(), - $server->getSession(), $c['AppName'], $app->isLoggedIn(), $app->isAdminUser(), @@ -236,7 +236,18 @@ class DIContainer extends SimpleContainer implements IAppContainer { $server->getContentSecurityPolicyNonceManager(), $server->getAppManager() ); + }); + + $this->registerService(OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware::class, function ($c) use ($app) { + /** @var \OC\Server $server */ + $server = $app->getServer(); + return new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware( + $c['ControllerMethodReflector'], + $server->getSession(), + $server->getUserSession(), + $server->query(ITimeFactory::class) + ); }); $this->registerService('BruteForceMiddleware', function($c) use ($app) { @@ -309,6 +320,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { $dispatcher->registerMiddleware($c['CORSMiddleware']); $dispatcher->registerMiddleware($c['OCSMiddleware']); $dispatcher->registerMiddleware($c['SecurityMiddleware']); + $dispatcher->registerMiddleware($c[OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware::class]); $dispatcher->registerMiddleware($c['TwoFactorMiddleware']); $dispatcher->registerMiddleware($c['BruteForceMiddleware']); $dispatcher->registerMiddleware($c['RateLimitingMiddleware']); diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php new file mode 100644 index 00000000000..463e7cd93c9 --- /dev/null +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -0,0 +1,81 @@ +<?php +/** + * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException; +use OC\AppFramework\Utility\ControllerMethodReflector; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Middleware; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\ISession; +use OCP\IUserSession; + +class PasswordConfirmationMiddleware extends Middleware { + /** @var ControllerMethodReflector */ + private $reflector; + /** @var ISession */ + private $session; + /** @var IUserSession */ + private $userSession; + /** @var ITimeFactory */ + private $timeFactory; + + /** + * PasswordConfirmationMiddleware constructor. + * + * @param ControllerMethodReflector $reflector + * @param ISession $session + * @param IUserSession $userSession + * @param ITimeFactory $timeFactory + */ + public function __construct(ControllerMethodReflector $reflector, + ISession $session, + IUserSession $userSession, + ITimeFactory $timeFactory) { + $this->reflector = $reflector; + $this->session = $session; + $this->userSession = $userSession; + $this->timeFactory = $timeFactory; + } + + /** + * @param Controller $controller + * @param string $methodName + * @throws NotConfirmedException + */ + public function beforeController($controller, $methodName) { + if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) { + $user = $this->userSession->getUser(); + $backendClassName = ''; + if ($user !== null) { + $backendClassName = $user->getBackendClassName(); + } + + $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 + throw new NotConfirmedException(); + } + } + } +} diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index ecd7b1bad5e..c147b5b2475 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -33,7 +33,6 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException; use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; -use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException; use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; @@ -50,7 +49,6 @@ use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\OCSController; use OCP\INavigationManager; -use OCP\ISession; use OCP\IURLGenerator; use OCP\IRequest; use OCP\ILogger; @@ -77,8 +75,6 @@ class SecurityMiddleware extends Middleware { private $urlGenerator; /** @var ILogger */ private $logger; - /** @var ISession */ - private $session; /** @var bool */ private $isLoggedIn; /** @var bool */ @@ -98,7 +94,6 @@ class SecurityMiddleware extends Middleware { * @param INavigationManager $navigationManager * @param IURLGenerator $urlGenerator * @param ILogger $logger - * @param ISession $session * @param string $appName * @param bool $isLoggedIn * @param bool $isAdminUser @@ -112,21 +107,20 @@ class SecurityMiddleware extends Middleware { INavigationManager $navigationManager, IURLGenerator $urlGenerator, ILogger $logger, - ISession $session, $appName, $isLoggedIn, $isAdminUser, ContentSecurityPolicyManager $contentSecurityPolicyManager, CsrfTokenManager $csrfTokenManager, ContentSecurityPolicyNonceManager $cspNonceManager, - IAppManager $appManager) { + IAppManager $appManager + ) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; $this->appName = $appName; $this->urlGenerator = $urlGenerator; $this->logger = $logger; - $this->session = $session; $this->isLoggedIn = $isLoggedIn; $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; @@ -163,13 +157,6 @@ class SecurityMiddleware extends Middleware { } } - if ($this->reflector->hasAnnotation('PasswordConfirmationRequired')) { - $lastConfirm = (int) $this->session->get('last-password-confirm'); - if ($lastConfirm < (time() - (30 * 60 + 15))) { // allow 15 seconds delay - throw new NotConfirmedException(); - } - } - // Check for strict cookie requirement if($this->reflector->hasAnnotation('StrictCookieRequired') || !$this->reflector->hasAnnotation('NoCSRFRequired')) { if(!$this->request->passesStrictCookieCheck()) { diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index 6556e7b169a..d4910834962 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -24,6 +24,7 @@ namespace OC\Files\ObjectStore; +use Aws\ClientResolver; use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; @@ -86,11 +87,15 @@ trait S3ConnectionTrait { ], 'endpoint' => $base_url, 'region' => $this->params['region'], - 'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false + 'use_path_style_endpoint' => isset($this->params['use_path_style']) ? $this->params['use_path_style'] : false, + 'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()) ]; if (isset($this->params['proxy'])) { $options['request.options'] = ['proxy' => $this->params['proxy']]; } + if (isset($this->params['legacy_auth']) && $this->params['legacy_auth']) { + $options['signature_version'] = 'v2'; + } $this->connection = new S3Client($options); if (!$this->connection->isBucketDnsCompatible($this->bucket)) { @@ -120,4 +125,14 @@ trait S3ConnectionTrait { sleep($this->timeout); } } + + public static function legacySignatureProvider($version, $service, $region) { + switch ($version) { + case 'v2': + case 's3': + return new S3Signature(); + default: + return null; + } + } } diff --git a/lib/private/Files/ObjectStore/S3Signature.php b/lib/private/Files/ObjectStore/S3Signature.php new file mode 100644 index 00000000000..d5bfbf4e53b --- /dev/null +++ b/lib/private/Files/ObjectStore/S3Signature.php @@ -0,0 +1,204 @@ +<?php + +namespace OC\Files\ObjectStore; + +use Aws\Credentials\CredentialsInterface; +use Aws\S3\S3Client; +use Aws\S3\S3UriParser; +use Aws\Signature\SignatureInterface; +use GuzzleHttp\Psr7; +use Psr\Http\Message\RequestInterface; + +/** + * Legacy Amazon S3 signature implementation + */ +class S3Signature implements SignatureInterface +{ + /** @var array Query string values that must be signed */ + private $signableQueryString = [ + 'acl', 'cors', 'delete', 'lifecycle', 'location', 'logging', + 'notification', 'partNumber', 'policy', 'requestPayment', + 'response-cache-control', 'response-content-disposition', + 'response-content-encoding', 'response-content-language', + 'response-content-type', 'response-expires', 'restore', 'tagging', + 'torrent', 'uploadId', 'uploads', 'versionId', 'versioning', + 'versions', 'website' + ]; + + /** @var array Sorted headers that must be signed */ + private $signableHeaders = ['Content-MD5', 'Content-Type']; + + /** @var \Aws\S3\S3UriParser S3 URI parser */ + private $parser; + + public function __construct() + { + $this->parser = new S3UriParser(); + // Ensure that the signable query string parameters are sorted + sort($this->signableQueryString); + } + + public function signRequest( + RequestInterface $request, + CredentialsInterface $credentials + ) { + $request = $this->prepareRequest($request, $credentials); + $stringToSign = $this->createCanonicalizedString($request); + $auth = 'AWS ' + . $credentials->getAccessKeyId() . ':' + . $this->signString($stringToSign, $credentials); + + return $request->withHeader('Authorization', $auth); + } + + public function presign( + RequestInterface $request, + CredentialsInterface $credentials, + $expires + ) { + $query = []; + // URL encoding already occurs in the URI template expansion. Undo that + // and encode using the same encoding as GET object, PUT object, etc. + $uri = $request->getUri(); + $path = S3Client::encodeKey(rawurldecode($uri->getPath())); + $request = $request->withUri($uri->withPath($path)); + + // Make sure to handle temporary credentials + if ($token = $credentials->getSecurityToken()) { + $request = $request->withHeader('X-Amz-Security-Token', $token); + $query['X-Amz-Security-Token'] = $token; + } + + if ($expires instanceof \DateTime) { + $expires = $expires->getTimestamp(); + } elseif (!is_numeric($expires)) { + $expires = strtotime($expires); + } + + // Set query params required for pre-signed URLs + $query['AWSAccessKeyId'] = $credentials->getAccessKeyId(); + $query['Expires'] = $expires; + $query['Signature'] = $this->signString( + $this->createCanonicalizedString($request, $expires), + $credentials + ); + + // Move X-Amz-* headers to the query string + foreach ($request->getHeaders() as $name => $header) { + $name = strtolower($name); + if (strpos($name, 'x-amz-') === 0) { + $query[$name] = implode(',', $header); + } + } + + $queryString = http_build_query($query, null, '&', PHP_QUERY_RFC3986); + + return $request->withUri($request->getUri()->withQuery($queryString)); + } + + /** + * @param RequestInterface $request + * @param CredentialsInterface $creds + * + * @return RequestInterface + */ + private function prepareRequest( + RequestInterface $request, + CredentialsInterface $creds + ) { + $modify = [ + 'remove_headers' => ['X-Amz-Date'], + 'set_headers' => ['Date' => gmdate(\DateTime::RFC2822)] + ]; + + // Add the security token header if one is being used by the credentials + if ($token = $creds->getSecurityToken()) { + $modify['set_headers']['X-Amz-Security-Token'] = $token; + } + + return Psr7\modify_request($request, $modify); + } + + private function signString($string, CredentialsInterface $credentials) + { + return base64_encode( + hash_hmac('sha1', $string, $credentials->getSecretKey(), true) + ); + } + + private function createCanonicalizedString( + RequestInterface $request, + $expires = null + ) { + $buffer = $request->getMethod() . "\n"; + + // Add the interesting headers + foreach ($this->signableHeaders as $header) { + $buffer .= $request->getHeaderLine($header) . "\n"; + } + + $date = $expires ?: $request->getHeaderLine('date'); + $buffer .= "{$date}\n" + . $this->createCanonicalizedAmzHeaders($request) + . $this->createCanonicalizedResource($request); + + return $buffer; + } + + private function createCanonicalizedAmzHeaders(RequestInterface $request) + { + $headers = []; + foreach ($request->getHeaders() as $name => $header) { + $name = strtolower($name); + if (strpos($name, 'x-amz-') === 0) { + $value = implode(',', $header); + if (strlen($value) > 0) { + $headers[$name] = $name . ':' . $value; + } + } + } + + if (!$headers) { + return ''; + } + + ksort($headers); + + return implode("\n", $headers) . "\n"; + } + + private function createCanonicalizedResource(RequestInterface $request) + { + $data = $this->parser->parse($request->getUri()); + $buffer = '/'; + + if ($data['bucket']) { + $buffer .= $data['bucket']; + if (!empty($data['key']) || !$data['path_style']) { + $buffer .= '/' . $data['key']; + } + } + + // Add sub resource parameters if present. + $query = $request->getUri()->getQuery(); + + if ($query) { + $params = Psr7\parse_query($query); + $first = true; + foreach ($this->signableQueryString as $key) { + if (array_key_exists($key, $params)) { + $value = $params[$key]; + $buffer .= $first ? '?' : '&'; + $first = false; + $buffer .= $key; + // Don't add values for empty sub-resources + if (strlen($value)) { + $buffer .= "={$value}"; + } + } + } + } + + return $buffer; + } +} diff --git a/lib/private/Template/CSSResourceLocator.php b/lib/private/Template/CSSResourceLocator.php index 3c30a9d3356..5ca05d1b953 100644 --- a/lib/private/Template/CSSResourceLocator.php +++ b/lib/private/Template/CSSResourceLocator.php @@ -108,7 +108,7 @@ class CSSResourceLocator extends ResourceLocator { if($this->scssCacher !== null) { if($this->scssCacher->process($root, $file, $app)) { - $this->append($root, $this->scssCacher->getCachedSCSS($app, $file), false, true, true); + $this->append($root, $this->scssCacher->getCachedSCSS($app, $file), \OC::$WEBROOT, true, true); return true; } else { $this->logger->warning('Failed to compile and/or save '.$root.'/'.$file, ['app' => 'core']); @@ -145,7 +145,7 @@ class CSSResourceLocator extends ResourceLocator { } } - $this->resources[] = array($webRoot? : '/', $webRoot, $file); + $this->resources[] = array($webRoot? : \OC::$WEBROOT, $webRoot, $file); } } } diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index 551fc3b9b0d..bdb747e1c9f 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -101,8 +101,10 @@ class JSConfigHelper { if ($this->currentUser !== null) { $uid = $this->currentUser->getUID(); + $userBackend = $this->currentUser->getBackendClassName(); } else { $uid = null; + $userBackend = ''; } // Get the config @@ -147,6 +149,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', "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/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index 8f6cb85a120..a4604425544 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -102,8 +102,7 @@ class SCSSCacher { $fileNameCSS = $this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)); $path = implode('/', $path); - - $webDir = substr($path, strlen($this->serverRoot)+1); + $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT); try { $folder = $this->appData->getFolder($app); @@ -188,7 +187,7 @@ class SCSSCacher { $scss = new Compiler(); $scss->setImportPaths([ $path, - \OC::$SERVERROOT . '/core/css/', + $this->serverRoot . '/core/css/', ]); // Continue after throw $scss->setIgnoreErrors(true); @@ -283,12 +282,7 @@ class SCSSCacher { */ private function rebaseUrls($css, $webDir) { $re = '/url\([\'"]([\.\w?=\/-]*)[\'"]\)/x'; - // OC\Route\Router:75 - if(($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { - $subst = 'url(\'../../'.$webDir.'/$1\')'; - } else { - $subst = 'url(\'../../../'.$webDir.'/$1\')'; - } + $subst = 'url(\''.$webDir.'/$1\')'; return preg_replace($re, $subst, $css); } @@ -315,4 +309,23 @@ class SCSSCacher { $frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true'); return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 8) . '-' . $cssFile; } + + /** + * Get WebDir root + * @param string $path the css file path + * @param string $appName the app name + * @param string $serverRoot the server root path + * @param string $webRoot the nextcloud installation root path + * @return string the webDir + */ + private function getWebDir($path, $appName, $serverRoot, $webRoot) { + // Detect if path is within server root AND if path is within an app path + if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) { + // Get the file path within the app directory + $appDirectoryPath = explode($appName, $path)[1]; + // Remove the webroot + return str_replace($webRoot, '', $appWebPath.$appDirectoryPath); + } + return $webRoot.substr($path, strlen($serverRoot)); + } } diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 264c10f5f1b..a8c4bbb0d16 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -108,6 +108,15 @@ class TemplateLayout extends \OC_Template { $this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0)); } + // check if app menu icons should be inverted + try { + /** @var \OCA\Theming\Util $util */ + $util = \OC::$server->query(\OCA\Theming\Util::class); + $this->assign('themingInvertMenu', $util->invertTextColor(\OC::$server->getThemingDefaults()->getColorPrimary())); + } catch (\OCP\AppFramework\QueryException $e) { + $this->assign('themingInvertMenu', false); + } + } else if ($renderAs == 'error') { parent::__construct('core', 'layout.guest', '', false); $this->assign('bodyid', 'body-login'); diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index c77e0ac89e1..5f2a010561c 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -152,16 +152,6 @@ class Manager extends PublicEmitter implements IUserManager { return $this->cachedUsers[$uid]; } - if (method_exists($backend, 'loginName2UserName')) { - $loginName = $backend->loginName2UserName($uid); - if ($loginName !== false) { - $uid = $loginName; - } - if (isset($this->cachedUsers[$uid])) { - return $this->cachedUsers[$uid]; - } - } - $user = new User($uid, $backend, $this, $this->config); if ($cacheUser) { $this->cachedUsers[$uid] = $user; |