Skip to content

Commit

Permalink
feature(Addressbook/Frontend/WebDAV): allow to configure readonly access
Browse files Browse the repository at this point in the history
CARDDAV_READONLY_POLICY

possible config values:
always, unknown, never
  • Loading branch information
pschuele committed Jul 4, 2024
1 parent 64073dd commit 74b9c44
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 21 deletions.
21 changes: 19 additions & 2 deletions tests/tine20/Addressbook/Frontend/WebDAV/ContactTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ public function testGetContact()
*/
public function testPutContactFromThunderbird()
{
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.21) Gecko/20110831 Lightning/1.0b2 Thunderbird/3.1.13';
$_SERVER['HTTP_USER_AGENT'] =
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.21) Gecko/20110831 Lightning/1.0b2 Thunderbird/3.1.13';

$contact = $this->testCreateContact();

Expand All @@ -180,7 +181,23 @@ public function testPutContactFromThunderbird()
$this->assertEquals('Kneschke', $record->n_family);
$this->assertEquals('+49 BUSINESS', $record->tel_work);
}


public function testPutContactWithReadOnlyConfig()
{
Addressbook_Config::getInstance()->set(Addressbook_Config::CARDDAV_READONLY_POLICY,
Addressbook_Config::CARDDAV_READONLY_POLICY_ALWAYS);

try {
$this->testPutContactFromThunderbird();
self::fail('config not working: Addressbook_Config::CARDDAV_READONLY_POLICY_ALWAYS');
} catch (Sabre\DAV\Exception\Forbidden $sdef) {
self::assertStringContainsString('Update denied', $sdef->getMessage());
} finally {
Addressbook_Config::getInstance()->set(Addressbook_Config::CARDDAV_READONLY_POLICY,
Addressbook_Config::CARDDAV_READONLY_POLICY_UNKNOWN);
}
}

/**
* test updating existing contact from MacOS X
* @depends testCreateContact
Expand Down
32 changes: 29 additions & 3 deletions tine20/Addressbook/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @subpackage Config
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Philipp Schüle <p.schuele@metaways.de>
* @copyright Copyright (c) 2011-2019 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2011-2024 Metaways Infosystems GmbH (http://www.metaways.de)
*/

/**
Expand All @@ -17,6 +17,19 @@ class Addressbook_Config extends Tinebase_Config_Abstract
{
const APP_NAME = 'Addressbook';

/**
* carddav readonly policy
*
* @var string
*/
public const CARDDAV_READONLY_POLICY = 'carddavReadonlyPolicy';
// unknown clients readonly
public const CARDDAV_READONLY_POLICY_UNKNOWN = 'unknown';
// all clients readonly
public const CARDDAV_READONLY_POLICY_ALWAYS = 'always';
// all clients have write access
public const CARDDAV_READONLY_POLICY_NEVER = 'never';

/**
* contact nominatim during contact import
*
Expand Down Expand Up @@ -138,7 +151,20 @@ class Addressbook_Config extends Tinebase_Config_Abstract
* (FEATURE_LIST_VIEW-PHPdoc)
* @see tine20/Tinebase/Config/Definition::$_properties
*/
protected static $_properties = array(
protected static $_properties = [
// TODO define possible values (CARDDAV_READONLY_POLICY_*)
self::CARDDAV_READONLY_POLICY => [
//_('CardDAV readonly policy')
self::LABEL => 'CardDAV readonly policy',
//_('Possible values: always, unknown, never')
self::DESCRIPTION => 'Possible values: always, unknown, never',
self::TYPE => self::TYPE_STRING,
self::DEFAULT_STR => self::CARDDAV_READONLY_POLICY_UNKNOWN,
self::CLIENTREGISTRYINCLUDE => false,
self::SETBYADMINMODULE => false,
self::SETBYSETUPMODULE => false,
],

self::ENABLED_FEATURES => [
//_('Enabled Features')
self::LABEL => 'Enabled Features',
Expand Down Expand Up @@ -387,7 +413,7 @@ class Addressbook_Config extends Tinebase_Config_Abstract
self::SETBYADMINMODULE => true,
self::SETBYSETUPMODULE => true,
],
);
];

