Merge branch 'master' of https://github.com/palmtown/user_sql into palmtown-master

# Conflicts:
#	lib/Backend/UserBackend.php
This commit is contained in:
Marcin Łojewski
2020-04-13 11:47:47 +02:00
10 changed files with 75 additions and 40 deletions

View File

@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Argon2id support - Argon2id support
- System wide values option - System wide values option
- Allow email login option - Allow email login option
- UID user table column
## [4.4.1] - 2020-02-02 ## [4.4.1] - 2020-02-02
### Fixed ### Fixed

View File

@@ -69,7 +69,8 @@ The definition of user table. The table containing user accounts.
Name | Description | Details 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. **UID** | UID column. | Mandatory for user backend.
**Username** | Username column. | Optional.
**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. **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.
@@ -119,7 +120,8 @@ If you don't have any database model yet you can use below tables (MySQL):
``` ```
CREATE TABLE sql_user CREATE TABLE sql_user
( (
username VARCHAR(16) PRIMARY KEY, uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(16) NOT NULL UNIQUE,
display_name TEXT NULL, display_name TEXT NULL,
email TEXT NULL, email TEXT NULL,
quota TEXT NULL, quota TEXT NULL,
@@ -244,3 +246,4 @@ Since version 4.0.0 the whole core implementation has been rewritten.
* Andreas Boehler for releasing the first version of this application * Andreas Boehler for releasing the first version of this application
* Johan Hendriks provided his user_postfixadmin * Johan Hendriks provided his user_postfixadmin
* Ed Wildgoose for fixing possible SQL injection vulnerability * Ed Wildgoose for fixing possible SQL injection vulnerability
* Brandon Lee for implementing feature to separate uid from username resolving issues #108 & #121

View File

@@ -119,7 +119,7 @@ user_sql.adminSettingsUI = function () {
); );
autocomplete( autocomplete(
"#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-disabled, #db-table-user-column-avatar, #db-table-user-column-salt", "#db-table-user-column-uid, #db-table-user-column-username, #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-disabled, #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

@@ -301,15 +301,15 @@ final class UserBackend extends ABackend implements
* Check if the user's password is correct then return its ID or * Check if the user's password is correct then return its ID or
* FALSE on failure. * FALSE on failure.
* *
* @param string $uid The user ID. * @param string $username The username.
* @param string $password The password. * @param string $password The password.
* *
* @return string|bool The user ID on success, false otherwise. * @return string|bool The user ID on success, false otherwise.
*/ */
public function checkPassword(string $uid, string $password) public function checkPassword(string $username, string $password)
{ {
$this->logger->debug( $this->logger->debug(
"Entering checkPassword($uid, *)", ["app" => $this->appName] "Entering checkPassword($username, *)", ["app" => $this->appName]
); );
$passwordAlgorithm = $this->getPasswordAlgorithm(); $passwordAlgorithm = $this->getPasswordAlgorithm();
@@ -320,9 +320,9 @@ final class UserBackend extends ABackend implements
$caseSensitive = empty($this->properties[Opt::CASE_INSENSITIVE_USERNAME]); $caseSensitive = empty($this->properties[Opt::CASE_INSENSITIVE_USERNAME]);
$emailLogin = !empty($this->properties[Opt::EMAIL_LOGIN]); $emailLogin = !empty($this->properties[Opt::EMAIL_LOGIN]);
if ($emailLogin) { if ($emailLogin) {
$user = $this->userRepository->findByUidOrEmail($uid, $caseSensitive); $user = $this->userRepository->findByUsernameOrEmail($username, $caseSensitive);
} else { } else {
$user = $this->userRepository->findByUid($uid, $caseSensitive); $user = $this->userRepository->findByUsername($username, $caseSensitive);
} }
if (!($user instanceof User)) { if (!($user instanceof User)) {

View File

@@ -2,7 +2,7 @@
/** /**
* Nextcloud - user_sql * Nextcloud - user_sql
* *
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me> * @copyright 2020 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -55,4 +55,5 @@ final class DB
const USER_QUOTA_COLUMN = "db.table.user.column.quota"; const USER_QUOTA_COLUMN = "db.table.user.column.quota";
const USER_SALT_COLUMN = "db.table.user.column.salt"; 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";
const USER_USERNAME_COLUMN = "db.table.user.column.username";
} }

View File

@@ -34,10 +34,11 @@ final class Query
const FIND_GROUP = "find_group"; const FIND_GROUP = "find_group";
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_BY_UID = "find_user_by_uid";
const FIND_USER_BY_UID_OR_EMAIL = "find_user_by_uid_or_email"; const FIND_USER_BY_USERNAME = "find_user_by_username";
const FIND_USER_BY_UID_OR_EMAIL_CASE_INSENSITIVE = "find_user_by_uid_or_email_case_insensitive"; const FIND_USER_BY_USERNAME_CASE_INSENSITIVE = "find_user_by_username_case_insensitive";
const FIND_USER_CASE_INSENSITIVE = "find_user_case_insensitive"; const FIND_USER_BY_USERNAME_OR_EMAIL = "find_user_by_username_or_email";
const FIND_USER_BY_USERNAME_OR_EMAIL_CASE_INSENSITIVE = "find_user_by_username_or_email_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 UPDATE_DISPLAY_NAME = "update_display_name"; const UPDATE_DISPLAY_NAME = "update_display_name";
@@ -52,4 +53,5 @@ final class Query
const QUOTA_PARAM = "quota"; const QUOTA_PARAM = "quota";
const SEARCH_PARAM = "search"; const SEARCH_PARAM = "search";
const UID_PARAM = "uid"; const UID_PARAM = "uid";
const USERNAME_PARAM = "username";
} }

View File

@@ -2,7 +2,7 @@
/** /**
* Nextcloud - user_sql * Nextcloud - user_sql
* *
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me> * @copyright 2020 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -29,9 +29,13 @@ namespace OCA\UserSQL\Model;
class User class User
{ {
/** /**
* @var string The UID (username). * @var mixed The UID.
*/ */
public $uid; public $uid;
/**
* @var string The user's username (login name).
*/
public $username;
/** /**
* @var string The user's email address. * @var string The user's email address.
*/ */

View File

@@ -64,18 +64,19 @@ class QueryProvider implements \ArrayAccess
$gAdmin = $this->properties[DB::GROUP_ADMIN_COLUMN]; $gAdmin = $this->properties[DB::GROUP_ADMIN_COLUMN];
$gGID = $this->properties[DB::GROUP_GID_COLUMN]; $gGID = $this->properties[DB::GROUP_GID_COLUMN];
$gName = $this->properties[DB::GROUP_NAME_COLUMN]; $gName = $this->properties[DB::GROUP_NAME_COLUMN] || $this->properties[DB::GROUP_GID_COLUMN];
$uActive = $this->properties[DB::USER_ACTIVE_COLUMN]; $uActive = $this->properties[DB::USER_ACTIVE_COLUMN];
$uAvatar = $this->properties[DB::USER_AVATAR_COLUMN]; $uAvatar = $this->properties[DB::USER_AVATAR_COLUMN];
$uDisabled = $this->properties[DB::USER_DISABLED_COLUMN]; $uDisabled = $this->properties[DB::USER_DISABLED_COLUMN];
$uEmail = $this->properties[DB::USER_EMAIL_COLUMN]; $uEmail = $this->properties[DB::USER_EMAIL_COLUMN];
$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] || $this->properties[DB::USER_USERNAME_COLUMN] || $this->properties[DB::USER_UID_COLUMN];
$uPassword = $this->properties[DB::USER_PASSWORD_COLUMN]; $uPassword = $this->properties[DB::USER_PASSWORD_COLUMN];
$uQuota = $this->properties[DB::USER_QUOTA_COLUMN]; $uQuota = $this->properties[DB::USER_QUOTA_COLUMN];
$uSalt = $this->properties[DB::USER_SALT_COLUMN]; $uSalt = $this->properties[DB::USER_SALT_COLUMN];
$uUID = $this->properties[DB::USER_UID_COLUMN]; $uUID = $this->properties[DB::USER_UID_COLUMN];
$uUsername = $this->properties[DB::USER_USERNAME_COLUMN] || $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];
@@ -87,16 +88,18 @@ class QueryProvider implements \ArrayAccess
$quotaParam = Query::QUOTA_PARAM; $quotaParam = Query::QUOTA_PARAM;
$searchParam = Query::SEARCH_PARAM; $searchParam = Query::SEARCH_PARAM;
$uidParam = Query::UID_PARAM; $uidParam = Query::UID_PARAM;
$usernameParam = Query::USERNAME_PARAM;
$reverseActiveOpt = $this->properties[Opt::REVERSE_ACTIVE]; $reverseActiveOpt = $this->properties[Opt::REVERSE_ACTIVE];
$groupColumns $groupColumns
= "g.$gGID AS gid, " . = "g.$gGID AS gid, " .
(empty($gName) ? "g." . $gGID : "g." . $gName) . " AS name, " . "g.$gName AS name, " .
(empty($gAdmin) ? "false" : "g." . $gAdmin) . " AS admin"; (empty($gAdmin) ? "false" : "g." . $gAdmin) . " AS admin";
$userColumns $userColumns
= "u.$uUID AS uid, " . = "u.$uUID AS uid, " .
(empty($uName) ? "u." . $uUID : "u." . $uName) . " AS name, " . "u.$uUsername AS username, " .
"u.$uName AS name, " .
(empty($uEmail) ? "null" : "u." . $uEmail) . " AS email, " . (empty($uEmail) ? "null" : "u." . $uEmail) . " AS email, " .
(empty($uQuota) ? "null" : "u." . $uQuota) . " AS quota, " . (empty($uQuota) ? "null" : "u." . $uQuota) . " AS quota, " .
(empty($uHome) ? "null" : "u." . $uHome) . " AS home, " . (empty($uHome) ? "null" : "u." . $uHome) . " AS home, " .
@@ -134,8 +137,7 @@ class QueryProvider implements \ArrayAccess
"SELECT ug.$ugUID AS uid " . "SELECT ug.$ugUID AS uid " .
"FROM $userGroup ug " . "FROM $userGroup ug " .
"WHERE ug.$ugGID = :$gidParam " . "WHERE ug.$ugGID = :$gidParam " .
"AND ug.$ugUID " . "AND ug.$ugUID LIKE :$searchParam " .
"LIKE :$searchParam " .
"ORDER BY ug.$ugUID", "ORDER BY ug.$ugUID",
Query::FIND_GROUPS => Query::FIND_GROUPS =>
@@ -145,28 +147,34 @@ class QueryProvider implements \ArrayAccess
(empty($gName) ? "" : "OR g.$gName LIKE :$searchParam ") . (empty($gName) ? "" : "OR g.$gName LIKE :$searchParam ") .
"ORDER BY g.$gGID", "ORDER BY g.$gGID",
Query::FIND_USER => Query::FIND_USER_BY_UID =>
"SELECT $userColumns, u.$uPassword AS password " . "SELECT $userColumns " .
"FROM $user u " . "FROM $user u " .
"WHERE u.$uUID = :$uidParam " . "WHERE u.$uUID = :$uidParam " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
Query::FIND_USER_BY_UID_OR_EMAIL => Query::FIND_USER_BY_USERNAME =>
"SELECT $userColumns, u.$uPassword AS password " . "SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " . "FROM $user u " .
"WHERE u.$uUID = :$uidParam OR u.$uEmail = :$emailParam " . "WHERE u.$uUsername = :$usernameParam " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
Query::FIND_USER_BY_UID_OR_EMAIL_CASE_INSENSITIVE => Query::FIND_USER_BY_USERNAME_CASE_INSENSITIVE =>
"SELECT $userColumns, u.$uPassword AS password " . "SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " . "FROM $user u " .
"WHERE lower(u.$uUID) = lower(:$uidParam) OR lower(u.$uEmail) = lower(:$emailParam) " . "WHERE lower(u.$uUsername) = lower(:$usernameParam) " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
Query::FIND_USER_CASE_INSENSITIVE => Query::FIND_USER_BY_USERNAME_OR_EMAIL =>
"SELECT $userColumns, u.$uPassword AS password " . "SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " . "FROM $user u " .
"WHERE lower(u.$uUID) = lower(:$uidParam) " . "WHERE u.$uUsername = :$usernameParam OR u.$uEmail = :$emailParam " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_OR_EMAIL_CASE_INSENSITIVE =>
"SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " .
"WHERE lower(u.$uUsername) = lower(:$usernameParam) OR lower(u.$uEmail) = lower(:$emailParam) " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
Query::FIND_USER_GROUPS => Query::FIND_USER_GROUPS =>

View File

@@ -55,22 +55,37 @@ class UserRepository
/** /**
* Get an user entity object. * Get an user entity object.
* *
* @param string $uid The user ID. * @param mixed $uid The user ID.
*
* @return User The user entity, NULL if it does not exists or
* FALSE on failure.
*/
public function findByUid($uid)
{
return $this->dataQuery->queryEntity(
Query::FIND_USER_BY_UID, User::class, [Query::UID_PARAM => $uid]
);
}
/**
* Get an user entity object.
*
* @param string $username The username.
* @param bool $caseSensitive TRUE for case sensitive search, * @param bool $caseSensitive TRUE for case sensitive search,
* FALSE for case insensitive 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, $caseSensitive = true) public function findByUsername($username, $caseSensitive = true)
{ {
if ($caseSensitive) { if ($caseSensitive) {
return $this->dataQuery->queryEntity( return $this->dataQuery->queryEntity(
Query::FIND_USER, User::class, [Query::UID_PARAM => $uid] Query::FIND_USER_BY_USERNAME, User::class, [Query::USERNAME_PARAM => $username]
); );
} else { } else {
return $this->dataQuery->queryEntity( return $this->dataQuery->queryEntity(
Query::FIND_USER_CASE_INSENSITIVE, User::class, [Query::UID_PARAM => $uid] Query::FIND_USER_BY_USERNAME_CASE_INSENSITIVE, User::class, [Query::USERNAME_PARAM => $username]
); );
} }
} }
@@ -78,24 +93,24 @@ class UserRepository
/** /**
* Get an user entity object. * Get an user entity object.
* *
* @param string $query The user ID or email address. * @param string $query The username or email address.
* @param bool $caseSensitive TRUE for case sensitive search, * @param bool $caseSensitive TRUE for case sensitive search,
* FALSE for case insensitive 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 findByUidOrEmail($query, $caseSensitive = true) public function findByUsernameOrEmail($query, $caseSensitive = true)
{ {
if ($caseSensitive) { if ($caseSensitive) {
return $this->dataQuery->queryEntity( return $this->dataQuery->queryEntity(
Query::FIND_USER_BY_UID_OR_EMAIL, User::class, Query::FIND_USER_BY_USERNAME_OR_EMAIL, User::class,
[Query::UID_PARAM => $query, Query::EMAIL_PARAM => $query] [Query::USERNAME_PARAM => $query, Query::EMAIL_PARAM => $query]
); );
} else { } else {
return $this->dataQuery->queryEntity( return $this->dataQuery->queryEntity(
Query::FIND_USER_BY_UID_OR_EMAIL_CASE_INSENSITIVE, User::class, Query::FIND_USER_BY_USERNAME_OR_EMAIL_CASE_INSENSITIVE, User::class,
[Query::UID_PARAM => $query, Query::EMAIL_PARAM => $query] [Query::USERNAME_PARAM => $query, Query::EMAIL_PARAM => $query]
); );
} }
} }

View File

@@ -154,7 +154,8 @@ function print_select_options(
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", "UID", $_["db.table.user.column.uid"]);
print_text_input($l, "db-table-user-column-username", "Username", $_["db.table.user.column.username"]);
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-quota", "Quota", $_["db.table.user.column.quota"]); print_text_input($l, "db-table-user-column-quota", "Quota", $_["db.table.user.column.quota"]);
print_text_input($l, "db-table-user-column-home", "Home", $_["db.table.user.column.home"]); print_text_input($l, "db-table-user-column-home", "Home", $_["db.table.user.column.home"]);