Browse Source

set a storage availability delay on auth issues to avoid lock out

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
tags/v18.0.0beta1
Arthur Schiwon 4 years ago
parent
commit
43bc31bacb
No account linked to committer's email address

+ 13
- 5
apps/files_external/lib/Lib/Storage/SMB.php View File

use OCP\Files\Notify\IChange; use OCP\Files\Notify\IChange;
use OCP\Files\Notify\IRenameChange; use OCP\Files\Notify\IRenameChange;
use OCP\Files\Storage\INotifyStorage; use OCP\Files\Storage\INotifyStorage;
use OCP\Files\StorageAuthException;
use OCP\Files\StorageNotAvailableException; use OCP\Files\StorageNotAvailableException;
use OCP\ILogger; use OCP\ILogger;


/** /**
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo * @return \Icewind\SMB\IFileInfo
* @throws StorageNotAvailableException
* @throws StorageAuthException
*/ */
protected function getFileInfo($path) { protected function getFileInfo($path) {
try { try {
} }
return $this->statCache[$path]; return $this->statCache[$path];
} catch (ConnectException $e) { } catch (ConnectException $e) {
$this->logger->logException($e, ['message' => 'Error while getting file info']);
throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
$this->throwUnavailable($e);
} catch (ForbiddenException $e) { } catch (ForbiddenException $e) {
// with php-smbclient, this exceptions is thrown when the provided password is invalid. // with php-smbclient, this exceptions is thrown when the provided password is invalid.
// Possible is also ForbiddenException with a different error code, so we check it. // Possible is also ForbiddenException with a different error code, so we check it.
if($e->getCode() === 1) { if($e->getCode() === 1) {
$this->logger->logException($e, ['message' => 'Error while getting file info']);
throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
$this->throwUnavailable($e);
} }
throw $e; throw $e;
} }
} }


/**
* @param \Exception $e
* @throws StorageAuthException
*/
protected function throwUnavailable(\Exception $e) {
$this->logger->logException($e, ['message' => 'Error while getting file info']);
throw new StorageAuthException($e->getMessage(), $e);
}

/** /**
* @param string $path * @param string $path
* @return \Icewind\SMB\IFileInfo[] * @return \Icewind\SMB\IFileInfo[]

+ 11
- 0
config/config.sample.php View File

*/ */
'quota_include_external_storage' => false, 'quota_include_external_storage' => false,


/**
* When an external storage is unavailable for some reasons, it will be flagged
* as such for 10 minutes. When the trigger is a failed authentication attempt
* the delay is higher and can be controlled with this option. The motivation
* is to make account lock outs at Active Directories (and compatible) more
* unlikely.
*
* Defaults to ``1800`` (seconds)
*/
'external_storage.auth_availability_delay' => 1800,

/** /**
* Specifies how often the local filesystem (the Nextcloud data/ directory, and * Specifies how often the local filesystem (the Nextcloud data/ directory, and
* NFS mounts in data/) is checked for changes made outside Nextcloud. This * NFS mounts in data/) is checked for changes made outside Nextcloud. This

+ 3
- 2
lib/private/Files/Cache/Storage.php View File



/** /**
* @param bool $isAvailable * @param bool $isAvailable
* @param int $delay amount of seconds to delay reconsidering that storage further
*/ */
public function setAvailability($isAvailable) {
public function setAvailability($isAvailable, int $delay = 0) {
$sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?'; $sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?';
$available = $isAvailable ? 1 : 0; $available = $isAvailable ? 1 : 0;
\OC_DB::executeAudited($sql, array($available, time(), $this->storageId));
\OC_DB::executeAudited($sql, [$available, time() + $delay, $this->storageId]);
} }


/** /**

+ 99
- 107
lib/private/Files/Storage/Wrapper/Availability.php View File

namespace OC\Files\Storage\Wrapper; namespace OC\Files\Storage\Wrapper;


use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IStorage;
use OCP\Files\StorageAuthException;
use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;


/** /**
* Availability checker for storages * Availability checker for storages
class Availability extends Wrapper { class Availability extends Wrapper {
const RECHECK_TTL_SEC = 600; // 10 minutes const RECHECK_TTL_SEC = 600; // 10 minutes


/** @var IConfig */
protected $config;

public function __construct($parameters) {
$this->config = $parameters['config'] ?? \OC::$server->getConfig();
parent::__construct($parameters);
}

public static function shouldRecheck($availability) { public static function shouldRecheck($availability) {
if (!$availability['available']) { if (!$availability['available']) {
// trigger a recheck if TTL reached // trigger a recheck if TTL reached
} }


/** /**
* @throws \OCP\Files\StorageNotAvailableException
* @throws StorageNotAvailableException
*/ */
private function checkAvailability() { private function checkAvailability() {
if (!$this->isAvailable()) { if (!$this->isAvailable()) {
throw new \OCP\Files\StorageNotAvailableException();
throw new StorageNotAvailableException();
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::mkdir($path); return parent::mkdir($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::rmdir($path); return parent::rmdir($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::opendir($path); return parent::opendir($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::is_dir($path); return parent::is_dir($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::is_file($path); return parent::is_file($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::stat($path); return parent::stat($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::filetype($path); return parent::filetype($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::filesize($path); return parent::filesize($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::isCreatable($path); return parent::isCreatable($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::isReadable($path); return parent::isReadable($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::isUpdatable($path); return parent::isUpdatable($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::isDeletable($path); return parent::isDeletable($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::isSharable($path); return parent::isSharable($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getPermissions($path); return parent::getPermissions($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::file_exists($path); return parent::file_exists($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::filemtime($path); return parent::filemtime($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::file_get_contents($path); return parent::file_get_contents($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::file_put_contents($path, $data); return parent::file_put_contents($path, $data);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::unlink($path); return parent::unlink($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::rename($path1, $path2); return parent::rename($path1, $path2);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::copy($path1, $path2); return parent::copy($path1, $path2);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::fopen($path, $mode); return parent::fopen($path, $mode);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getMimeType($path); return parent::getMimeType($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::hash($type, $path, $raw); return parent::hash($type, $path, $raw);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::free_space($path); return parent::free_space($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::search($query); return parent::search($query);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::touch($path, $mtime); return parent::touch($path, $mtime);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getLocalFile($path); return parent::getLocalFile($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::hasUpdated($path, $time); return parent::hasUpdated($path, $time);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


public function getOwner($path) { public function getOwner($path) {
try { try {
return parent::getOwner($path); return parent::getOwner($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getETag($path); return parent::getETag($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getDirectDownload($path); return parent::getDirectDownload($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }


$this->checkAvailability(); $this->checkAvailability();
try { try {
return parent::getMetaData($path); return parent::getMetaData($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
$this->setAvailability(false);
throw $e;
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
} }
} }

/**
* @throws StorageNotAvailableException
*/
protected function setUnavailable(StorageNotAvailableException $e) {
$delay = self::RECHECK_TTL_SEC;
if($e instanceof StorageAuthException) {
$delay = max(
// 30min
$this->config->getSystemValueInt('external_storage.auth_availability_delay', 1800),
self::RECHECK_TTL_SEC
);
}
$this->getStorageCache()->setAvailability(false, $delay);
throw $e;
}
} }

Loading…
Cancel
Save