Skip to content

Commit

Permalink
Merge branch 'master' into as_streams
Browse files Browse the repository at this point in the history
Conflicts:
	framework/ActiveSync/package.xml
  • Loading branch information
mrubinsk committed Oct 2, 2013
2 parents ee85cd8 + 08ef11f commit 92d9dd7
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 73 deletions.
27 changes: 18 additions & 9 deletions framework/ActiveSync/lib/Horde/ActiveSync.php
Expand Up @@ -515,8 +515,12 @@ public function authenticate($username = '')
if (!empty($serverVars['PHP_AUTH_PW'])) {
$user = empty($username) ? $serverVars['PHP_AUTH_USER'] : $username;
$pass = $serverVars['PHP_AUTH_PW'];
} elseif (!empty($serverVars['Authorization'])) {
$hash = base64_decode(str_replace('Basic ', '', $serverVars['Authorization']));
} elseif (!empty($serverVars['HTTP_AUTHORIZATION']) || !empty($serverVars['Authorization'])) {
// Some clients use the non-standard 'Authorization' header.
$authorization = !empty($serverVars['HTTP_AUTHORIZATION'])
? $serverVars['HTTP_AUTHORIZATION']
: $serverVars['Authorization'];
$hash = base64_decode(str_replace('Basic ', '', $authorization));
if (strpos($hash, ':') !== false) {
list($user, $pass) = explode(':', $hash, 2);
}
Expand Down Expand Up @@ -623,11 +627,13 @@ public function setLogger(Horde_ActiveSync_Interface_LoggerFactory $logger)

protected function _setLogger(array $options)
{
self::$_logger = $this->_loggerFactory->create($options);
$this->_encoder->setLogger(self::$_logger);
$this->_decoder->setLogger(self::$_logger);
$this->_driver->setLogger(self::$_logger);
$this->_state->setLogger(self::$_logger);
if (!empty($this->_loggerFactory)) {
self::$_logger = $this->_loggerFactory->create($options);
$this->_encoder->setLogger(self::$_logger);
$this->_decoder->setLogger(self::$_logger);
$this->_driver->setLogger(self::$_logger);
$this->_state->setLogger(self::$_logger);
}
}

/**
Expand Down Expand Up @@ -688,9 +694,12 @@ public function handleRequest($cmd, $devId)
// Autodiscovery handles authentication on it's own.
if ($cmd == 'Autodiscover') {
$request = new Horde_ActiveSync_Request_Autodiscover($this, new Horde_ActiveSync_Device($this->_state));
$request->setLogger(self::$_logger);

$result = $request->handle();
if (!empty(self::$_logger)) {
$request->setLogger(self::$_logger);
}

$result = $request->handle($this->_request);
$this->_driver->clearAuthentication();
return $result;
}
Expand Down
19 changes: 13 additions & 6 deletions framework/ActiveSync/lib/Horde/ActiveSync/Request/Autodiscover.php
Expand Up @@ -30,20 +30,23 @@ class Horde_ActiveSync_Request_Autodiscover extends Horde_ActiveSync_Request_Bas
*
* @return text The content type of the response (text/xml).
*/
public function handle()
public function handle(Horde_Controller_Request $request = null)
{
$parser = xml_parser_create();
xml_parse_into_struct(
$parser,
$this->_decoder->getStream()->getString(),
$values);

// Get $_SERVER
$server = $request->getServerVars();

// Some broken clients *cough* android *cough* don't send the actual
// XML data structure at all, but instead use the email address as
// the username in the HTTP_AUTHENTICATION data. There are so many things
// wrong with this, but try to work around it if we can.
if (empty($values) && !empty($_SERVER['HTTP_AUTHORIZATION'])) {
$hash = base64_decode(str_replace('Basic ', '', $_SERVER['HTTP_AUTHORIZATION']));
if (empty($values) && !empty($server['HTTP_AUTHORIZATION'])) {
$hash = base64_decode(str_replace('Basic ', '', $server['HTTP_AUTHORIZATION']));
if (strpos($hash, ':') !== false) {
list($email, $pass) = explode(':', $hash, 2);
}
Expand All @@ -58,16 +61,20 @@ public function handle()
}

if (!empty($values)) {
$params = array('request_schema' => $values[0]['attributes']['XMLNS']);
$params = array('request_schema' => trim($values[0]['attributes']['XMLNS']));
// Response Schema is not in a set place.
foreach ($values as $value) {
if ($value['tag'] == 'ACCEPTABLERESPONSESCHEMA') {
$params['response_schema'] = $value['value'];
$params['response_schema'] = trim($value['value']);
break;
}
}
} else {
$params = array();
// Assume broken clients want these schemas.
$params = array(
'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006',
'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006'
);
}
$results = $this->_driver->autoDiscover($params);
if (empty($results['raw_xml'])) {
Expand Down
2 changes: 1 addition & 1 deletion framework/ActiveSync/lib/Horde/ActiveSync/Request/Base.php
Expand Up @@ -134,7 +134,7 @@ public function __construct(Horde_ActiveSync $as)
$this->_provisioning = $as->provisioning;

// Get the state object
$this->_state = &$as->state;
$this->_state = $as->state;

// Device info
$this->_device = $as->device;
Expand Down
4 changes: 2 additions & 2 deletions framework/ActiveSync/lib/Horde/ActiveSync/State/Sql.php
Expand Up @@ -1681,7 +1681,7 @@ protected function _resetDeviceState($id)
try {
$this->_db->delete($state_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user));
$this->_db->delete($map_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user));
$this->_db->delete($map_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user));
$this->_db->delete($mailmap_query, array($this->_deviceInfo->id, $id, $this->_deviceInfo->user));
} catch (Horde_Db_Exception $e) {
throw new Horde_ActiveSync_Exception($e);
}
Expand All @@ -1701,4 +1701,4 @@ protected function _resetDeviceState($id)
$cache->save();
}

}
}
14 changes: 11 additions & 3 deletions framework/ActiveSync/package.xml
Expand Up @@ -10,7 +10,7 @@
<email>mrubinsk@horde.org</email>
<active>yes</active>
</lead>
<date>2013-10-01</date>
<date>2013-10-02</date>
<version>
<release>2.8.3</release>
<api>2.8.0</api>
Expand All @@ -21,7 +21,12 @@
</stability>
<license uri="http://www.horde.org/licenses/gpl">GPL-2.0</license>
<notes>
<<<<<<< HEAD
*
=======
* [mjr] Fix some authentication issues when certain clients issue broken AutoDiscover requests.
* [mjr] Fix some minor issues with Autodiscover requests caught by unit testing.
>>>>>>> master
</notes>
<contents>
<dir baseinstalldir="/" name="/">
Expand Down Expand Up @@ -231,6 +236,7 @@
</dir> <!-- /test/Horde/ActiveSync/StateTest -->
<file name="AllTests.php" role="test" />
<file name="AppointmentTest.php" role="test" />
<file name="AutodiscoverTest.php" role="test" />
<file name="bootstrap.php" role="test" />
<file name="CacheTest.php" role="test" />
<file name="conf.php.dist" role="test" />
Expand Down Expand Up @@ -459,6 +465,7 @@
<install as="migration/20_horde_activesync_removesynccounters.php" name="migration/Horde/ActiveSync/20_horde_activesync_removesynccounters.php" />
<install as="Horde/ActiveSync/AllTests.php" name="test/Horde/ActiveSync/AllTests.php" />
<install as="Horde/ActiveSync/AppointmentTest.php" name="test/Horde/ActiveSync/AppointmentTest.php" />
<install as="Horde/ActiveSync/AutodiscoverTest.php" name="test/Horde/ActiveSync/AutodiscoverTest.php" />
<install as="Horde/ActiveSync/bootstrap.php" name="test/Horde/ActiveSync/bootstrap.php" />
<install as="Horde/ActiveSync/CacheTest.php" name="test/Horde/ActiveSync/CacheTest.php" />
<install as="Horde/ActiveSync/conf.php.dist" name="test/Horde/ActiveSync/conf.php.dist" />
Expand Down Expand Up @@ -1742,10 +1749,11 @@
<stability>
<release>stable</release>
<api>stable</api></stability>
<date>2013-10-01</date>
<date>2013-10-02</date>
<license uri="http://www.horde.org/licenses/gpl">GPL-2.0</license>
<notes>
*
* [mjr] Fix some authentication issues when certain clients issue broken AutoDiscover requests.
* [mjr] Fix some minor issues with Autodiscover requests caught by unit testing.
</notes>
</release>
</changelog>
Expand Down
191 changes: 191 additions & 0 deletions framework/ActiveSync/test/Horde/ActiveSync/AutodiscoverTest.php
@@ -0,0 +1,191 @@
<?php
/*
* Unit tests for Horde_ActiveSync_Timezone utilities
*
* @author Michael J. Rubinsky <mrubinsk@horde.org>
* @category Horde
* @package ActiveSync
*/
class Horde_ActiveSync_AutoDiscoverTest extends Horde_Test_Case
{

static protected $_server;
static protected $_input;
static protected $_driver;
static protected $_request;

public function setup()
{
self::$_driver = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base');
self::$_input = fopen('php://memory', 'wb+');
$decoder = new Horde_ActiveSync_Wbxml_Decoder(self::$_input);
$output = fopen('php://memory', 'wb+');
$encoder = new Horde_ActiveSync_Wbxml_Encoder($output);
$state = $this->getMockSkipConstructor('Horde_ActiveSync_State_Base');
self::$_request = $this->getMockSkipConstructor('Horde_Controller_Request_Http');
self::$_server = new Horde_ActiveSync(self::$_driver, $decoder, $encoder, $state, self::$_request);
}


/**
* Tests autodiscover functionality when passed a proper XML data structure
* containing an email address that needs to be mapped to a username.
*
*/
public function testAutodiscoverWithProperXML()
{
$request = <<<EOT
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006">
<Request>
<EMailAddress>mike@example.com</EMailAddress>
<AcceptableResponseSchema>
http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006
</AcceptableResponseSchema>
</Request>
</Autodiscover>
EOT;
fwrite(self::$_input, $request);
rewind(self::$_input);

// Mock the getUsernameFromEmail method to return 'mike' when 'mike@example.com'
// is passed.
self::$_driver->expects($this->once())
->method('getUsernameFromEmail')
->will($this->returnValueMap(array(array('mike@example.com', 'mike'))));

// Mock authenticate to return true only if mike is passed as username.
self::$_driver->expects($this->any())
->method('authenticate')
->will($this->returnValueMap(array(array('mike', '', null, true))));

// Setup is called once, and must return true.
self::$_driver->expects($this->once())
->method('setup')
->will($this->returnValue(true));

// Checks that the correct schema was detected.
$mock_driver_parameters = array(
'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006',
'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006');

// ...and will only return this if it was.
$mock_driver_results = array(
'display_name' => 'Michael Rubinsky',
'email' => 'mike@example.com',
'culture' => 'en:en',
'username' => 'mike',
'url' => 'https://example.com/Microsoft-Server-ActiveSync'
);

self::$_driver->expects($this->once())
->method('autoDiscover')
->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results))));

