Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ run-tests.log
/test/*/*/*/*.exp
/test/*/*/*/*.log
/test/*/*/*/*.out

## composer plugin's var/config tree shall not be committed.
/var
/vendor
/composer.lock
/web
Expand Down
13 changes: 9 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"horde/util": "^3 || dev-FRAMEWORK_6_0",
"horde/view": "^3 || dev-FRAMEWORK_6_0",
"php81_bc/strftime": "^0.7",
"ext-session": "*"
"ext-session": "*",
"horde/ldap": "^3.0@dev"
},
"require-dev": {
"horde/test": "^3 || dev-FRAMEWORK_6_0",
Expand All @@ -82,6 +83,7 @@
"horde/vfs": "^3 || dev-FRAMEWORK_6_0"
},
"suggest": {
"horde/ldap": "For LDAP connection support",
"mikepultz/netdns2": "For DNS resolver functionality (replaces pear/net_dns2)",
"pear/text_captcha": "*",
"pear/text_figlet": "*",
Expand All @@ -104,11 +106,14 @@
}
},
"config": {
"allow-plugins": {}
"allow-plugins": {
"horde/horde-installer-plugin": true
}
},
"extra": {
"branch-alias": {
"dev-FRAMEWORK_6_0": "3.x-dev"
}
}
}
},
"minimum-stability": "dev"
}
10 changes: 10 additions & 0 deletions lib/Horde/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ public function __construct($session_flags = 0, array $args = [])
Horde\Log\Logger::class => Horde\Core\Factory\LoggerFactory::class,
'Horde\\Horde\\Service\\JwtService' => 'Horde\\Horde\\Factory\\JwtServiceFactory',
'Horde\\Horde\\Service\\AuthenticationService' => 'Horde\\Horde\\Factory\\AuthenticationServiceFactory',
'Horde\\Core\\Config\\ConfigLoader' => 'Horde\\Core\\Factory\\ConfigLoaderFactory',
'Horde\\Core\\Service\\HordeDbService' => 'Horde\\Core\\Factory\\DbServiceFactory',
'Horde\\Core\\Service\\PrefsService' => 'Horde\\Core\\Factory\\PrefsServiceFactory',
'Horde\\Core\\Service\\IdentityService' => 'Horde\\Core\\Factory\\IdentityServiceFactory',
'Horde\\Core\\Service\\GroupService' => 'Horde\\Core\\Factory\\GroupServiceFactory',
'Horde\\Core\\Config\\RegistryConfigLoader' => 'Horde\\Core\\Factory\\RegistryConfigLoaderFactory',
'Horde\\Core\\Service\\ApplicationService' => 'Horde\\Core\\Factory\\ApplicationServiceFactory',
'Horde\\Core\\Auth\\AuthService' => 'Horde\\Core\\Factory\\AuthServiceFactory',
'Horde\\Core\\Service\\HordeLdapService' => 'Horde\\Core\\Factory\\HordeLdapServiceFactory',
'Horde\\Core\\Service\\PermissionService' => 'Horde\\Core\\Factory\\PermissionServiceFactory',
];

/* Define implementations. */
Expand Down
34 changes: 34 additions & 0 deletions src/Auth/AuthNotSupportedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/**
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @package Core
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/

namespace Horde\Core\Auth;

use RuntimeException;

/**
* Exception thrown when auth backend doesn't support an operation
*
* Used to distinguish unsupported operations (e.g., listUsers() on
* auth backends without listing capability) from actual errors.
*
* @category Horde
* @package Core
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/
class AuthNotSupportedException extends RuntimeException
{
}
197 changes: 197 additions & 0 deletions src/Auth/AuthService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<?php

declare(strict_types=1);

/**
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @package Core
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/

namespace Horde\Core\Auth;

use Horde_Auth_Base;
use Horde_Auth_Exception;

/**
* Modern wrapper for Horde_Auth_Base with proper dependency injection
*
* Provides capability detection, consistent API, and eliminates
* reliance on global state in modern controllers.
*
* Wraps any Horde_Auth_Base implementation (Horde_Core_Auth_Application,
* Horde_Auth_Sql, Horde_Auth_Ldap, etc.)
*
* @category Horde
* @package Core
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/
class AuthService
{
/**
* Constructor
*
* @param Horde_Auth_Base $auth Legacy auth backend instance
*/
public function __construct(
private Horde_Auth_Base $auth
) {
}

