diff --git a/README.md b/README.md
index ee11548..a5b4d09 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ Name | Description | Details
**Email sync** | Sync e-mail address with the Nextcloud.
- *None* - Disables this feature. This is the default option.
- *Synchronise only once* - Copy the e-mail address to the Nextcloud preferences if its not set.
- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.
- *SQL always wins* - Always copy the e-mail address to the Nextcloud preferences. | Optional.
Default: *None*.
Requires: user *Email* column.
**Quota sync** | Sync user quota with the Nextcloud.
- *None* - Disables this feature. This is the default option.
- *Synchronise only once* - Copy the user quota to the Nextcloud preferences if its not set.
- *Nextcloud always wins* - Always copy the user quota to the database. This updates the user table.
- *SQL always wins* - Always copy the user quota to the Nextcloud preferences. | Optional.
Default: *None*.
Requires: user *Quota* column.
**Home mode** | User storage path.
- *Default* - Let the Nextcloud manage this. The default option.
- *Query* - Use location from the user table pointed by the *home* column.
- *Static* - Use static location pointed by the *Home Location* option. | Optional
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`.
+**Home location** | User storage path for the `Static` *Home mode*. The `%u` variable is replaced with the uid of the user. | Mandatory if the *Home mode* is set to `Static`.
**Default group** | Default group for all 'User SQL' users. | Optional.
#### User table
@@ -74,7 +74,7 @@ Name | Description | Details
--- | --- | ---
**Table name** | The table name. | Mandatory for user backend.
**UID** | User ID column. | Mandatory for user backend.
-**Username** | Username column. | Optional.
+**Username** | Username column which is used **only** for password verification. | Optional. If unsure leave it blank and use only the `uid` column.
**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.
@@ -120,12 +120,15 @@ For all options to work three tables are required:
If you already have an existing database you can always create database views which fits this model,
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). Please note that the optional `username` above really is only
+used for password matching and defaults to be equal to the `uid`
+column. You also may want to compare with the `oc_users` and
+`oc_groups` table from you Nextcloud instance.
```
CREATE TABLE sql_user
(
- uid INT PRIMARY KEY AUTO_INCREMENT,
- username VARCHAR(16) NOT NULL UNIQUE,
+ uid VARCHAR(64) PRIMARY KEY,
display_name TEXT NULL,
email TEXT NULL,
quota TEXT NULL,
@@ -139,15 +142,15 @@ CREATE TABLE sql_user
CREATE TABLE sql_group
(
- gid INT PRIMARY KEY AUTO_INCREMENT,
- name VARCHAR(16) NOT NULL UNIQUE,
- admin BOOLEAN NOT NULL DEFAULT FALSE
+ gid VARCHAR(64) PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ admin BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE sql_user_group
(
- uid INT NOT NULL,
- gid INT NOT NULL,
+ uid VARCHAR(64),
+ gid VARCHAR(64),
PRIMARY KEY (uid, gid),
FOREIGN KEY (uid) REFERENCES sql_user (uid),
FOREIGN KEY (gid) REFERENCES sql_group (gid),
diff --git a/appinfo/info.xml b/appinfo/info.xml
index be1bfa0..78b30f4 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -21,8 +21,8 @@
auth
-
-
+
+
\OCA\UserSQL\Settings\Admin
diff --git a/lib/Backend/GroupBackend.php b/lib/Backend/GroupBackend.php
index c41a40d..e48a2a5 100644
--- a/lib/Backend/GroupBackend.php
+++ b/lib/Backend/GroupBackend.php
@@ -31,7 +31,11 @@
use OCP\Group\Backend\ICountUsersBackend;
use OCP\Group\Backend\IGroupDetailsBackend;
use OCP\Group\Backend\IIsAdminBackend;
+use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\ILogger;
+use OCP\IUserManager;
+
+use OC\User\LazyUser;
/**
* The SQL group backend manager.
@@ -41,7 +45,8 @@
final class GroupBackend extends ABackend implements
ICountUsersBackend,
IGroupDetailsBackend,
- IIsAdminBackend
+ IIsAdminBackend,
+ ISearchableGroupBackend
{
const USER_SQL_GID = "user_sql";
@@ -354,16 +359,16 @@ public function usersInGroup($gid, $search = "", $limit = -1, $offset = 0)
["app" => $this->appName]
);
- $cacheKey = self::class . "group_users_" . $gid . "_" . $search . "_"
+ $cacheKey = self::class . "group_uids_" . $gid . "_" . $search . "_"
. $limit . "_" . $offset;
- $users = $this->cache->get($cacheKey);
+ $uids = $this->cache->get($cacheKey);
- if (!is_null($users)) {
+ if (!is_null($uids)) {
$this->logger->debug(
"Returning from cache usersInGroup($gid, $search, $limit, $offset): count("
- . count($users) . ")", ["app" => $this->appName]
+ . count($uids) . ")", ["app" => $this->appName]
);
- return $users;
+ return $uids;
}
$uids = $this->groupRepository->findAllUidsBySearchTerm(
@@ -383,6 +388,50 @@ public function usersInGroup($gid, $search = "", $limit = -1, $offset = 0)
return $uids;
}
+ /**
+ * @inheritdoc
+ */
+ public function searchInGroup(string $gid, string $search = '', int $limit = -1, int $offset = 0): array
+ {
+ $this->logger->debug(
+ "Entering searchInGroup($gid, $search, $limit, $offset)",
+ ["app" => $this->appName]
+ );
+
+ $cacheKey = self::class . "group_users_" . $gid . "_" . $search . "_"
+ . $limit . "_" . $offset;
+ $names = $this->cache->get($cacheKey);
+
+ if ($names === null) {
+ $names = $this->groupRepository->findAllUsersBySearchTerm(
+ $this->substituteGid($gid), "%" . $search . "%", $limit, $offset
+ );
+
+ if ($names === false) {
+ return [];
+ }
+
+ $this->cache->set($cacheKey, $names);
+ $this->logger->debug(
+ "Using from DB searchInGroup($gid, $search, $limit, $offset): count("
+ . count($names) . ")", ["app" => $this->appName]
+ );
+ } else {
+ $this->logger->debug(
+ "Using from cache searchInGroup($gid, $search, $limit, $offset): count("
+ . count($names) . ")", ["app" => $this->appName]
+ );
+ }
+
+ $users = [];
+ $userManager = \OCP\Server::get(IUserManager::class);
+ foreach ($names as $uid => $name) {
+ $users[$uid] = new LazyUser($uid, $userManager, $name);
+ }
+
+ return $users;
+ }
+
/**
* @inheritdoc
*/
diff --git a/lib/Backend/UserBackend.php b/lib/Backend/UserBackend.php
index c78bc0d..1824c4a 100644
--- a/lib/Backend/UserBackend.php
+++ b/lib/Backend/UserBackend.php
@@ -459,6 +459,18 @@ public function getUsers($search = "", $limit = null, $offset = null, $callback
"Returning from cache getUsers($search, $limit, $offset): count("
. count($users) . ")", ["app" => $this->appName]
);
+ // convert to user-model
+ foreach ($users as $index => $cachedUser) {
+ if (!is_array($cachedUser)) {
+ break;
+ }
+ $user = new User();
+ foreach ($cachedUser as $key => $value) {
+ $user->{$key} = $value;
+ }
+ $users[$index] = $user;
+ }
+
return $users;
}
diff --git a/lib/Constant/Query.php b/lib/Constant/Query.php
index 68ceff8..373e2ca 100644
--- a/lib/Constant/Query.php
+++ b/lib/Constant/Query.php
@@ -32,6 +32,7 @@ final class Query
const COUNT_GROUPS = "count_groups";
const COUNT_USERS = "count_users";
const FIND_GROUP = "find_group";
+ const FIND_GROUP_UIDS = "find_group_uids";
const FIND_GROUP_USERS = "find_group_users";
const FIND_GROUPS = "find_groups";
const FIND_USER_BY_UID = "find_user_by_uid";
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index 845abf9..8ae3668 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -92,6 +92,8 @@ public function __construct(
* Verify the database connection parameters.
*
* @return array The request status.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function verifyDbConnection()
{
@@ -189,6 +191,8 @@ private function getConnection()
* Save application properties.
*
* @return array The request status.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function saveProperties()
{
@@ -329,6 +333,8 @@ private function cryptoClassConfiguration($cryptoClass)
* Clear the application cache memory.
*
* @return array The request status.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function clearCache()
{
@@ -356,6 +362,8 @@ public function clearCache()
* Autocomplete for table select options.
*
* @return array The database table list.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function tableAutocomplete()
{
@@ -385,6 +393,8 @@ public function tableAutocomplete()
* Autocomplete for column select options - user table.
*
* @return array The database table's column list.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function userTableAutocomplete()
{
@@ -430,6 +440,8 @@ private function columnAutocomplete($table)
* Autocomplete for column select options - user_group table.
*
* @return array The database table's column list.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function userGroupTableAutocomplete()
{
@@ -451,6 +463,8 @@ public function userGroupTableAutocomplete()
* Autocomplete for column select options - group table.
*
* @return array The database table's column list.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function groupTableAutocomplete()
{
@@ -473,6 +487,8 @@ public function groupTableAutocomplete()
*
* @return array Password algorithm parameters.
* @throws ReflectionException Whenever Opt class cannot be initiated.
+ *
+ * @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/
public function cryptoParams()
{
diff --git a/lib/Properties.php b/lib/Properties.php
index d30ae1a..762ce3c 100644
--- a/lib/Properties.php
+++ b/lib/Properties.php
@@ -203,7 +203,7 @@ public function getArray()
/**
* @inheritdoc
*/
- public function offsetExists($offset)
+ public function offsetExists(mixed $offset):bool
{
return isset($this->data[$offset]);
}
@@ -211,7 +211,7 @@ public function offsetExists($offset)
/**
* @inheritdoc
*/
- public function offsetGet($offset)
+ public function offsetGet(mixed $offset):mixed
{
if (isset($this->data[$offset])) {
return $this->data[$offset];
@@ -223,7 +223,7 @@ public function offsetGet($offset)
/**
* @inheritdoc
*/
- public function offsetSet($offset, $value)
+ public function offsetSet(mixed $offset, mixed $value):void
{
if ($offset == Opt::SAFE_STORE) {
$this->safeStore = ($value === App::TRUE_VALUE);
@@ -255,7 +255,7 @@ public function offsetSet($offset, $value)
/**
* @inheritdoc
*/
- public function offsetUnset($offset)
+ public function offsetUnset(mixed $offset):void
{
if ($offset == Opt::SAFE_STORE) {
$this->safeStore = App::FALSE_VALUE;
diff --git a/lib/Query/DataQuery.php b/lib/Query/DataQuery.php
index 2f11c55..1a7e2ae 100644
--- a/lib/Query/DataQuery.php
+++ b/lib/Query/DataQuery.php
@@ -109,26 +109,30 @@ private function execQuery(
}
$query = $this->queryProvider[$queryName];
- $result = $this->connection->prepare($query, $limit, $offset);
+
+ try {
+ $result = $this->connection->prepare($query, $limit, $offset);
+ } catch (DBALException $exception) {
+ $this->logger->logException(
+ $exception, [ 'message' => "Could not prepare the query: " . $query ]
+ );
+ return false;
+ }
foreach ($params as $param => $value) {
$result->bindValue(":" . $param, $value);
}
- $this->logger->debug(
- "Executing query: " . $query . ", " . implode(",", $params),
- ["app" => $this->appName]
- );
+ $this->logger->debug("Executing query: " . $query . ", " . implode(",", $params));
try {
$result = $result->execute();
return $result;
} catch (DBALException $exception) {
- $this->logger->error(
- "Could not execute the query: " . $exception->getMessage(),
- ["app" => $this->appName]
- );
+ $this->logger->logException(
+ $exception, [ 'message' => "Could not execute the query: " . $query ]
+ );
return false;
}
}
@@ -219,6 +223,27 @@ public function queryColumn(
return $result->fetchFirstColumn();
}
+ /**
+ * Fetch values from all columns which the given query returns.
+ *
+ * @param string $queryName The query to execute.
+ * @param array $params The query parameters to bind.
+ * @param int $limit Results limit. Defaults to -1 (no limit).
+ * @param int $offset Results offset. Defaults to 0.
+ *
+ * @return array|bool Queried column or FALSE on failure.
+ */
+ public function queryColumns(
+ $queryName, $params = [], $limit = -1, $offset = 0
+ ) {
+ $result = $this->execQuery($queryName, $params, $limit, $offset);
+ if ($result === false) {
+ return false;
+ }
+
+ return $result->fetchAll();
+ }
+
/**
* Fetch entity returned by the given query.
*
diff --git a/lib/Query/QueryProvider.php b/lib/Query/QueryProvider.php
index 7252cda..13514f0 100644
--- a/lib/Query/QueryProvider.php
+++ b/lib/Query/QueryProvider.php
@@ -129,16 +129,21 @@ private function loadQueries()
$this->queries = [
Query::BELONGS_TO_ADMIN =>
"SELECT COUNT(g.$gGID) > 0 AS admin " .
- "FROM $group g, $userGroup ug " .
+ "FROM $group g " .
+ "LEFT JOIN $userGroup ug ON ug.$ugGID = g.$gGID " .
+ (empty($uDisabled) ? "" : "LEFT JOIN $user u ON u.$uUID = ug.$ugUID ") .
"WHERE ug.$ugGID = g.$gGID " .
"AND ug.$ugUID = :$uidParam " .
- "AND g.$gAdmin",
+ "AND g.$gAdmin" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::COUNT_GROUPS =>
- "SELECT COUNT(ug.$ugGID) " .
+ "SELECT COUNT(DISTINCT ug.$ugUID) " .
"FROM $userGroup ug " .
+ (empty($uDisabled) ? "" : "LEFT JOIN $user u ON u.$uUID = ug.$ugUID ") .
"WHERE ug.$ugGID LIKE :$gidParam " .
- "AND ug.$ugUID LIKE :$searchParam",
+ "AND ug.$ugUID LIKE :$searchParam" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::COUNT_USERS =>
"SELECT COUNT(u.$uUID) AS count " .
@@ -151,12 +156,23 @@ private function loadQueries()
"FROM $group g " .
"WHERE g.$gGID = :$gidParam",
+ Query::FIND_GROUP_UIDS =>
+ "SELECT DISTINCT u.$uUID AS uid " .
+ "FROM $user u " .
+ "LEFT JOIN $userGroup ug ON u.$uUID = ug.$ugUID " .
+ "WHERE ug.$ugGID LIKE :$gidParam " .
+ "AND u.$uUID LIKE :$searchParam " .
+ (empty($uDisabled) ? "" : "AND NOT u.$uDisabled ") .
+ "ORDER BY u.$uUID",
+
Query::FIND_GROUP_USERS =>
- "SELECT ug.$ugUID AS uid " .
- "FROM $userGroup ug " .
+ "SELECT DISTINCT u.$uUID AS uid, u.$uName AS name " .
+ "FROM $user u " .
+ "LEFT JOIN $userGroup ug ON u.$uUID = ug.$ugUID " .
"WHERE ug.$ugGID LIKE :$gidParam " .
- "AND ug.$ugUID LIKE :$searchParam " .
- "ORDER BY ug.$ugUID",
+ "AND u.$uUID LIKE :$searchParam " .
+ (empty($uDisabled) ? "" : "AND NOT u.$uDisabled ") .
+ "ORDER BY u.$uUID",
Query::FIND_GROUPS =>
"SELECT $groupColumns " .
@@ -168,38 +184,38 @@ private function loadQueries()
Query::FIND_USER_BY_UID =>
"SELECT $userColumns " .
"FROM $user u " .
- "WHERE u.$uUID = :$uidParam " .
- (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
+ "WHERE u.$uUID = :$uidParam" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME =>
"SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " .
- "WHERE u.$uUsername = :$usernameParam " .
- (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
+ "WHERE u.$uUsername = :$usernameParam" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_CASE_INSENSITIVE =>
"SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " .
- "WHERE lower(u.$uUsername) = lower(:$usernameParam) " .
- (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
+ "WHERE lower(u.$uUsername) = lower(:$usernameParam)" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_OR_EMAIL =>
"SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " .
- "WHERE u.$uUsername = :$usernameParam OR u.$uEmail = :$emailParam " .
- (empty($uDisabled) ? "" : "AND NOT u.$uDisabled"),
+ "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"),
+ "WHERE lower(u.$uUsername) = lower(:$usernameParam) OR lower(u.$uEmail) = lower(:$emailParam)" .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_GROUPS =>
"SELECT $groupColumns " .
- "FROM $group g, $userGroup ug " .
- "WHERE ug.$ugGID = g.$gGID " .
- "AND ug.$ugUID = :$uidParam " .
+ "FROM $group g " .
+ "LEFT JOIN $userGroup ug ON ug.$ugGID = g.$gGID " .
+ "WHERE ug.$ugUID = :$uidParam " .
"ORDER BY g.$gGID",
Query::FIND_USERS =>
@@ -210,7 +226,7 @@ private function loadQueries()
(empty($uName) ? "" : "OR u.$uName LIKE :$searchParam ") .
(empty($uEmail) ? "" : "OR u.$uEmail LIKE :$searchParam ") .
")" .
- (empty($uDisabled) ? "" : "AND NOT u.$uDisabled ") .
+ (empty($uDisabled) ? "" : " AND NOT u.$uDisabled ") .
"ORDER BY u.$uUID",
Query::UPDATE_DISPLAY_NAME =>
@@ -238,7 +254,7 @@ private function loadQueries()
/**
* @inheritdoc
*/
- public function offsetExists($offset)
+ public function offsetExists(mixed $offset):bool
{
return isset($this->queries[$offset]);
}
@@ -246,7 +262,7 @@ public function offsetExists($offset)
/**
* @inheritdoc
*/
- public function offsetGet($offset)
+ public function offsetGet(mixed $offset):mixed
{
if (isset($this->queries[$offset])) {
return $this->queries[$offset];
@@ -258,7 +274,7 @@ public function offsetGet($offset)
/**
* @inheritdoc
*/
- public function offsetSet($offset, $value)
+ public function offsetSet(mixed $offset, mixed $value):void
{
$this->queries[$offset] = $value;
}
@@ -266,7 +282,7 @@ public function offsetSet($offset, $value)
/**
* @inheritdoc
*/
- public function offsetUnset($offset)
+ public function offsetUnset(mixed $offset):void
{
unset($this->queries[$offset]);
}
diff --git a/lib/Repository/GroupRepository.php b/lib/Repository/GroupRepository.php
index f0203c3..1ac8470 100644
--- a/lib/Repository/GroupRepository.php
+++ b/lib/Repository/GroupRepository.php
@@ -92,10 +92,33 @@ public function findAllUidsBySearchTerm(
$gid, $search = "", $limit = -1, $offset = 0
) {
return $this->dataQuery->queryColumn(
+ Query::FIND_GROUP_UIDS,
+ [Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit,
+ $offset
+ );
+ }
+
+ /**
+ * Get a list of all user IDs and their display-name belonging to the group.
+ *
+ * @param string $gid The group ID.
+ * @param string $search The UID search term. Defaults to "" (empty string).
+ * @param int $limit (optional) Results limit.
+ * Defaults to -1 (no limit).
+ * @param int $offset (optional) Results offset. Defaults to 0.
+ *
+ * @return array Array of display-names indexed by UIDs belonging to the group
+ * or FALSE on failure.
+ */
+ public function findAllUsersBySearchTerm(
+ $gid, $search = "", $limit = -1, $offset = 0
+ ) {
+ $data = $this->dataQuery->queryColumns(
Query::FIND_GROUP_USERS,
[Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit,
$offset
);
+ return array_column($data, QUERY::NAME_PARAM, Query::UID_PARAM);
}
/**
diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php
index e578dc3..286b9ce 100644
--- a/lib/Settings/Admin.php
+++ b/lib/Settings/Admin.php
@@ -23,14 +23,14 @@
use OCA\UserSQL\Properties;
use OCP\AppFramework\Http\TemplateResponse;
-use OCP\Settings\ISettings;
+use OCP\Settings\IDelegatedSettings;
/**
* The administrator's settings page.
*
* @author Marcin Ćojewski
*/
-class Admin implements ISettings
+class Admin implements IDelegatedSettings
{
/**
* @var string The application name.
@@ -76,4 +76,12 @@ public function getPriority()
{
return 25;
}
+
+ public function getName(): ?string {
+ return null; // Only one setting in this section
+ }
+
+ public function getAuthorizedAppConfig(): array {
+ return []; // Custom controller
+ }
}