diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php
index 351b87c4c8..f33012b5f0 100644
--- a/apps/files_external/appinfo/app.php
+++ b/apps/files_external/appinfo/app.php
@@ -40,7 +40,6 @@ OC::$CLASSPATH['OC\Files\Storage\SMB'] = 'files_external/lib/smb.php';
OC::$CLASSPATH['OC\Files\Storage\AmazonS3'] = 'files_external/lib/amazons3.php';
OC::$CLASSPATH['OC\Files\Storage\Dropbox'] = 'files_external/lib/dropbox.php';
OC::$CLASSPATH['OC\Files\Storage\SFTP'] = 'files_external/lib/sftp.php';
-OC::$CLASSPATH['OC\Files\Storage\SFTP_Key'] = 'files_external/lib/sftp_key.php';
OC::$CLASSPATH['OC_Mount_Config'] = 'files_external/lib/config.php';
OC::$CLASSPATH['OCA\Files\External\Api'] = 'files_external/lib/api.php';
@@ -68,17 +67,5 @@ if (OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes') == '
// connecting hooks
OCP\Util::connectHook('OC_Filesystem', 'post_initMountPoints', '\OC_Mount_Config', 'initMountPointsHook');
-OC_Mount_Config::registerBackend('\OC\Files\Storage\SFTP_Key', [
- 'backend' => (string)$l->t('SFTP with secret key login'),
- 'priority' => 100,
- 'configuration' => array(
- 'host' => (string)$l->t('Host'),
- 'user' => (string)$l->t('Username'),
- 'public_key' => (string)$l->t('Public key'),
- 'private_key' => '#private_key',
- 'root' => '&'.$l->t('Remote subfolder')),
- 'custom' => 'sftp_key',
- ]
-);
$mountProvider = $appContainer->query('OCA\Files_External\Config\ConfigAdapter');
\OC::$server->getMountProviderCollection()->registerProvider($mountProvider);
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php
index e5275c29b3..ed236af0ec 100644
--- a/apps/files_external/appinfo/application.php
+++ b/apps/files_external/appinfo/application.php
@@ -69,6 +69,7 @@ class Application extends App {
$container->query('OCA\Files_External\Lib\Backend\Dropbox'),
$container->query('OCA\Files_External\Lib\Backend\Google'),
$container->query('OCA\Files_External\Lib\Backend\Swift'),
+ $container->query('OCA\Files_External\Lib\Backend\SFTP_Key'),
]);
if (!\OC_Util::runningOnWindows()) {
@@ -103,6 +104,9 @@ class Application extends App {
// AuthMechanism::SCHEME_OAUTH2 mechanisms
$container->query('OCA\Files_External\Lib\Auth\OAuth2\OAuth2'),
+ // AuthMechanism::SCHEME_PUBLICKEY mechanisms
+ $container->query('OCA\Files_External\Lib\Auth\PublicKey\RSA'),
+
// AuthMechanism::SCHEME_OPENSTACK mechanisms
$container->query('OCA\Files_External\Lib\Auth\OpenStack\OpenStack'),
$container->query('OCA\Files_External\Lib\Auth\OpenStack\Rackspace'),
diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php
index 5d7018c347..a371273e74 100644
--- a/apps/files_external/appinfo/routes.php
+++ b/apps/files_external/appinfo/routes.php
@@ -38,7 +38,7 @@ namespace OCA\Files_External\AppInfo;
'routes' => array(
array(
'name' => 'Ajax#getSshKeys',
- 'url' => '/ajax/sftp_key.php',
+ 'url' => '/ajax/public_key.php',
'verb' => 'POST',
'requirements' => array()
)
diff --git a/apps/files_external/js/public_key.js b/apps/files_external/js/public_key.js
new file mode 100644
index 0000000000..a854606745
--- /dev/null
+++ b/apps/files_external/js/public_key.js
@@ -0,0 +1,46 @@
+$(document).ready(function() {
+
+ OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme) {
+ if (scheme === 'publickey') {
+ var config = $tr.find('.configuration');
+ if ($(config).find('[name="public_key_generate"]').length === 0) {
+ setupTableRow($tr, config);
+ }
+ }
+ });
+
+ $('#externalStorage').on('click', '[name="public_key_generate"]', function(event) {
+ event.preventDefault();
+ var tr = $(this).parent().parent();
+ generateKeys(tr);
+ });
+
+ function setupTableRow(tr, config) {
+ $(config).append($(document.createElement('input'))
+ .addClass('button auth-param')
+ .attr('type', 'button')
+ .attr('value', t('files_external', 'Generate keys'))
+ .attr('name', 'public_key_generate')
+ );
+ // If there's no private key, build one
+ if (0 === $(config).find('[data-parameter="private_key"]').val().length) {
+ generateKeys(tr);
+ }
+ }
+
+ function generateKeys(tr) {
+ var config = $(tr).find('.configuration');
+
+ $.post(OC.filePath('files_external', 'ajax', 'public_key.php'), {}, function(result) {
+ if (result && result.status === 'success') {
+ $(config).find('[data-parameter="public_key"]').val(result.data.public_key);
+ $(config).find('[data-parameter="private_key"]').val(result.data.private_key);
+ OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
+ // Nothing to do
+ });
+ } else {
+ OC.dialogs.alert(result.data.message, t('files_external', 'Error generating key pair') );
+ }
+ });
+ }
+});
diff --git a/apps/files_external/js/sftp_key.js b/apps/files_external/js/sftp_key.js
deleted file mode 100644
index 55b11b1fac..0000000000
--- a/apps/files_external/js/sftp_key.js
+++ /dev/null
@@ -1,53 +0,0 @@
-$(document).ready(function() {
-
- $('#externalStorage tbody tr.\\\\OC\\\\Files\\\\Storage\\\\SFTP_Key').each(function() {
- var tr = $(this);
- var config = $(tr).find('.configuration');
- if ($(config).find('.sftp_key').length === 0) {
- setupTableRow(tr, config);
- }
- });
-
- // We can't catch the DOM elements being added, but we can pick up when
- // they receive focus
- $('#externalStorage').on('focus', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\SFTP_Key', function() {
- var tr = $(this);
- var config = $(tr).find('.configuration');
-
- if ($(config).find('.sftp_key').length === 0) {
- setupTableRow(tr, config);
- }
- });
-
- $('#externalStorage').on('click', '.sftp_key', function(event) {
- event.preventDefault();
- var tr = $(this).parent().parent();
- generateKeys(tr);
- });
-
- function setupTableRow(tr, config) {
- $(config).append($(document.createElement('input')).addClass('button sftp_key')
- .attr('type', 'button')
- .attr('value', t('files_external', 'Generate keys')));
- // If there's no private key, build one
- if (0 === $(config).find('[data-parameter="private_key"]').val().length) {
- generateKeys(tr);
- }
- }
-
- function generateKeys(tr) {
- var config = $(tr).find('.configuration');
-
- $.post(OC.filePath('files_external', 'ajax', 'sftp_key.php'), {}, function(result) {
- if (result && result.status === 'success') {
- $(config).find('[data-parameter="public_key"]').val(result.data.public_key);
- $(config).find('[data-parameter="private_key"]').val(result.data.private_key);
- OCA.External.mountConfig.saveStorageConfig(tr, function() {
- // Nothing to do
- });
- } else {
- OC.dialogs.alert(result.data.message, t('files_external', 'Error generating key pair') );
- }
- });
- }
-});
diff --git a/apps/files_external/lib/auth/publickey/rsa.php b/apps/files_external/lib/auth/publickey/rsa.php
new file mode 100644
index 0000000000..b5eecb4271
--- /dev/null
+++ b/apps/files_external/lib/auth/publickey/rsa.php
@@ -0,0 +1,65 @@
+
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\Files_External\Lib\Auth\PublicKey;
+
+use \OCP\IL10N;
+use \OCA\Files_External\Lib\DefinitionParameter;
+use \OCA\Files_External\Lib\Auth\AuthMechanism;
+use \OCA\Files_External\Lib\StorageConfig;
+use \OCP\IConfig;
+use \phpseclib\Crypt\RSA as RSACrypt;
+
+/**
+ * RSA public key authentication
+ */
+class RSA extends AuthMechanism {
+
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(IL10N $l, IConfig $config) {
+ $this->config = $config;
+
+ $this
+ ->setIdentifier('publickey::rsa')
+ ->setScheme(self::SCHEME_PUBLICKEY)
+ ->setText($l->t('RSA public key'))
+ ->addParameters([
+ (new DefinitionParameter('user', $l->t('Username'))),
+ (new DefinitionParameter('public_key', $l->t('Public key'))),
+ (new DefinitionParameter('private_key', 'private_key'))
+ ->setType(DefinitionParameter::VALUE_HIDDEN),
+ ])
+ ->setCustomJs('public_key')
+ ;
+ }
+
+ public function manipulateStorageConfig(StorageConfig &$storage) {
+ $auth = new RSACrypt();
+ $auth->setPassword($this->config->getSystemValue('secret', ''));
+ if (!$auth->loadKey($storage->getBackendOption('private_key'))) {
+ throw new \RuntimeException('unable to load private key');
+ }
+ $storage->setBackendOption('public_key_auth', $auth);
+ }
+
+}
diff --git a/apps/files_external/lib/backend/sftp.php b/apps/files_external/lib/backend/sftp.php
index dd0f5d8e2e..c0bcd27c54 100644
--- a/apps/files_external/lib/backend/sftp.php
+++ b/apps/files_external/lib/backend/sftp.php
@@ -43,6 +43,7 @@ class SFTP extends Backend {
->setFlag(DefinitionParameter::FLAG_OPTIONAL),
])
->addAuthScheme(AuthMechanism::SCHEME_PASSWORD)
+ ->addAuthScheme(AuthMechanism::SCHEME_PUBLICKEY)
->setLegacyAuthMechanism($legacyAuth)
;
}
diff --git a/apps/files_external/lib/backend/sftp_key.php b/apps/files_external/lib/backend/sftp_key.php
new file mode 100644
index 0000000000..4a7f565eb1
--- /dev/null
+++ b/apps/files_external/lib/backend/sftp_key.php
@@ -0,0 +1,48 @@
+
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+
+namespace OCA\Files_External\Lib\Backend;
+
+use \OCP\IL10N;
+use \OCA\Files_External\Lib\Backend\Backend;
+use \OCA\Files_External\Lib\DefinitionParameter;
+use \OCA\Files_External\Lib\Auth\AuthMechanism;
+use \OCA\Files_External\Service\BackendService;
+use \OCA\Files_External\Lib\Auth\PublicKey\RSA;
+
+class SFTP_Key extends Backend {
+
+ public function __construct(IL10N $l, RSA $legacyAuth) {
+ $this
+ ->setIdentifier('\OC\Files\Storage\SFTP_Key')
+ ->setStorageClass('\OC\Files\Storage\SFTP')
+ ->setText($l->t('SFTP with secret key login [DEPRECATED]'))
+ ->addParameters([
+ (new DefinitionParameter('host', $l->t('Host'))),
+ (new DefinitionParameter('root', $l->t('Remote subfolder')))
+ ->setFlag(DefinitionParameter::FLAG_OPTIONAL),
+ ])
+ ->addAuthScheme(AuthMechanism::SCHEME_PUBLICKEY)
+ ->setLegacyAuthMechanism($legacyAuth)
+ ;
+ }
+
+}
diff --git a/apps/files_external/lib/sftp.php b/apps/files_external/lib/sftp.php
index 7f921b5342..921e7283c6 100644
--- a/apps/files_external/lib/sftp.php
+++ b/apps/files_external/lib/sftp.php
@@ -40,10 +40,11 @@ use phpseclib\Net\SFTP\Stream;
class SFTP extends \OC\Files\Storage\Common {
private $host;
private $user;
- private $password;
private $root;
private $port = 22;
+ private $auth;
+
/**
* @var SFTP
*/
@@ -73,8 +74,15 @@ class SFTP extends \OC\Files\Storage\Common {
}
$this->user = $params['user'];
- $this->password
- = isset($params['password']) ? $params['password'] : '';
+
+ if (isset($params['public_key_auth'])) {
+ $this->auth = $params['public_key_auth'];
+ } elseif (isset($params['password'])) {
+ $this->auth = $params['password'];
+ } else {
+ throw new \UnexpectedValueException('no authentication parameters specified');
+ }
+
$this->root
= isset($params['root']) ? $this->cleanPath($params['root']) : '/';
@@ -112,7 +120,7 @@ class SFTP extends \OC\Files\Storage\Common {
$this->writeHostKeys($hostKeys);
}
- if (!$this->client->login($this->user, $this->password)) {
+ if (!$this->client->login($this->user, $this->auth)) {
throw new \Exception('Login failed');
}
return $this->client;
@@ -125,7 +133,6 @@ class SFTP extends \OC\Files\Storage\Common {
if (
!isset($this->host)
|| !isset($this->user)
- || !isset($this->password)
) {
return false;
}
diff --git a/apps/files_external/lib/sftp_key.php b/apps/files_external/lib/sftp_key.php
deleted file mode 100644
index a193b32367..0000000000
--- a/apps/files_external/lib/sftp_key.php
+++ /dev/null
@@ -1,215 +0,0 @@
-
- * @author Morris Jobke
- * @author Ross Nicoll
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see
- *
- */
-namespace OC\Files\Storage;
-
-use phpseclib\Crypt\RSA;
-
-class SFTP_Key extends \OC\Files\Storage\SFTP {
- private $publicKey;
- private $privateKey;
-
- /**
- * {@inheritdoc}
- */
- public function __construct($params) {
- parent::__construct($params);
- $this->publicKey = $params['public_key'];
- $this->privateKey = $params['private_key'];
- }
-
- /**
- * Returns the connection.
- *
- * @return \phpseclib\Net\SFTP connected client instance
- * @throws \Exception when the connection failed
- */
- public function getConnection() {
- if (!is_null($this->client)) {
- return $this->client;
- }
-
- $hostKeys = $this->readHostKeys();
- $this->client = new \phpseclib\Net\SFTP($this->getHost());
-
- // The SSH Host Key MUST be verified before login().
- $currentHostKey = $this->client->getServerPublicHostKey();
- if (array_key_exists($this->getHost(), $hostKeys)) {
- if ($hostKeys[$this->getHost()] !== $currentHostKey) {
- throw new \Exception('Host public key does not match known key');
- }
- } else {
- $hostKeys[$this->getHost()] = $currentHostKey;
- $this->writeHostKeys($hostKeys);
- }
-
- $key = $this->getPrivateKey();
- if (is_null($key)) {
- throw new \Exception('Secret key could not be loaded');
- }
- if (!$this->client->login($this->getUser(), $key)) {
- throw new \Exception('Login failed');
- }
- return $this->client;
- }
-
- /**
- * Returns the private key to be used for authentication to the remote server.
- *
- * @return RSA instance or null in case of a failure to load the key.
- */
- private function getPrivateKey() {
- $key = new RSA();
- $key->setPassword(\OC::$server->getConfig()->getSystemValue('secret', ''));
- if (!$key->loadKey($this->privateKey)) {
- // Should this exception rather than return null?
- return null;
- }
- return $key;
- }
-
- /**
- * Throws an exception if the provided host name/address is invalid (cannot be resolved
- * and is not an IPv4 address).
- *
- * @return true; never returns in case of a problem, this return value is used just to
- * make unit tests happy.
- */
- public function assertHostAddressValid($hostname) {
- // TODO: Should handle IPv6 addresses too
- if (!preg_match('/^\d+\.\d+\.\d+\.\d+$/', $hostname) && gethostbyname($hostname) === $hostname) {
- // Hostname is not an IPv4 address and cannot be resolved via DNS
- throw new \InvalidArgumentException('Cannot resolve hostname.');
- }
- return true;
- }
-
- /**
- * Throws an exception if the provided port number is invalid (cannot be resolved
- * and is not an IPv4 address).
- *
- * @return true; never returns in case of a problem, this return value is used just to
- * make unit tests happy.
- */
- public function assertPortNumberValid($port) {
- if (!preg_match('/^\d+$/', $port)) {
- throw new \InvalidArgumentException('Port number must be a number.');
- }
- if ($port < 0 || $port > 65535) {
- throw new \InvalidArgumentException('Port number must be between 0 and 65535 inclusive.');
- }
- return true;
- }
-
- /**
- * Replaces anything that's not an alphanumeric character or "." in a hostname
- * with "_", to make it safe for use as part of a file name.
- */
- protected function sanitizeHostName($name) {
- return preg_replace('/[^\d\w\._]/', '_', $name);
- }
-
- /**
- * Replaces anything that's not an alphanumeric character or "_" in a username
- * with "_", to make it safe for use as part of a file name.
- */
- protected function sanitizeUserName($name) {
- return preg_replace('/[^\d\w_]/', '_', $name);
- }
-
- public function test() {
-
- // FIXME: Use as expression in empty once PHP 5.4 support is dropped
- $host = $this->getHost();
- if (empty($host)) {
- \OC::$server->getLogger()->warning('Hostname has not been specified');
- return false;
- }
- // FIXME: Use as expression in empty once PHP 5.4 support is dropped
- $user = $this->getUser();
- if (empty($user)) {
- \OC::$server->getLogger()->warning('Username has not been specified');
- return false;
- }
- if (!isset($this->privateKey)) {
- \OC::$server->getLogger()->warning('Private key was missing from the request');
- return false;
- }
-
- // Sanity check the host
- $hostParts = explode(':', $this->getHost());
- try {
- if (count($hostParts) == 1) {
- $hostname = $hostParts[0];
- $this->assertHostAddressValid($hostname);
- } else if (count($hostParts) == 2) {
- $hostname = $hostParts[0];
- $this->assertHostAddressValid($hostname);
- $this->assertPortNumberValid($hostParts[1]);
- } else {
- throw new \Exception('Host connection string is invalid.');
- }
- } catch(\Exception $e) {
- \OC::$server->getLogger()->warning($e->getMessage());
- return false;
- }
-
- // Validate the key
- $key = $this->getPrivateKey();
- if (is_null($key)) {
- \OC::$server->getLogger()->warning('Secret key could not be loaded');
- return false;
- }
-
- try {
- if ($this->getConnection()->nlist() === false) {
- return false;
- }
- } catch(\Exception $e) {
- // We should be throwing a more specific error, so we're not just catching
- // Exception here
- \OC::$server->getLogger()->warning($e->getMessage());
- return false;
- }
-
- // Save the key somewhere it can easily be extracted later
- if (\OC::$server->getUserSession()->getUser()) {
- $view = new \OC\Files\View('/'.\OC::$server->getUserSession()->getUser()->getUId().'/files_external/sftp_keys');
- if (!$view->is_dir('')) {
- if (!$view->mkdir('')) {
- \OC::$server->getLogger()->warning('Could not create secret key directory.');
- return false;
- }
- }
- $key_filename = $this->sanitizeUserName($this->getUser()).'@'.$this->sanitizeHostName($hostname).'.pub';
- $key_file = $view->fopen($key_filename, "w");
- if ($key_file) {
- fwrite($key_file, $this->publicKey);
- fclose($key_file);
- } else {
- \OC::$server->getLogger()->warning('Could not write secret key file.');
- }
- }
-
- return true;
- }
-}