Merge branch 'release/v4.1.0'

This commit is contained in:
Marcin Łojewski
2018-10-28 18:30:40 +01:00
20 changed files with 403 additions and 53 deletions

View File

@@ -4,7 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [4.0.1] - 2018-08-11 ## [4.1.0] - 2018-10-28
### Added
- Whirlpool hash algorithm
- 'Prepend salt' toggle
- Drupal 7 hash algorithm
- 'Case-insensitive username' option
### Fixed
- Error when 'Display name' not set
- Encoding of iteration for 'Extended DES (Crypt)'
- 'Trying to get property of non-object' warning
## [4.0.1] - 2018-08-16
### Fixed ### Fixed
- Leftover lines break the admin page - Leftover lines break the admin page
@@ -87,6 +98,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed ### Changed
- Supported version of ownCloud, Nextcloud: ownCloud 10, Nextcloud 12 - Supported version of ownCloud, Nextcloud: ownCloud 10, Nextcloud 12
[4.1.0]: https://github.com/nextcloud/user_sql/compare/v4.0.1...v4.1.0
[4.0.1]: https://github.com/nextcloud/user_sql/compare/v4.0.0...v4.0.1 [4.0.1]: https://github.com/nextcloud/user_sql/compare/v4.0.0...v4.0.1
[4.0.0]: https://github.com/nextcloud/user_sql/compare/v4.0.0-rc2...v4.0.0 [4.0.0]: https://github.com/nextcloud/user_sql/compare/v4.0.0-rc2...v4.0.0
[4.0.0-rc2]: https://github.com/nextcloud/user_sql/compare/v4.0.0-rc1...v4.0.0-rc2 [4.0.0-rc2]: https://github.com/nextcloud/user_sql/compare/v4.0.0-rc1...v4.0.0-rc2

View File

