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
63 changes: 63 additions & 0 deletions lib/Horde/Core/ActiveSync/Driver.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Psr\Http\Message\ServerRequestInterface;

/**
* Copyright 2010-2017 Horde LLC (http://www.horde.org/)
*
Expand Down Expand Up @@ -108,6 +110,20 @@ class Horde_Core_ActiveSync_Driver extends Horde_ActiveSync_Driver_Base
*/
protected $_cache;

/**
* PSR-7 server request
*
* @var ServerRequestInterface
*/
protected $_serverRequest;

/**
* Horde Registry
*
* @var Horde_Registry
*/
protected $_registry;

/**
* Class => Id map
*
Expand Down Expand Up @@ -157,6 +173,20 @@ public function __construct(array $params = [])
throw new InvalidArgumentException('Missing required Auth object');
}

$serverRequest = self::extractArrayValue($params, 'serverrequest');
if ($serverRequest instanceof ServerRequestInterface) {
$this->_serverRequest = $serverRequest;
} else {
throw new InvalidArgumentException('Missing required PSR-7 ServerRequest object.');
}

$registry = self::extractArrayValue($params, 'registry');
if ($registry instanceof Horde_Registry) {
$this->_registry = $registry;
} else {
throw new InvalidArgumentException('Missing required Horde_Registry object.');
}

$this->_imap = self::extractArrayValue($params, 'imap');

$this->_cache = self::extractArrayValue($params, 'cache');
Expand Down Expand Up @@ -310,6 +340,39 @@ public function clearAuthentication()
return true;
}

/**
* Get the username for this request with Horde-specific resolution logic.
*
* Priority order:
* 1. Authenticated user (from authenticate() flow)
* 2. GET parameter ?User=username (Horde ActiveSync extension for device provisioning)
* 3. Registry authenticated user (fallback for edge cases)
*
* @return string The resolved username
*/
public function getUser()
{
// Priority 1: Authenticated user (from parent::authenticate())
$user = parent::getUser();

// Priority 2: GET parameter (Horde-specific ActiveSync extension)
// Used in device provisioning scenarios where auth hasn't completed yet
if (empty($user)) {
$queryParams = $this->_serverRequest->getQueryParams();
if (!empty($queryParams['User'])) {
$user = $queryParams['User'];
}
}

// Priority 3: Registry auth fallback
// For scenarios where request comes from authenticated session
if (empty($user)) {
$user = $this->_registry->getAuth();
}

return $user;
}

