mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-25 16:19:00 +01:00
Revert "* CalDAV/OutlookSynchronizer: reject invitations when client deletes then without appropriate rights in his calendar"
This reverts commit 4c6e41d479
.
This commit is contained in:
parent
78fe939b00
commit
44d9fcc790
36
.travis.yml
36
.travis.yml
@ -13,15 +13,25 @@ matrix:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
- memcached
|
- memcached
|
||||||
- mysql
|
- mysql #we use mariadb instead installed via addons below
|
||||||
# - postgres
|
# - postgres
|
||||||
|
|
||||||
|
#addons:
|
||||||
|
# mariadb: '10.0'
|
||||||
|
|
||||||
sudo: required
|
sudo: required
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -y libpcre3-dev apache2 libapache2-mod-fastcgi
|
- sudo apt-get install -y libpcre3-dev
|
||||||
- case $(phpenv version-name) in
|
- case $(phpenv version-name) in
|
||||||
|
"5.6")
|
||||||
|
yes "" | pecl install memcache;
|
||||||
|
yes "" | pecl install apcu-4.0.11;
|
||||||
|
yes "" | pecl install igbinary;
|
||||||
|
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
||||||
|
phpenv config-rm xdebug.ini;
|
||||||
|
;;
|
||||||
"7"|"7.0"|"7.1"|"7.2")
|
"7"|"7.0"|"7.1"|"7.2")
|
||||||
yes "" | pecl install apcu;
|
yes "" | pecl install apcu;
|
||||||
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
||||||
@ -31,23 +41,15 @@ before_script:
|
|||||||
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
# enable apache with php-fpm see https://docs.travis-ci.com/user/languages/php/#apache--php
|
|
||||||
- sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
|
|
||||||
- sudo a2enmod rewrite actions fastcgi alias
|
|
||||||
- echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
|
||||||
- sudo sed -i -e "s,www-data,travis,g" /etc/apache2/envvars
|
|
||||||
- sudo chown -R travis:travis /var/lib/apache2/fastcgi
|
|
||||||
- ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
|
|
||||||
# configure apache virtual hosts
|
|
||||||
- sudo cp -f doc/travis-ci-apache.conf /etc/apache2/sites-available/000-default.conf
|
|
||||||
- sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/000-default.conf
|
|
||||||
- sudo service apache2 restart
|
|
||||||
- case $(phpenv version-name) in
|
- case $(phpenv version-name) in
|
||||||
"7.2")
|
"5.6")
|
||||||
composer require --ignore-platform-reqs 'phpunit/phpunit:~8';
|
composer require 'phpunit/phpunit:~5.7';
|
||||||
;;
|
;;
|
||||||
|
"7"|"7.0")
|
||||||
|
composer require 'phpunit/phpunit:~6';
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
composer require --ignore-platform-reqs 'phpunit/phpunit:~9';
|
composer require --ignore-platform-reqs 'phpunit/phpunit:~7';
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
- php -m
|
- php -m
|
||||||
|
@ -983,7 +983,7 @@ class Accounts
|
|||||||
if (!is_array($app_users))
|
if (!is_array($app_users))
|
||||||
{
|
{
|
||||||
self::setup_cache();
|
self::setup_cache();
|
||||||
$cache = &self::$cache['account_split'][$app_users];
|
$cache = &self::$cache['account_split'][$app_user];
|
||||||
|
|
||||||
if (is_array($cache))
|
if (is_array($cache))
|
||||||
{
|
{
|
||||||
|
@ -125,7 +125,7 @@ class Ldap
|
|||||||
*
|
*
|
||||||
* @var Api\Accounts
|
* @var Api\Accounts
|
||||||
*/
|
*/
|
||||||
protected $frontend;
|
private $frontend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance of the ldap class
|
* Instance of the ldap class
|
||||||
|
@ -769,10 +769,9 @@ class Cache
|
|||||||
if (is_null(Cache::$default_provider))
|
if (is_null(Cache::$default_provider))
|
||||||
{
|
{
|
||||||
Cache::$default_provider =
|
Cache::$default_provider =
|
||||||
PHP_SAPI === 'cli' ? 'EGroupware\Api\Cache\Files' :
|
function_exists('apcu_fetch') && Cache\Apcu::available() ? 'EGroupware\Api\Cache\Apcu' :
|
||||||
(function_exists('apcu_fetch') && Cache\Apcu::available() ? 'EGroupware\Api\Cache\Apcu' :
|
(function_exists('apc_fetch') && Cache\Apc::available() ? 'EGroupware\Api\Cache\Apc' :
|
||||||
(function_exists('apc_fetch') && Cache\Apc::available() ? 'EGroupware\Api\Cache\Apc' :
|
'EGroupware\Api\Cache\Files');
|
||||||
'EGroupware\Api\Cache\Files'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));
|
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));
|
||||||
|
@ -13,9 +13,6 @@
|
|||||||
|
|
||||||
namespace EGroupware\Api\Cache;
|
namespace EGroupware\Api\Cache;
|
||||||
|
|
||||||
// fix warning in tests, if memcache extension not available
|
|
||||||
if (defined('MEMCACHE_COMPRESSED')) define('MEMCACHE_COMPRESSED', 2);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caching provider storing data in memcached via PHP's memcache extension
|
* Caching provider storing data in memcached via PHP's memcache extension
|
||||||
*
|
*
|
||||||
|
@ -1658,8 +1658,6 @@ class Session
|
|||||||
*/
|
*/
|
||||||
private static function set_cookiedomain()
|
private static function set_cookiedomain()
|
||||||
{
|
{
|
||||||
if (PHP_SAPI === "cli") return; // gives warnings and has no benefit
|
|
||||||
|
|
||||||
if ($GLOBALS['egw_info']['server']['cookiedomain'])
|
if ($GLOBALS['egw_info']['server']['cookiedomain'])
|
||||||
{
|
{
|
||||||
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com
|
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com
|
||||||
|
@ -395,11 +395,7 @@ class Translation
|
|||||||
static function &load_app($app,$lang)
|
static function &load_app($app,$lang)
|
||||||
{
|
{
|
||||||
//$start = microtime(true);
|
//$start = microtime(true);
|
||||||
if (!isset(self::$db))
|
if (is_null(self::$db)) self::init(false);
|
||||||
{
|
|
||||||
self::init(false);
|
|
||||||
if (!isset(self::$db)) return;
|
|
||||||
}
|
|
||||||
$loaded = array();
|
$loaded = array();
|
||||||
foreach(self::$db->select(self::LANG_TABLE,'message_id,content',array(
|
foreach(self::$db->select(self::LANG_TABLE,'message_id,content',array(
|
||||||
'lang' => $lang,
|
'lang' => $lang,
|
||||||
|
@ -1,375 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* CalDAV tests base class
|
|
||||||
*
|
|
||||||
* @link http://www.egroupware.org
|
|
||||||
* @author Ralf Becker
|
|
||||||
* @package api
|
|
||||||
* @subpackage caldav
|
|
||||||
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
|
||||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace EGroupware\Api;
|
|
||||||
|
|
||||||
// so tests can run standalone
|
|
||||||
require_once __DIR__.'/../src/loader/common.php'; // autoloader
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use GuzzleHttp\Client, GuzzleHttp\RequestOptions;
|
|
||||||
use Horde_Icalendar, Horde_Icalendar_Exception;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract CalDAVTest using GuzzleHttp\Client against EGroupware CalDAV/CardDAV server
|
|
||||||
*
|
|
||||||
* @see http://docs.guzzlephp.org/en/v6/quickstart.html
|
|
||||||
*
|
|
||||||
* @package EGroupware\Api
|
|
||||||
*/
|
|
||||||
abstract class CalDAVTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Base URL of CalDAV server
|
|
||||||
*/
|
|
||||||
const CALDAV_BASE = 'http://localhost/egroupware/groupdav.php';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get full URL for a CalDAV path
|
|
||||||
*
|
|
||||||
* @param string $path CalDAV path
|
|
||||||
* @return string URL
|
|
||||||
*/
|
|
||||||
protected function url($path='/')
|
|
||||||
{
|
|
||||||
$base = self::CALDAV_BASE;
|
|
||||||
if (!empty($GLOBALS['EGW_DOMAIN']) && $GLOBALS['EGW_DOMAIN'] !== 'default')
|
|
||||||
{
|
|
||||||
$base = str_replace('localhost', $GLOBALS['EGW_DOMAIN'], $base);
|
|
||||||
}
|
|
||||||
return $base.$path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default options for GuzzleHttp\Client
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
* @see http://docs.guzzlephp.org/en/v6/request-options.html
|
|
||||||
*/
|
|
||||||
protected $client_options = [
|
|
||||||
RequestOptions::HTTP_ERRORS => false, // return all HTTP status, not throwing exceptions
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Cookie' => 'XDEBUG_SESSION=PHPSTORM',
|
|
||||||
//'User-Agent' => 'CalDAVSynchronizer',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get HTTP client for tests
|
|
||||||
*
|
|
||||||
* It will use by default the always existing user "demo" with password "guest" (use [] to NOT authenticate).
|
|
||||||
* Additional users need to be created with $this->createUser("name").
|
|
||||||
*
|
|
||||||
* @param string|array $user_or_options ='demo' string with account_lid of user for authentication or array of options
|
|
||||||
* @return Client
|
|
||||||
* @see http://docs.guzzlephp.org/en/v6/request-options.html
|
|
||||||
* @see http://docs.guzzlephp.org/en/v6/quickstart.html
|
|
||||||
*/
|
|
||||||
protected function getClient($user_or_options='demo')
|
|
||||||
{
|
|
||||||
if (!is_array($user_or_options))
|
|
||||||
{
|
|
||||||
$user_or_options = $this->auth($user_or_options);
|
|
||||||
}
|
|
||||||
return new Client(array_merge($this->client_options, $user_or_options));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a number of users with optional ACL rights too
|
|
||||||
*
|
|
||||||
* Example with boss granting secretary full rights on his calendar, plus one other user:
|
|
||||||
*
|
|
||||||
* $this->users = [
|
|
||||||
* 'boss' => [],
|
|
||||||
* 'secretary' => ['rights' => ['boss' => Acl::READ|Acl::ADD|Acl::EDIT|Acl::DELETE]],
|
|
||||||
* 'other' => [],
|
|
||||||
* ];
|
|
||||||
* $this->createUsersACL($this->users);
|
|
||||||
*
|
|
||||||
* @param array& $users $account_lid => array with values for keys with (defaults) "firstname" ($_acount_lid), "lastname" ("User"),
|
|
||||||
* "email" ("$_account_lid@example.org"), "password" (random string), "primary_group" ("NoGroups" to not set rights)
|
|
||||||
* "rights" array with $grantee => $rights pairs (need to be created before!)
|
|
||||||
* @param string $app app to create the rights for, default "calendar"
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function createUsersACL(array &$users, $app='calendar')
|
|
||||||
{
|
|
||||||
foreach($users as $user => $data)
|
|
||||||
{
|
|
||||||
$data['id'] = $this->createUser($user, $data);
|
|
||||||
|
|
||||||
foreach($data['rights'] ?? [] as $grantee => $rights)
|
|
||||||
{
|
|
||||||
$this->addAcl('calendar', $data['id'], $grantee, $rights);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array to track created users for tearDown and authentication
|
|
||||||
*
|
|
||||||
* @var array $account_lid => array with other data pairs
|
|
||||||
*/
|
|
||||||
private $created_users = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a user
|
|
||||||
*
|
|
||||||
* Created users are automatic deleted in tearDown() and can be passed to auth() or getClient() methods.
|
|
||||||
* Users have random passwords to force new/different sessions!
|
|
||||||
*
|
|
||||||
* @param string $_account_lid
|
|
||||||
* @param array& $data =[] values for keys with (defaults) "firstname" ($_acount_lid), "lastname" ("User"),
|
|
||||||
* "email" ("$_account_lid@example.org"), "password" (random string), "primary_group" ("NoGroups" to not set rights)
|
|
||||||
* on return: with defaults set
|
|
||||||
* @return int account_id of created user
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function createUser($_account_lid, array &$data=[])
|
|
||||||
{
|
|
||||||
// add some defaults
|
|
||||||
$data = array_merge([
|
|
||||||
'firstname' => ucfirst($_account_lid),
|
|
||||||
'lastname' => 'User',
|
|
||||||
'email' => $_account_lid.'@example.org',
|
|
||||||
'password' => 'secret',//Auth::randomstring(12),
|
|
||||||
'primary_group' => 'NoGroup',
|
|
||||||
], $data);
|
|
||||||
|
|
||||||
$data['id'] = $this->getSetup()->add_account($_account_lid, $data['firstname'], $data['lastname'],
|
|
||||||
$data['password'], $data['primary_group'], false, $data['email']);
|
|
||||||
|
|
||||||
// give use run rights for CalDAV apps, as NoGroup does NOT!
|
|
||||||
$this->addAcl(['groupdav','calendar','infolog','addressbook'], 'run', $data['id']);
|
|
||||||
|
|
||||||
$this->created_users[$_account_lid] = $data;
|
|
||||||
|
|
||||||
return $data['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get authentication information for given user to use
|
|
||||||
*
|
|
||||||
* @param string $_account_lid ='demo'
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function auth($_account_lid='demo')
|
|
||||||
{
|
|
||||||
if ($_account_lid === 'demo')
|
|
||||||
{
|
|
||||||
$password = 'guest';
|
|
||||||
}
|
|
||||||
elseif (!isset($this->created_users[$_account_lid]))
|
|
||||||
{
|
|
||||||
throw new \InvalidArgumentException("No user '$_account_lid' exist, need to create it with createUser('$_account_lid')");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$password = $this->created_users[$_account_lid]['password'];
|
|
||||||
}
|
|
||||||
return [RequestOptions::AUTH => [$_account_lid, $password]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tear down:
|
|
||||||
* - delete users created by createUser() incl. their ACL and data
|
|
||||||
*/
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
$setup = $this->getSetup();
|
|
||||||
|
|
||||||
foreach($this->created_users as $account_lid => $data)
|
|
||||||
{
|
|
||||||
// if ($id) $setup->accounts->delete($data['id']);
|
|
||||||
unset($this->created_users[$account_lid]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add ACL rights
|
|
||||||
*
|
|
||||||
* @param string|array $apps app-names
|
|
||||||
* @param string $location eg. "run"
|
|
||||||
* @param int|string $account accountid or account_lid
|
|
||||||
* @param int $rights rights to set, default 1
|
|
||||||
*/
|
|
||||||
function addAcl($apps, $location, $account, $rights=1)
|
|
||||||
{
|
|
||||||
return $this->getSetup()->add_acl($apps, $location, $account, $rights);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return instance of setup object eg. to create users
|
|
||||||
*
|
|
||||||
* @return \setup
|
|
||||||
*/
|
|
||||||
private function getSetup()
|
|
||||||
{
|
|
||||||
if (!isset($_REQUEST['domain']))
|
|
||||||
{
|
|
||||||
$_REQUEST['domain'] = $GLOBALS['EGW_DOMAIN'] ?? 'default';
|
|
||||||
}
|
|
||||||
$_REQUEST['ConfigDomain'] = $_REQUEST['domain'];
|
|
||||||
require_once __DIR__.'/../../setup/inc/functions.inc.php';
|
|
||||||
|
|
||||||
return $GLOBALS['egw_setup'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check HTTP status in response
|
|
||||||
*
|
|
||||||
* @param int|array $expected one or more valid status codes
|
|
||||||
* @param ResponseInterface $response
|
|
||||||
* @param string $message ='' additional message to prefix result message
|
|
||||||
*/
|
|
||||||
protected function assertHttpStatus($expected, ResponseInterface $response, $message='')
|
|
||||||
{
|
|
||||||
$status = $response->getStatusCode();
|
|
||||||
$this->assertEquals(in_array($status, (array)$expected) ? $status : ((array)$expected)[0], $status,
|
|
||||||
(!empty($message) ? $message.': ' : ''). 'Expected HTTP status: '.json_encode($expected).
|
|
||||||
", Server returned: $status ".$response->getReasonPhrase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts an iCal file matches an expected one taking into account $_overwrites
|
|
||||||
*
|
|
||||||
* @param string $_expected
|
|
||||||
* @param string $_acctual
|
|
||||||
* @param string $_message
|
|
||||||
* @param array $_overwrites =[] eg. ['vEvent' => [['ATTENDEE' => ['mailto:boss@example.org' => ['PARTSTAT' => 'DECLINED']]]]]
|
|
||||||
* (first vEvent attendee with value 'mailto:boss@...' has param 'PARTSTAT=DECLINED')
|
|
||||||
* @throws Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
protected function assertIcal($_expected, $_acctual, $_message=null, $_overwrites=[])
|
|
||||||
{
|
|
||||||
// enable to see full iCals
|
|
||||||
//$this->assertEquals($_expected, (string)$_acctual, $_message.": iCal not byte-by-byte identical");
|
|
||||||
|
|
||||||
$expected = new Horde_Icalendar();
|
|
||||||
$expected->parsevCalendar($_expected);
|
|
||||||
$acctual = new Horde_Icalendar();
|
|
||||||
$acctual->parsevCalendar($_acctual);
|
|
||||||
|
|
||||||
if (($msgs = $this->checkComponentEqual($expected, $acctual, $_overwrites)))
|
|
||||||
{
|
|
||||||
$this->assertEquals($_expected, (string)$_acctual, ($_message ? $_message.":\n" : '').implode("\n", $msgs));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->assertTrue(true); // due to $_overwrite probable $_expected !== $_acctual
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check two iCal components are equal modulo overwrites / expected difference
|
|
||||||
*
|
|
||||||
* Only a whitelist of attributes per component are checked, see $component_attrs2check variable.
|
|
||||||
*
|
|
||||||
* @param Horde_Icalendar $_expected
|
|
||||||
* @param Horde_Icalendar $_acctual
|
|
||||||
* @param string $_message
|
|
||||||
* @param array $_overwrites =[] eg. ['ATTENDEE' => ['boss@example.org' => ['PARTSTAT' => 'DECLINED']]]
|
|
||||||
* @throws Horde_Icalendar_Exception
|
|
||||||
* @return array message(s) what's not equal
|
|
||||||
*/
|
|
||||||
protected function checkComponentEqual(Horde_Icalendar $_expected, Horde_Icalendar $_acctual, $_overwrites=[])
|
|
||||||
{
|
|
||||||
// only following attributes in these components are checked:
|
|
||||||
static $component_attrs2check = [
|
|
||||||
'vcalendar' => ['VERSION'],
|
|
||||||
'vTimeZone' => ['TZID'],
|
|
||||||
'vEvent' => ['UID', 'SUMMARY', 'LOCATION', 'DESCRIPTION', 'DTSTART', 'DTEND', 'ORGANIZER', 'ATTENDEE'],
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($_expected->getType() !== $_acctual->getType())
|
|
||||||
{
|
|
||||||
return ["component type not equal"];
|
|
||||||
}
|
|
||||||
$msgs = [];
|
|
||||||
foreach ($component_attrs2check[$_expected->getType()] ?? [] as $attr)
|
|
||||||
{
|
|
||||||
$acctualAttrs = $_acctual->getAllAttributes($attr);
|
|
||||||
foreach($_expected->getAllAttributes($attr) as $expectedAttr)
|
|
||||||
{
|
|
||||||
$found = false;
|
|
||||||
foreach($acctualAttrs as $acctualAttr)
|
|
||||||
{
|
|
||||||
if (count($acctualAttrs) === 1 || $expectedAttr['value'] === $acctualAttr['value'])
|
|
||||||
{
|
|
||||||
$found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$found)
|
|
||||||
{
|
|
||||||
$msgs[] = "No $attr {$expectedAttr['value']} found";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// remove / ignore X-parameters, eg. X-EGROUPWARE-UID in ATTENDEE or ORGANIZER
|
|
||||||
$acctualAttr['params'] = array_filter($acctualAttr['params'], function ($key) {
|
|
||||||
return substr($key, 0, 2) !== 'X-';
|
|
||||||
}, ARRAY_FILTER_USE_KEY);
|
|
||||||
|
|
||||||
if (isset($_overwrites[$attr]) && is_scalar($_overwrites[$attr]))
|
|
||||||
{
|
|
||||||
$expectedAttr = [
|
|
||||||
'name' => $attr,
|
|
||||||
'value' => $_overwrites[$attr],
|
|
||||||
'values' => [$_overwrites[$attr]],
|
|
||||||
'params' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
elseif (isset($_overwrites[$attr]) && is_array($_overwrites[$attr]))
|
|
||||||
{
|
|
||||||
foreach ($_overwrites[$attr] as $value => $params)
|
|
||||||
{
|
|
||||||
if ($value === $expectedAttr['value'])
|
|
||||||
{
|
|
||||||
$expectedAttr['params'] = array_merge($expectedAttr['params'], $params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($expectedAttr != $acctualAttr)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expectedAttr, $acctualAttr, "$attr not equal");
|
|
||||||
$msgs[] = "$attr not equal";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check sub-components, overrites use an index by type eg. 1. vEvent: ['vEvent'=>[[<overwrites for 1. vEvent]]]
|
|
||||||
$idx_by_type = [];
|
|
||||||
foreach($_expected->getComponents() as $idx => $component)
|
|
||||||
{
|
|
||||||
if (!isset($idx_by_type[$type = $component->getType()])) $idx_by_type[$type] = 0;
|
|
||||||
$msgs = array_merge($msgs, $this->checkComponentEqual($component, $_acctual->getComponent($idx),
|
|
||||||
$_overwrites[$type][$idx_by_type[$type]] ?? []));
|
|
||||||
$idx_by_type[$type]++;
|
|
||||||
}
|
|
||||||
return $msgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
|
||||||
{
|
|
||||||
parent::setUpBeforeClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function tearDownAfterClass()
|
|
||||||
{
|
|
||||||
parent::tearDownAfterClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1338,7 +1338,7 @@ class calendar_bo
|
|||||||
elseif ($grants[$uid] & Acl::READ)
|
elseif ($grants[$uid] & Acl::READ)
|
||||||
{
|
{
|
||||||
// if we have a READ grant from a participant, we dont give an implicit privat grant too
|
// if we have a READ grant from a participant, we dont give an implicit privat grant too
|
||||||
$grant |= self::ACL_FREEBUSY | Acl::READ;
|
$grant |= Acl::READ;
|
||||||
// we cant break here, as we might be a participant too, and would miss the privat grant
|
// we cant break here, as we might be a participant too, and would miss the privat grant
|
||||||
}
|
}
|
||||||
elseif (!is_numeric($uid))
|
elseif (!is_numeric($uid))
|
||||||
|
@ -1375,56 +1375,35 @@ class calendar_groupdav extends Api\CalDAV\Handler
|
|||||||
return true; // simply ignore DELETE in inbox for now
|
return true; // simply ignore DELETE in inbox for now
|
||||||
}
|
}
|
||||||
$return_no_access = true; // to allow to check if current use is a participant and reject the event for him
|
$return_no_access = true; // to allow to check if current use is a participant and reject the event for him
|
||||||
$event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access);
|
if (!is_array($event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access)) || !$return_no_access ||
|
||||||
|
// Work around problems with Outlook CalDAV Synchroniser (https://caldavsynchronizer.org/)
|
||||||
// no event found --> 404 Not Found
|
// - sends a DELETE to reject a meeting request --> deletes event for all participants, if user has delete rights on the calendar
|
||||||
if (!is_array($event))
|
// --> only set status for everyone else but the organizer
|
||||||
|
self::get_agent() == 'caldavsynchronizer' && is_array($event) && $event['owner'] != $user)
|
||||||
{
|
{
|
||||||
$ret = $event;
|
if (is_array($event) && (!$return_no_access || $event['owner'] != $user))
|
||||||
error_log("_common_get_put_delete('DELETE', ..., $id) user=$user, return_no_access=".array2string($return_no_access)." returned ".array2string($event));
|
|
||||||
}
|
|
||||||
// Work around problems with Outlook CalDAV Synchronizer (https://caldavsynchronizer.org/)
|
|
||||||
// - sends a DELETE to reject a meeting request --> deletes event for all participants, if user has delete rights from the organizer
|
|
||||||
// --> only set status for everyone else but the organizer
|
|
||||||
// OR no delete rights and deleting an event in someone else calendar --> check if calendar owner is a participant --> reject him
|
|
||||||
elseif ((!$return_no_access || (self::get_agent() === 'caldavsynchronizer' && $event['owner'] != $user)) &&
|
|
||||||
// check if current user has edit rights for calendar of $user, can change status / reject invitation for him
|
|
||||||
$this->bo->check_perms(Acl::EDIT, 0, $user))
|
|
||||||
{
|
|
||||||
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
|
|
||||||
$ret = '403 Forbidden';
|
|
||||||
$memberships = $GLOBALS['egw']->accounts->memberships($user, true);
|
|
||||||
foreach(array_keys($event['participants']) as $uid)
|
|
||||||
{
|
{
|
||||||
if ($user == $uid || in_array($uid, $memberships))
|
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
|
||||||
|
$ret = '403 Forbidden';
|
||||||
|
$memberships = $GLOBALS['egw']->accounts->memberships($this->bo->user, true);
|
||||||
|
foreach(array_keys($event['participants']) as $uid)
|
||||||
{
|
{
|
||||||
$this->bo->set_status($event, $user, 'R');
|
if ($this->bo->user == $uid || in_array($uid, $memberships))
|
||||||
$ret = true;
|
{
|
||||||
break;
|
$this->bo->set_status($event,$this->bo->user, 'R');
|
||||||
|
$ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
// current user has no delete rights for event --> reject invitation, if he is a participant
|
|
||||||
elseif (!$return_no_access)
|
|
||||||
{
|
|
||||||
// check if current user is a participant or one of the groups he is a member of --> reject the meeting request
|
|
||||||
$ret = '403 Forbidden';
|
|
||||||
$memberships = $GLOBALS['egw']->accounts->memberships($this->bo->user, true);
|
|
||||||
foreach(array_keys($event['participants']) as $uid)
|
|
||||||
{
|
{
|
||||||
if ($this->bo->user == $uid || in_array($uid, $memberships))
|
$ret = $event;
|
||||||
{
|
|
||||||
$this->bo->set_status($event, $this->bo->user, 'R');
|
|
||||||
$ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we have delete rights on the event and (try to) delete it
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$ret = $this->bo->delete($event['id']);
|
$ret = $this->bo->delete($event['id']);
|
||||||
if (!$ret) { error_log("delete($event[id]) returned FALSE"); $ret = '400 Failed to delete event';}
|
|
||||||
}
|
}
|
||||||
if ($this->debug) error_log(__METHOD__."(,$id) return_no_access=$return_no_access, event[participants]=".array2string(is_array($event)?$event['participants']:null).", user={$this->bo->user} --> return ".array2string($ret));
|
if ($this->debug) error_log(__METHOD__."(,$id) return_no_access=$return_no_access, event[participants]=".array2string(is_array($event)?$event['participants']:null).", user={$this->bo->user} --> return ".array2string($ret));
|
||||||
return $ret;
|
return $ret;
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* CalDAV tests: create, read and delete an event
|
|
||||||
*
|
|
||||||
* @link https://www.egroupware.org
|
|
||||||
* @author Ralf Becker <rb@egroupware.org>
|
|
||||||
* @package calendar
|
|
||||||
* @subpackage tests
|
|
||||||
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
|
||||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace EGroupware\calendar;
|
|
||||||
|
|
||||||
require_once __DIR__.'/../../../api/tests/CalDAVTest.php';
|
|
||||||
|
|
||||||
use EGroupware\Api\CalDAVTest;
|
|
||||||
use GuzzleHttp\RequestOptions;
|
|
||||||
|
|
||||||
class CalDAVcreateReadDelete extends CalDAVTest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test accessing CalDAV without authentication
|
|
||||||
*/
|
|
||||||
public function testNoAuth()
|
|
||||||
{
|
|
||||||
$response = $this->getClient([])->get($this->url('/'));
|
|
||||||
|
|
||||||
$this->assertHttpStatus(401, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test accessing CalDAV with authentication
|
|
||||||
*/
|
|
||||||
public function testAuth()
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->get($this->url('/'));
|
|
||||||
|
|
||||||
$this->assertHttpStatus(200, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENT_URL = '/demo/calendar/new-event-1233456789-new.ics';
|
|
||||||
const EVENT_ICAL = <<<EOICAL
|
|
||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
BEGIN:VTIMEZONE
|
|
||||||
TZID:Europe/Berlin
|
|
||||||
BEGIN:DAYLIGHT
|
|
||||||
TZOFFSETFROM:+0100
|
|
||||||
TZOFFSETTO:+0200
|
|
||||||
TZNAME:CEST
|
|
||||||
DTSTART:19700329T020000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
|
||||||
END:DAYLIGHT
|
|
||||||
BEGIN:STANDARD
|
|
||||||
TZOFFSETFROM:+0200
|
|
||||||
TZOFFSETTO:+0100
|
|
||||||
TZNAME:CET
|
|
||||||
DTSTART:19701025T030000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
|
||||||
END:STANDARD
|
|
||||||
END:VTIMEZONE
|
|
||||||
BEGIN:VEVENT
|
|
||||||
DTSTART;TZID=Europe/Berlin:20110406T210000
|
|
||||||
DTEND;TZID=Europe/Berlin:20110406T220000
|
|
||||||
DTSTAMP:20110406T183747Z
|
|
||||||
LAST-MODIFIED:20110406T183747Z
|
|
||||||
LOCATION:Somewhere
|
|
||||||
SUMMARY:Tonight
|
|
||||||
UID:new-event-1233456789-new
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
EOICAL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an event
|
|
||||||
*/
|
|
||||||
public function testCreate()
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->put($this->url(self::EVENT_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'If-None-Match' => '*',
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_ICAL,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertHttpStatus(201, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read created event
|
|
||||||
*/
|
|
||||||
public function testRead()
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->get($this->url(self::EVENT_URL));
|
|
||||||
|
|
||||||
$this->assertHttpStatus(200, $response);
|
|
||||||
$this->assertIcal(self::EVENT_ICAL, $response->getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete created event
|
|
||||||
*/
|
|
||||||
public function testDelete()
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->delete($this->url(self::EVENT_URL));
|
|
||||||
|
|
||||||
$this->assertHttpStatus(204, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read created event
|
|
||||||
*/
|
|
||||||
public function testReadDeleted()
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->get($this->url(self::EVENT_URL));
|
|
||||||
|
|
||||||
$this->assertHttpStatus(404, $response);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,368 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* CalDAV tests: DELETE requests for non-series by Outlook CalDAV Synchronizer and other clients
|
|
||||||
*
|
|
||||||
* @link https://www.egroupware.org
|
|
||||||
* @author Ralf Becker <rb@egroupware.org>
|
|
||||||
* @package calendar
|
|
||||||
* @subpackage tests
|
|
||||||
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
|
||||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace EGroupware\calendar;
|
|
||||||
|
|
||||||
require_once __DIR__.'/../../../api/tests/CalDAVTest.php';
|
|
||||||
|
|
||||||
use EGroupware\Api\CalDAVTest;
|
|
||||||
use GuzzleHttp\RequestOptions;
|
|
||||||
use EGroupware\Api\Acl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class CalDAVsingleDELETE
|
|
||||||
*
|
|
||||||
* This tests check all sorts of DELETE requests by organizer and attendees, with and without (delete) rights on the organizer.
|
|
||||||
*
|
|
||||||
* For CalDAV Synchronizer, which does not distingues between deleting and rejecting events, we only allow the
|
|
||||||
* organizer to delete an event.
|
|
||||||
*
|
|
||||||
* @package EGroupware\calendar
|
|
||||||
* @covers \calendar_groupdav::delete()
|
|
||||||
* @uses \calendar_groupdav::put()
|
|
||||||
* @uses \calendar_groupdav::get()
|
|
||||||
*/
|
|
||||||
class CalDAVsingleDELETE extends CalDAVTest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Users and their ACL for the test
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $users = [
|
|
||||||
'boss' => [],
|
|
||||||
'secretary' => [
|
|
||||||
'rights' => [
|
|
||||||
'boss' => Acl::READ|Acl::ADD|Acl::EDIT|Acl::DELETE,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'other' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create some users incl. ACL
|
|
||||||
*/
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->createUsersACL($this->users, 'calendar');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check created users
|
|
||||||
*/
|
|
||||||
public function testPrincipals()
|
|
||||||
{
|
|
||||||
foreach($this->users as $user => &$data)
|
|
||||||
{
|
|
||||||
$response = $this->getClient()->propfind($this->url('/principals/users/'.$user.'/'), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Depth' => 0,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus(207, $response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENT_BOSS_ATTENDEE_ORGANIZER_URL = '/other/calendar/new-event-boss-attendee-123456789-new.ics';
|
|
||||||
const EVENT_BOSS_ATTENDEE_URL = '/boss/calendar/new-event-boss-attendee-123456789-new.ics';
|
|
||||||
const EVENT_BOSS_ATTENDEE_ICAL = <<<EOICAL
|
|
||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
BEGIN:VTIMEZONE
|
|
||||||
TZID:Europe/Berlin
|
|
||||||
BEGIN:DAYLIGHT
|
|
||||||
TZOFFSETFROM:+0100
|
|
||||||
TZOFFSETTO:+0200
|
|
||||||
TZNAME:CEST
|
|
||||||
DTSTART:19700329T020000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
|
||||||
END:DAYLIGHT
|
|
||||||
BEGIN:STANDARD
|
|
||||||
TZOFFSETFROM:+0200
|
|
||||||
TZOFFSETTO:+0100
|
|
||||||
TZNAME:CET
|
|
||||||
DTSTART:19701025T030000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
|
||||||
END:STANDARD
|
|
||||||
END:VTIMEZONE
|
|
||||||
BEGIN:VEVENT
|
|
||||||
DTSTART;TZID=Europe/Berlin:20110406T210000
|
|
||||||
DTEND;TZID=Europe/Berlin:20110406T220000
|
|
||||||
DTSTAMP:20110406T183747Z
|
|
||||||
LAST-MODIFIED:20110406T183747Z
|
|
||||||
LOCATION:Somewhere
|
|
||||||
SUMMARY:Tonight
|
|
||||||
ORGANIZER;CN="Other User":mailto:other@example.org
|
|
||||||
ATTENDEE;CN="Other User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:other@example.org
|
|
||||||
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:boss@example.org
|
|
||||||
UID:new-event-boss-attendee-123456789-new
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
EOICAL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check secretary deletes in boss's calendar event he is an attendee / invited
|
|
||||||
*
|
|
||||||
* @throws \Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
public function testSecretaryDeletesBossAttendee()
|
|
||||||
{
|
|
||||||
// create invitation by organizer
|
|
||||||
$response = $this->getClient('other')->put($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'Prefer' => 'return=representation'
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_BOSS_ATTENDEE_ICAL,
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus([200,201], $response);
|
|
||||||
$this->assertIcal(self::EVENT_BOSS_ATTENDEE_ICAL, $response->getBody());
|
|
||||||
|
|
||||||
// secretrary deletes event in boss's calendar
|
|
||||||
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ATTENDEE_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Secretary delete/rejects for boss');
|
|
||||||
|
|
||||||
// use organizer to check event still exists and boss rejected
|
|
||||||
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(200, $response, 'Check event still exists after DELETE in attendee calendar');
|
|
||||||
$this->assertIcal(self::EVENT_BOSS_ATTENDEE_ICAL, $response->getBody(),
|
|
||||||
'Boss should have declined the invitation',
|
|
||||||
['vEvent' => [['ATTENDEE' => ['mailto:boss@example.org' => ['PARTSTAT' => 'DECLINED', 'RSVP' => 'FALSE']]]]]
|
|
||||||
);
|
|
||||||
|
|
||||||
// secretary tries to delete event in organizers calendar
|
|
||||||
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(403, $response, 'Secretary not allowed to delete for organizer');
|
|
||||||
|
|
||||||
// boss deletes/rejects event in his calendar
|
|
||||||
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ATTENDEE_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Boss deletes/rejects in his calendar');
|
|
||||||
|
|
||||||
// boss deletes/rejects event in organizers calendar
|
|
||||||
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Boss deletes/rejects in organizers calendar');
|
|
||||||
|
|
||||||
// use organizer to delete event
|
|
||||||
$response = $this->getClient('other')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(204, $response);
|
|
||||||
|
|
||||||
// use organizer to check event deleted
|
|
||||||
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENT_BOSS_ORGANIZER_URL = '/boss/calendar/new-event-boss-organizer-123456789-new.ics';
|
|
||||||
const EVENT_BOSS_ORGANIZER_OTHER_URL = '/other/calendar/new-event-boss-organizer-123456789-new.ics';
|
|
||||||
const EVENT_BOSS_ORGANIZER_ICAL = <<<EOICAL
|
|
||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
BEGIN:VTIMEZONE
|
|
||||||
TZID:Europe/Berlin
|
|
||||||
BEGIN:DAYLIGHT
|
|
||||||
TZOFFSETFROM:+0100
|
|
||||||
TZOFFSETTO:+0200
|
|
||||||
TZNAME:CEST
|
|
||||||
DTSTART:19700329T020000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
|
||||||
END:DAYLIGHT
|
|
||||||
BEGIN:STANDARD
|
|
||||||
TZOFFSETFROM:+0200
|
|
||||||
TZOFFSETTO:+0100
|
|
||||||
TZNAME:CET
|
|
||||||
DTSTART:19701025T030000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
|
||||||
END:STANDARD
|
|
||||||
END:VTIMEZONE
|
|
||||||
BEGIN:VEVENT
|
|
||||||
DTSTART;TZID=Europe/Berlin:20110406T210000
|
|
||||||
DTEND;TZID=Europe/Berlin:20110406T220000
|
|
||||||
DTSTAMP:20110406T183747Z
|
|
||||||
LAST-MODIFIED:20110406T183747Z
|
|
||||||
LOCATION:Somewhere
|
|
||||||
SUMMARY:Tonight
|
|
||||||
ORGANIZER;CN="Boss User":mailto:boss@example.org
|
|
||||||
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:boss@example.org
|
|
||||||
ATTENDEE;CN="Other User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:other@example.org
|
|
||||||
UID:new-event-boss-organizer-123456789-new
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
EOICAL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check secretary deletes for boss, which is organizer of event
|
|
||||||
*
|
|
||||||
* @throws \Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
public function testSecretaryDeletesBossOrganizer()
|
|
||||||
{
|
|
||||||
// create invitation by boss as organizer
|
|
||||||
$response = $this->getClient('boss')->put($this->url(self::EVENT_BOSS_ORGANIZER_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'Prefer' => 'return=representation'
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_BOSS_ORGANIZER_ICAL,
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus([200,201], $response);
|
|
||||||
$this->assertIcal(self::EVENT_BOSS_ORGANIZER_ICAL, $response->getBody());
|
|
||||||
|
|
||||||
// attendee deletes/rejects event in his calendar
|
|
||||||
$response = $this->getClient('other')->delete($this->url(self::EVENT_BOSS_ORGANIZER_OTHER_URL));
|
|
||||||
$this->assertHttpStatus(204, $response);
|
|
||||||
|
|
||||||
// secretrary deletes event in boss's calendar
|
|
||||||
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Secretary deletes for boss');
|
|
||||||
|
|
||||||
// use organizer/boss to check event deleted
|
|
||||||
$response = $this->getClient('boss')->get($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by secretary");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check organizer (boss) can delete event in his calendar
|
|
||||||
*
|
|
||||||
* @throws \Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
public function testOrganizerDeletes()
|
|
||||||
{
|
|
||||||
// create invitation by boss as organizer
|
|
||||||
$response = $this->getClient('boss')->put($this->url(self::EVENT_BOSS_ORGANIZER_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'Prefer' => 'return=representation'
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_BOSS_ORGANIZER_ICAL,
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus([200,201], $response);
|
|
||||||
$this->assertIcal(self::EVENT_BOSS_ORGANIZER_ICAL, $response->getBody());
|
|
||||||
|
|
||||||
// organizer deletes event in his calendar
|
|
||||||
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Organizer deletes');
|
|
||||||
|
|
||||||
// use organizer/boss to check event deleted
|
|
||||||
$response = $this->getClient('boss')->get($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
|
||||||
|
|
||||||
// use attendee to check event deleted
|
|
||||||
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ORGANIZER_OTHER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
|
||||||
}
|
|
||||||
|
|
||||||
const EVENT_SECRETARY_ATTENDEE_URL = '/secretary/calendar/new-event-secreatary-attendee-123456789-new.ics';
|
|
||||||
const EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL = '/boss/calendar/new-event-secreatary-attendee-123456789-new.ics';
|
|
||||||
const EVENT_SECRETARY_ATTENDEE_ICAL = <<<EOICAL
|
|
||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
BEGIN:VTIMEZONE
|
|
||||||
TZID:Europe/Berlin
|
|
||||||
BEGIN:DAYLIGHT
|
|
||||||
TZOFFSETFROM:+0100
|
|
||||||
TZOFFSETTO:+0200
|
|
||||||
TZNAME:CEST
|
|
||||||
DTSTART:19700329T020000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
|
||||||
END:DAYLIGHT
|
|
||||||
BEGIN:STANDARD
|
|
||||||
TZOFFSETFROM:+0200
|
|
||||||
TZOFFSETTO:+0100
|
|
||||||
TZNAME:CET
|
|
||||||
DTSTART:19701025T030000
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
|
||||||
END:STANDARD
|
|
||||||
END:VTIMEZONE
|
|
||||||
BEGIN:VEVENT
|
|
||||||
DTSTART;TZID=Europe/Berlin:20110406T210000
|
|
||||||
DTEND;TZID=Europe/Berlin:20110406T220000
|
|
||||||
DTSTAMP:20110406T183747Z
|
|
||||||
LAST-MODIFIED:20110406T183747Z
|
|
||||||
LOCATION:Somewhere
|
|
||||||
SUMMARY:Tonight
|
|
||||||
ORGANIZER;CN="Boss User":mailto:boss@example.org
|
|
||||||
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:boss@example.org
|
|
||||||
ATTENDEE;CN="Secretary User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:secretary@example.org
|
|
||||||
UID:new-event-secreatary-attendee-123456789-new
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
EOICAL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check secretary as attendee deletes event
|
|
||||||
*
|
|
||||||
* @throws \Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
public function testSecretaryAttendeeDeletes()
|
|
||||||
{
|
|
||||||
// create invitation by boss as organizer
|
|
||||||
$response = $this->getClient('boss')->put($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'Prefer' => 'return=representation'
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_SECRETARY_ATTENDEE_ICAL,
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus([200,201], $response);
|
|
||||||
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody());
|
|
||||||
|
|
||||||
// secretary deletes in her calendar
|
|
||||||
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_URL));
|
|
||||||
$this->assertHttpStatus(204, $response, 'Secretary (attendee) deletes');
|
|
||||||
|
|
||||||
// use organizer to check it's really deleted
|
|
||||||
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by secretary");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check secretary as attendee deletes event with CalDAVSynchronizer
|
|
||||||
*
|
|
||||||
* @throws \Horde_Icalendar_Exception
|
|
||||||
*/
|
|
||||||
public function testSecretaryAttendeeDeletesCalDAVSynchronizer()
|
|
||||||
{
|
|
||||||
// create invitation by boss as organizer
|
|
||||||
$response = $this->getClient('boss')->put($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL), [
|
|
||||||
RequestOptions::HEADERS => [
|
|
||||||
'Content-Type' => 'text/calendar',
|
|
||||||
'Prefer' => 'return=representation'
|
|
||||||
],
|
|
||||||
RequestOptions::BODY => self::EVENT_SECRETARY_ATTENDEE_ICAL,
|
|
||||||
]);
|
|
||||||
$this->assertHttpStatus([200,201], $response);
|
|
||||||
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody());
|
|
||||||
|
|
||||||
// secretary deletes in her calendar with CalDAVSynchronizer
|
|
||||||
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_URL),
|
|
||||||
[RequestOptions::HEADERS => ['User-Agent' => 'CalDAVSynchronizer']]);
|
|
||||||
$this->assertHttpStatus(204, $response, 'Secretary (attendee) deletes/rejects');
|
|
||||||
|
|
||||||
// use organizer to check it's NOT deleted, as CalDAVSynchronizer / Outlook does not distinguish between reject and delete
|
|
||||||
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(200, $response, "Check event NOT deleted by secretary");
|
|
||||||
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody(),
|
|
||||||
'Secretary should have declined the invitation',
|
|
||||||
['vEvent' => [['ATTENDEE' => ['mailto:secretary@example.org' => ['PARTSTAT' => 'DECLINED', 'RSVP' => 'FALSE']]]]]
|
|
||||||
);
|
|
||||||
|
|
||||||
// organizer deletes in his calendar with CalDAVSynchronizer
|
|
||||||
$response = $this->getClient('boss')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL),
|
|
||||||
[RequestOptions::HEADERS => ['User-Agent' => 'CalDAVSynchronizer']]);
|
|
||||||
$this->assertHttpStatus(204, $response, 'Organizer deletes');
|
|
||||||
|
|
||||||
// use organizer to check it's deleted, as CalDAVSynchronizer / Outlook should still delete for organizer
|
|
||||||
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
|
||||||
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
|
||||||
}
|
|
||||||
}
|
|
@ -101,8 +101,6 @@
|
|||||||
"tinymce/tinymce": "^5.0"
|
"tinymce/tinymce": "^5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"guzzlehttp/guzzle": "^6.5",
|
|
||||||
"phpunit/phpunit": "^6.5"
|
|
||||||
},
|
},
|
||||||
"suggests": {
|
"suggests": {
|
||||||
"ext-opcache": "Opcode cache to speed up PHP",
|
"ext-opcache": "Opcode cache to speed up PHP",
|
||||||
|
1749
composer.lock
generated
1749
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,27 +0,0 @@
|
|||||||
<VirtualHost *:80>
|
|
||||||
# https://docs.travis-ci.com/user/languages/php/#apache--php
|
|
||||||
|
|
||||||
DocumentRoot %TRAVIS_BUILD_DIR%
|
|
||||||
|
|
||||||
# tests assume EGroupware to be under /egroupware not docroot
|
|
||||||
Alias /egroupware %TRAVIS_BUILD_DIR%
|
|
||||||
|
|
||||||
<Directory "%TRAVIS_BUILD_DIR%/">
|
|
||||||
Options FollowSymLinks MultiViews ExecCGI
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
# Wire up Apache to use Travis CI's php-fpm.
|
|
||||||
<IfModule mod_fastcgi.c>
|
|
||||||
AddHandler php5-fcgi .php
|
|
||||||
Action php5-fcgi /php5-fcgi
|
|
||||||
Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
|
|
||||||
FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
|
|
||||||
|
|
||||||
<Directory /usr/lib/cgi-bin>
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
</VirtualHost>
|
|
@ -253,7 +253,6 @@ class setup
|
|||||||
case PHP_SESSION_DISABLED:
|
case PHP_SESSION_DISABLED:
|
||||||
throw new \ErrorException('EGroupware requires PHP session extension!');
|
throw new \ErrorException('EGroupware requires PHP session extension!');
|
||||||
case PHP_SESSION_NONE:
|
case PHP_SESSION_NONE:
|
||||||
if (headers_sent()) return false;
|
|
||||||
ini_set('session.use_cookie', true);
|
ini_set('session.use_cookie', true);
|
||||||
session_name(self::SESSIONID);
|
session_name(self::SESSIONID);
|
||||||
session_set_cookie_params(0, '/', self::cookiedomain(),
|
session_set_cookie_params(0, '/', self::cookiedomain(),
|
||||||
|
@ -25,9 +25,9 @@ $GLOBALS['egw_info'] = array(
|
|||||||
'currentapp' => 'setup',
|
'currentapp' => 'setup',
|
||||||
'noapi' => True
|
'noapi' => True
|
||||||
));
|
));
|
||||||
if(file_exists(__DIR__.'/../../header.inc.php'))
|
if(file_exists('../header.inc.php'))
|
||||||
{
|
{
|
||||||
include_once(__DIR__.'/../../header.inc.php');
|
include('../header.inc.php');
|
||||||
}
|
}
|
||||||
// for an old header we need to setup a reference for the domains
|
// for an old header we need to setup a reference for the domains
|
||||||
if (!is_array($GLOBALS['egw_domain'])) $GLOBALS['egw_domain'] =& $GLOBALS['phpgw_domain'];
|
if (!is_array($GLOBALS['egw_domain'])) $GLOBALS['egw_domain'] =& $GLOBALS['phpgw_domain'];
|
||||||
@ -56,17 +56,14 @@ require_once(EGW_INCLUDE_ROOT . '/api/src/loader/common.php');
|
|||||||
* function to handle multilanguage support
|
* function to handle multilanguage support
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
if (!function_exists('lang'))
|
function lang($key,$vars=null)
|
||||||
{
|
{
|
||||||
function lang($key, $vars = null)
|
if(!is_array($vars))
|
||||||
{
|
{
|
||||||
if (!is_array($vars))
|
$vars = func_get_args();
|
||||||
{
|
array_shift($vars); // remove $key
|
||||||
$vars = func_get_args();
|
|
||||||
array_shift($vars); // remove $key
|
|
||||||
}
|
|
||||||
return $GLOBALS['egw_setup']->translation->translate("$key", $vars);
|
|
||||||
}
|
}
|
||||||
|
return $GLOBALS['egw_setup']->translation->translate("$key", $vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(file_exists(EGW_SERVER_ROOT.'/api/setup/setup.inc.php'))
|
if(file_exists(EGW_SERVER_ROOT.'/api/setup/setup.inc.php'))
|
||||||
|
Loading…
Reference in New Issue
Block a user