@@ -49,6 +49,7 @@ Name | Description | Details
--- | --- | --- --- | --- | ---
**Allow display name change** | With this option enabled user can change its display name. The display name change is propagated to the database. | Optional.<br/>Default: false.<br/>Requires: user *Display name* column. **Allow display name change** | With this option enabled user can change its display name. The display name change is propagated to the database. | Optional.<br/>Default: false.<br/>Requires: user *Display name* column.
**Allow password change** | Can user change its password. The password change is propagated to the database. See [Hash algorithms](#hash-algorithms). | Optional.<br/>Default: false. **Allow password change** | Can user change its password. The password change is propagated to the database. See [Hash algorithms](#hash-algorithms). | Optional.<br/>Default: false.
**Case-insensitive username** | Whether user query should be case-sensitive or case-insensitive. | Optional.<br/>Default: false.
**Use cache** | Use database query results cache. The cache can be cleared any time with the *Clear cache* button click. | Optional.<br/>Default: false. **Use cache** | Use database query results cache. The cache can be cleared any time with the *Clear cache* button click. | Optional.<br/>Default: false.
**Hash algorithm** | How users passwords are stored in the database. See [Hash algorithms](#hash-algorithms). | Mandatory. **Hash algorithm** | How users passwords are stored in the database. See [Hash algorithms](#hash-algorithms). | Mandatory.
**Email sync** | Sync e-mail address with the Nextcloud.<br/>- *None* - Disables this feature. This is the default option.<br/>- *Synchronise only once* - Copy the e-mail address to the Nextcloud preferences if its not set.<br/>- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.<br/>- *SQL always wins* - Always copy the e-mail address to the Nextcloud preferences. | Optional.<br/>Default: *None*.<br/>Requires: user *Email* column. **Email sync** | Sync e-mail address with the Nextcloud.<br/>- *None* - Disables this feature. This is the default option.<br/>- *Synchronise only once* - Copy the e-mail address to the Nextcloud preferences if its not set.<br/>- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.<br/>- *SQL always wins* - Always copy the e-mail address to the Nextcloud preferences. | Optional.<br/>Default: *None*.<br/>Requires: user *Email* column.
@@ -72,6 +73,7 @@ Name | Description | Details
**Active** | Flag indicating if user can log in. | Optional.<br/>Default: true. **Active** | Flag indicating if user can log in. | Optional.<br/>Default: true.
**Provide avatar** | Flag indicating if user can change its avatar. | Optional.<br/>Default: false. **Provide avatar** | Flag indicating if user can change its avatar. | Optional.<br/>Default: false.
**Salt** | Salt which is appended to password when checking or changing the password. | Optional. **Salt** | Salt which is appended to password when checking or changing the password. | Optional.
**Prepend salt** | Prepend a salt to the password instead of appending it. | Optional.<br/>Default: false.
#### Group table #### Group table
@@ -184,11 +186,12 @@ Courier base64-encoded SHA256 | No salt supported. | {SHA256}XohImNooBHFR0OVvjcY
Unix (Crypt) | See [crypt](http://php.net/manual/en/function.crypt.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq Unix (Crypt) | See [crypt](http://php.net/manual/en/function.crypt.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq
Argon2 (Crypt) | Requires PHP >= 7.2.<br/>Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $argon2i$v=19$m=1024,t=2,p=2$NnpSNlRNLlZobnJHUDh0Sw$oW5E1cfdPzLWfkTvQFUyzTR00R0aLwEdYwldcqW6Pmo Argon2 (Crypt) | Requires PHP >= 7.2.<br/>Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $argon2i$v=19$m=1024,t=2,p=2$NnpSNlRNLlZobnJHUDh0Sw$oW5E1cfdPzLWfkTvQFUyzTR00R0aLwEdYwldcqW6Pmo
Blowfish (Crypt) | Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq Blowfish (Crypt) | Uses default parameters. See [password_hash](http://php.net/manual/en/function.password-hash.php). | $2y$10$5rsN1fmoSkaRy9bqhozAXOr0mn0QiVIfd2L04Bbk1Go9MjdvotwBq
Extended DES (Crypt) | | ..UZoIyj/Hy/c Extended DES (Crypt) | | cDRpdxPmHpzS.
MD5 (Crypt) | | $1$RzaFbNcU$u9adfTY/Q6za6nu0Ogrl1/ MD5 (Crypt) | | $1$RzaFbNcU$u9adfTY/Q6za6nu0Ogrl1/
SHA256 (Crypt) | Generates hash with 5000 rounds. | $5$rounds=5000$VIYD0iHkg7uY9SRc$v2XLS/9dvfFN84mzGvW9wxnVt9Xd/urXaaTkpW8EwD1 SHA256 (Crypt) | Generates hash with 5000 rounds. | $5$rounds=5000$VIYD0iHkg7uY9SRc$v2XLS/9dvfFN84mzGvW9wxnVt9Xd/urXaaTkpW8EwD1
SHA512 (Crypt) | Generates hash with 5000 rounds. | $6$rounds=5000$yH.Q0OL4qbCOUJ3q$Xry5EVFva3wKnfo8/ktrugmBd8tcl34NK6rXInv1HhmdSUNLEm0La9JnA57rqwQ.9/Bz513MD4tvmmISLUIHs/ SHA512 (Crypt) | Generates hash with 5000 rounds. | $6$rounds=5000$yH.Q0OL4qbCOUJ3q$Xry5EVFva3wKnfo8/ktrugmBd8tcl34NK6rXInv1HhmdSUNLEm0La9JnA57rqwQ.9/Bz513MD4tvmmISLUIHs/
Standard DES (Crypt) | | yTBnb7ab/N072 Standard DES (Crypt) | | yTBnb7ab/N072
Drupal 7 | See [phpass](http://www.openwall.com/phpass/). | $S$DC7eCpJQ3SUQtW4Bp.vKb2rpeaffi4iqk9OpYwJyEoSMsezn67Sl
Joomla MD5 Encryption | Generates 32 chars salt. | 14d21b49b0f13e2acba962b6b0039edd:haJK0yTvBXTNMh76xwEw5RYEVpJsN8us Joomla MD5 Encryption | Generates 32 chars salt. | 14d21b49b0f13e2acba962b6b0039edd:haJK0yTvBXTNMh76xwEw5RYEVpJsN8us
MD5 | No salt supported. | 5f4dcc3b5aa765d61d8327deb882cf99 MD5 | No salt supported. | 5f4dcc3b5aa765d61d8327deb882cf99
Portable PHP password | See [phpass](http://www.openwall.com/phpass/). | $P$BxrwraqNTi4as0EI.IpiA/K.muk9ke/ Portable PHP password | See [phpass](http://www.openwall.com/phpass/). | $P$BxrwraqNTi4as0EI.IpiA/K.muk9ke/
@@ -197,6 +200,7 @@ SHA512 Whirlpool | No salt supported. | a96b16ebb691dbe968b0d66d0d924cff5cf5de5e
SSHA256 | Generates 32 chars salt. | {SSHA256}+WxTB3JxprNteeovsuSYtgI+UkVPA9lfwGoYkz3Ff7hjd1FSdmlTMkNsSExyR21KM3NvNTZ5V0p4WXJMUjFzUg== SSHA256 | Generates 32 chars salt. | {SSHA256}+WxTB3JxprNteeovsuSYtgI+UkVPA9lfwGoYkz3Ff7hjd1FSdmlTMkNsSExyR21KM3NvNTZ5V0p4WXJMUjFzUg==
SSHA512 | Generates 32 chars salt. | {SSHA512}It+v1kAEUBbhMJYJ2swAtz+RLE6ispv/FB6G/ALhK/YWwEmrloY+0jzrWIfmu+rWUXp8u0Tg4jLXypC5oXAW00IyYnRVdEZJbE9wak96bkNRVWFCYmlJNWxrdTA0QmhL SSHA512 | Generates 32 chars salt. | {SSHA512}It+v1kAEUBbhMJYJ2swAtz+RLE6ispv/FB6G/ALhK/YWwEmrloY+0jzrWIfmu+rWUXp8u0Tg4jLXypC5oXAW00IyYnRVdEZJbE9wak96bkNRVWFCYmlJNWxrdTA0QmhL
WoltLab Community Framework 2.x | Double salted bcrypt. | $2a$08$XEQDKNU/Vbootwxv5Gp7gujxFX/RUFsZLvQPYM435Dd3/p17fto02 WoltLab Community Framework 2.x | Double salted bcrypt. | $2a$08$XEQDKNU/Vbootwxv5Gp7gujxFX/RUFsZLvQPYM435Dd3/p17fto02
Whirlpool | | 74dfc2b27acfa364da55f93a5caee29ccad3557247eda238831b3e9bd931b01d77fe994e4f12b9d4cfa92a124461d2065197d8cf7f33fc88566da2db2a4d6eae
## Development ## Development

View File

@@ -8,7 +8,7 @@
Retrieve the users and groups info. Allow the users to change their passwords. Retrieve the users and groups info. Allow the users to change their passwords.
Sync the users' email addresses with the addresses stored by Nextcloud. Sync the users' email addresses with the addresses stored by Nextcloud.
</description> </description>
<version>4.0.1</version> <version>4.1.0</version>
<licence>agpl</licence> <licence>agpl</licence>
<author>Marcin Łojewski</author> <author>Marcin Łojewski</author>
<author>Andreas Böhler</author> <author>Andreas Böhler</author>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -111,7 +111,7 @@ class EmailSync implements IUserAction
} }
$user->email = $ncMail; $user->email = $ncMail;
$result = $this->userRepository->save($user); $result = $this->userRepository->save($user, UserRepository::EMAIL_FIELD);
} }
break; break;

View File

@@ -111,7 +111,7 @@ class QuotaSync implements IUserAction
} }
$user->quota = $ncQuota; $user->quota = $ncQuota;
$result = $this->userRepository->save($user); $result = $this->userRepository->save($user, UserRepository::QUOTA_FIELD);
} }
break; break;

