Merge branch 'release/v4.0.0'

This commit is contained in:
Marcin Łojewski
2018-08-11 11:46:20 +02:00
63 changed files with 1000 additions and 190 deletions

View File

@@ -4,18 +4,36 @@ 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).
## [v4.0.0-rc2] ## [4.0.0] - 2018-08-11
### Added
- SHA512 Whirlpool hash algorithm
- WoltLab Community Framework 2.x hash algorithm
- phpass hash implementation
- Support for salt column
- User quota synchronization
### Changed
- Example SQL script in README file
- Fixed misspelling
### Changed
- Support for Nextcloud 14 only
- Group backend implementation
- User backend implementation
### Fixed
- Table and column autocomplete in settings panel
## [4.0.0-rc2] - 2018-06-14
### Added ### Added
- User active column - User active column
### Changed ### Changed
- Fixed "Use of undefined constant" error for Argon2 Crypt with PHP below 7.2. - Fixed "Use of undefined constant" error for Argon2 Crypt with PHP below 7.2.
## [4.0.0-rc1] ## [4.0.0-rc1] - 2018-06-13
### Added ### Added
- New hashing algorithms: Argon2 Crypt (PHP 7.2 and above), Blowfish Crypt, Courier base64-encoded MD5, Courier base64-encoded SHA1, - New hash algorithms: Argon2 Crypt (PHP 7.2 and above), Blowfish Crypt, Courier base64-encoded MD5, Courier base64-encoded SHA1, Courier base64-encoded SHA256, Courier hexadecimal MD5, Extended DES Crypt, SHA256 Crypt, SHA512 Crypt, SSHA512, Standard DES Crypt
Courier base64-encoded SHA256, Courier hexadecimal MD5, Extended DES Crypt, SHA256 Crypt,
SHA512 Crypt, SSHA512, Standard DES Crypt
- Option to allow users to change their display names - Option to allow users to change their display names
- Option to allow user to change its avatar - Option to allow user to change its avatar
- Database query results cache - Database query results cache
@@ -26,10 +44,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- The whole core implementation, which is NOT COMPATIBLE with the previous versions. - The whole core implementation, which is NOT COMPATIBLE with the previous versions.
- Minimum supported PHP version - 7.0 - Minimum supported PHP version - 7.0
## Removed ### Removed
- MySQL ENCRYPT() hashing implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release. - MySQL ENCRYPT() hash implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release.
- MySQL PASSWORD() hashing implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release. - MySQL PASSWORD() hash implementation - Function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release.
- Redmine hashing implementation - Cannot implement in new core system. - Redmine hash implementation - Cannot implement in new core system.
- User active column - Use database view instead - User active column - Use database view instead
- Domain support - Domain support
@@ -65,6 +83,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
[v4.0.0-rc2]: https://github.com/nextcloud/user_sql/compare/v4.0.0-rc1...v4.0.0-rc2 [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-rc1]: https://github.com/nextcloud/user_sql/compare/v3.1.0...v4.0.0-rc1 [4.0.0-rc1]: https://github.com/nextcloud/user_sql/compare/v3.1.0...v4.0.0-rc1
[3.1.0]: https://github.com/nextcloud/user_sql/compare/v2.4.0...v3.1.0 [3.1.0]: https://github.com/nextcloud/user_sql/compare/v2.4.0...v3.1.0

View File

