diff --git a/appinfo/routes.php b/appinfo/routes.php index 8224fa4..dde5395 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -61,6 +61,11 @@ $application->registerRoutes( "url" => "/settings/autocomplete/table/group", "verb" => "POST" ], + [ + "name" => "settings#cryptoParams", + "url" => "/settings/crypto/params", + "verb" => "GET" + ], ] ] ); diff --git a/css/settings.css b/css/settings.css index 33f454a..9fae0e2 100644 --- a/css/settings.css +++ b/css/settings.css @@ -33,6 +33,13 @@ float: right; } +#user_sql .main .inner-fieldset { + border-bottom: 1px solid var(--color-border); + border-top: 1px solid var(--color-border); + margin: 8px 0; + padding: 8px 0 8px 16px; +} + #user_sql .msg { left: 0; padding: 3px; @@ -43,16 +50,23 @@ } #user_sql .msg.error { - background-color: #d2322d; - color: #fff; + background-color: var(--color-error); + color: var(--color-primary-text); } #user_sql .msg.success { - background-color: #47a447; - color: #fff; + background-color: var(--color-success); + color: var(--color-primary-text); } #user_sql .msg.waiting { - background-color: #ff8f00; - color: #fff; -} \ No newline at end of file + background-color: var(--color-warning); + color: var(--color-primary-text); +} + +#user_sql .loading { + display: inline-block; + height: 32px; + margin: 5px 0; + width: 32px; +} diff --git a/js/settings.js b/js/settings.js index 0a42eaf..cf49ac5 100644 --- a/js/settings.js +++ b/js/settings.js @@ -58,6 +58,49 @@ user_sql.adminSettingsUI = function () { }); }; + var cryptoParams = function () { + var cryptoChanged = function () { + var content = $("#opt-crypto_params_content"); + var loading = $("#opt-crypto_params_loading"); + + content.hide(); + loading.show(); + + $.get(OC.generateUrl("/apps/user_sql/settings/crypto/params"), {cryptoClass: $("#opt-crypto_class").val()}, + function (data) { + content.empty(); + loading.hide(); + + if (data.status === "success") { + for (var index = 0, length = data.data.length; index < length; ++index) { + var param = $("
"); + var label = $("").attr({for: "opt-crypto_param_" + index}); + var title = $("").text(data.data[index]["name"]); + var input = $("").attr({ + type: "number", + id: "opt-crypto_param_" + index, + name: "opt-crypto_param_" + index, + step: 1, + min: data.data[index]["min"], + max: data.data[index]["max"], + value: data.data[index]["value"] + }); + + label.append(title); + param.append(label); + param.append(input); + content.append(param); + content.show(); + } + } + }, "json"); + }; + $("#opt-crypto_class").change(function () { + cryptoChanged(); + }); + cryptoChanged(); + }; + $("#user_sql-db_connection_verify").click(function (event) { return click(event, "/apps/user_sql/settings/db/verify"); }); @@ -89,6 +132,8 @@ user_sql.adminSettingsUI = function () { "#db-table-group-column-admin, #db-table-group-column-name, #db-table-group-column-gid", "/apps/user_sql/settings/autocomplete/table/group" ); + + cryptoParams(); } }; @@ -96,4 +141,4 @@ $(document).ready(function () { if ($(form_id)) { user_sql.adminSettingsUI(); } -}); \ No newline at end of file +}); diff --git a/lib/Backend/UserBackend.php b/lib/Backend/UserBackend.php index b8c1a5d..d8c6ae8 100644 --- a/lib/Backend/UserBackend.php +++ b/lib/Backend/UserBackend.php @@ -343,7 +343,12 @@ final class UserBackend extends ABackend implements private function getPasswordAlgorithm() { $cryptoType = $this->properties[Opt::CRYPTO_CLASS]; - $passwordAlgorithm = new $cryptoType($this->localization); + $cryptoParam0 = $this->properties[Opt::CRYPTO_PARAM_0]; + $cryptoParam1 = $this->properties[Opt::CRYPTO_PARAM_1]; + $cryptoParam2 = $this->properties[Opt::CRYPTO_PARAM_2]; + $passwordAlgorithm = new $cryptoType( + $this->localization, $cryptoParam0, $cryptoParam1, $cryptoParam2 + ); if ($passwordAlgorithm === null) { $this->logger->error( diff --git a/lib/Constant/Opt.php b/lib/Constant/Opt.php index 94e9db5..9be2eb0 100644 --- a/lib/Constant/Opt.php +++ b/lib/Constant/Opt.php @@ -31,6 +31,9 @@ final class Opt const APPEND_SALT = "opt.append_salt"; const CASE_INSENSITIVE_USERNAME = "opt.case_insensitive_username"; const CRYPTO_CLASS = "opt.crypto_class"; + const CRYPTO_PARAM_0 = "opt.crypto_param_0"; + const CRYPTO_PARAM_1 = "opt.crypto_param_1"; + const CRYPTO_PARAM_2 = "opt.crypto_param_2"; const EMAIL_SYNC = "opt.email_sync"; const HOME_LOCATION = "opt.home_location"; const HOME_MODE = "opt.home_mode"; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index a192daf..ef06b2d 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -28,12 +28,16 @@ use OC\DB\Connection; use OC\DB\ConnectionFactory; use OCA\UserSQL\Cache; use OCA\UserSQL\Constant\App; +use OCA\UserSQL\Constant\Opt; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; use OCA\UserSQL\Platform\PlatformFactory; use OCA\UserSQL\Properties; use OCP\AppFramework\Controller; use OCP\IL10N; use OCP\ILogger; use OCP\IRequest; +use ReflectionClass; +use ReflectionException; /** * The settings controller. @@ -72,7 +76,8 @@ class SettingsController extends Controller public function __construct( $appName, IRequest $request, ILogger $logger, IL10N $localization, Properties $properties, Cache $cache - ) { + ) + { parent::__construct($appName, $request); $this->appName = $appName; $this->logger = $logger; @@ -193,6 +198,16 @@ class SettingsController extends Controller ]; } + if (!$this->validateCryptoParams()) { + return [ + "status" => "error", "data" => [ + "message" => $this->localization->t( + "Hash algorithm parameter is out of range." + ) + ] + ]; + } + foreach ($properties as $key => $value) { $reqValue = $this->request->getParam(str_replace(".", "-", $key)); $appValue = $this->properties[$key]; @@ -208,6 +223,9 @@ class SettingsController extends Controller "Property '$key' has been set to: " . $value, ["app" => $this->appName] ); + } elseif (!is_bool($appValue) && !isset($reqValue)) { + unset($this->properties[$key]); + } } @@ -225,6 +243,48 @@ class SettingsController extends Controller ]; } + /** + * Validate request crypto params. + * + * @return bool TRUE if crypto params are correct FALSE otherwise. + */ + private function validateCryptoParams() + { + $cryptoClass = $this->request->getParam("opt-crypto_class"); + $configuration = $this->cryptoClassConfiguration($cryptoClass); + + for ($i = 0; $i < count($configuration); ++$i) { + $reqParam = $this->request->getParam( + "opt-crypto_param_" . $i, null + ); + $cryptoParam = $configuration[$i]; + + if (is_null($reqParam) || $reqParam < $cryptoParam->min + || $reqParam > $cryptoParam->max + ) { + return false; + } + } + + return true; + } + + /** + * Get a crypto class configuration from request. + * + * @param $cryptoClass string Crypto class name. + * + * @return array A crypto class configuration. + */ + private function cryptoClassConfiguration($cryptoClass) + { + /** + * @var $passwordAlgorithm IPasswordAlgorithm + */ + $passwordAlgorithm = new $cryptoClass($this->localization); + return $passwordAlgorithm->configuration(); + } + /** * Clear the application cache memory. * @@ -367,4 +427,40 @@ class SettingsController extends Controller return $columns; } + + /** + * Get parameters for a password algorithm. + * + * @return array Password algorithm parameters. + * @throws ReflectionException Whenever Opt class cannot be initiated. + */ + public function cryptoParams() + { + $this->logger->debug( + "Entering cryptoParams()", ["app" => $this->appName] + ); + + $cryptoClass = $this->request->getParam("cryptoClass"); + $configuration = $this->cryptoClassConfiguration($cryptoClass); + + if ($cryptoClass === $this->properties[Opt::CRYPTO_CLASS]) { + foreach ($configuration as $key => $value) { + $opt = new ReflectionClass("OCA\UserSQL\Constant\Opt"); + $param = $this->properties[$opt->getConstant( + "CRYPTO_PARAM_" . $key + )]; + + if (!is_null($param)) { + $value->value = $param; + } + } + } + + $this->logger->debug( + "Returning cryptoParams(): count(" . count($configuration) . ")", + ["app" => $this->appName] + ); + + return ["status" => "success", "data" => (array)$configuration]; + } } diff --git a/lib/Crypto/AbstractAlgorithm.php b/lib/Crypto/AbstractAlgorithm.php index 99f344e..6290d7d 100644 --- a/lib/Crypto/AbstractAlgorithm.php +++ b/lib/Crypto/AbstractAlgorithm.php @@ -74,4 +74,12 @@ abstract class AbstractAlgorithm implements IPasswordAlgorithm * @inheritdoc */ public abstract function getPasswordHash($password, $salt = null); + + /** + * @inheritdoc + */ + public function configuration() + { + return []; + } } diff --git a/lib/Crypto/CryptArgon2.php b/lib/Crypto/CryptArgon2.php index 6e086cc..ddbfe0a 100644 --- a/lib/Crypto/CryptArgon2.php +++ b/lib/Crypto/CryptArgon2.php @@ -100,6 +100,23 @@ class CryptArgon2 extends AbstractAlgorithm ); } + /** + * @inheritdoc + */ + public function configuration() + { + return [ + new CryptoParam( + "Memory cost (KiB)", PASSWORD_ARGON2_DEFAULT_MEMORY_COST, 1, + 1048576 + ), + new CryptoParam( + "Time cost", PASSWORD_ARGON2_DEFAULT_TIME_COST, 1, 1024 + ), + new CryptoParam("Threads", PASSWORD_ARGON2_DEFAULT_THREADS, 1, 1024) + ]; + } + /** * @inheritdoc */ diff --git a/lib/Crypto/CryptBlowfish.php b/lib/Crypto/CryptBlowfish.php index 73a27b1..fb9e28b 100644 --- a/lib/Crypto/CryptBlowfish.php +++ b/lib/Crypto/CryptBlowfish.php @@ -67,6 +67,14 @@ class CryptBlowfish extends AbstractAlgorithm ); } + /** + * @inheritdoc + */ + public function configuration() + { + return [new CryptoParam("Cost", 10, 4, 31)]; + } + /** * Get the algorithm name. * diff --git a/lib/Crypto/CryptExtendedDES.php b/lib/Crypto/CryptExtendedDES.php index c91e04c..8cde30a 100644 --- a/lib/Crypto/CryptExtendedDES.php +++ b/lib/Crypto/CryptExtendedDES.php @@ -48,6 +48,14 @@ class CryptExtendedDES extends AbstractCrypt $this->iterationCount = $iterationCount; } + /** + * @inheritdoc + */ + public function configuration() + { + return [new CryptoParam("Iterations", 1000, 0, 16777215)]; + } + /** * @inheritdoc */ diff --git a/lib/Crypto/CryptSHA256.php b/lib/Crypto/CryptSHA256.php index b4e2b41..91063ef 100644 --- a/lib/Crypto/CryptSHA256.php +++ b/lib/Crypto/CryptSHA256.php @@ -49,6 +49,14 @@ class CryptSHA256 extends AbstractCrypt $this->rounds = $rounds; } + /** + * @inheritdoc + */ + public function configuration() + { + return [new CryptoParam("Rounds", 5000, 1000, 999999999)]; + } + /** * @inheritdoc */ diff --git a/lib/Crypto/CryptSHA512.php b/lib/Crypto/CryptSHA512.php index e32238f..1cafee5 100644 --- a/lib/Crypto/CryptSHA512.php +++ b/lib/Crypto/CryptSHA512.php @@ -49,6 +49,14 @@ class CryptSHA512 extends AbstractCrypt $this->rounds = $rounds; } + /** + * @inheritdoc + */ + public function configuration() + { + return [new CryptoParam("Rounds", 5000, 1000, 999999999)]; + } + /** * @inheritdoc */ diff --git a/lib/Crypto/CryptoParam.php b/lib/Crypto/CryptoParam.php new file mode 100644 index 0000000..d694cf2 --- /dev/null +++ b/lib/Crypto/CryptoParam.php @@ -0,0 +1,63 @@ + + * @author Marcin ŁojewskiCryptoParam
+ *
+ * @return array The configuration array.
+ */
+ public function configuration();
}
diff --git a/lib/Crypto/Phpass.php b/lib/Crypto/Phpass.php
index bea911d..7edd728 100644
--- a/lib/Crypto/Phpass.php
+++ b/lib/Crypto/Phpass.php
@@ -155,6 +155,14 @@ class Phpass extends AbstractAlgorithm
return $output;
}
+ /**
+ * @inheritdoc
+ */
+ public function configuration()
+ {
+ return [new CryptoParam("Iterations (log2)", 8, 4, 31)];
+ }
+
/**
* @inheritdoc
*/
diff --git a/lib/Properties.php b/lib/Properties.php
index b030b82..5affa85 100644
--- a/lib/Properties.php
+++ b/lib/Properties.php
@@ -70,7 +70,8 @@ class Properties implements \ArrayAccess
*/
public function __construct(
$AppName, IConfig $config, ILogger $logger, Cache $cache
- ) {
+ )
+ {
$this->appName = $AppName;
$this->config = $config;
$this->logger = $logger;
@@ -99,10 +100,12 @@ class Properties implements \ArrayAccess
foreach ($params as $param) {
$value = $this->config->getAppValue($this->appName, $param, null);
- if ($value === App::FALSE_VALUE) {
- $value = false;
- } elseif ($value === App::TRUE_VALUE) {
- $value = true;
+ if ($this->isBooleanParam($param)) {
+ if ($value === App::FALSE_VALUE) {
+ $value = false;
+ } elseif ($value === App::TRUE_VALUE) {
+ $value = true;
+ }
}
$this->data[$param] = $value;
@@ -141,6 +144,24 @@ class Properties implements \ArrayAccess
return $params;
}
+ /**
+ * Is given parameter a boolean parameter.
+ *
+ * @param $param string Parameter name.
+ *
+ * @return bool Is a boolean parameter.
+ */
+ private function isBooleanParam($param)
+ {
+ return in_array(
+ $param, [
+ Opt::APPEND_SALT, Opt::CASE_INSENSITIVE_USERNAME,
+ Opt::NAME_CHANGE, Opt::PASSWORD_CHANGE, Opt::PREPEND_SALT,
+ Opt::REVERSE_ACTIVE, Opt::USE_CACHE
+ ]
+ );
+ }
+
/**
* Store properties in the cache memory.
*/
@@ -186,10 +207,12 @@ class Properties implements \ArrayAccess
{
$this->config->setAppValue($this->appName, $offset, $value);
- if ($value === App::FALSE_VALUE) {
- $value = false;
- } elseif ($value === App::TRUE_VALUE) {
- $value = true;
+ if ($this->isBooleanParam($offset)) {
+ if ($value === App::FALSE_VALUE) {
+ $value = false;
+ } elseif ($value === App::TRUE_VALUE) {
+ $value = true;
+ }
}
$this->data[$offset] = $value;
@@ -206,6 +229,7 @@ class Properties implements \ArrayAccess
*/
public function offsetUnset($offset)
{
+ $this->config->deleteAppValue($this->appName, $offset);
unset($this->data[$offset]);
}
}
diff --git a/templates/admin.php b/templates/admin.php
index d799b32..579fb94 100644
--- a/templates/admin.php
+++ b/templates/admin.php
@@ -132,7 +132,12 @@ function print_select_options(
}
}
- print_select_options($l, "opt-crypto_class", "Hash algorithm", $hashes, $_["opt.crypto_class"]);
+ print_select_options($l, "opt-crypto_class", "Hash algorithm", $hashes, $_["opt.crypto_class"]); ?>
+
+
+ "None", "initial" => "Synchronise only once", "force_nc"=>"Nextcloud always wins", "force_sql"=>"SQL always wins"], $_["opt.email_sync"]);
print_select_options($l, "opt-quota_sync", "Quota sync", ["" => "None", "initial" => "Synchronise only once", "force_nc"=>"Nextcloud always wins", "force_sql"=>"SQL always wins"], $_["opt.quota_sync"]);
print_select_options($l, "opt-home_mode", "Home mode", ["" => "Default", "query" => "Query", "static" => "Static"], $_["opt.home_mode"]);
@@ -154,9 +159,12 @@ function print_select_options(
print_text_input($l, "db-table-user-column-name", "Display name", $_["db.table.user.column.name"]);
print_text_input($l, "db-table-user-column-active", "Active", $_["db.table.user.column.active"]);
print_text_input($l, "db-table-user-column-avatar", "Provide avatar", $_["db.table.user.column.avatar"]);
- print_text_input($l, "db-table-user-column-salt", "Salt", $_["db.table.user.column.salt"]);
- print_checkbox_input($l, "opt-append_salt", "Append salt", $_["opt.append_salt"]);
- print_checkbox_input($l, "opt-prepend_salt", "Prepend salt", $_["opt.prepend_salt"]); ?>
+ print_text_input($l, "db-table-user-column-salt", "Salt", $_["db.table.user.column.salt"]); ?>
+