Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1958b1d
refactor: Try to use less prototypejs idioms
ralflang Apr 11, 2026
ef68f2e
refactor: More vanilla javascript (activesyncadmin, categoryprefs, lo…
ralflang Apr 11, 2026
220f1a4
fix: Be more robust about absolute urls in an app's weburi
ralflang Apr 12, 2026
ce1a09d
docs: Advise external consumers to use the new horde/core equivalents…
ralflang Apr 14, 2026
199f099
fix: Session screen will not crash on listing encrypted keys
ralflang Apr 14, 2026
10c4203
style: php-cs-fixer
ralflang Apr 14, 2026
a16ae73
chore(release): bump version to 6.0.0-beta14
ralflang Apr 15, 2026
7ec8866
fix: Protect against null bytes from tainted IP records
ralflang Apr 15, 2026
272d7e8
feat: Add CSS for Forms V3
ralflang Apr 19, 2026
275d98a
feat: Add OAuth Token Service, SQL implementation, factory etc
ralflang Apr 20, 2026
2074327
feat: Horde Base's first DB Schema - oauth token table and provider t…
ralflang Apr 20, 2026
2742806
feat: Admin controller for registering / maintaining OAuth, OIDC and …
ralflang Apr 20, 2026
3a79191
test: Cover Token Service and Repository
ralflang Apr 20, 2026
641008b
feat: Add oauth/federated auth management related routes
ralflang Apr 20, 2026
7e6d253
feat: Collapsed OIDC/OAuth 2.0 based login and service feature
ralflang Apr 22, 2026
c743f70
test: Cover OAuth related services
ralflang Apr 22, 2026
64a2c30
chore(release): bump version to 6.0.0-beta15
ralflang Apr 22, 2026
396dee0
feat: Support linking and unlinking third party identities with your …
ralflang Apr 22, 2026
17dab41
feat: Support SQL-based storage for pending oauth flows
ralflang Apr 22, 2026
e15307e
feat: horde/base#73 Add requesting_app to SqlOAuthFlowStore
ralflang Apr 23, 2026
f8c205b
feat: base#71 — Incremental consent in OAuthAccountController
ralflang Apr 23, 2026
af45684
feat: Support .local.php files for oauth_presets
ralflang Apr 24, 2026
4fdac5a
fix: Workaround for fully qualified URLs
ralflang Apr 24, 2026
f09c3f8
refactor(registry): Evolve to a format more independent of legacy stack
ralflang Apr 26, 2026
1d428fe
feat: Draft an admin screen listing legacy and modern APIs
ralflang Apr 27, 2026
db39d55
feat: API overview in admin screen
ralflang Apr 27, 2026
e867d92
chore(release): bump version to 6.0.0-beta16
ralflang Apr 27, 2026
09639f3
fix: Unexpected identifier "version" in test.php reported by @bofus
ralflang Apr 28, 2026
8715746
feat: Build a modern AjaxDispatchController for #78
ralflang Apr 28, 2026
41840f5
refactor: Prefer "use" statements over FQCN and register routes for A…
ralflang Apr 28, 2026
dc10845
feat: Implement two-form and three-form calls in AjaxDispatchControll…
ralflang Apr 28, 2026
acee156
refactor: Accept Horde\Util\Variables in most places which accept Hor…
ralflang Apr 28, 2026
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
13 changes: 8 additions & 5 deletions .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ authors:
active: false
role: lead
version:
release: 6.0.0-beta13
release: 6.0.0-beta16
api: 6.0.0alpha1
state:
release: beta
Expand All @@ -65,7 +65,7 @@ conflicts:
horde/ansel: <= 3.9
dependencies:
required:
php: ^8.2
php: ^8.1
composer:
horde/alarm: ^3
horde/argv: ^3
Expand All @@ -79,6 +79,7 @@ dependencies:
horde/group: ^3
horde/hordeymlfile: ^1
horde/http: ^3
horde/identity: ^1
horde/image: ^3
horde/logintasks: ^3
horde/mail: ^3
Expand All @@ -98,6 +99,9 @@ dependencies:
horde/view: ^3
horde/vfs: ^3
php81_bc/strftime: ^0.7
horde/db: ^3
horde/oauth: ^4
horde/secret: ^3
ext:
filter: '*'
gettext: '*'
Expand All @@ -110,9 +114,7 @@ dependencies:
horde/activeSync: ^3
horde/backup: ^2
horde/cli_application: ^2
horde/db: ^3
horde/feed: ^3
horde/oauth: ^3
horde/openxchange: ^2
horde/service_facebook: ^3
horde/service_twitter: ^3
Expand All @@ -127,5 +129,6 @@ autoload:
classmap:
- lib/
psr-4:
Horde\Horde\: /src
Horde\Horde\: src/
vendor: horde
keywords: []
5 changes: 5 additions & 0 deletions admin/groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@
]);
}

/**
* ARCHITECTURE VIOLATION: Using deprecated Horde::img()
* @deprecated Use Horde_Themes_Image::tag() instead
* @see Horde_Deprecated::img()
*/
echo '<h1 class="header">' . Horde::img('group.png') . ' ' . _("Groups") . '</h1>';
$tree->renderTree();
$page_output->footer();
62 changes: 52 additions & 10 deletions admin/sessions.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
* @package Horde
*/