self::$_server->handleRequest('Autodiscover', 'testdevice');

// Test the results
$expected = <<<EOT
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
<Culture>en:en</Culture>
<User>
<DisplayName>Michael Rubinsky</DisplayName>
<EMailAddress>mike@example.com</EMailAddress>
</User>
<Action>
<Settings>
<Server>
<Type>MobileSync</Type>
<Url>https://example.com/Microsoft-Server-ActiveSync</Url>
<Name>https://example.com/Microsoft-Server-ActiveSync</Name>
</Server>
</Settings>
</Action>
</Response>
</Autodiscover>
EOT;
rewind(self::$_server->encoder->getStream());
$this->assertEquals($expected, stream_get_contents(self::$_server->encoder->getStream()));

}

/**
* Test workarounds for broken clients that don't send proper XML with
* autodiscover requests. In this case, the user/email is taken from the
* HTTP Basic auth data.
*/
public function testAutodiscoverWithMissingXML()
{
// Basic auth: mike:password
$auth = 'Basic bWlrZTpwYXNzd29yZA==';

self::$_request->expects($this->any())
->method('getServerVars')
->will($this->returnValue(array('HTTP_AUTHORIZATION' => $auth)));

// Mock the getUsernameFromEmail method to return 'mike' when 'mike'
// is passed.
self::$_driver->expects($this->once())
->method('getUsernameFromEmail')
->will($this->returnValueMap(array(array('mike', 'mike'))));

// Mock authenticate to return true only if 'mike' is passed as username
// and 'password' is passed as the password.
self::$_driver->expects($this->any())
->method('authenticate')
->will($this->returnValueMap(array(array('mike', 'password', null, true))));

// Setup is called once, and must return true.
self::$_driver->expects($this->once())
->method('setup')
->will($this->returnValue(true));

// Checks that the correct schema was detected.
$mock_driver_parameters = array(
'request_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006',
'response_schema' => 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006');

// ...and will only return this if it was.
$mock_driver_results = array(
'display_name' => 'Michael Rubinsky',
'email' => 'mike@example.com',
'culture' => 'en:en',
'username' => 'mike',
'url' => 'https://example.com/Microsoft-Server-ActiveSync'
);

self::$_driver->expects($this->once())
->method('autoDiscover')
->will($this->returnValueMap(array(array($mock_driver_parameters, $mock_driver_results))));

self::$_server->handleRequest('Autodiscover', 'testdevice');

// Test the results
$expected = <<<EOT
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
<Culture>en:en</Culture>
<User>
<DisplayName>Michael Rubinsky</DisplayName>
<EMailAddress>mike@example.com</EMailAddress>
</User>
<Action>
<Settings>
<Server>
<Type>MobileSync</Type>
<Url>https://example.com/Microsoft-Server-ActiveSync</Url>
<Name>https://example.com/Microsoft-Server-ActiveSync</Name>
</Server>
</Settings>
</Action>
</Response>
</Autodiscover>
EOT;
rewind(self::$_server->encoder->getStream());
$this->assertEquals($expected, stream_get_contents(self::$_server->encoder->getStream()));
}

}
4 changes: 2 additions & 2 deletions framework/ActiveSync/test/Horde/ActiveSync/ServerTest.php
Expand Up @@ -8,7 +8,7 @@
*/
class Horde_ActiveSync_ServerTest extends Horde_Test_Case
{
static $_server;
static protected $_server;
public function setup()
{
$driver = $this->getMockSkipConstructor('Horde_ActiveSync_Driver_Base');
Expand Down Expand Up @@ -39,4 +39,4 @@ public function testSupportedCommands()
$this->assertEquals('Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Search,Ping', self::$_server->getSupportedCommands());
}

}
}
4 changes: 4 additions & 0 deletions framework/Core/lib/Horde/Core/ActiveSync/Driver.php
Expand Up @@ -193,6 +193,10 @@ public function authenticate($username, $password, $domain = null)
}

// Now check Basic. Happens for authtype == 'basic' || 'basic_cert'
if (empty($password)) {
$this->_logger->notice('Device failed to pass the user password.');
return false;
}
if ((empty($conf['activesync']['auth']['type']) || (!empty($conf['activesync']['auth']['type']) && $conf['activesync']['auth']['type'] != 'cert')) &&
!$this->_auth->authenticate($username, array('password' => $password))) {

Expand Down

0 comments on commit 92d9dd7

Please sign in to comment.