/**
* Setup sync parameters. The user provided here is the user the backend
* will sync with. This allows you to authenticate as one user, and sync as
Expand Down
23 changes: 21 additions & 2 deletions lib/Horde/Core/Factory/ActiveSyncBackend.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Horde\Http\ServerRequest;

/**
* @category Horde
* @package Core
Expand All @@ -10,6 +12,21 @@ public function create(Horde_Injector $injector)
{
global $conf, $registry;

// Get PSR-7 ServerRequest from injector
try {
$serverRequest = $injector->get(ServerRequest::class);
} catch (Horde_Exception_NotFound $e) {
// Fallback: Create from PHP superglobals
$serverRequest = new ServerRequest(
$_SERVER['REQUEST_METHOD'] ?? 'GET',
$_SERVER['REQUEST_URI'] ?? '/',
getallheaders() ?: [],
fopen('php://input', 'r'),
$_SERVER['SERVER_PROTOCOL'] ?? '1.1',
$_SERVER
);
}

// Backend driver and dependencies
$params = ['registry' => $registry];
$adapter_params = ['factory' => new Horde_Core_ActiveSync_Imap_Factory()];
Expand All @@ -21,13 +38,15 @@ public function create(Horde_Injector $injector)

$driver_params = [
'connector' => new Horde_Core_ActiveSync_Connector($params),
'serverrequest' => $serverRequest,
'registry' => $registry,
'imap' => $emailsyncEnabled
? new Horde_ActiveSync_Imap_Adapter($adapter_params)
: null,
'ping' => $conf['activesync']['ping'],
'state' => $injector->getInstance('Horde_ActiveSyncState'),
'state' => $injector->get('Horde_ActiveSyncState'),
'auth' => $this->_getAuth(),
'cache' => $injector->getInstance('Horde_Cache')];
'cache' => $injector->get('Horde_Cache')];

return new Horde_Core_ActiveSync_Driver($driver_params);
}
Expand Down
92 changes: 91 additions & 1 deletion test/ActiveSyncTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ActiveSyncTests extends TestCase
protected $_mailboxes;
protected $_special;

public function setUp()
public function setUp(): void
{
$this->_auth = $this->getMockSkipConstructor('Horde_Auth_Auto');
$this->_state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Sql');
Expand Down Expand Up @@ -465,4 +465,94 @@ public function testFbGeneration()
$expected = '440000000000000000000000000000220000000000000000';
$this->assertEquals($expected, $fb);
}

public function testGetUserReturnsAuthenticatedUser()
{
$serverRequest = new \Horde\Http\ServerRequest('POST', '/');
$connector = new MockConnector();
$mockRegistry = $this->getMockSkipConstructor('Horde_Registry');

$driver = new Horde_Core_ActiveSync_Driver([
'state' => $this->_state,
'connector' => $connector,
'auth' => $this->_auth,
'serverrequest' => $serverRequest,
'registry' => $mockRegistry,
'imap' => null,
]);

// Simulate authentication
$driver->authenticate('authenticated_user', 'password');

$this->assertEquals('authenticated_user', $driver->getUser());
}

public function testGetUserFallsBackToGetParameter()
{
$uri = new \Horde\Http\Uri('http://example.com/path?User=get_param_user');
$serverRequest = new \Horde\Http\ServerRequest('POST', $uri);
$connector = new MockConnector();
$mockRegistry = $this->getMockSkipConstructor('Horde_Registry');

$mockRegistry->expects($this->never())
->method('getAuth');

$driver = new Horde_Core_ActiveSync_Driver([
'state' => $this->_state,
'connector' => $connector,
'auth' => $this->_auth,
'serverrequest' => $serverRequest,
'registry' => $mockRegistry,
'imap' => null,
]);

// No authentication, should use GET parameter
$this->assertEquals('get_param_user', $driver->getUser());
}

public function testGetUserFallsBackToRegistry()
{
$serverRequest = new \Horde\Http\ServerRequest('POST', '/');
$connector = new MockConnector();
$mockRegistry = $this->getMockSkipConstructor('Horde_Registry');

$mockRegistry->expects($this->once())
->method('getAuth')
->willReturn('registry_user');

$driver = new Horde_Core_ActiveSync_Driver([
'state' => $this->_state,
'connector' => $connector,
'auth' => $this->_auth,
'serverrequest' => $serverRequest,
'registry' => $mockRegistry,
'imap' => null,
]);

// No authentication, no GET parameter, should use registry
$this->assertEquals('registry_user', $driver->getUser());
}

public function testGetParameterOverridesRegistry()
{
$uri = new \Horde\Http\Uri('http://example.com/path?User=get_param_user');
$serverRequest = new \Horde\Http\ServerRequest('POST', $uri);
$connector = new MockConnector();
$mockRegistry = $this->getMockSkipConstructor('Horde_Registry');

$mockRegistry->expects($this->never())
->method('getAuth');

$driver = new Horde_Core_ActiveSync_Driver([
'state' => $this->_state,
'connector' => $connector,
'auth' => $this->_auth,
'serverrequest' => $serverRequest,
'registry' => $mockRegistry,
'imap' => null,
]);

// No authentication, GET parameter present, should not call registry
$this->assertEquals('get_param_user', $driver->getUser());
}
}
Loading
Loading