use Horde\Core\Session\HordeSessionFactory;
use Horde\Core\Session\SessionMetaInterface;
use Horde\SessionHandler\NativePhpSessionSerializer;
use Horde\SessionHandler\SerializedSessionPayload;
use Horde\SessionHandler\SessionId;

require_once __DIR__ . '/../lib/Application.php';
Horde_Registry::appInit('horde', [
'permission' => ['horde:administration:sessions'],
Expand All @@ -29,30 +35,66 @@
$resolver = $injector->getInstance('Net_DNS2_Resolver');
$s_info = [];

foreach ($session->sessionHandler->getSessionsInfo() as $id => $data) {
$serializer = new NativePhpSessionSerializer();
$factory = new HordeSessionFactory();

$sessionIds = $session->sessionHandler->getSessionIDs();

foreach ($sessionIds as $id) {
try {
$raw = $session->sessionHandler->read($id);
$session->sessionHandler->close();
} catch (Horde_SessionHandler_Exception $e) {
continue;
}

if (empty($raw)) {
continue;
}

$payload = new SerializedSessionPayload($raw);
$data = $serializer->deserialize($payload);
$sessionObj = $factory->restore(new SessionId($id), $data);

if (!$sessionObj instanceof SessionMetaInterface) {
continue;
}

$userId = $sessionObj->getAuthenticatedUser();
if ($userId === null) {
continue;
}

$ts = $sessionObj->getAuthTimestamp();
$tmp = [
'auth' => implode(', ', $data['apps']),
'browser' => $data['browser'],
'auth' => implode(', ', $sessionObj->getAuthenticatedApps()),
'browser' => $sessionObj->getBrowserFingerprint() ?? '',
'id' => $id,
'remotehost' => '[' . _("Unknown") . ']',
'timestamp' => date('r', $data['timestamp']),
'userid' => $data['userid'],
'timestamp' => $ts !== null ? $ts->format('r') : '',
'userid' => $userId,
];

if (!empty($data['remoteAddr'])) {
$remoteAddr = $sessionObj->getRemoteAddress();
// Strip null bytes — existing sessions may contain tainted IPs
// and PHP 8.x gethostbyaddr() throws ValueError on null bytes.
if ($remoteAddr !== null) {
$remoteAddr = trim(str_replace("\0", '', $remoteAddr));
}
if ($remoteAddr !== null && $remoteAddr !== '') {
$host = null;
if ($resolver) {
try {
if ($resp = $resolver->query($data['remoteAddr'], 'PTR')) {
if ($resp = $resolver->query($remoteAddr, 'PTR')) {
$host = $resp->answer[0]->ptrdname;
}
} catch (\NetDNS2\Exception $e) {
} catch (NetDNS2\Exception $e) {
}
}
if (is_null($host)) {
$host = @gethostbyaddr($data['remoteAddr']);
$host = @gethostbyaddr($remoteAddr);
}
$tmp['remotehost'] = $host . ' [' . $data['remoteAddr'] . '] ';
$tmp['remotehost'] = $host . ' [' . $remoteAddr . '] ';
$tmp['remotehostimage'] = Horde_Core_Ui_FlagImage::generateFlagImageByHost($host);
}

Expand Down
12 changes: 7 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
"role": "lead"
}
],
"time": "2026-04-02",
"time": "2026-04-27",
"repositories": [],
"require": {
"horde/horde-installer-plugin": "dev-FRAMEWORK_6_0 || ^3 || ^2",
"php": "^8.2",
"php": "^8.1",
"horde/alarm": "^3 || dev-FRAMEWORK_6_0",
"horde/argv": "^3 || dev-FRAMEWORK_6_0",
"horde/auth": "^3 || dev-FRAMEWORK_6_0",
Expand All @@ -43,6 +43,7 @@
"horde/group": "^3 || dev-FRAMEWORK_6_0",
"horde/hordeymlfile": "^1 || dev-FRAMEWORK_6_0",
"horde/http": "^3 || dev-FRAMEWORK_6_0",
"horde/identity": "^1 || dev-FRAMEWORK_6_0",
"horde/image": "^3 || dev-FRAMEWORK_6_0",
"horde/logintasks": "^3 || dev-FRAMEWORK_6_0",
"horde/mail": "^3 || dev-FRAMEWORK_6_0",
Expand All @@ -62,6 +63,9 @@
"horde/view": "^3 || dev-FRAMEWORK_6_0",
"horde/vfs": "^3 || dev-FRAMEWORK_6_0",
"php81_bc/strftime": "^0.7",
"horde/db": "^3 || dev-FRAMEWORK_6_0",
"horde/oauth": "^4 || dev-FRAMEWORK_6_0",
"horde/secret": "^3 || dev-FRAMEWORK_6_0",
"ext-filter": "*",
"ext-gettext": "*",
"ext-mbstring": "*",
Expand All @@ -76,9 +80,7 @@
"horde/activesync": "^3 || dev-FRAMEWORK_6_0",
"horde/backup": "^2 || dev-FRAMEWORK_6_0",
"horde/cli_application": "^2 || dev-FRAMEWORK_6_0",
"horde/db": "^3 || dev-FRAMEWORK_6_0",
"horde/feed": "^3 || dev-FRAMEWORK_6_0",
"horde/oauth": "^3 || dev-FRAMEWORK_6_0",
"horde/openxchange": "^2 || dev-FRAMEWORK_6_0",
"horde/service_facebook": "^3 || dev-FRAMEWORK_6_0",
"horde/service_twitter": "^3 || dev-FRAMEWORK_6_0",
Expand All @@ -91,7 +93,7 @@
"lib/"
],
"psr-4": {
"Horde\\Horde\\": "/src"
"Horde\\Horde\\": "src/"
}
},
"autoload-dev": {
Expand Down
59 changes: 0 additions & 59 deletions config/bootstrap/jwt.php

This file was deleted.

83 changes: 83 additions & 0 deletions config/conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2508,4 +2508,87 @@ cookie policy. See session configuration options.">$_SERVER['SERVER_NAME'] ?? $_
</configswitch>
</configsection>
</configtab>

<configtab name="oauth_server" desc="OAuth2 / OIDC Server">
<configsection name="oauth_server">
<configheader>OAuth2 / OpenID Connect Server</configheader>
<configdescription>These settings configure Horde as an OAuth 2.0 authorization
server and OpenID Connect provider. External applications can use OAuth2 to
obtain delegated access to Horde resources on behalf of users. This is separate
from the Authentication tab, which controls how users log in to Horde
itself.</configdescription>

<configboolean name="enabled" desc="Enable the OAuth2 / OIDC server endpoints?
When enabled, Horde serves /oauth2/* and /.well-known/* routes for token
issuance, introspection, discovery, and JWKS.">false</configboolean>

<configstring name="private_key_file" required="false" desc="Path to RSA private key (PEM format)
used for signing OAuth2 access tokens and OIDC ID tokens (RS256).

If the file does not exist, Horde generates a 2048-bit RSA keypair
automatically on first use via php-openssl. The file is created with
mode 0600. You can also generate one manually:

openssl genrsa -out ${HORDE_CONFIG_BASE}/horde/oauth2.pem 2048
chmod 600 ${HORDE_CONFIG_BASE}/horde/oauth2.pem

The public key is derived automatically from this file and exposed
via the /.well-known/jwks.json endpoint so relying parties can verify tokens.

NOTE: This is NOT the same as the JWT session secret (auth.jwt.secret_file).
Session tokens use HS256 (symmetric HMAC); OAuth2 tokens use RS256 (asymmetric RSA).
They are cryptographically incompatible and must be separate files.

Example: /var/horde/secrets/oauth2.pem"></configstring>

<configstring name="key_id" required="false" desc="Key ID (kid) published in the JWKS endpoint.
Change this when you rotate the signing key so relying parties can distinguish
old from new keys during the rotation window.">horde-1</configstring>

<configstring name="issuer" required="false" desc="OAuth2/OIDC issuer identifier.
Must be an absolute URL. Used in token claims and the discovery document.
Leave empty to auto-detect from the Horde base URL."></configstring>

<configinteger name="access_token_ttl" desc="Access token lifetime in seconds.
Recommended: 3600 (1 hour). Shorter values are more secure but cause more
token refresh traffic.">3600</configinteger>

<configinteger name="refresh_token_ttl" desc="Refresh token lifetime in seconds.
Recommended: 2592000 (30 days). Set to 0 to disable refresh tokens
entirely.">2592000</configinteger>

</configsection>
</configtab>

<configtab name="oauth_login" desc="OAuth/OIDC Login">
<configsection name="oauth_login">
<configheader>OAuth / OIDC Login</configheader>
<configdescription>Allow users to sign in to Horde using external OAuth2 or
OpenID Connect providers (e.g. Mastodon, GitHub, Google). Providers must be
configured in Administration &gt; Authentication &gt; OAuth Providers with a
Client ID and Client Secret before they appear on the login page.</configdescription>

<configboolean name="enabled" desc="Show OAuth/OIDC login buttons on the login
page? When enabled, each configured and enabled provider with a Client ID appears
as a button below the username/password form.">false</configboolean>

<configswitch name="flow_store_driver" desc="Where to store in-progress OAuth
flows (state + PKCE verifier)?">File
<case name="File" desc="Filesystem (one file per flow, single-server)">
<configsection name="flow_store_params">
<configstring name="directory" required="false"
desc="Directory for flow files. Leave empty to use Horde's configured temp directory." />
<configstring name="prefix" required="false"
desc="Filename prefix (default: horde_oauth_)" />
</configsection>
</case>
<case name="Sql" desc="SQL Database (shared across workers/servers)">
<configsection name="flow_store_params">
<configsql switchname="driverconfig" />
</configsection>
</case>
</configswitch>

</configsection>
</configtab>
</configuration>
Loading