Compare commits

..

28 Commits

Author SHA1 Message Date
Claus-Justus Heine
5cc12730c5 Cache: use at least a capped memory cache if a distributed cache is not available 2025-06-30 11:55:18 +02:00
Claus-Justus Heine
f565705b4f Backend\UserBackend: avoid recursion in getUser(). 2025-06-30 11:37:50 +02:00
Claus-Justus Heine
cafa6bbd78 Action\EmailSync -- ignore case when comparing email addresses. 2025-06-30 11:35:33 +02:00
Claus-Justus Heine
1354242744 Tweak debug logging 2025-05-07 23:44:16 +02:00
Claus-Justus Heine
dc5458e9a8 Get rid of logException() 2025-04-18 17:46:51 +02:00
Claus-Justus Heine
964260ab7a Bump NC version requirement to 31. 2025-04-17 11:55:19 +02:00
Claus-Justus Heine
560f8cdf08 Silence a PHP warning 2025-04-17 11:55:19 +02:00
Claus-Justus Heine
47f598d42a Get rid of ILogger. 2025-04-17 11:55:19 +02:00
Claus-Justus Heine
f6bcde7e6d Move to IBootstrap initialization and remove the deprecated appinfo/app.php 2025-04-17 11:54:20 +02:00
Claus-Justus Heine
1dfe332e78 Claim to support NC 30 2024-08-17 17:44:19 +02:00
Claus-Justus Heine
c1475ce743 Claim to support v29 2024-04-08 17:11:20 +02:00
Claus-Justus Heine
9d0ef4e4fe Add getBackendName() and inherit from INamedBackend 2024-01-29 08:58:38 +01:00
Claus-Justus Heine
a484e1a327 Update appinfo vo NC 28 2024-01-06 18:50:58 +01:00
Claus-Justus Heine
3cc3bf294a Do not count disabled users.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 15:59:06 +02:00
Claus-Justus Heine
2d83a888f7 Claim to support >= PHP 8.0 and NC 25-27
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:23 +02:00
Claus-Justus Heine
7da80d207a Implement ISearchableGroupBackend
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:23 +02:00
Claus-Justus Heine
662b849ed9 Fix broken MySQL example in the README and clarify the meaning of the username column.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:23 +02:00
Claus-Justus Heine
639b9cf995 QueryProvider: use left joins and fix the use of the "disabled" column.
The code used union selects; left joins at least are easier to read. In
some places it was also not so clear that the WHERE part for the
disabled column really worked.

The query FIND_GROUP_USERS also needs to take the disabled column in to
account.

Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:23 +02:00
Claus-Justus Heine
df5a550fbd DataQuery: properly log exceptions.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:22 +02:00
Claus-Justus Heine
338c6b6ffb Claim NC 27 is supported
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:22 +02:00
Claus-Justus Heine
bb1f370e16 Fix \ArrayAccess type warnings
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:22 +02:00
Claus-Justus Heine
26930efc25 DateQuery: use try-catch around statement prepare.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:22 +02:00
Claus-Justus Heine
e9ccbd95d6 UserBackend: users fetched from the cache have to be converted back from an array representation to the User-object.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:21 +02:00
Claus-Justus Heine
05360d8aa2 Support NC 25.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:21 +02:00
Claus-Justus Heine
f1ca19cb67 Set max version to 24
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:21 +02:00
Claus-Justus Heine
b39421d32d Selecting and counting users from the User-Group table needs DISTINCT to avoid duplicates.
In particular when using the catch-all group ("Default Group" setting)
the queries which count and select users from the User-Group table need
the "DISTINCT" option: the catch-all group is replaced by a '%'
wild-card in the query. As users may belong by design to more than one
group counting and selecting users comes out wrong. Even worse: the many
duplicates interfere with the paging logic of the user admin-settings.

Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:21 +02:00
Claus-Justus Heine
b1819aa32e Add @AuthorizedAdminSetting to all controller methods.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:20 +02:00
Claus-Justus Heine
ad513bda3e Allow delegation of admin settings.
Signed-off-by: Claus-Justus Heine <himself@claus-justus-heine.de>
2023-08-22 12:59:20 +02:00
17 changed files with 317 additions and 169 deletions

View File