@@ -50,10 +50,11 @@ 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.
**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.
**Hashing 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 storage 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 storage. | 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.
**Home mode** | User storage path.<br/>- *Default* - Let the Nextcloud manage this. The default option.<br/>- *Query* - Use location from the user table pointed by the *home* column.<br/>- *Static* - Use static location. The `%u` variable is replaced with the username of the user. | Optional<br/>Default: *Default*. **Quota sync** | Sync user quota with the Nextcloud.<br/>- *None* - Disables this feature. This is the default option.<br/>- *Synchronise only once* - Copy the user quota to the Nextcloud preferences if its not set.<br/>- *Nextcloud always wins* - Always copy the user quota to the database. This updates the user table.<br/>- *SQL always wins* - Always copy the user quota to the Nextcloud preferences. | Optional.<br/>Default: *None*.<br/>Requires: user *Quota* column.
**Home Location** | User storage path for the `static` *home mode*. | Mandatory if the *Home mode* is set to `Static`. **Home mode** | User storage path.<br/>- *Default* - Let the Nextcloud manage this. The default option.<br/>- *Query* - Use location from the user table pointed by the *home* column.<br/>- *Static* - Use static location pointed by the *Home Location* option. | Optional<br/>Default: *Default*.
**Home Location** | User storage path for the `Static` *Home mode*. The `%u` variable is replaced with the username of the user. | Mandatory if the *Home mode* is set to `Static`.
#### User table #### User table
@@ -64,11 +65,13 @@ Name | Description | Details
**Table name** | The table name. | Mandatory for user backend. **Table name** | The table name. | Mandatory for user backend.
**Username** | Username column. | Mandatory for user backend. **Username** | Username column. | Mandatory for user backend.
**Email** | E-mail column. | Mandatory for *Email sync* option. **Email** | E-mail column. | Mandatory for *Email sync* option.
**Quota** | Quota column. | Mandatory for *Quota sync* option.
**Home** | Home path column. | Mandatory for `Query` *Home sync* option. **Home** | Home path column. | Mandatory for `Query` *Home sync* option.
**Password** | Password hash column. | Mandatory for user backend. **Password** | Password hash column. | Mandatory for user backend.
**Display name** | Display name column. | Optional. **Display name** | Display name column. | Optional.
**Active** | Flag indicating if user can log in. | Optional.<br/>Default: true. **Active** | Flag indicating if user can log in. | Optional.<br/>Default: true.
**Can change 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.
#### Group table #### Group table
@@ -105,36 +108,34 @@ but be aware that some functionalities requires data changes (update queries).
If you don't have any database model yet you can use below tables (MySQL): If you don't have any database model yet you can use below tables (MySQL):
``` ```
CREATE TABLE sql_users CREATE TABLE sql_user
( (
id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(16) PRIMARY KEY,
username VARCHAR(16) NOT NULL, display_name TEXT NULL,
display_name TEXT NULL, email TEXT NULL,
email TEXT NULL, quota TEXT NULL,
home TEXT NULL, home TEXT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
active TINYINT(1) NOT NULL DEFAULT '1', active TINYINT(1) NOT NULL DEFAULT '1',
can_change_avatar BOOLEAN NOT NULL DEFAULT FALSE, provide_avatar BOOLEAN NOT NULL DEFAULT FALSE
CONSTRAINT users_username_uindex UNIQUE (username)
); );
CREATE TABLE sql_group CREATE TABLE sql_group
( (
id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(16) PRIMARY KEY,
name VARCHAR(16) NOT NULL,
display_name TEXT NULL, display_name TEXT NULL,
admin BOOLEAN NOT NULL DEFAULT FALSE, admin BOOLEAN NOT NULL DEFAULT FALSE
CONSTRAINT group_name_uindex UNIQUE (name)
); );
CREATE TABLE sql_user_group CREATE TABLE sql_user_group
( (
id INT AUTO_INCREMENT PRIMARY KEY,
group_name VARCHAR(16) NOT NULL,
username VARCHAR(16) NOT NULL, username VARCHAR(16) NOT NULL,
CONSTRAINT user_group_group_name_username_uindex UNIQUE (group_name, username), group_name VARCHAR(16) NOT NULL,
INDEX user_group_group_name_index (group_name), PRIMARY KEY (username, group_name),
INDEX user_group_username_index (username) FOREIGN KEY (username) REFERENCES sql_user (username),
FOREIGN KEY (group_name) REFERENCES sql_group (name),
INDEX sql_user_group_username_idx (username),
INDEX sql_user_group_group_name_idx (group_name)
); );
``` ```
@@ -148,7 +149,7 @@ User table: wp_users
Username column: user_login Username column: user_login
Password column: user_pass Password column: user_pass
Hashing algorithm: Unix (Crypt) Hash algorithm: Unix (Crypt) or Portable PHP password
``` ```
#### JHipster #### JHipster
@@ -165,7 +166,7 @@ Password column: password_hash
Email column: email Email column: email
Active column: activated Active column: activated
Hashing algorithm: Unix (Crypt) Hash algorithm: Unix (Crypt)
``` ```
## Hash algorithms ## Hash algorithms
@@ -190,9 +191,12 @@ SHA512 (Crypt) | Generates hash with 5000 rounds. | $6$rounds=5000$yH.Q0OL4qbCOU
Standard DES (Crypt) | | yTBnb7ab/N072 Standard DES (Crypt) | | yTBnb7ab/N072
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/
SHA1 | No salt supported. | 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 SHA1 | No salt supported. | 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
SHA512 Whirlpool | No salt supported. | a96b16ebb691dbe968b0d66d0d924cff5cf5de5e0885181d00761d87f295b2bf3d3c66187c050fc01c196ff3acaa48d3561ffd170413346e934a32280d632f2e
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
## Development ## Development
@@ -202,7 +206,7 @@ Add a new class in the `OCA\UserSQL\Platform` namespace which extends the `Abstr
Add this driver in `admin.php` template to `$drivers` variable and in method `getPlatform(Connection $connection)` Add this driver in `admin.php` template to `$drivers` variable and in method `getPlatform(Connection $connection)`
of `PlatformFactory` class. of `PlatformFactory` class.
#### New hashing algorithm support #### New hash algorithm support
Create a new class in `OCA\UserSQL\Crypto` namespace which implements `IPasswordAlgorithm` interface. Create a new class in `OCA\UserSQL\Crypto` namespace which implements `IPasswordAlgorithm` interface.
Do not forget to write unit tests. Do not forget to write unit tests.

View File

@@ -8,10 +8,10 @@
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.0-rc2</version> <version>4.0.0</version>
<licence>agpl</licence> <licence>agpl</licence>
<author>Andreas Böhler &lt;dev (at) aboehler (dot) at&gt;</author> <author>Marcin Łojewski</author>
<author>Marcin Łojewski &lt;dev@mlojewski.me&gt;</author> <author>Andreas Böhler</author>
<namespace>UserSQL</namespace> <namespace>UserSQL</namespace>
<bugs>https://github.com/nextcloud/user_sql/issues</bugs> <bugs>https://github.com/nextcloud/user_sql/issues</bugs>
<repository>https://github.com/nextcloud/user_sql</repository> <repository>https://github.com/nextcloud/user_sql</repository>
@@ -22,7 +22,7 @@
<category>auth</category> <category>auth</category>
<dependencies> <dependencies>
<php min-version="7.0"/> <php min-version="7.0"/>
<nextcloud min-version="13" max-version="13"/> <nextcloud min-version="14" max-version="14"/>
</dependencies> </dependencies>
<settings> <settings>
<admin>\OCA\UserSQL\Settings\Admin</admin> <admin>\OCA\UserSQL\Settings\Admin</admin>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -41,6 +41,7 @@ user_sql.adminSettingsUI = function () {
$(ids).autocomplete({ $(ids).autocomplete({
source: function (request, response) { source: function (request, response) {
var post = $(form_id).serializeArray(); var post = $(form_id).serializeArray();
post.push({name: "input", value: request["term"]});
$.post(OC.generateUrl(path), post, response, "json"); $.post(OC.generateUrl(path), post, response, "json");
}, },
minLength: 0, minLength: 0,
@@ -75,7 +76,7 @@ user_sql.adminSettingsUI = function () {
); );
autocomplete( autocomplete(
"#db-table-user-column-uid, #db-table-user-column-email, #db-table-user-column-home, #db-table-user-column-password, #db-table-user-column-name, #db-table-user-column-active, #db-table-user-column-avatar", "#db-table-user-column-uid, #db-table-user-column-email, #db-table-user-column-quota, #db-table-user-column-home, #db-table-user-column-password, #db-table-user-column-name, #db-table-user-column-active, #db-table-user-column-avatar, #db-table-user-column-salt",
"/apps/user_sql/settings/autocomplete/table/user" "/apps/user_sql/settings/autocomplete/table/user"
); );

View File

@@ -94,7 +94,7 @@ class EmailSync implements IUserAction
$result = false; $result = false;
switch ($this->properties[Opt::EMAIL_SYNC]) { switch ($this->properties[Opt::EMAIL_SYNC]) {
case App::EMAIL_INITIAL: case App::SYNC_INITIAL:
if (empty($ncMail) && !empty($user->email)) { if (empty($ncMail) && !empty($user->email)) {
$this->config->setUserValue( $this->config->setUserValue(
$user->uid, "settings", "email", $user->email $user->uid, "settings", "email", $user->email
@@ -103,7 +103,7 @@ class EmailSync implements IUserAction
$result = true; $result = true;
break; break;
case App::EMAIL_FORCE_NC: case App::SYNC_FORCE_NC:
if (!empty($ncMail) && $user->email !== $ncMail) { if (!empty($ncMail) && $user->email !== $ncMail) {
$user = $this->userRepository->findByUid($user->uid); $user = $this->userRepository->findByUid($user->uid);
if (!($user instanceof User)) { if (!($user instanceof User)) {
@@ -115,7 +115,7 @@ class EmailSync implements IUserAction
} }
break; break;
case App::EMAIL_FORCE_SQL: case App::SYNC_FORCE_SQL:
if (!empty($user->email) && $user->email !== $ncMail) { if (!empty($user->email) && $user->email !== $ncMail) {
$this->config->setUserValue( $this->config->setUserValue(
$user->uid, "settings", "email", $user->email $user->uid, "settings", "email", $user->email

137
lib/Action/QuotaSync.php Normal file
View File

@@ -0,0 +1,137 @@
<?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\Action;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\Opt;
use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig;
use OCP\ILogger;
/**
* Synchronizes the user quota.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class QuotaSync implements IUserAction
{
/**
* @var string The application name.
*/
private $appName;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var IConfig The config instance.
*/
private $config;
/**
* @var UserRepository The user repository.
*/
private $userRepository;
/**
* The default constructor.
*
* @param string $appName The application name.
* @param ILogger $logger The logger instance.
* @param Properties $properties The properties array.
* @param IConfig $config The config instance.
* @param UserRepository $userRepository The user repository.
*/
public function __construct(
$appName, ILogger $logger, Properties $properties, IConfig $config,
UserRepository $userRepository
) {
$this->appName = $appName;
$this->logger = $logger;
$this->properties = $properties;
$this->config = $config;
$this->userRepository = $userRepository;
}
/**
* @inheritdoc
* @throws \OCP\PreConditionNotMetException
*/
public function doAction(User $user)
{
$this->logger->debug(
"Entering QuotaSync#doAction($user->uid)", ["app" => $this->appName]
);
$ncQuota = $this->config->getUserValue(
$user->uid, "files", "quota", ""
);
$result = false;
switch ($this->properties[Opt::QUOTA_SYNC]) {
case App::SYNC_INITIAL:
if (empty($ncQuota) && !empty($user->quota)) {
$this->config->setUserValue(
$user->uid, "files", "quota", $user->quota
);
}
$result = true;
break;
case App::SYNC_FORCE_NC:
if (!empty($ncQuota) && $user->quota !== $ncQuota) {
$user = $this->userRepository->findByUid($user->uid);
if (!($user instanceof User)) {
break;
}
$user->quota = $ncQuota;
$result = $this->userRepository->save($user);
}
break;
case App::SYNC_FORCE_SQL:
if (!empty($user->quota) && $user->quota !== $ncQuota) {
$this->config->setUserValue(
$user->uid, "files", "quota", $user->quota
);
}
$result = true;
break;
}
$this->logger->debug(
"Returning QuotaSync#doAction($user->uid): " . ($result ? "true"
: "false"),
["app" => $this->appName]
);
return $result;
}
}

View File

@@ -21,12 +21,15 @@
namespace OCA\UserSQL\Backend; namespace OCA\UserSQL\Backend;
use OC\Group\Backend;
use OCA\UserSQL\Cache; use OCA\UserSQL\Cache;
use OCA\UserSQL\Constant\DB; use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Model\Group; use OCA\UserSQL\Model\Group;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\GroupRepository; use OCA\UserSQL\Repository\GroupRepository;
use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\ICountUsersBackend;
use OCP\Group\Backend\IGroupDetailsBackend;
use OCP\Group\Backend\IIsAdminBackend;
use OCP\ILogger; use OCP\ILogger;
/** /**
@@ -34,7 +37,10 @@ use OCP\ILogger;
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */
final class GroupBackend extends Backend final class GroupBackend extends ABackend implements
ICountUsersBackend,
IGroupDetailsBackend,
IIsAdminBackend
{ {
/** /**
* @var string The application name. * @var string The application name.
@@ -128,14 +134,9 @@ final class GroupBackend extends Backend
} }
/** /**
* Returns the number of users in given group matching the search term. * @inheritdoc
*
* @param string $gid The group ID.
* @param string $search The search term.
*
* @return int The number of users in given group matching the search term.
*/ */
public function countUsersInGroup($gid, $search = "") public function countUsersInGroup(string $gid, string $search = ""): int
{ {
$this->logger->debug( $this->logger->debug(
"Entering countUsersInGroup($gid, $search)", "Entering countUsersInGroup($gid, $search)",
@@ -355,18 +356,18 @@ final class GroupBackend extends Backend
} }
/** /**
* Checks if a user is in the admin group. * @inheritdoc
*
* @param string $uid User ID.
*
* @return bool TRUE if a user is in the admin group, FALSE otherwise.
*/ */
public function isAdmin($uid) public function isAdmin(string $uid = null): bool
{ {
$this->logger->debug( $this->logger->debug(
"Entering isAdmin($uid)", ["app" => $this->appName] "Entering isAdmin($uid)", ["app" => $this->appName]
); );
if (empty($this->properties[DB::GROUP_ADMIN_COLUMN]) || $uid === null) {
return false;
}
$cacheKey = self::class . "admin_" . $uid; $cacheKey = self::class . "admin_" . $uid;
$admin = $this->cache->get($cacheKey); $admin = $this->cache->get($cacheKey);
@@ -394,18 +395,18 @@ final class GroupBackend extends Backend
} }
/** /**
* Get associative array of the group details. * @inheritdoc
*
* @param string $gid The group ID.
*
* @return array Associative array of the group details.
*/ */
public function getGroupDetails($gid) public function getGroupDetails(string $gid): array
{ {
$this->logger->debug( $this->logger->debug(
"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)) {
@@ -421,21 +422,6 @@ final class GroupBackend extends Backend
return $details; return $details;
} }
/**
* @inheritdoc
*/
public function getSupportedActions()
{
$actions = parent::getSupportedActions();
$actions &= empty($this->properties[DB::GROUP_ADMIN_COLUMN])
? ~Backend::IS_ADMIN : ~0;
$actions &= empty($this->properties[DB::GROUP_NAME_COLUMN])
? ~Backend::GROUP_DETAILS : ~0;
return $actions;
}
/** /**
* Check if this backend is correctly set and can be enabled. * Check if this backend is correctly set and can be enabled.
* *

View File

@@ -21,9 +21,9 @@
namespace OCA\UserSQL\Backend; namespace OCA\UserSQL\Backend;
use OC\User\Backend;
use OCA\UserSQL\Action\EmailSync; use OCA\UserSQL\Action\EmailSync;
use OCA\UserSQL\Action\IUserAction; use OCA\UserSQL\Action\IUserAction;
use OCA\UserSQL\Action\QuotaSync;
use OCA\UserSQL\Cache; use OCA\UserSQL\Cache;
use OCA\UserSQL\Constant\App; use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\DB; use OCA\UserSQL\Constant\DB;
@@ -35,13 +35,28 @@ use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\User\Backend\ABackend;
use OCP\User\Backend\ICheckPasswordBackend;
use OCP\User\Backend\ICountUsersBackend;
use OCP\User\Backend\IGetDisplayNameBackend;
use OCP\User\Backend\IGetHomeBackend;
use OCP\User\Backend\IProvideAvatarBackend;
use OCP\User\Backend\ISetDisplayNameBackend;
use OCP\User\Backend\ISetPasswordBackend;
/** /**
* The SQL user backend manager. * The SQL user backend manager.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */
final class UserBackend extends Backend final class UserBackend extends ABackend implements
ICheckPasswordBackend,
ICountUsersBackend,
IGetDisplayNameBackend,
IGetHomeBackend,
IProvideAvatarBackend,
ISetDisplayNameBackend,
ISetPasswordBackend
{ {
/** /**
* @var string The application name. * @var string The application name.
@@ -116,6 +131,14 @@ final class UserBackend extends Backend
$this->userRepository $this->userRepository
); );
} }
if (!empty($this->properties[Opt::QUOTA_SYNC])
&& !empty($this->properties[DB::USER_QUOTA_COLUMN])
) {
$this->actions[] = new QuotaSync(
$this->appName, $this->logger, $this->properties, $this->config,
$this->userRepository
);
}
} }
/** /**
@@ -228,7 +251,7 @@ final class UserBackend extends Backend
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function getDisplayName($uid) public function getDisplayName($uid): string
{ {
$this->logger->debug( $this->logger->debug(
"Entering getDisplayName($uid)", ["app" => $this->appName] "Entering getDisplayName($uid)", ["app" => $this->appName]
@@ -258,7 +281,7 @@ final class UserBackend extends Backend
* *
* @return string|bool The user ID on success, false otherwise. * @return string|bool The user ID on success, false otherwise.
*/ */
public function checkPassword($uid, $password) public function checkPassword(string $uid, string $password)
{ {
$this->logger->debug( $this->logger->debug(
"Entering checkPassword($uid, *)", ["app" => $this->appName] "Entering checkPassword($uid, *)", ["app" => $this->appName]
@@ -274,6 +297,10 @@ final class UserBackend extends Backend
return false; return false;
} }
if ($user->salt !== null) {
$password .= $user->salt;
}
$isCorrect = $passwordAlgorithm->checkPassword( $isCorrect = $passwordAlgorithm->checkPassword(
$password, $user->password $password, $user->password
); );
@@ -333,6 +360,10 @@ final class UserBackend extends Backend
["app" => $this->appName] ["app" => $this->appName]
); );
if (empty($this->properties[DB::USER_NAME_COLUMN])) {
return false;
}
$users = $this->getUsers($search, $limit, $offset); $users = $this->getUsers($search, $limit, $offset);
$names = []; $names = [];
@@ -406,24 +437,32 @@ final class UserBackend extends Backend
* *
* @return bool TRUE if the password has been set, FALSE otherwise. * @return bool TRUE if the password has been set, FALSE otherwise.
*/ */
public function setPassword($uid, $password) public function setPassword(string $uid, string $password): bool
{ {
$this->logger->debug( $this->logger->debug(
"Entering setPassword($uid, *)", ["app" => "user_sql"] "Entering setPassword($uid, *)", ["app" => "user_sql"]
); );
if (empty($this->properties[Opt::PASSWORD_CHANGE])) {
return false;
}
$passwordAlgorithm = $this->getPasswordAlgorithm(); $passwordAlgorithm = $this->getPasswordAlgorithm();
if ($passwordAlgorithm === false) { if ($passwordAlgorithm === false) {
return false; return false;
} }
$passwordHash = $passwordAlgorithm->getPasswordHash($password); $user = $this->userRepository->findByUid($uid);
if ($passwordHash === false) { if (!($user instanceof User)) {
return false; return false;
} }
$user = $this->userRepository->findByUid($uid); if ($user->salt !== null) {
if (!($user instanceof User)) { $password .= $user->salt;
}
$passwordHash = $passwordAlgorithm->getPasswordHash($password);
if ($passwordHash === false) {
return false; return false;
} }
@@ -444,12 +483,16 @@ final class UserBackend extends Backend
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function getHome($uid) public function getHome(string $uid)
{ {
$this->logger->debug( $this->logger->debug(
"Entering getHome($uid)", ["app" => $this->appName] "Entering getHome($uid)", ["app" => $this->appName]
); );
if (empty($this->properties[Opt::HOME_MODE])) {
return false;
}
$home = false; $home = false;
switch ($this->properties[Opt::HOME_MODE]) { switch ($this->properties[Opt::HOME_MODE]) {
case App::HOME_STATIC: case App::HOME_STATIC:
@@ -479,12 +522,16 @@ final class UserBackend extends Backend
* *
* @return bool TRUE if the user can change its avatar, FALSE otherwise. * @return bool TRUE if the user can change its avatar, FALSE otherwise.
*/ */
public function canChangeAvatar($uid) public function canChangeAvatar(string $uid): bool
{ {
$this->logger->debug( $this->logger->debug(
"Entering canChangeAvatar($uid)", ["app" => $this->appName] "Entering canChangeAvatar($uid)", ["app" => $this->appName]
); );
if (empty($this->properties[DB::USER_AVATAR_COLUMN])) {
return false;
}
$user = $this->userRepository->findByUid($uid); $user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) { if (!($user instanceof User)) {
return false; return false;
@@ -507,13 +554,17 @@ final class UserBackend extends Backend
* *
* @return bool TRUE if the password has been set, FALSE otherwise. * @return bool TRUE if the password has been set, FALSE otherwise.
*/ */
public function setDisplayName($uid, $displayName) public function setDisplayName(string $uid, string $displayName): bool
{ {
$this->logger->debug( $this->logger->debug(
"Entering setDisplayName($uid, $displayName)", "Entering setDisplayName($uid, $displayName)",
["app" => $this->appName] ["app" => $this->appName]
); );
if (empty($this->properties[Opt::NAME_CHANGE])) {
return false;
}
$user = $this->userRepository->findByUid($uid); $user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) { if (!($user instanceof User)) {
return false; return false;
@@ -533,28 +584,6 @@ final class UserBackend extends Backend
return false; return false;
} }
/**
* @inheritdoc
*/
public function getSupportedActions()
{
$actions = parent::getSupportedActions();
$actions &= empty($this->properties[DB::USER_NAME_COLUMN])
? ~Backend::GET_DISPLAYNAME : ~0;
$actions &= empty($this->properties[Opt::HOME_MODE])
? ~Backend::GET_HOME : ~0;
$actions &= empty($this->properties[DB::USER_AVATAR_COLUMN])
? ~Backend::PROVIDE_AVATAR : ~0;
$actions &= (!empty($this->properties[DB::USER_NAME_COLUMN])
&& $this->properties[Opt::NAME_CHANGE]) ? ~0
: ~Backend::SET_DISPLAYNAME;
$actions &= $this->properties[Opt::PASSWORD_CHANGE] ? ~0
: ~Backend::SET_PASSWORD;
return $actions;
}
/** /**
* Check if this backend is correctly set and can be enabled. * Check if this backend is correctly set and can be enabled.
* *
@@ -572,4 +601,20 @@ final class UserBackend extends Backend
&& !empty($this->properties[DB::USER_PASSWORD_COLUMN]) && !empty($this->properties[DB::USER_PASSWORD_COLUMN])
&& !empty($this->properties[Opt::CRYPTO_CLASS]); && !empty($this->properties[Opt::CRYPTO_CLASS]);
} }
/**
* @inheritdoc
*/
public function getBackendName()
{
return "User SQL";
}
/**
* @inheritdoc
*/
public function deleteUser($uid)
{
return false;
}
} }

View File

@@ -34,7 +34,7 @@ final class App
const HOME_QUERY = "query"; const HOME_QUERY = "query";
const HOME_STATIC = "static"; const HOME_STATIC = "static";
const EMAIL_FORCE_NC = "force_nc"; const SYNC_FORCE_NC = "force_nc";
const EMAIL_FORCE_SQL = "force_sql"; const SYNC_FORCE_SQL = "force_sql";
const EMAIL_INITIAL = "initial"; const SYNC_INITIAL = "initial";
} }

View File

@@ -51,5 +51,7 @@ final class DB
const USER_HOME_COLUMN = "db.table.user.column.home"; const USER_HOME_COLUMN = "db.table.user.column.home";
const USER_NAME_COLUMN = "db.table.user.column.name"; const USER_NAME_COLUMN = "db.table.user.column.name";
const USER_PASSWORD_COLUMN = "db.table.user.column.password"; const USER_PASSWORD_COLUMN = "db.table.user.column.password";
const USER_QUOTA_COLUMN = "db.table.user.column.quota";
const USER_SALT_COLUMN = "db.table.user.column.salt";
const USER_UID_COLUMN = "db.table.user.column.uid"; const USER_UID_COLUMN = "db.table.user.column.uid";
} }

View File

@@ -34,5 +34,6 @@ final class Opt
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 QUOTA_SYNC = "opt.quota_sync";
const USE_CACHE = "opt.use_cache"; const USE_CACHE = "opt.use_cache";
} }

View File

@@ -39,9 +39,11 @@ final class Query
const FIND_USERS = "find_users"; const FIND_USERS = "find_users";
const SAVE_USER = "save_user"; const SAVE_USER = "save_user";
const EMAIL_PARAM = "email";
const GID_PARAM = "gid"; const GID_PARAM = "gid";
const NAME_PARAM = "name"; const NAME_PARAM = "name";
const PASSWORD_PARAM = "password"; const PASSWORD_PARAM = "password";
const QUOTA_PARAM = "quota";
const SEARCH_PARAM = "search"; const SEARCH_PARAM = "search";
const UID_PARAM = "uid"; const UID_PARAM = "uid";
} }

View File

@@ -266,7 +266,8 @@ class SettingsController extends Controller
try { try {
$connection = $this->getConnection(); $connection = $this->getConnection();
$platform = PlatformFactory::getPlatform($connection); $platform = PlatformFactory::getPlatform($connection);
$tables = $platform->getTables(); $input = $this->request->getParam("input");
$tables = $platform->getTables($input);
$this->logger->debug( $this->logger->debug(
"Returning tableAutocomplete(): count(" . count($tables) . ")", "Returning tableAutocomplete(): count(" . count($tables) . ")",
@@ -314,7 +315,8 @@ class SettingsController extends Controller
$connection = $this->getConnection(); $connection = $this->getConnection();
$platform = PlatformFactory::getPlatform($connection); $platform = PlatformFactory::getPlatform($connection);
$columns = $platform->getColumns( $columns = $platform->getColumns(
$this->request->getParam($table) $this->request->getParam($table),
$this->request->getParam("input")
); );
return $columns; return $columns;

View File

@@ -22,8 +22,8 @@
namespace OCA\UserSQL\Crypto; namespace OCA\UserSQL\Crypto;
/** /**
* Abstract Unix Crypt hashing implementation. * Abstract Unix Crypt hash implementation.
* The hashing algorithm depends on the chosen salt. * The hash algorithm depends on the chosen salt.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
@@ -52,12 +52,9 @@ abstract class AbstractCrypt extends AbstractAlgorithm
} }
/** /**
* Generate a salt string for the hashing algorithm. * Generate a salt string for the hash algorithm.
* *
* @return string The salt string. * @return string The salt string.
*/ */
protected function getSalt() protected abstract function getSalt();
{
return "";
}
} }

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Courier MD5 hashing implementation. * Courier MD5 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Courier MD5 RAW hashing implementation. * Courier MD5 RAW hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Courier SHA1 hashing implementation. * Courier SHA1 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Courier SHA256 hashing implementation. * Courier SHA256 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Unix Crypt hashing implementation. * Unix Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
@@ -56,4 +56,12 @@ class Crypt extends AbstractCrypt
{ {
return "Unix (Crypt)"; return "Unix (Crypt)";
} }
/**
* Not used.
*/
protected function getSalt()
{
return null;
}
} }

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Argon2 Crypt hashing implementation. * Argon2 Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Blowfish Crypt hashing implementation. * Blowfish Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Extended DES Crypt hashing implementation. * Extended DES Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* MD5 Crypt hashing implementation. * MD5 Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SHA256 Crypt hashing implementation. * SHA256 Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SHA512 Crypt hashing implementation. * SHA512 Crypt hash implementation.
* *
* @see crypt() * @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Standard DES Crypt hashing implementation. * Standard DES Crypt hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* Joomla hashing implementation. * Joomla hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* MD5 hashing implementation. * MD5 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

153
lib/Crypto/Phpass.php Normal file
View File

@@ -0,0 +1,153 @@
<?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;
/**
* phpass hash implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Phpass extends AbstractAlgorithm
{
const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
private $iterationCount;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $iterationCount Iteration count (log2).
* This value must be between 4 and 31.
*/
public function __construct(IL10N $localization, $iterationCount = 8)
{
parent::__construct($localization);
$this->iterationCount = $iterationCount;
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->crypt($password, $dbHash));
}
/**
* @param string $password Password to encrypt.
* @param string $setting Hash settings.
*
* @return string|null Generated hash. Null on invalid settings.
*/
private function crypt($password, $setting)
{
$countLog2 = strpos(self::ITOA64, $setting[3]);
if ($countLog2 < 7 || $countLog2 > 30) {
return null;
}
$count = 1 << $countLog2;
$salt = substr($setting, 4, 8);
if (strlen($salt) !== 8) {
return null;
}
$hash = md5($salt . $password, true);
do {
$hash = md5($hash . $password, true);
} while (--$count);
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
/**
* Encode binary input to base64 string.
*
* @param string $input Binary data.
* @param int $count Data size.
*
* @return string Base64 encoded data.
*/
private function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= self::ITOA64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= self::ITOA64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= self::ITOA64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= self::ITOA64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
return $this->crypt($password, $this->genSalt());
}
/**
* Generate salt for the hash.
*
* @return string Salt string.
*/
private function genSalt()
{
$output = '$P$';
$output .= self::ITOA64[min($this->iterationCount + 5, 30)];
$output .= $this->encode64(random_bytes(6), 6);
return $output;
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Portable PHP password";
}
}

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SHA1 hashing implementation. * SHA1 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

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;
/**
* SHA512 Whirlpool hash implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class SHA512Whirlpool 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('sha512', hash('whirlpool', $password));
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SHA512 Whirlpool";
}
}

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SSHA* hashing implementation. * SSHA* hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SSHA256 hashing implementation. * SSHA256 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -24,7 +24,7 @@ namespace OCA\UserSQL\Crypto;
use OCP\IL10N; use OCP\IL10N;
/** /**
* SSHA512 hashing implementation. * SSHA512 hash implementation.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */

View File

@@ -56,7 +56,7 @@ final class Utils
{ {
$string = ""; $string = "";
for ($idx = 0; $idx != $length; ++$idx) { for ($idx = 0; $idx != $length; ++$idx) {
$string .= $alphabet[mt_rand(0, strlen($alphabet) - 1)]; $string .= $alphabet[random_int(0, strlen($alphabet) - 1)];
} }
return $string; return $string;
} }

63
lib/Crypto/WCF2.php Normal file
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;
/**
* WCF2 hash implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class WCF2 extends AbstractCrypt
{
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, crypt(crypt($password, $dbHash), $dbHash));
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
$salt = $this->getSalt();
return crypt(crypt($password, $salt), $salt);
}
/**
* @inheritdoc
*/
protected function getSalt()
{
return "$2a$08$" . Utils::randomString(22, self::SALT_ALPHABET) . "$";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "WoltLab Community Framework 2.x";
}
}

View File

@@ -36,6 +36,10 @@ class User
* @var string The user's email address. * @var string The user's email address.
*/ */
public $email; public $email;
/**
* @var string The user quota.
*/
public $quota;
/** /**
* @var string The user's display name. * @var string The user's display name.
*/ */
@@ -56,4 +60,8 @@ class User
* @var bool Can user change its avatar. * @var bool Can user change its avatar.
*/ */
public $avatar; public $avatar;
/**
* @var string The password's salt.
*/
public $salt;
} }

View File

@@ -49,12 +49,13 @@ abstract class AbstractPlatform
/** /**
* Get all the tables defined in the database. * Get all the tables defined in the database.
* *
* @param bool $schemaPrefix Show schema name in the results. * @param string $phrase Show only tables containing given phrase.
* @param bool $schemaPrefix Show schema name in the results.
* *
* @return array Array with table names. * @return array Array with table names.
* @throws DBALException On a database exception. * @throws DBALException On a database exception.
*/ */
public function getTables($schemaPrefix = false) public function getTables($phrase = "", $schemaPrefix = false)
{ {
$platform = $this->connection->getDatabasePlatform(); $platform = $this->connection->getDatabasePlatform();
@@ -68,13 +69,17 @@ abstract class AbstractPlatform
$result = $this->connection->executeQuery($queryTables); $result = $this->connection->executeQuery($queryTables);
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$name = $this->getTableName($row, $schemaPrefix); $name = $this->getTableName($row, $schemaPrefix);
$tables[] = $name; if (preg_match("/.*$phrase.*/i", $name)) {
$tables[] = $name;
}
} }
$result = $this->connection->executeQuery($queryViews); $result = $this->connection->executeQuery($queryViews);
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$name = $this->getViewName($row, $schemaPrefix); $name = $this->getViewName($row, $schemaPrefix);
$tables[] = $name; if (preg_match("/.*$phrase.*/i", $name)) {
$tables[] = $name;
}
} }
return $tables; return $tables;
@@ -103,12 +108,13 @@ abstract class AbstractPlatform
/** /**
* Get all the columns defined in the table. * Get all the columns defined in the table.
* *
* @param string $table The table name. * @param string $table The table name.
* @param string $phrase Show only columns containing given phrase.
* *
* @return array Array with column names. * @return array Array with column names.
* @throws DBALException On a database exception. * @throws DBALException On a database exception.
*/ */
public function getColumns($table) public function getColumns($table, $phrase = "")
{ {
$platform = $this->connection->getDatabasePlatform(); $platform = $this->connection->getDatabasePlatform();
$query = $platform->getListTableColumnsSQL($table); $query = $platform->getListTableColumnsSQL($table);
@@ -118,7 +124,9 @@ abstract class AbstractPlatform
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$name = $this->getColumnName($row); $name = $this->getColumnName($row);
$columns[] = $name; if (preg_match("/.*$phrase.*/i", $name)) {
$columns[] = $name;
}
} }
return $columns; return $columns;

View File

@@ -71,14 +71,18 @@ class QueryProvider implements \ArrayAccess
$uHome = $this->properties[DB::USER_HOME_COLUMN]; $uHome = $this->properties[DB::USER_HOME_COLUMN];
$uName = $this->properties[DB::USER_NAME_COLUMN]; $uName = $this->properties[DB::USER_NAME_COLUMN];
$uPassword = $this->properties[DB::USER_PASSWORD_COLUMN]; $uPassword = $this->properties[DB::USER_PASSWORD_COLUMN];
$uQuota = $this->properties[DB::USER_QUOTA_COLUMN];
$uSalt = $this->properties[DB::USER_SALT_COLUMN];
$uUID = $this->properties[DB::USER_UID_COLUMN]; $uUID = $this->properties[DB::USER_UID_COLUMN];
$ugGID = $this->properties[DB::USER_GROUP_GID_COLUMN]; $ugGID = $this->properties[DB::USER_GROUP_GID_COLUMN];
$ugUID = $this->properties[DB::USER_GROUP_UID_COLUMN]; $ugUID = $this->properties[DB::USER_GROUP_UID_COLUMN];
$emailParam = Query::EMAIL_PARAM;
$gidParam = Query::GID_PARAM; $gidParam = Query::GID_PARAM;
$nameParam = Query::NAME_PARAM; $nameParam = Query::NAME_PARAM;
$passwordParam = Query::PASSWORD_PARAM; $passwordParam = Query::PASSWORD_PARAM;
$quotaParam = Query::QUOTA_PARAM;
$searchParam = Query::SEARCH_PARAM; $searchParam = Query::SEARCH_PARAM;
$uidParam = Query::UID_PARAM; $uidParam = Query::UID_PARAM;
@@ -90,9 +94,11 @@ class QueryProvider implements \ArrayAccess
= "$uUID AS uid, " . = "$uUID AS uid, " .
(empty($uName) ? "null" : $uName) . " AS name, " . (empty($uName) ? "null" : $uName) . " AS name, " .
(empty($uEmail) ? "null" : $uEmail) . " AS email, " . (empty($uEmail) ? "null" : $uEmail) . " AS email, " .
(empty($uQuota) ? "null" : $uQuota) . " AS quota, " .
(empty($uHome) ? "null" : $uHome) . " AS home, " . (empty($uHome) ? "null" : $uHome) . " AS home, " .
(empty($uActive) ? "true" : $uActive) . " AS active, " . (empty($uActive) ? "true" : $uActive) . " AS active, " .
(empty($uAvatar) ? "false" : $uAvatar) . " AS avatar"; (empty($uAvatar) ? "false" : $uAvatar) . " AS avatar, " .
(empty($uSalt) ? "null" : $uSalt) . " AS salt";
$this->queries = [ $this->queries = [
Query::BELONGS_TO_ADMIN => Query::BELONGS_TO_ADMIN =>
@@ -154,7 +160,9 @@ class QueryProvider implements \ArrayAccess
Query::SAVE_USER => Query::SAVE_USER =>
"UPDATE $user " . "UPDATE $user " .
"SET $uPassword = :$passwordParam, " . "SET $uPassword = :$passwordParam, " .
"$uName = :$nameParam " . "$uName = :$nameParam, " .
"$uEmail = :$emailParam, " .
"$uQuota = :$quotaParam " .
"WHERE $uUID = :$uidParam", "WHERE $uUID = :$uidParam",
]; ];
} }

View File

@@ -107,6 +107,8 @@ class UserRepository
Query::SAVE_USER, [ Query::SAVE_USER, [
Query::NAME_PARAM => $user->name, Query::NAME_PARAM => $user->name,
Query::PASSWORD_PARAM => $user->password, Query::PASSWORD_PARAM => $user->password,
Query::EMAIL_PARAM => $user->email,
Query::QUOTA_PARAM => $user->quota,
Query::UID_PARAM => $user->uid Query::UID_PARAM => $user->uid
] ]
); );

View File

@@ -21,8 +21,8 @@
use OCP\IL10N; use OCP\IL10N;
script('user_sql', 'settings'); script("user_sql", "settings");
style('user_sql', 'settings'); style("user_sql", "settings");
function print_text_input(IL10N $l, $id, $label, $value = "", $type = "text") function print_text_input(IL10N $l, $id, $label, $value = "", $type = "text")
{ {
@@ -71,6 +71,16 @@ function print_select_options(
echo "</option>"; echo "</option>";
} }
#54: Merge develop-14 into develop Conflicts
Resolved all conflicts
2 conflicting files
CHANGELOG.md
CHANGELOG.md
admin.php
templates/admin.php
templates/admin.php
Resolved
echo "</select>"; echo "</select>";
echo "</label></div>"; echo "</label></div>";
} }
@@ -94,11 +104,11 @@ function print_select_options(
<p class="settings-hint"><?php p($l->t("Define your database connection parameters.")); ?></p> <p class="settings-hint"><?php p($l->t("Define your database connection parameters.")); ?></p>
<fieldset><?php <fieldset><?php
$drivers = ["mysql" => "MySQL", "pgsql" => "PostgreSQL"]; $drivers = ["mysql" => "MySQL", "pgsql" => "PostgreSQL"];
print_select_options($l, "db-driver", "SQL driver", $drivers, $_['db.driver']); print_select_options($l, "db-driver", "SQL driver", $drivers, $_["db.driver"]);
print_text_input($l, "db-hostname", "Hostname", $_['db.hostname']); print_text_input($l, "db-hostname", "Hostname", $_["db.hostname"]);
print_text_input($l, "db-database", "Database", $_['db.database']); print_text_input($l, "db-database", "Database", $_["db.database"]);
print_text_input($l, "db-username", "Username", $_['db.username']); print_text_input($l, "db-username", "Username", $_["db.username"]);
print_text_input($l, "db-password", "Password", $_['db.password'], "password"); ?> print_text_input($l, "db-password", "Password", $_["db.password"], "password"); ?>
<div class="button-right"> <div class="button-right">
<input type="submit" id="user_sql-db_connection_verify" value="<?php p($l->t("Verify settings")); ?>"> <input type="submit" id="user_sql-db_connection_verify" value="<?php p($l->t("Verify settings")); ?>">
</div> </div>
@@ -108,76 +118,79 @@ function print_select_options(
<h2><?php p($l->t("Options")); ?></h2> <h2><?php p($l->t("Options")); ?></h2>
<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"]); ?>
<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")); ?>">
</div> </div>
<?php <?php
$hashing = []; $hashes = [];
foreach (glob(__DIR__ . "/../lib/Crypto/*.php") as $filename) { foreach (glob(__DIR__ . "/../lib/Crypto/*.php") as $filename) {
$class = 'OCA\\UserSQL\\Crypto\\' . basename(substr($filename, 0, -4)); $class = "OCA\\UserSQL\\Crypto\\" . basename(substr($filename, 0, -4));
try { try {
$passwordAlgorithm = new $class($l); $passwordAlgorithm = new $class($l);
if ($passwordAlgorithm instanceof if ($passwordAlgorithm instanceof
\OCA\UserSQL\Crypto\IPasswordAlgorithm \OCA\UserSQL\Crypto\IPasswordAlgorithm
) { ) {
$hashing[$class] = $passwordAlgorithm->getVisibleName(); $hashes[$class] = $passwordAlgorithm->getVisibleName();
} }
} catch (Throwable $e) { } catch (Throwable $e) {
} }
} }
print_select_options($l, "opt-crypto_class", "Hashing algorithm", $hashing, $_['opt.crypto_class']); print_select_options($l, "opt-crypto_class", "Hash algorithm", $hashes, $_["opt.crypto_class"]);
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-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-home_mode", "Home mode", ["" => "Default", "query" => "Query", "static" => "Static"], $_['opt.home_mode']); 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_text_input($l, "opt-home_location", "Home Location", $_['opt.home_location']); ?> print_select_options($l, "opt-home_mode", "Home mode", ["" => "Default", "query" => "Query", "static" => "Static"], $_["opt.home_mode"]);
print_text_input($l, "opt-home_location", "Home Location", $_["opt.home_location"]); ?>
</fieldset> </fieldset>
</div> </div>
<div class="section clear-left"> <div class="section clear-left">
<h2><?php p($l->t("User table")); ?></h2> <h2><?php p($l->t("User table")); ?></h2>
<p class="settings-hint"><?php p($l->t("Table containing user accounts.")); ?></p> <p class="settings-hint"><?php p($l->t("Table containing user accounts.")); ?></p>
<fieldset><?php <fieldset><?php
print_text_input($l, "db-table-user", "Table name", $_['db.table.user']) ;?> print_text_input($l, "db-table-user", "Table name", $_["db.table.user"]); ?>
<h3><?php p($l->t("Columns")); ?></h3> <h3><?php p($l->t("Columns")); ?></h3>
<?php <?php
print_text_input($l, "db-table-user-column-uid", "Username", $_['db.table.user.column.uid']); print_text_input($l, "db-table-user-column-uid", "Username", $_["db.table.user.column.uid"]);
print_text_input($l, "db-table-user-column-email", "Email", $_['db.table.user.column.email']); print_text_input($l, "db-table-user-column-email", "Email", $_["db.table.user.column.email"]);
print_text_input($l, "db-table-user-column-home", "Home", $_['db.table.user.column.home']); print_text_input($l, "db-table-user-column-quota", "Quota", $_["db.table.user.column.quota"]);
print_text_input($l, "db-table-user-column-password", "Password", $_['db.table.user.column.password']); print_text_input($l, "db-table-user-column-home", "Home", $_["db.table.user.column.home"]);
print_text_input($l, "db-table-user-column-name", "Display name", $_['db.table.user.column.name']); print_text_input($l, "db-table-user-column-password", "Password", $_["db.table.user.column.password"]);
print_text_input($l, "db-table-user-column-active", "Active", $_['db.table.user.column.active']); print_text_input($l, "db-table-user-column-name", "Display name", $_["db.table.user.column.name"]);
print_text_input($l, "db-table-user-column-avatar", "Can change avatar", $_['db.table.user.column.avatar']); ?> 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"]); ?>
</fieldset> </fieldset>
</div> </div>
<div class="section"> <div class="section">
<h2><?php p($l->t("Group table")); ?></h2> <h2><?php p($l->t("Group table")); ?></h2>
<p class="settings-hint"><?php p($l->t("Group definitions table.")); ?></p> <p class="settings-hint"><?php p($l->t("Group definitions table.")); ?></p>
<fieldset><?php <fieldset><?php
print_text_input($l, "db-table-group", "Table name", $_['db.table.group']); ?> print_text_input($l, "db-table-group", "Table name", $_["db.table.group"]); ?>
<h3><?php p($l->t("Columns")); ?></h3> <h3><?php p($l->t("Columns")); ?></h3>
<?php <?php
print_text_input($l, "db-table-group-column-admin", "Is admin", $_['db.table.group.column.admin']); print_text_input($l, "db-table-group-column-admin", "Is admin", $_["db.table.group.column.admin"]);
print_text_input($l, "db-table-group-column-name", "Display name", $_['db.table.group.column.name']); print_text_input($l, "db-table-group-column-name", "Display name", $_["db.table.group.column.name"]);
print_text_input($l, "db-table-group-column-gid", "Group name", $_['db.table.group.column.gid']); ?> print_text_input($l, "db-table-group-column-gid", "Group name", $_["db.table.group.column.gid"]); ?>
</fieldset> </fieldset>
</div> </div>
<div class="section"> <div class="section">
<h2><?php p($l->t("User group table")); ?></h2> <h2><?php p($l->t("User group table")); ?></h2>
<p class="settings-hint"><?php p($l->t("Associative table which maps users to groups.")); ?></p> <p class="settings-hint"><?php p($l->t("Associative table which maps users to groups.")); ?></p>
<fieldset><?php <fieldset><?php
print_text_input($l, "db-table-user_group", "Table name", $_['db.table.user_group']); ?> print_text_input($l, "db-table-user_group", "Table name", $_["db.table.user_group"]); ?>
<h3><?php p($l->t("Columns")); ?></h3> <h3><?php p($l->t("Columns")); ?></h3>
<?php <?php
print_text_input($l, "db-table-user_group-column-uid", "Username", $_['db.table.user_group.column.uid']); print_text_input($l, "db-table-user_group-column-uid", "Username", $_["db.table.user_group.column.uid"]);
print_text_input($l, "db-table-user_group-column-gid", "Group name", $_['db.table.user_group.column.gid']); ?> print_text_input($l, "db-table-user_group-column-gid", "Group name", $_["db.table.user_group.column.gid"]); ?>
</fieldset> </fieldset>
</div> </div>
</div> </div>
<div class="section"> <div class="section">
<input type="hidden" name="appname" value="user_sql"/> <input type="hidden" name="appname" value="user_sql"/>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>" id="requesttoken"/> <input type="hidden" name="requesttoken" value="<?php p($_["requesttoken"]); ?>" id="requesttoken"/>
<input id="user_sql-save" type="submit" value="<?php p($l->t('Save')); ?>"/> <input id="user_sql-save" type="submit" value="<?php p($l->t("Save")); ?>"/>
</div> </div>
</form> </form>

View File

@@ -43,6 +43,12 @@ class CleartextTest extends TestCase
$this->assertTrue($this->crypto->checkPassword("password", "password")); $this->assertTrue($this->crypto->checkPassword("password", "password"));
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -47,6 +47,12 @@ class CourierMD5RawTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -47,6 +47,12 @@ class CourierMD5Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -47,6 +47,12 @@ class CourierSHA1Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CourierSHA256Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CryptArgon2Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CryptBlowfishTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -45,6 +45,12 @@ class CryptExtendedDESTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -47,6 +47,12 @@ class CryptMD5Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CryptSHA256Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CryptSHA512Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -45,6 +45,12 @@ class CryptStandardDESTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class CryptTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class JoomlaTest extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -47,6 +47,12 @@ class MD5Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

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\Phpass;
use OCA\UserSQL\Crypto\IPasswordAlgorithm;
use OCP\IL10N;
use Test\TestCase;
/**
* Unit tests for class <code>PhpassTest</code>.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class PhpassTest extends TestCase
{
/**
* @var IPasswordAlgorithm
*/
private $crypto;
public function testCheckPassword()
{
$this->assertTrue(
$this->crypto->checkPassword(
"password", "\$P\$BxrwraqNTi4as0EI.IpiA/K.muk9ke/"
)
);
}
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp()
{
parent::setUp();
$this->crypto = new Phpass($this->createMock(IL10N::class));
}
}

View File

@@ -47,6 +47,12 @@ class SHA1Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

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

View File

@@ -48,6 +48,12 @@ class SSHA256Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

View File

@@ -48,6 +48,12 @@ class SSHA512Test extends TestCase
); );
} }
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();

62
tests/Crypto/WCF2Test.php Normal file
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\WCF2;
use OCP\IL10N;
use Test\TestCase;
/**
* Unit tests for class <code>WCF2</code>.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class WCF2Test extends TestCase
{
/**
* @var IPasswordAlgorithm
*/
private $crypto;
public function testCheckPassword()
{
$this->assertTrue(
$this->crypto->checkPassword(
"password",
"$2a$08\$XEQDKNU/Vbootwxv5Gp7gujxFX/RUFsZLvQPYM435Dd3/p17fto02"
)
);
}
public function testPasswordHash()
{
$hash = $this->crypto->getPasswordHash("password");
$this->assertTrue($this->crypto->checkPassword("password", $hash));
}
protected function setUp()
{
parent::setUp();
$this->crypto = new WCF2($this->createMock(IL10N::class));
}
}