/**
* Check if backend supports user listing
*
* Tests capability by attempting to call listUsers() and catching
* the "Unsupported" exception thrown by base implementation.
*
* @return bool True if backend supports listing
*/
public function supportsListing(): bool
{
try {
$this->auth->listUsers();
return true;
} catch (Horde_Auth_Exception $e) {
if (str_contains($e->getMessage(), 'Unsupported')) {
return false;
}
// Re-throw unexpected errors (e.g., connection failures)
throw $e;
}
}

/**
* List all users
*
* Returns array of usernames from auth backend.
*
* @param bool $sort Sort the users alphabetically
* @return string[] Array of usernames
* @throws AuthNotSupportedException If backend doesn't support listing
* @throws Horde_Auth_Exception On backend errors
*/
public function listUsers(bool $sort = false): array
{
try {
return $this->auth->listUsers($sort);
} catch (Horde_Auth_Exception $e) {
if (str_contains($e->getMessage(), 'Unsupported')) {
throw new AuthNotSupportedException(
'Auth backend does not support user listing'
);
}
throw $e;
}
}

/**
* Check if user exists
*
* @param string $username Username to check
* @return bool True if user exists
* @throws Horde_Auth_Exception On backend errors
*/
public function exists(string $username): bool
{
return $this->auth->exists($username);
}

/**
* Update user password
*
* @param string $username Username to update
* @param string $newPassword New password
* @return void
* @throws Horde_Auth_Exception On backend errors or if user doesn't exist
*/
public function updatePassword(string $username, string $newPassword): void
{
$this->auth->updateUser($username, $username, [
'password' => $newPassword,
]);
}

/**
* Create new user
*
* @param string $username Username for new user
* @param array $credentials User credentials and attributes
* Required: 'password'
* Optional: 'email', 'full_name', etc.
* @return void
* @throws Horde_Auth_Exception On backend errors or if user exists
*/
public function createUser(string $username, array $credentials): void
{
$this->auth->addUser($username, $credentials);
}

/**
* Delete user
*
* @param string $username Username to delete
* @return void
* @throws Horde_Auth_Exception On backend errors
*/
public function deleteUser(string $username): void
{
$this->auth->removeUser($username);
}

/**
* Authenticate user credentials
*
* @param string $username Username to authenticate
* @param string $password Password to verify
* @return bool True if credentials valid
* @throws Horde_Auth_Exception On backend errors
*/
public function authenticate(string $username, string $password): bool
{
return $this->auth->authenticate($username, [
'password' => $password,
]);
}

/**
* Search users by substring
*
* @param string $search Search term
* @return string[] Array of matching usernames
* @throws AuthNotSupportedException If backend doesn't support searching
* @throws Horde_Auth_Exception On backend errors
*/
public function searchUsers(string $search): array
{
try {
return $this->auth->searchUsers($search);
} catch (Horde_Auth_Exception $e) {
if (str_contains($e->getMessage(), 'Unsupported')) {
throw new AuthNotSupportedException(
'Auth backend does not support user searching'
);
}
throw $e;
}
}

/**
* Get underlying auth backend instance
*
* Provides escape hatch for operations not yet wrapped by this service.
* Use sparingly - prefer adding methods to AuthService instead.
*
* @return Horde_Auth_Base
*/
public function getBackend(): Horde_Auth_Base
{
return $this->auth;
}
}
38 changes: 38 additions & 0 deletions src/Factory/ApplicationServiceFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

/**
* Copyright 2026 The Horde Project (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @copyright 2026 The Horde Project
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Core
*/

namespace Horde\Core\Factory;

use Horde\Core\Config\RegistryConfigLoader;
use Horde\Core\Service\ApplicationService;
use Horde_Injector;

/**
* Factory for ApplicationService
*
* @category Horde
* @copyright 2026 The Horde Project
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Core
*/
class ApplicationServiceFactory
{
public function create(Horde_Injector $injector): ApplicationService
{
$registryLoader = $injector->getInstance(RegistryConfigLoader::class);
return new ApplicationService($registryLoader);
}
}
Loading
Loading