/**
* (non-PHPdoc)
Expand Down
36 changes: 27 additions & 9 deletions tine20/Addressbook/Frontend/WebDAV/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,14 @@ public function getSize()
* Updates the VCard-formatted object
*
* @param string $cardData
* @throws DAV\Exception\Forbidden
* @return string
* @throws DAV\Exception\Forbidden
* @throws DAV\Exception\NotFound
* @throws DAV\Exception\PreconditionFailed
*/
public function put($cardData)
public function put($cardData)
{
if (get_class($this->_converter) == 'Addressbook_Convert_Contact_VCard_Generic') {
if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
. " update by generic client not allowed. See Addressbook_Convert_Contact_VCard_Factory for supported clients.");
throw new DAV\Exception\Forbidden('Update denied for unknown client');
}

$this->_checkPutPermission();
$contact = $this->_converter->toTine20Model($cardData, $this->getRecord(), array(
Addressbook_Convert_Contact_VCard_Abstract::OPTION_USE_SERVER_MODLOG => true,
));
Expand All @@ -289,6 +285,28 @@ public function put($cardData)
return $this->getETag();
}

protected function _checkPutPermission(): void
{
$configReadonlyPolicy = Addressbook_Config::getInstance()->get(Addressbook_Config::CARDDAV_READONLY_POLICY);
if ($configReadonlyPolicy === Addressbook_Config::CARDDAV_READONLY_POLICY_UNKNOWN
&& get_class($this->_converter) === Addressbook_Convert_Contact_VCard_Generic::class)
{
if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
. ' Update by generic client not allowed.'
. ' See Addressbook_Convert_Contact_VCard_Factory for supported clients.');
}
throw new DAV\Exception\Forbidden('Update denied for unknown client');
} else if ($configReadonlyPolicy === Addressbook_Config::CARDDAV_READONLY_POLICY_ALWAYS) {
if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
. ' Update not allowed.'
. ' See Addressbook_Config::CARDDAV_READONLY_POLICY');
}
throw new DAV\Exception\Forbidden('Update denied');
}
}

/**
* Updates the ACL
*
Expand Down
15 changes: 8 additions & 7 deletions tine20/Tasks/Frontend/WebDAV/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,19 +348,20 @@ public function getSize()
public function put($cardData)
{
if (get_class($this->_converter) == 'Tasks_Convert_Task_VCalendar_Generic') {
if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " update by generic client not allowed. See Tasks_Convert_Task_VCalendar_Factory for supported clients.");
throw new Sabre\DAV\Exception\Forbidden('Update denied for unknow client');
// TODO generalize this? see \Addressbook_Frontend_WebDAV_Contact::_checkPutPermission
if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
. " Update by generic client not allowed. See Tasks_Convert_Task_VCalendar_Factory for supported clients.");
}
throw new Sabre\DAV\Exception\Forbidden('Update denied for unknown client');
}

if (is_resource($cardData)) {
$cardData = stream_get_contents($cardData);
}
// Converting to UTF-8, if needed
$cardData = Sabre\DAV\StringUtil::ensureUTF8($cardData);

#Sabre_CalDAV_ICalendarUtil::validateICalendarObject($cardData, array('VTODO', 'VFREEBUSY'));


$vobject = Tasks_Convert_Task_VCalendar_Abstract::getVObject($cardData);
foreach ($vobject->children() as $component) {
if (isset($component->{'X-TINE20-CONTAINER'})) {
Expand All @@ -376,7 +377,7 @@ public function put($cardData)
Tasks_Convert_Task_VCalendar_Abstract::OPTION_USE_SERVER_MODLOG => true,
));

// iCal does sends back an old value, because it does not refresh the vcalendar after
// iCal does send back an old value, because it does not refresh the vcalendar after
// update. Therefor we must reapply the value of last_modified_time after the convert
$task->last_modified_time = $recordBeforeUpdate->last_modified_time;

Expand Down

0 comments on commit 74b9c44

Please sign in to comment.