View File

@@ -403,10 +403,6 @@ final class GroupBackend extends ABackend implements
"Entering getGroupDetails($gid)", ["app" => $this->appName] "Entering getGroupDetails($gid)", ["app" => $this->appName]
); );
if (empty($this->properties[DB::GROUP_NAME_COLUMN])) {
return [];
}
$group = $this->getGroup($gid); $group = $this->getGroup($gid);
if (!($group instanceof Group)) { if (!($group instanceof Group)) {

View File

@@ -292,14 +292,14 @@ final class UserBackend extends ABackend implements
return false; return false;
} }
$user = $this->userRepository->findByUid($uid); $caseSensitive = empty($this->properties[Opt::CASE_INSENSITIVE_USERNAME]);
if (!($user instanceof User)) { $user = $this->userRepository->findByUid($uid, $caseSensitive);
if (!($user instanceof User) || ($caseSensitive && $user->uid !== $uid)) {
return false; return false;
} }
if ($user->salt !== null) { $uid = $user->uid;
$password .= $user->salt; $password = $this->addSalt($user, $password);
}
$isCorrect = $passwordAlgorithm->checkPassword( $isCorrect = $passwordAlgorithm->checkPassword(
$password, $user->password $password, $user->password
@@ -350,6 +350,27 @@ final class UserBackend extends ABackend implements
return $passwordAlgorithm; return $passwordAlgorithm;
} }
/**
* Append or prepend salt from external column if available.
*
* @param User $user The user instance.
* @param string $password The password.
*
* @return string Salted password.
*/
private function addSalt(User $user, string $password): string
{
if ($user->salt !== null) {
if (empty($this->properties[Opt::PREPEND_SALT])) {
return $password . $user->salt;
} else {
return $user->salt . $password;
}
}
return $password;
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
@@ -368,7 +389,7 @@ final class UserBackend extends ABackend implements
$names = []; $names = [];
foreach ($users as $user) { foreach ($users as $user) {
$names[$user->uid] = $user->name; $names[$user] = $user->name;
} }
$this->logger->debug( $this->logger->debug(
@@ -457,9 +478,7 @@ final class UserBackend extends ABackend implements
return false; return false;
} }
if ($user->salt !== null) { $password = $this->addSalt($user, $password);
$password .= $user->salt;
}
$passwordHash = $passwordAlgorithm->getPasswordHash($password); $passwordHash = $passwordAlgorithm->getPasswordHash($password);
if ($passwordHash === false) { if ($passwordHash === false) {
@@ -467,7 +486,7 @@ final class UserBackend extends ABackend implements
} }
$user->password = $passwordHash; $user->password = $passwordHash;
$result = $this->userRepository->save($user); $result = $this->userRepository->save($user, UserRepository::PASSWORD_FIELD);
if ($result === true) { if ($result === true) {
$this->logger->info( $this->logger->info(
@@ -571,7 +590,7 @@ final class UserBackend extends ABackend implements
} }
$user->name = $displayName; $user->name = $displayName;
$result = $this->userRepository->save($user); $result = $this->userRepository->save($user, UserRepository::DISPLAY_NAME_FIELD);
if ($result === true) { if ($result === true) {
$this->logger->info( $this->logger->info(

View File

@@ -28,12 +28,14 @@ namespace OCA\UserSQL\Constant;
*/ */
final class Opt final class Opt
{ {
const CASE_INSENSITIVE_USERNAME = "opt.case_insensitive_username";
const CRYPTO_CLASS = "opt.crypto_class"; const CRYPTO_CLASS = "opt.crypto_class";
const EMAIL_SYNC = "opt.email_sync"; const EMAIL_SYNC = "opt.email_sync";
const HOME_LOCATION = "opt.home_location"; const HOME_LOCATION = "opt.home_location";
const HOME_MODE = "opt.home_mode"; const HOME_MODE = "opt.home_mode";
const NAME_CHANGE = "opt.name_change"; const NAME_CHANGE = "opt.name_change";
const PASSWORD_CHANGE = "opt.password_change"; const PASSWORD_CHANGE = "opt.password_change";
const PREPEND_SALT = "opt.prepend_salt";
const QUOTA_SYNC = "opt.quota_sync"; const QUOTA_SYNC = "opt.quota_sync";
const USE_CACHE = "opt.use_cache"; const USE_CACHE = "opt.use_cache";
} }

View File

@@ -35,9 +35,13 @@ final class Query
const FIND_GROUP_USERS = "find_group_users"; const FIND_GROUP_USERS = "find_group_users";
const FIND_GROUPS = "find_groups"; const FIND_GROUPS = "find_groups";
const FIND_USER = "find_user"; const FIND_USER = "find_user";
const FIND_USER_CASE_INSENSITIVE = "find_user_case_insensitive";
const FIND_USER_GROUPS = "find_user_groups"; const FIND_USER_GROUPS = "find_user_groups";
const FIND_USERS = "find_users"; const FIND_USERS = "find_users";
const SAVE_USER = "save_user"; const UPDATE_DISPLAY_NAME = "update_display_name";
const UPDATE_EMAIL = "update_email";
const UPDATE_PASSWORD = "update_password";
const UPDATE_QUOTA = "update_quota";
const EMAIL_PARAM = "email"; const EMAIL_PARAM = "email";
const GID_PARAM = "gid"; const GID_PARAM = "gid";

View File

@@ -76,7 +76,7 @@ class CryptExtendedDES extends AbstractCrypt
while ($number) { while ($number) {
$rem = $number % $base; $rem = $number % $base;
$number = (int)($number / $base); $number = (int)($number / $base);
$arr[] = $alphabet[$rem]; $chars[] = $alphabet[$rem];
} }
return str_pad(implode($chars), 4, ".", STR_PAD_RIGHT); return str_pad(implode($chars), 4, ".", STR_PAD_RIGHT);

60
lib/Crypto/Drupal7.php Normal file
View File

@@ -0,0 +1,60 @@
<?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;
/**
* Drupal 7 overrides of phpass hash implementation.
*
* @author BrandonKerr
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Drupal7 extends Phpass
{
/**
* The expected (and maximum) number of characters in a hashed password.
*/
const DRUPAL_HASH_LENGTH = 55;
/**
* @inheritdoc
*/
protected function crypt($password, $setting)
{
return substr(parent::crypt($password, $setting), 0, self::DRUPAL_HASH_LENGTH);
}
/**
* @inheritdoc
*/
protected function hash($input)
{
return hash('sha512', $input, true);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Drupal 7";
}
}

View File

@@ -61,7 +61,7 @@ class Phpass extends AbstractAlgorithm
* *
* @return string|null Generated hash. Null on invalid settings. * @return string|null Generated hash. Null on invalid settings.
*/ */
private function crypt($password, $setting) protected function crypt($password, $setting)
{ {
$countLog2 = strpos(self::ITOA64, $setting[3]); $countLog2 = strpos(self::ITOA64, $setting[3]);
if ($countLog2 < 7 || $countLog2 > 30) { if ($countLog2 < 7 || $countLog2 > 30) {
@@ -75,17 +75,29 @@ class Phpass extends AbstractAlgorithm
return null; return null;
} }
$hash = md5($salt . $password, true); $hash = $this->hash($salt . $password);
do { do {
$hash = md5($hash . $password, true); $hash = $this->hash($hash . $password);
} while (--$count); } while (--$count);
$output = substr($setting, 0, 12); $output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16); $output .= $this->encode64($hash, strlen($hash));
return $output; return $output;
} }
/**
* Apply hash function to input.
*
* @param string $input Input string.
*
* @return string Hashed input.
*/
protected function hash($input)
{
return md5($input, true);
}
/** /**
* Encode binary input to base64 string. * Encode binary input to base64 string.
* *

58
lib/Crypto/Whirlpool.php Normal file
View File

@@ -0,0 +1,58 @@
<?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;
use OCP\IL10N;
/**
* Whirlpool hash implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Whirlpool extends AbstractAlgorithm
{
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function __construct(IL10N $localization)
{
parent::__construct($localization);
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
return hash('whirlpool', $password);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Whirlpool";
}
}

View File

@@ -88,11 +88,11 @@ class QueryProvider implements \ArrayAccess
$groupColumns $groupColumns
= "$gGID AS gid, " . = "$gGID AS gid, " .
(empty($gName) ? "null" : $gName) . " AS name, " . (empty($gName) ? $gGID : $gName) . " AS name, " .
(empty($gAdmin) ? "false" : $gAdmin) . " AS admin"; (empty($gAdmin) ? "false" : $gAdmin) . " AS admin";
$userColumns $userColumns
= "$uUID AS uid, " . = "$uUID AS uid, " .
(empty($uName) ? "null" : $uName) . " AS name, " . (empty($uName) ? $uUID : $uName) . " AS name, " .
(empty($uEmail) ? "null" : $uEmail) . " AS email, " . (empty($uEmail) ? "null" : $uEmail) . " AS email, " .
(empty($uQuota) ? "null" : $uQuota) . " AS quota, " . (empty($uQuota) ? "null" : $uQuota) . " AS quota, " .
(empty($uHome) ? "null" : $uHome) . " AS home, " . (empty($uHome) ? "null" : $uHome) . " AS home, " .
@@ -144,6 +144,11 @@ class QueryProvider implements \ArrayAccess
"FROM $user " . "FROM $user " .
"WHERE $uUID = :$uidParam", "WHERE $uUID = :$uidParam",
Query::FIND_USER_CASE_INSENSITIVE =>
"SELECT $userColumns, $uPassword AS password " .
"FROM $user " .
"WHERE lower($uUID) = lower(:$uidParam)",
Query::FIND_USER_GROUPS => Query::FIND_USER_GROUPS =>
"SELECT $groupColumns " . "SELECT $groupColumns " .
"FROM $group, $userGroup " . "FROM $group, $userGroup " .
@@ -157,12 +162,24 @@ class QueryProvider implements \ArrayAccess
"WHERE $uUID LIKE :$searchParam " . "WHERE $uUID LIKE :$searchParam " .
"ORDER BY $uUID", "ORDER BY $uUID",
Query::SAVE_USER => Query::UPDATE_DISPLAY_NAME =>
"UPDATE $user " . "UPDATE $user " .
"SET $uPassword = :$passwordParam, " . "SET $uName = :$nameParam " .
"$uName = :$nameParam, " . "WHERE $uUID = :$uidParam",
"$uEmail = :$emailParam, " .
"$uQuota = :$quotaParam " . Query::UPDATE_EMAIL =>
"UPDATE $user " .
"SET $uEmail = :$emailParam " .
"WHERE $uUID = :$uidParam",
Query::UPDATE_PASSWORD =>
"UPDATE $user " .
"SET $uPassword = :$passwordParam " .
"WHERE $uUID = :$uidParam",
Query::UPDATE_QUOTA =>
"UPDATE $user " .
"SET $uQuota = :$quotaParam " .
"WHERE $uUID = :$uidParam", "WHERE $uUID = :$uidParam",
]; ];
} }

View File

@@ -32,6 +32,11 @@ use OCA\UserSQL\Query\DataQuery;
*/ */
class UserRepository class UserRepository
{ {
const DISPLAY_NAME_FIELD = 0b0001;
const EMAIL_FIELD = 0b0010;
const PASSWORD_FIELD = 0b0100;
const QUOTA_FIELD = 0b1000;
/** /**
* @var DataQuery The data query object. * @var DataQuery The data query object.
*/ */
@@ -48,18 +53,26 @@ class UserRepository
} }
/** /**
* Get a user entity object. * Get an user entity object.
* *
* @param string $uid The user ID. * @param string $uid The user ID.
* @param bool $caseSensitive TRUE for case sensitive search,
* FALSE for case insensitive search.
* *
* @return User The user entity, NULL if it does not exists or * @return User The user entity, NULL if it does not exists or
* FALSE on failure. * FALSE on failure.
*/ */
public function findByUid($uid) public function findByUid($uid, $caseSensitive = true)
{ {
return $this->dataQuery->queryEntity( if ($caseSensitive) {
Query::FIND_USER, User::class, [Query::UID_PARAM => $uid] return $this->dataQuery->queryEntity(
); Query::FIND_USER, User::class, [Query::UID_PARAM => $uid]
);
} else {
return $this->dataQuery->queryEntity(
Query::FIND_USER_CASE_INSENSITIVE, User::class, [Query::UID_PARAM => $uid]
);
}
} }
/** /**
@@ -97,20 +110,48 @@ class UserRepository
/** /**
* Save an user entity object. * Save an user entity object.
* *
* @param User $user The user entity. * @param User $user The user entity.
* @param int $fields Fields to update.
* *
* @return bool TRUE on success, FALSE otherwise. * @return bool TRUE on success, FALSE otherwise.
*/ */
public function save($user) public function save($user, $fields)
{ {
return $this->dataQuery->update( $status = true;
Query::SAVE_USER, [
Query::NAME_PARAM => $user->name, if ($fields & self::DISPLAY_NAME_FIELD) {
Query::PASSWORD_PARAM => $user->password, $status =& $this->dataQuery->update(
Query::EMAIL_PARAM => $user->email, Query::UPDATE_DISPLAY_NAME, [
Query::QUOTA_PARAM => $user->quota, Query::NAME_PARAM => $user->name,
Query::UID_PARAM => $user->uid Query::UID_PARAM => $user->uid
] ]
); );
}
if ($fields & self::PASSWORD_FIELD) {
$status =& $this->dataQuery->update(
Query::UPDATE_PASSWORD, [
Query::PASSWORD_PARAM => $user->password,
Query::UID_PARAM => $user->uid
]
);
}
if ($fields & self::EMAIL_FIELD) {
$status =& $this->dataQuery->update(
Query::UPDATE_EMAIL, [
Query::EMAIL_PARAM => $user->email,
Query::UID_PARAM => $user->uid
]
);
}
if ($fields & self::QUOTA_FIELD) {
$status =& $this->dataQuery->update(
Query::UPDATE_QUOTA, [
Query::QUOTA_PARAM => $user->quota,
Query::UID_PARAM => $user->uid
]
);
}
return $status;
} }
} }

View File

@@ -109,7 +109,8 @@ function print_select_options(
<p class="settings-hint"><?php p($l->t("Here are all currently supported options.")); ?></p> <p class="settings-hint"><?php p($l->t("Here are all currently supported options.")); ?></p>
<fieldset><?php <fieldset><?php
print_checkbox_input($l, "opt-name_change", "Allow display name change", $_["opt.name_change"]); print_checkbox_input($l, "opt-name_change", "Allow display name change", $_["opt.name_change"]);
print_checkbox_input($l, "opt-password_change", "Allow password change", $_["opt.password_change"]); ?> print_checkbox_input($l, "opt-password_change", "Allow password change", $_["opt.password_change"]);
print_checkbox_input($l, "opt-case_insensitive_username", "Case-insensitive username", $_["opt.case_insensitive_username"]); ?>
<div class="button-right"><?php <div class="button-right"><?php
print_checkbox_input($l, "opt-use_cache", "Use cache", $_["opt.use_cache"], false); ?> print_checkbox_input($l, "opt-use_cache", "Use cache", $_["opt.use_cache"], false); ?>
<input type="submit" id="user_sql-clear_cache" value="<?php p($l->t("Clear cache")); ?>"> <input type="submit" id="user_sql-clear_cache" value="<?php p($l->t("Clear cache")); ?>">
@@ -151,7 +152,8 @@ 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-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-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-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"]);
print_checkbox_input($l, "opt-prepend_salt", "Prepend salt", $_["opt.prepend_salt"]); ?>
</fieldset> </fieldset>
</div> </div>
<div class="section"> <div class="section">

View File

@@ -41,7 +41,7 @@ class CryptExtendedDESTest extends TestCase
public function testCheckPassword() public function testCheckPassword()
{ {
$this->assertTrue( $this->assertTrue(
$this->crypto->checkPassword("password", "..UZoIyj/Hy/c") $this->crypto->checkPassword("password", "cDRpdxPmHpzS.")
); );
} }

View File

@@ -0,0 +1,61 @@
<?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 Tests\UserSQL\Crypto;
use OCA\UserSQL\Crypto\Drupal7;
use OCA\UserSQL\Crypto\IPasswordAlgorithm;
use OCP\IL10N;
use Test\TestCase;
/**
* Unit tests for class <code>Drupal7</code>.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Drupal7Test extends TestCase
{
/**
* @var IPasswordAlgorithm
*/
private $crypto;
public function testCheckPassword()
{
$this->assertTrue(
$this->crypto->checkPassword(
"password", "\$S\$DC7eCpJQ3SUQtW4Bp.vKb2rpeaffi4iqk9OpYwJyEoSMsezn67Sl"
)
);
}
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp()
{
parent::setUp();
$this->crypto = new Drupal7($this->createMock(IL10N::class));
}
}

View File

@@ -0,0 +1,62 @@
<?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 Tests\UserSQL\Crypto;
use OCA\UserSQL\Crypto\IPasswordAlgorithm;
use OCA\UserSQL\Crypto\Whirlpool;
use OCP\IL10N;
use Test\TestCase;
/**
* Unit tests for class <code>SHA512Whirlpool</code>.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class WhirlpoolTest extends TestCase
{
/**
* @var IPasswordAlgorithm
*/
private $crypto;
public function testCheckPassword()
{
$this->assertTrue(
$this->crypto->checkPassword(
"password",
"74dfc2b27acfa364da55f93a5caee29ccad3557247eda238831b3e9bd931b01d77fe994e4f12b9d4cfa92a124461d2065197d8cf7f33fc88566da2db2a4d6eae"
)
);
}
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp()
{
parent::setUp();
$this->crypto = new Whirlpool($this->createMock(IL10N::class));
}
}