Merge branch 'feature/issue#46' into develop

This commit is contained in:
Marcin Łojewski
2018-12-30 20:34:56 +01:00
18 changed files with 367 additions and 23 deletions

View File

@@ -61,6 +61,11 @@ $application->registerRoutes(
"url" => "/settings/autocomplete/table/group",
"verb" => "POST"
],
[
"name" => "settings#cryptoParams",
"url" => "/settings/crypto/params",
"verb" => "GET"
],
]
]
);

View File

@@ -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;
background-color: var(--color-warning);
color: var(--color-primary-text);
}
#user_sql .loading {
display: inline-block;
height: 32px;
margin: 5px 0;
width: 32px;
}

View File

@@ -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 = $("<div></div>");
var label = $("<label></label>").attr({for: "opt-crypto_param_" + index});
var title = $("<span></span>").text(data.data[index]["name"]);
var input = $("<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();
}
};

View File

@@ -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(

View File

@@ -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";

View File

@@ -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];
}
}

View File

@@ -74,4 +74,12 @@ abstract class AbstractAlgorithm implements IPasswordAlgorithm
* @inheritdoc
*/
public abstract function getPasswordHash($password, $salt = null);
/**
* @inheritdoc
*/
public function configuration()
{
return [];
}
}

View File

@@ -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
*/

View File

@@ -67,6 +67,14 @@ class CryptBlowfish extends AbstractAlgorithm
);
}
/**
* @inheritdoc
*/
public function configuration()
{
return [new CryptoParam("Cost", 10, 4, 31)];
}
/**
* Get the algorithm name.
*

View File

@@ -48,6 +48,14 @@ class CryptExtendedDES extends AbstractCrypt
$this->iterationCount = $iterationCount;
}
/**
* @inheritdoc
*/
public function configuration()
{
return [new CryptoParam("Iterations", 1000, 0, 16777215)];
}
/**
* @inheritdoc
*/

View File

@@ -49,6 +49,14 @@ class CryptSHA256 extends AbstractCrypt
$this->rounds = $rounds;
}
/**
* @inheritdoc
*/
public function configuration()
{
return [new CryptoParam("Rounds", 5000, 1000, 999999999)];
}
/**
* @inheritdoc
*/

View File

@@ -49,6 +49,14 @@ class CryptSHA512 extends AbstractCrypt
$this->rounds = $rounds;
}
/**
* @inheritdoc
*/
public function configuration()
{
return [new CryptoParam("Rounds", 5000, 1000, 999999999)];
}
/**
* @inheritdoc
*/

View File

@@ -0,0 +1,63 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
/**
* A parameter of a hash algorithm.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptoParam
{
/**
* @var string Parameter name.
*/
public $name;
/**
* @var int Parameter default value.
*/
public $value;
/**
* @var int Minimal value for parameter.
*/
public $min;
/**
* @var int Maximum value for parameter.
*/
public $max;
/**
* Class constructor.
*
* @param $name string Parameter name.
* @param $value int Parameter default value.
* @param $min int Minimal value for parameter.
* @param $max int Maximum value for parameter.
*/
public function __construct($name, $value, $min, $max)
{
$this->name = $name;
$this->value = $value;
$this->min = $min;
$this->max = $max;
}
}

View File

@@ -34,6 +34,14 @@ class Drupal7 extends Phpass
*/
const DRUPAL_HASH_LENGTH = 55;
/**
* @inheritdoc
*/
public function configuration()
{
return [];
}
/**
* @inheritdoc
*/

View File

@@ -58,4 +58,12 @@ interface IPasswordAlgorithm
* @return boolean True if the password is correct, false otherwise.
*/
public function checkPassword($password, $dbHash, $salt = null);
/**
* Configuration for the algorithm.
* The return array should contain entries of class <code>CryptoParam</code>
*
* @return array The configuration array.
*/
public function configuration();
}

View File

@@ -155,6 +155,14 @@ class Phpass extends AbstractAlgorithm
return $output;
}
/**
* @inheritdoc
*/
public function configuration()
{
return [new CryptoParam("Iterations (log2)", 8, 4, 31)];
}
/**
* @inheritdoc
*/

View File

@@ -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,11 +100,13 @@ class Properties implements \ArrayAccess
foreach ($params as $param) {
$value = $this->config->getAppValue($this->appName, $param, null);
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,11 +207,13 @@ class Properties implements \ArrayAccess
{
$this->config->setAppValue($this->appName, $offset, $value);
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]);
}
}

View File

@@ -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"]); ?>
<div id="opt-crypto_params_loading" style="display: none">
<span class="icon loading"></span>
</div>
<fieldset id="opt-crypto_params_content" class="inner-fieldset" style="display: none"></fieldset>
<?php
print_select_options($l, "opt-email_sync", "Email sync", ["" => "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_text_input($l, "db-table-user-column-salt", "Salt", $_["db.table.user.column.salt"]); ?>
<div class="inner-fieldset">
<?php
print_checkbox_input($l, "opt-append_salt", "Append salt", $_["opt.append_salt"]);
print_checkbox_input($l, "opt-prepend_salt", "Prepend salt", $_["opt.prepend_salt"]); ?>
</div>
</fieldset>
</div>
<div class="section">