@@ -63,7 +63,7 @@ Name | Description | Details
**Email sync** | Sync e-mail address with the Nextcloud.<br/>- *None* - Disables this feature. This is the default option.<br/>- *Synchronise only once* - Copy the e-mail address to the Nextcloud preferences if its not set.<br/>- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.<br/>- *SQL always wins* - Always copy the e-mail address to the Nextcloud preferences. | Optional.<br/>Default: *None*.<br/>Requires: user *Email* column. **Email sync** | Sync e-mail address with the Nextcloud.<br/>- *None* - Disables this feature. This is the default option.<br/>- *Synchronise only once* - Copy the e-mail address to the Nextcloud preferences if its not set.<br/>- *Nextcloud always wins* - Always copy the e-mail address to the database. This updates the user table.<br/>- *SQL always wins* - Always copy the e-mail address to the Nextcloud preferences. | Optional.<br/>Default: *None*.<br/>Requires: user *Email* column.
**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. **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 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 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`. **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. **Default group** | Default group for all 'User SQL' users. | Optional.
#### User table #### User table
@@ -74,7 +74,7 @@ Name | Description | Details
--- | --- | --- --- | --- | ---
**Table name** | The table name. | Mandatory for user backend. **Table name** | The table name. | Mandatory for user backend.
**UID** | User ID column. | 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. **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.
@@ -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, 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). 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 CREATE TABLE sql_user
( (
uid INT PRIMARY KEY AUTO_INCREMENT, uid VARCHAR(64) PRIMARY KEY,
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,
@@ -139,15 +142,15 @@ CREATE TABLE sql_user
CREATE TABLE sql_group CREATE TABLE sql_group
( (
gid INT PRIMARY KEY AUTO_INCREMENT, gid VARCHAR(64) PRIMARY KEY,
name VARCHAR(16) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL,
admin BOOLEAN NOT NULL DEFAULT FALSE admin BOOLEAN NOT NULL DEFAULT FALSE
); );
CREATE TABLE sql_user_group CREATE TABLE sql_user_group
( (
uid INT NOT NULL, uid VARCHAR(64),
gid INT NOT NULL, gid VARCHAR(64),
PRIMARY KEY (uid, gid), PRIMARY KEY (uid, gid),
FOREIGN KEY (uid) REFERENCES sql_user (uid), FOREIGN KEY (uid) REFERENCES sql_user (uid),
FOREIGN KEY (gid) REFERENCES sql_group (gid), FOREIGN KEY (gid) REFERENCES sql_group (gid),

View File

@@ -1,31 +0,0 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2012-2015 Andreas Böhler <dev (at) aboehler (dot) at>
* @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/>.
*/
use OCA\UserSQL\AppInfo\Application;
use OCP\AppFramework\QueryException;
try {
$app = new Application();
$app->registerBackends();
} catch (QueryException $queryException) {
OC::$server->getLogger()->logException($queryException);
}

View File

@@ -21,8 +21,8 @@
</types> </types>
<category>auth</category> <category>auth</category>
<dependencies> <dependencies>
<php min-version="7.1"/> <php min-version="8.0"/>
<nextcloud min-version="21" max-version="23"/> <nextcloud min-version="31" max-version="31"/>
</dependencies> </dependencies>
<settings> <settings>
<admin>\OCA\UserSQL\Settings\Admin</admin> <admin>\OCA\UserSQL\Settings\Admin</admin>

View File

@@ -27,7 +27,7 @@ use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository; use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig; use OCP\IConfig;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Synchronizes the user email address. * Synchronizes the user email address.
@@ -41,7 +41,7 @@ class EmailSync implements IUserAction
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -61,13 +61,13 @@ class EmailSync implements IUserAction
* The default constructor. * The default constructor.
* *
* @param string $appName The application name. * @param string $appName The application name.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param IConfig $config The config instance. * @param IConfig $config The config instance.
* @param UserRepository $userRepository The user repository. * @param UserRepository $userRepository The user repository.
*/ */
public function __construct( public function __construct(
$appName, ILogger $logger, Properties $properties, IConfig $config, $appName, LoggerInterface $logger, Properties $properties, IConfig $config,
UserRepository $userRepository UserRepository $userRepository
) { ) {
$this->appName = $appName; $this->appName = $appName;
@@ -87,6 +87,11 @@ class EmailSync implements IUserAction
"Entering EmailSync#doAction($user->uid)", ["app" => $this->appName] "Entering EmailSync#doAction($user->uid)", ["app" => $this->appName]
); );
// enforce lowercase
if (!empty($user->email)) {
$user->email = strtolower($user->email);
}
$ncMail = $this->config->getUserValue( $ncMail = $this->config->getUserValue(
$user->uid, "settings", "email", "" $user->uid, "settings", "email", ""
); );
@@ -117,7 +122,7 @@ class EmailSync implements IUserAction
break; break;
case App::SYNC_FORCE_SQL: case App::SYNC_FORCE_SQL:
if (!empty($user->email) && $user->email !== $ncMail) { if (!empty($user->email) && $user->email !== strtolower($ncMail)) {
$this->config->setUserValue( $this->config->setUserValue(
$user->uid, "settings", "email", $user->email $user->uid, "settings", "email", $user->email
); );

View File

@@ -27,7 +27,7 @@ use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository; use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig; use OCP\IConfig;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Synchronizes the user name. * Synchronizes the user name.
@@ -41,7 +41,7 @@ class NameSync implements IUserAction
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -61,13 +61,13 @@ class NameSync implements IUserAction
* The default constructor. * The default constructor.
* *
* @param string $appName The application name. * @param string $appName The application name.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param IConfig $config The config instance. * @param IConfig $config The config instance.
* @param UserRepository $userRepository The user repository. * @param UserRepository $userRepository The user repository.
*/ */
public function __construct( public function __construct(
$appName, ILogger $logger, Properties $properties, IConfig $config, $appName, LoggerInterface $logger, Properties $properties, IConfig $config,
UserRepository $userRepository UserRepository $userRepository
) { ) {
$this->appName = $appName; $this->appName = $appName;

View File

@@ -27,7 +27,7 @@ use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository; use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig; use OCP\IConfig;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Synchronizes the user quota. * Synchronizes the user quota.
@@ -41,7 +41,7 @@ class QuotaSync implements IUserAction
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -61,13 +61,13 @@ class QuotaSync implements IUserAction
* The default constructor. * The default constructor.
* *
* @param string $appName The application name. * @param string $appName The application name.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param IConfig $config The config instance. * @param IConfig $config The config instance.
* @param UserRepository $userRepository The user repository. * @param UserRepository $userRepository The user repository.
*/ */
public function __construct( public function __construct(
$appName, ILogger $logger, Properties $properties, IConfig $config, $appName, LoggerInterface $logger, Properties $properties, IConfig $config,
UserRepository $userRepository UserRepository $userRepository
) { ) {
$this->appName = $appName; $this->appName = $appName;

View File

@@ -4,6 +4,8 @@
* *
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me> * @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
* @copyright 2025 Claus-Justus Heine <himself@claus-justus-heine.de>
* @author Claus-Justus Heine <himself@claus-justus-heine.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -22,14 +24,20 @@
namespace OCA\UserSQL\AppInfo; namespace OCA\UserSQL\AppInfo;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\QueryException; use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCA\UserSQL\Backend;
/** /**
* The application bootstrap class. * The application bootstrap class.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */
class Application extends App class Application extends App implements IBootstrap
{ {
/** /**
* The class constructor. * The class constructor.
@@ -42,26 +50,21 @@ class Application extends App
parent::__construct("user_sql", $urlParams); parent::__construct("user_sql", $urlParams);
} }
/** /** {@inheritdoc} */
* Register the application backends public function register(IRegistrationContext $context): void
* if all necessary configuration is provided. {}
*
* @throws QueryException If the query container's could not be resolved
*/
public function registerBackends()
{
$userBackend = $this->getContainer()->query(
'\OCA\UserSQL\Backend\UserBackend'
);
$groupBackend = $this->getContainer()->query(
'\OCA\UserSQL\Backend\GroupBackend'
);
if ($userBackend->isConfigured()) { /** {@inheritdoc} */
\OC::$server->getUserManager()->registerBackend($userBackend); public function boot(IBootContext $context): void
} {
if ($groupBackend->isConfigured()) { $context->injectFn(function(
\OC::$server->getGroupManager()->addBackend($groupBackend); IUserManager $userManager,
} Backend\UserBackend $userBackend,
IGroupManager $groupManager,
Backend\GroupBackend $groupBackend,
) {
$userManager->registerBackend($userBackend);
$groupManager->addBackend($groupBackend);
});
} }
} }

View File

@@ -3,6 +3,7 @@
* Nextcloud - user_sql * Nextcloud - user_sql
* *
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me> * @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @copyright 2022-2025 Claus-Justus Heine <himself@claus-justus-heine.de>
* @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
@@ -31,7 +32,12 @@ use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\ICountUsersBackend; use OCP\Group\Backend\ICountUsersBackend;
use OCP\Group\Backend\IGroupDetailsBackend; use OCP\Group\Backend\IGroupDetailsBackend;
use OCP\Group\Backend\IIsAdminBackend; use OCP\Group\Backend\IIsAdminBackend;
use OCP\ILogger; use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\Group\Backend\INamedBackedn;
use Psr\Log\LoggerInterface;
use OCP\IUserManager;
use OC\User\LazyUser;
/** /**
* The SQL group backend manager. * The SQL group backend manager.
@@ -41,7 +47,8 @@ use OCP\ILogger;
final class GroupBackend extends ABackend implements final class GroupBackend extends ABackend implements
ICountUsersBackend, ICountUsersBackend,
IGroupDetailsBackend, IGroupDetailsBackend,
IIsAdminBackend IIsAdminBackend,
ISearchableGroupBackend
{ {
const USER_SQL_GID = "user_sql"; const USER_SQL_GID = "user_sql";
@@ -50,7 +57,7 @@ final class GroupBackend extends ABackend implements
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -71,12 +78,12 @@ final class GroupBackend extends ABackend implements
* *
* @param string $AppName The application name. * @param string $AppName The application name.
* @param Cache $cache The cache instance. * @param Cache $cache The cache instance.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param GroupRepository $groupRepository The group repository. * @param GroupRepository $groupRepository The group repository.
*/ */
public function __construct( public function __construct(
$AppName, Cache $cache, ILogger $logger, Properties $properties, $AppName, Cache $cache, LoggerInterface $logger, Properties $properties,
GroupRepository $groupRepository GroupRepository $groupRepository
) { ) {
$this->appName = $AppName; $this->appName = $AppName;
@@ -88,6 +95,14 @@ final class GroupBackend extends ABackend implements
/** /**
* @inheritdoc * @inheritdoc
*/
public function getBackendName(): string
{
return "User SQL";
}
/**
* @inheritdoc
*/ */
public function getGroups($search = "", $limit = null, $offset = null) public function getGroups($search = "", $limit = null, $offset = null)
{ {
@@ -354,16 +369,16 @@ final class GroupBackend extends ABackend implements
["app" => $this->appName] ["app" => $this->appName]
); );
$cacheKey = self::class . "group_users_" . $gid . "_" . $search . "_" $cacheKey = self::class . "group_uids_" . $gid . "_" . $search . "_"
. $limit . "_" . $offset; . $limit . "_" . $offset;
$users = $this->cache->get($cacheKey); $uids = $this->cache->get($cacheKey);
if (!is_null($users)) { if (!is_null($uids)) {
$this->logger->debug( $this->logger->debug(
"Returning from cache usersInGroup($gid, $search, $limit, $offset): count(" "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( $uids = $this->groupRepository->findAllUidsBySearchTerm(
@@ -386,7 +401,51 @@ final class GroupBackend extends ABackend implements
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function isAdmin(string $uid = null): bool 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
*/
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]

View File

@@ -37,7 +37,7 @@ use OCA\UserSQL\Repository\UserRepository;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use Psr\Log\LoggerInterface;
use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\Security\Events\ValidatePasswordPolicyEvent;
use OCP\User\Backend\ABackend; use OCP\User\Backend\ABackend;
use OCP\User\Backend\ICheckPasswordBackend; use OCP\User\Backend\ICheckPasswordBackend;
@@ -69,7 +69,7 @@ final class UserBackend extends ABackend implements
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -106,7 +106,7 @@ final class UserBackend extends ABackend implements
* *
* @param string $AppName The application name. * @param string $AppName The application name.
* @param Cache $cache The cache instance. * @param Cache $cache The cache instance.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param UserRepository $userRepository The user repository. * @param UserRepository $userRepository The user repository.
* @param IL10N $localization The localization service. * @param IL10N $localization The localization service.
@@ -114,7 +114,7 @@ final class UserBackend extends ABackend implements
* @param IEventDispatcher $eventDispatcher The event dispatcher. * @param IEventDispatcher $eventDispatcher The event dispatcher.
*/ */
public function __construct( public function __construct(
$AppName, Cache $cache, ILogger $logger, Properties $properties, $AppName, Cache $cache, LoggerInterface $logger, Properties $properties,
UserRepository $userRepository, IL10N $localization, IConfig $config, UserRepository $userRepository, IL10N $localization, IConfig $config,
IEventDispatcher $eventDispatcher IEventDispatcher $eventDispatcher
) { ) {
@@ -261,9 +261,13 @@ final class UserBackend extends ABackend implements
if ($user instanceof User) { if ($user instanceof User) {
$this->cache->set($cacheKey, $user); $this->cache->set($cacheKey, $user);
// avoid recursion as the action may very well call into the UserManager again ...
$actions = $this->actions;
$this->actions = [];
foreach ($this->actions as $action) { foreach ($this->actions as $action) {
$action->doAction($user); $action->doAction($user);
} }
$this->actions = $actions;
} }
return $user; return $user;
@@ -345,9 +349,12 @@ final class UserBackend extends ABackend implements
} }
if ($isCorrect !== true) { if ($isCorrect !== true) {
$this->logger->info( $this->logger->error(
"Invalid password attempt for user: $uid", "Invalid password attempt for user: $uid",
["app" => $this->appName] [
"app" => $this->appName,
'exception' => new \Exception('TRACE PROVIDER'),
]
); );
return false; return false;
} }
@@ -459,6 +466,18 @@ final class UserBackend extends ABackend implements
"Returning from cache getUsers($search, $limit, $offset): count(" "Returning from cache getUsers($search, $limit, $offset): count("
. count($users) . ")", ["app" => $this->appName] . 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; return $users;
} }

View File

@@ -5,6 +5,9 @@
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me> * @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
* *
* @copyright 2025 Claus-Justus Heine <himself@claus-justus-heine.de>
* @author Claus-Justus Heine <himself@claus-justus-heine.de>
*
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the * published by the Free Software Foundation, either version 3 of the
@@ -26,7 +29,7 @@ use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\Opt; use OCA\UserSQL\Constant\Opt;
use OCP\ICache; use OCP\ICache;
use OCP\IConfig; use OCP\IConfig;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Used to store key-value pairs in the cache memory. * Used to store key-value pairs in the cache memory.
@@ -39,16 +42,16 @@ class Cache
/** /**
* @var ICache The cache instance. * @var ICache The cache instance.
*/ */
private $cache; private $cache = null;
/** /**
* The default constructor. Initiates the cache memory. * The default constructor. Initiates the cache memory.
* *
* @param string $AppName The application name. * @param string $AppName The application name.
* @param IConfig $config The config instance. * @param IConfig $config The config instance.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
*/ */
public function __construct($AppName, IConfig $config, ILogger $logger) public function __construct($AppName, IConfig $config, LoggerInterface $logger)
{ {
$factory = \OC::$server->getMemCacheFactory(); $factory = \OC::$server->getMemCacheFactory();
$useCache = $config->getAppValue( $useCache = $config->getAppValue(
@@ -57,15 +60,19 @@ class Cache
if ($useCache === App::FALSE_VALUE) { if ($useCache === App::FALSE_VALUE) {
$this->cache = new NullCache(); $this->cache = new NullCache();
} elseif ($factory->isAvailable()) {
$this->cache = $factory->createDistributed();
$logger->debug("Distributed cache initiated.", ["app" => $AppName]);
} else { } else {
$logger->warning( if ($factory->isAvailable()) {
"There's no distributed cache available, fallback to null cache.", $this->cache = $factory->createDistributed();
}
if ($this->cache === null || ($this->cache instanceof NullCache)) {
$logger->debug(
"There's no distributed cache available, fallback to capped in-memory cache.",
["app" => $AppName] ["app" => $AppName]
); );
$this->cache = new NullCache(); $this->cache = $factory->createInMemory(128);
} else {
$logger->debug("Distributed cache initiated.", ["app" => $AppName]);
}
} }
} }

View File

@@ -32,6 +32,7 @@ final class Query
const COUNT_GROUPS = "count_groups"; const COUNT_GROUPS = "count_groups";
const COUNT_USERS = "count_users"; const COUNT_USERS = "count_users";
const FIND_GROUP = "find_group"; const FIND_GROUP = "find_group";
const FIND_GROUP_UIDS = "find_group_uids";
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_BY_UID = "find_user_by_uid"; const FIND_USER_BY_UID = "find_user_by_uid";

View File

@@ -37,7 +37,7 @@ use OCA\UserSQL\Platform\PlatformFactory;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use Psr\Log\LoggerInterface;
use OCP\IRequest; use OCP\IRequest;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
@@ -50,7 +50,7 @@ use ReflectionException;
class SettingsController extends Controller class SettingsController extends Controller
{ {
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -71,13 +71,13 @@ class SettingsController extends Controller
* *
* @param string $appName The application name. * @param string $appName The application name.
* @param IRequest $request An instance of the request. * @param IRequest $request An instance of the request.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param IL10N $localization The localization service. * @param IL10N $localization The localization service.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param Cache $cache The cache instance. * @param Cache $cache The cache instance.
*/ */
public function __construct( public function __construct(
$appName, IRequest $request, ILogger $logger, IL10N $localization, $appName, IRequest $request, LoggerInterface $logger, IL10N $localization,
Properties $properties, Cache $cache Properties $properties, Cache $cache
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
@@ -92,6 +92,8 @@ class SettingsController extends Controller
* Verify the database connection parameters. * Verify the database connection parameters.
* *
* @return array The request status. * @return array The request status.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function verifyDbConnection() public function verifyDbConnection()
{ {
@@ -189,6 +191,8 @@ class SettingsController extends Controller
* Save application properties. * Save application properties.
* *
* @return array The request status. * @return array The request status.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function saveProperties() public function saveProperties()
{ {
@@ -329,6 +333,8 @@ class SettingsController extends Controller
* Clear the application cache memory. * Clear the application cache memory.
* *
* @return array The request status. * @return array The request status.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function clearCache() public function clearCache()
{ {
@@ -356,6 +362,8 @@ class SettingsController extends Controller
* Autocomplete for table select options. * Autocomplete for table select options.
* *
* @return array The database table list. * @return array The database table list.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function tableAutocomplete() public function tableAutocomplete()
{ {
@@ -376,7 +384,7 @@ class SettingsController extends Controller
return $tables; return $tables;
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->logException($e); $this->logger->error('Error during table autocompletion', [ 'exception' => $e ]);
return []; return [];
} }
} }
@@ -385,6 +393,8 @@ class SettingsController extends Controller
* Autocomplete for column select options - user table. * Autocomplete for column select options - user table.
* *
* @return array The database table's column list. * @return array The database table's column list.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function userTableAutocomplete() public function userTableAutocomplete()
{ {
@@ -421,7 +431,7 @@ class SettingsController extends Controller
return $columns; return $columns;
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->logException($e); $this->logger->error('Error during column autocompletion', [ 'exception' => $e ]);
return []; return [];
} }
} }
@@ -430,6 +440,8 @@ class SettingsController extends Controller
* Autocomplete for column select options - user_group table. * Autocomplete for column select options - user_group table.
* *
* @return array The database table's column list. * @return array The database table's column list.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function userGroupTableAutocomplete() public function userGroupTableAutocomplete()
{ {
@@ -451,6 +463,8 @@ class SettingsController extends Controller
* Autocomplete for column select options - group table. * Autocomplete for column select options - group table.
* *
* @return array The database table's column list. * @return array The database table's column list.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function groupTableAutocomplete() public function groupTableAutocomplete()
{ {
@@ -473,6 +487,8 @@ class SettingsController extends Controller
* *
* @return array Password algorithm parameters. * @return array Password algorithm parameters.
* @throws ReflectionException Whenever Opt class cannot be initiated. * @throws ReflectionException Whenever Opt class cannot be initiated.
*
* @AuthorizedAdminSetting(settings=OCA\UserSQL\Settings\Admin)
*/ */
public function cryptoParams() public function cryptoParams()
{ {

View File

@@ -25,7 +25,7 @@ use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\DB; use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Opt; use OCA\UserSQL\Constant\Opt;
use OCP\IConfig; use OCP\IConfig;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Store and retrieve application properties. * Store and retrieve application properties.
@@ -48,7 +48,7 @@ class Properties implements \ArrayAccess
*/ */
private $config; private $config;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -69,11 +69,11 @@ class Properties implements \ArrayAccess
* *
* @param string $AppName The application name. * @param string $AppName The application name.
* @param IConfig $config The config instance. * @param IConfig $config The config instance.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Cache $cache The cache instance. * @param Cache $cache The cache instance.
*/ */
public function __construct( public function __construct(
$AppName, IConfig $config, ILogger $logger, Cache $cache $AppName, IConfig $config, LoggerInterface $logger, Cache $cache
) { ) {
$this->appName = $AppName; $this->appName = $AppName;
$this->config = $config; $this->config = $config;
@@ -144,9 +144,7 @@ class Properties implements \ArrayAccess
$params, array_values($reflection->getConstants()) $params, array_values($reflection->getConstants())
); );
} catch (\ReflectionException $exception) { } catch (\ReflectionException $exception) {
$this->logger->logException( $this->logger->error('Unable to determine parameter names', [ 'exception'=> $exception ]);
$exception, ["app" => $this->appName]
);
} }
} }
@@ -203,7 +201,7 @@ class Properties implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetExists($offset) public function offsetExists(mixed $offset):bool
{ {
return isset($this->data[$offset]); return isset($this->data[$offset]);
} }
@@ -211,7 +209,7 @@ class Properties implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetGet($offset) public function offsetGet(mixed $offset):mixed
{ {
if (isset($this->data[$offset])) { if (isset($this->data[$offset])) {
return $this->data[$offset]; return $this->data[$offset];
@@ -223,7 +221,7 @@ class Properties implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetSet($offset, $value) public function offsetSet(mixed $offset, mixed $value):void
{ {
if ($offset == Opt::SAFE_STORE) { if ($offset == Opt::SAFE_STORE) {
$this->safeStore = ($value === App::TRUE_VALUE); $this->safeStore = ($value === App::TRUE_VALUE);
@@ -255,7 +253,7 @@ class Properties implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetUnset($offset) public function offsetUnset(mixed $offset):void
{ {
if ($offset == Opt::SAFE_STORE) { if ($offset == Opt::SAFE_STORE) {
$this->safeStore = App::FALSE_VALUE; $this->safeStore = App::FALSE_VALUE;

View File

@@ -28,7 +28,7 @@ use OC\DB\ConnectionFactory;
use OCA\UserSQL\Constant\DB; use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Query; use OCA\UserSQL\Constant\Query;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCP\ILogger; use Psr\Log\LoggerInterface;
/** /**
* Used to query a database. * Used to query a database.
@@ -42,7 +42,7 @@ class DataQuery
*/ */
private $appName; private $appName;
/** /**
* @var ILogger The logger instance. * @var LoggerInterface The logger instance.
*/ */
private $logger; private $logger;
/** /**
@@ -62,12 +62,12 @@ class DataQuery
* The class constructor. * The class constructor.
* *
* @param string $AppName The application name. * @param string $AppName The application name.
* @param ILogger $logger The logger instance. * @param LoggerInterface $logger The logger instance.
* @param Properties $properties The properties array. * @param Properties $properties The properties array.
* @param QueryProvider $queryProvider The query provider. * @param QueryProvider $queryProvider The query provider.
*/ */
public function __construct( public function __construct(
$AppName, ILogger $logger, Properties $properties, $AppName, LoggerInterface $logger, Properties $properties,
QueryProvider $queryProvider QueryProvider $queryProvider
) { ) {
$this->appName = $AppName; $this->appName = $AppName;
@@ -109,26 +109,26 @@ class DataQuery
} }
$query = $this->queryProvider[$queryName]; $query = $this->queryProvider[$queryName];
try {
$result = $this->connection->prepare($query, $limit, $offset); $result = $this->connection->prepare($query, $limit, $offset);
} catch (DBALException $exception) {
$this->logger->error('Could not prepare the query: ' . $query, [ 'exception' => $exception ]);
return false;
}
foreach ($params as $param => $value) { foreach ($params as $param => $value) {
$result->bindValue(":" . $param, $value); $result->bindValue(":" . $param, $value);
} }
$this->logger->debug( $this->logger->debug("Executing query: " . $query . ", " . implode(",", $params));
"Executing query: " . $query . ", " . implode(",", $params),
["app" => $this->appName]
);
try { try {
$result = $result->execute(); $result = $result->execute();
return $result; return $result;
} catch (DBALException $exception) { } catch (DBALException $exception) {
$this->logger->error( $this->logger->error('Could not execute the query: ' . $query, [ 'exception' => $exception ]);
"Could not execute the query: " . $exception->getMessage(),
["app" => $this->appName]
);
return false; return false;
} }
} }
@@ -219,6 +219,27 @@ class DataQuery
return $result->fetchFirstColumn(); 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. * Fetch entity returned by the given query.
* *

View File

@@ -129,16 +129,21 @@ class QueryProvider implements \ArrayAccess
$this->queries = [ $this->queries = [
Query::BELONGS_TO_ADMIN => Query::BELONGS_TO_ADMIN =>
"SELECT COUNT(g.$gGID) > 0 AS 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 " . "WHERE ug.$ugGID = g.$gGID " .
"AND ug.$ugUID = :$uidParam " . "AND ug.$ugUID = :$uidParam " .
"AND g.$gAdmin", "AND g.$gAdmin" .
(empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::COUNT_GROUPS => Query::COUNT_GROUPS =>
"SELECT COUNT(ug.$ugGID) " . "SELECT COUNT(DISTINCT ug.$ugUID) " .
"FROM $userGroup ug " . "FROM $userGroup ug " .
(empty($uDisabled) ? "" : "LEFT JOIN $user u ON u.$uUID = ug.$ugUID ") .
"WHERE ug.$ugGID LIKE :$gidParam " . "WHERE ug.$ugGID LIKE :$gidParam " .
"AND ug.$ugUID LIKE :$searchParam", "AND ug.$ugUID LIKE :$searchParam" .
(empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::COUNT_USERS => Query::COUNT_USERS =>
"SELECT COUNT(u.$uUID) AS count " . "SELECT COUNT(u.$uUID) AS count " .
@@ -151,12 +156,23 @@ class QueryProvider implements \ArrayAccess
"FROM $group g " . "FROM $group g " .
"WHERE g.$gGID = :$gidParam", "WHERE g.$gGID = :$gidParam",
Query::FIND_GROUP_USERS => Query::FIND_GROUP_UIDS =>
"SELECT ug.$ugUID AS uid " . "SELECT DISTINCT u.$uUID AS uid " .
"FROM $userGroup ug " . "FROM $user u " .
"LEFT JOIN $userGroup ug ON u.$uUID = ug.$ugUID " .
"WHERE ug.$ugGID LIKE :$gidParam " . "WHERE ug.$ugGID LIKE :$gidParam " .
"AND ug.$ugUID LIKE :$searchParam " . "AND u.$uUID LIKE :$searchParam " .
"ORDER BY ug.$ugUID", (empty($uDisabled) ? "" : "AND NOT u.$uDisabled ") .
"ORDER BY u.$uUID",
Query::FIND_GROUP_USERS =>
"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 u.$uUID LIKE :$searchParam " .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled ") .
"ORDER BY u.$uUID",
Query::FIND_GROUPS => Query::FIND_GROUPS =>
"SELECT $groupColumns " . "SELECT $groupColumns " .
@@ -168,38 +184,38 @@ class QueryProvider implements \ArrayAccess
Query::FIND_USER_BY_UID => Query::FIND_USER_BY_UID =>
"SELECT $userColumns " . "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_USERNAME => 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.$uUsername = :$usernameParam " . "WHERE u.$uUsername = :$usernameParam" .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_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.$uUsername) = lower(:$usernameParam) " . "WHERE lower(u.$uUsername) = lower(:$usernameParam)" .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_OR_EMAIL => 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 u.$uUsername = :$usernameParam OR u.$uEmail = :$emailParam " . "WHERE u.$uUsername = :$usernameParam OR u.$uEmail = :$emailParam" .
(empty($uDisabled) ? "" : "AND NOT u.$uDisabled"), (empty($uDisabled) ? "" : " AND NOT u.$uDisabled"),
Query::FIND_USER_BY_USERNAME_OR_EMAIL_CASE_INSENSITIVE => Query::FIND_USER_BY_USERNAME_OR_EMAIL_CASE_INSENSITIVE =>
"SELECT $userColumns, u.$uPassword AS password " . "SELECT $userColumns, u.$uPassword AS password " .
"FROM $user u " . "FROM $user u " .
"WHERE lower(u.$uUsername) = lower(:$usernameParam) OR lower(u.$uEmail) = lower(:$emailParam) " . "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 =>
"SELECT $groupColumns " . "SELECT $groupColumns " .
"FROM $group g, $userGroup ug " . "FROM $group g " .
"WHERE ug.$ugGID = g.$gGID " . "LEFT JOIN $userGroup ug ON ug.$ugGID = g.$gGID " .
"AND ug.$ugUID = :$uidParam " . "WHERE ug.$ugUID = :$uidParam " .
"ORDER BY g.$gGID", "ORDER BY g.$gGID",
Query::FIND_USERS => Query::FIND_USERS =>
@@ -210,7 +226,7 @@ class QueryProvider implements \ArrayAccess
(empty($uName) ? "" : "OR u.$uName LIKE :$searchParam ") . (empty($uName) ? "" : "OR u.$uName LIKE :$searchParam ") .
(empty($uEmail) ? "" : "OR u.$uEmail 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", "ORDER BY u.$uUID",
Query::UPDATE_DISPLAY_NAME => Query::UPDATE_DISPLAY_NAME =>
@@ -238,7 +254,7 @@ class QueryProvider implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetExists($offset) public function offsetExists(mixed $offset):bool
{ {
return isset($this->queries[$offset]); return isset($this->queries[$offset]);
} }
@@ -246,7 +262,7 @@ class QueryProvider implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetGet($offset) public function offsetGet(mixed $offset):mixed
{ {
if (isset($this->queries[$offset])) { if (isset($this->queries[$offset])) {
return $this->queries[$offset]; return $this->queries[$offset];
@@ -258,7 +274,7 @@ class QueryProvider implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetSet($offset, $value) public function offsetSet(mixed $offset, mixed $value):void
{ {
$this->queries[$offset] = $value; $this->queries[$offset] = $value;
} }
@@ -266,7 +282,7 @@ class QueryProvider implements \ArrayAccess
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function offsetUnset($offset) public function offsetUnset(mixed $offset):void
{ {
unset($this->queries[$offset]); unset($this->queries[$offset]);
} }

View File

@@ -92,10 +92,33 @@ class GroupRepository
$gid, $search = "", $limit = -1, $offset = 0 $gid, $search = "", $limit = -1, $offset = 0
) { ) {
return $this->dataQuery->queryColumn( 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<string, string> 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::FIND_GROUP_USERS,
[Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit, [Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit,
$offset $offset
); );
return array_column($data, QUERY::NAME_PARAM, Query::UID_PARAM);
} }
/** /**

View File

@@ -23,14 +23,14 @@ namespace OCA\UserSQL\Settings;
use OCA\UserSQL\Properties; use OCA\UserSQL\Properties;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\Settings\ISettings; use OCP\Settings\IDelegatedSettings;
/** /**
* The administrator's settings page. * The administrator's settings page.
* *
* @author Marcin Łojewski <dev@mlojewski.me> * @author Marcin Łojewski <dev@mlojewski.me>
*/ */
class Admin implements ISettings class Admin implements IDelegatedSettings
{ {
/** /**
* @var string The application name. * @var string The application name.
@@ -76,4 +76,12 @@ class Admin implements ISettings
{ {
return 25; return 25;
} }
public function getName(): ?string {
return null; // Only one setting in this section
}
public function getAuthorizedAppConfig(): array {
return []; // Custom controller
}
} }