From 083dcbb201bb6a2cc3aa9ced9bf344f39513d29c Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 22 Oct 2014 16:03:46 +0200 Subject: [PATCH 01/30] updated auth-backend --- Resources/config/services/plugins/auth.xml | 1 + SabreDav/AuthBackend.php | 94 ++++++++++++++++++++-- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/Resources/config/services/plugins/auth.xml b/Resources/config/services/plugins/auth.xml index 6417561..c368634 100644 --- a/Resources/config/services/plugins/auth.xml +++ b/Resources/config/services/plugins/auth.xml @@ -12,6 +12,7 @@ + diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index b2b4ff1..1f4b5e4 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -14,7 +14,9 @@ use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Exception; use Sabre\DAV\Server; +use Sabre\HTTP; use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; class AuthBackend implements BackendInterface { @@ -23,33 +25,115 @@ class AuthBackend implements BackendInterface */ private $context; + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var type + */ + private $currentUser; + /** * Constructor * * @param SecurityContextInterface $context */ - public function __construct(SecurityContextInterface $context) + public function __construct(SecurityContextInterface $context, ContainerInterface $container) { $this->context = $context; + $this->container = $container; } + + /** + * Checks if username and password are valid. (Checked by the FOSUserManager) + * Returns + * + * @param type $username + * @param type $password + * @return boolean + */ + public function validateUserPass($username, $password) + { + + $userManager = $this->container->get('fos_user.user_manager'); + $user = $userManager->findUserByUsername($username); + + if (is_null($user)) { + return false; + } + $encoder_service = $this->container->get('security.encoder_factory'); + $encoder = $encoder_service->getEncoder($user); + $encoded_pass = $encoder->encodePassword($password, $user->getSalt()); + + if ($encoded_pass === $user->getPassword()) { + return true; + } + return false; + } + /** - * @inheritdoc + * Authenticate + * + * Authenticates the User with the HTTP\BasicAuth() + * if the user is not logged in via Browser in the Tool. + * + * + * @param Server $server + * @param type $realm + * @return void */ public function authenticate(Server $server, $realm) { if (null === $this->context->getToken()) { - throw new Exception('The security token is NULL'); + throw new Exception\NotAuthenticated('The security token is NULL'); + } + + $auth = new HTTP\BasicAuth(); + $auth->setHTTPRequest($server->httpRequest); + $auth->setHTTPResponse($server->httpResponse); + $userpass = $auth->getUserPass(); + + if (!$userpass) { + $auth->requireLogin(); + throw new Exception\NotAuthenticated('No authentication headers were found'); + } + + // Authenticates the user + if (!$this->validateUserPass($userpass[0],$userpass[1])) { + $auth->requireLogin(); + throw new Exception\NotAuthenticated('Username or password does not match'); } - return $this->context->getToken()->isAuthenticated(); + $this->currentUser = $userpass[0]; + + return true; } + /** + * Save User-Login to session + * + * @param type $userpass + */ + private function userLoginAction($userpass) + { + //process Symfony2 Login + $userManager = $this->container->get('fos_user.user_manager'); + $user = $userManager->findUserByUsername($userpass[0]); + + $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles()); + $request = $this->container->get('request'); + $session = $request->getSession(); + $session->set('_security_main', serialize($token)); + } + /** * @inheritdoc */ public function getCurrentUser() { - return $this->context->getToken()->getUsername(); + return $this->currentUser; } } From 371d5ffa39946fa509ddf90089eb36d9b7f373a0 Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 22 Oct 2014 16:09:09 +0200 Subject: [PATCH 02/30] Updated plugin- & base-config; modified route --- Controller/SabreDavController.php | 19 +++++++++++++- DependencyInjection/Configuration.php | 26 +++++++++++++++---- .../SecotrustSabreDavExtension.php | 23 +++++++++------- Resources/config/routing.xml | 4 ++- Resources/config/services/plugins/webdav.xml | 17 ++++++++++++ Resources/config/services/services.xml | 15 ++++++----- 6 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 Resources/config/services/plugins/webdav.xml diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index 28e4d76..ef984ea 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -17,6 +17,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Routing\RouterInterface; /** * Class SabreDavController @@ -33,15 +35,30 @@ class SabreDavController */ private $dispatcher; + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var RouterInterface + */ + private $router; + /** * Constructor * * @param Server $dav * @param EventDispatcherInterface $dispatcher + * @param ContainerInterface $container + * @param Router $router */ - public function __construct(Server $dav, EventDispatcherInterface $dispatcher) + public function __construct(Server $dav, EventDispatcherInterface $dispatcher, ContainerInterface $container, RouterInterface $router) { $this->dav = $dav; + $this->dav->setBaseUri($router->generate('secotrust_sabre_dav')); + + $this->container = $container; $this->dispatcher = $dispatcher; // TODO needed? } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index afafee6..658f2bb 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -27,26 +27,42 @@ public function getConfigTreeBuilder() $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('secotrust_sabre_dav'); - // TODO needs "little" improvement ;-) - + $default_base_uri = '/app_dev.php/remote'; + $rootNode ->children() - ->scalarNode('root_dir')->example('%kernel.root_dir%/../web/dav')->defaultNull()->end() - ->scalarNode('base_uri')->example('/app_dev.php/dav/')->isRequired()->end() + ->scalarNode('root_dir') + ->example('%kernel.root_dir%/../web/dav/') + ->end() + ->scalarNode('base_uri') + ->example($default_base_uri) + ->end() ->arrayNode('plugins') ->addDefaultsIfNotSet() ->children() ->booleanNode('acl')->defaultFalse()->end() ->booleanNode('auth')->defaultFalse()->end() ->booleanNode('browser')->defaultFalse()->end() - ->booleanNode('caldav')->defaultFalse()->end() ->booleanNode('lock')->defaultTrue()->end() ->booleanNode('temp')->defaultTrue()->end() ->booleanNode('mount')->defaultFalse()->end() ->booleanNode('patch')->defaultFalse()->end() ->booleanNode('content_type')->defaultFalse()->end() + ->booleanNode('webdav')->defaultFalse()->end() + ->booleanNode('principal')->defaultFalse()->end() + ->booleanNode('carddav')->defaultFalse()->end() + ->booleanNode('caldav')->defaultFalse()->end() ->end() ->end() + ->arrayNode('settings') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('cards_class')->defaultValue('')->end() + ->scalarNode('addressbooks_class')->defaultValue('')->end() + ->scalarNode('principals_class')->defaultValue('')->end() + ->end() + ->end() + ->end() ->end(); return $treeBuilder; diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index fd1bbb1..8b25a24 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -31,21 +31,26 @@ public function load(array $configs, ContainerBuilder $container) $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services/services.xml'); - - if ($config['base_uri']) { + + if (isset($config['base_uri'])) { $container->getDefinition('secotrust.sabredav.server')->addMethodCall('setBaseUri', array($config['base_uri'])); } - - if ($config['root_dir']) { - $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); - } else { - $container->getDefinition('secotrust.sabredav_root')->clearTag('secotrust.sabredav.collection'); - } - + + // load all plugins foreach ($config['plugins'] as $plugin => $enabled) { if ($enabled) { $loader->load(sprintf('services/plugins/%s.xml', $plugin)); } } + + // no root dir is set, but webdav plugin is active: throw exception + if (array_key_exists('root_dir', $config) && $config['root_dir'] !== '' && $config['plugins']['webdav'] === true ) { + //replace argument + $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); + } + + $container->setParameter('secotrust.cards_class', $config['settings']['cards_class']); + $container->setParameter('secotrust.addressbooks_class', $config['settings']['addressbooks_class']); + $container->setParameter('secotrust.principals_class', $config['settings']['principals_class']); } } diff --git a/Resources/config/routing.xml b/Resources/config/routing.xml index 94f34c0..2e7060c 100644 --- a/Resources/config/routing.xml +++ b/Resources/config/routing.xml @@ -4,8 +4,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + secotrust.sabredav.controller:execAction + /?.* + diff --git a/Resources/config/services/plugins/webdav.xml b/Resources/config/services/plugins/webdav.xml new file mode 100644 index 0000000..f85ce5e --- /dev/null +++ b/Resources/config/services/plugins/webdav.xml @@ -0,0 +1,17 @@ + + + + + + Sabre\DAV\FS\Directory + + + + + + + + + diff --git a/Resources/config/services/services.xml b/Resources/config/services/services.xml index 3493065..902cc78 100644 --- a/Resources/config/services/services.xml +++ b/Resources/config/services/services.xml @@ -7,20 +7,21 @@ Secotrust\Bundle\SabreDavBundle\Controller\SabreDavController Sabre\DAV\Server - Sabre\DAV\FS\Directory + + + + + + - - - - - - + + From c81f0d6c5f1bfbc92ea1ef5b0434c2e39bef0131 Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 22 Oct 2014 16:10:33 +0200 Subject: [PATCH 03/30] Added principal-backend --- Entity/PrincipalInterface.php | 41 +++ .../PrincipalRepositoryInterface.php | 35 ++ .../config/services/plugins/principal.xml | 30 ++ SabreDav/PrincipalBackend.php | 314 ++++++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 Entity/PrincipalInterface.php create mode 100644 Entity/Repository/PrincipalRepositoryInterface.php create mode 100644 Resources/config/services/plugins/principal.xml create mode 100644 SabreDav/PrincipalBackend.php diff --git a/Entity/PrincipalInterface.php b/Entity/PrincipalInterface.php new file mode 100644 index 0000000..d0c243f --- /dev/null +++ b/Entity/PrincipalInterface.php @@ -0,0 +1,41 @@ + + + + + + Secotrust\Bundle\SabreDavBundle\SabreDav\PrincipalBackend + Sabre\DAVACL\Plugin + Sabre\CalDAV\Principal\Collection + + + + + + + + + + false + + + + + + + + + + diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php new file mode 100644 index 0000000..926fceb --- /dev/null +++ b/SabreDav/PrincipalBackend.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Secotrust\Bundle\SabreDavBundle\SabreDav; + +use Sabre\DAVACL\PrincipalBackend\BackendInterface; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + + +class PrincipalBackend implements BackendInterface +{ + /** + * @var SecurityContextInterface + */ + private $context; + + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var ContainerInterface + */ + private $_em; + + /** + * @var type + */ + private $principals_class; + + /** + * A list of additional fields to support + * + * @var array + */ + protected $fieldMap = array( + + /** + * This property can be used to display the users' real name. + */ + '{DAV:}displayname' => array( + 'getter' => 'getUsername', + ), + + /** + * This property is actually used by the CardDAV plugin, where it gets + * mapped to {http://calendarserver.orgi/ns/}me-card. + * + * The reason we don't straight-up use that property, is because + * me-card is defined as a property on the users' addressbook + * collection. + */ + '{http://sabredav.org/ns}vcard-url' => array( + 'getter' => 'getVCardUrl', + ), + /** + * This is the users' primary email-address. + */ + '{http://sabredav.org/ns}email-address' => array( + 'getter' => 'getEmail', + ), + ); + + /** + * Constructor + * + * @param SecurityContextInterface $context + * @param ContainerInterface $container + */ + public function __construct(SecurityContextInterface $context, ContainerInterface $container) + { + $this->context = $context; + $this->container = $container; + + $this->_em = $container->get('doctrine')->getManager(); + + $this->principals_class = $this->container->getParameter('secotrust.principals_class'); +// $this->cards_class = $this->container->getParameter('secotrust.cards_class'); + } + + /** + * get Array with Principal-Data from User-Object + * + * @param $userObject + * @param type $show_id + * @return array + */ + private function getPrincipalArray($userObject, $show_id = false){ + + $principal = array(); + if ($show_id){ + $principal['id'] = $userObject->getId(); + } + + $principal['uri'] = 'principals/' . $userObject->getUsername(); + + foreach($this->fieldMap as $key=>$value) { + if (method_exists($userObject, $value['getter']) && call_user_func(array($userObject, $value['getter']))) { + $principal[$key] = call_user_func(array($userObject, $value['getter'])); + } + } + + return $principal; + } + + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * {http://sabredav.org/ns}email-address - This is a custom SabreDAV + * field that's actually injected in a number of other properties. If + * you have an email address, use this property. + * + * @param string $prefixPath + * @return array + */ + public function getPrincipalsByPrefix($prefixPath){ + + $userlist = $this->_em->getRepository($this->principals_class)->findBy(array('enabled' => true)); + $principals = array(); + + foreach($userlist as $user) { + $principals[] = $this->getPrincipalArray($user); + } + + return $principals; + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + public function getPrincipalByPath($path){ + + $username = str_replace('principals/', '', $path); + if (!(strpos($username,'/')=== false)){ + $username = substr($username, 0, strpos($username, '/')); + } + + $user = $this->_em->getRepository($this->principals_class)->findByUsername($username); + + $user = $user[0]; + + return $this->getPrincipalArray($user, true); + } + + /** + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is supplied as an array. Each key in the array is + * a propertyname, such as {DAV:}displayname. + * + * Each value is the actual value to be updated. If a value is null, it + * must be deleted. + * + * This method should be atomic. It must either completely succeed, or + * completely fail. Success and failure can simply be returned as 'true' or + * 'false'. + * + * It is also possible to return detailed failure information. In that case + * an array such as this should be returned: + * + * array( + * 200 => array( + * '{DAV:}prop1' => null, + * ), + * 201 => array( + * '{DAV:}prop2' => null, + * ), + * 403 => array( + * '{DAV:}prop3' => null, + * ), + * 424 => array( + * '{DAV:}prop4' => null, + * ), + * ); + * + * In this previous example prop1 was successfully updated or deleted, and + * prop2 was succesfully created. + * + * prop3 failed to update due to '403 Forbidden' and because of this prop4 + * also could not be updated with '424 Failed dependency'. + * + * This last example was actually incorrect. While 200 and 201 could appear + * in 1 response, if there's any error (403) the other properties should + * always fail with 423 (failed dependency). + * + * But anyway, if you don't want to scratch your head over this, just + * return true or false. + * + * @param string $path + * @param array $mutations + * @return array|bool + */ + public function updatePrincipal($path, $mutations){ + $this->container->get('logger')->error('CardDAV update of Principal currently not possible!'); + return false; + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * If multiple properties are being searched on, the search should be + * AND'ed. + * + * This method should simply return an array with full principal uri's. + * + * If somebody attempted to search on a property the backend does not + * support, you should simply return 0 results. + * + * You can also just return 0 results if you choose to not support + * searching at all, but keep in mind that this may stop certain features + * from working. + * + * @param string $prefixPath + * @param array $searchProperties + * @return array + */ + public function searchPrincipals($prefixPath, array $searchProperties){ + + foreach($searchProperties as $property => $value) { + + switch($property) { + + case '{DAV:}displayname' : + $searchArray['email'] = $value; + break; + case '{http://sabredav.org/ns}email-address' : + $searchArray['email'] = $value; + break; + default : + // Unsupported property + return array(); + } + } + + $principals = $this->_em->getRepository($this->principals_class)->searchPrincipals($prefixPath, $searchArray); + + return $principals; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + public function getGroupMemberSet($principal){ + $principal = $this->getPrincipalByPath($principal); + + $groupMemberSet = array(); + //TODO: list group membership for all addressbooks(contactgroups): + + $groupMemberSet[] = $principal['uri']; + + return $groupMemberSet; + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + function getGroupMembership($principal){ +// $principal = $this->getPrincipalByPath($principal); + + $groupMembership = array($principal); + + return $groupMembership; + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members + * @return void + */ + function setGroupMemberSet($principal, array $members){ + $this->container->get('logger')->error('CardDAV update of Principal-Group-Membership currently possible!'); + } + +} From 4dfb80e90a0727938e800bcac89322b0528dbd03 Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 22 Oct 2014 16:14:11 +0200 Subject: [PATCH 04/30] Added CardDAV & CalDAV-Backends --- Entity/AddressbookInterface.php | 80 ++++ Entity/CardInterface.php | 90 +++++ .../AddressbookRepositoryInterface.php | 20 + Entity/Repository/CardRepositoryInterface.php | 25 ++ Resources/config/services/plugins/caldav.xml | 16 +- Resources/config/services/plugins/carddav.xml | 31 ++ SabreDav/CalDavBackend.php | 290 +++++++++++++ SabreDav/CardDavBackend.php | 381 ++++++++++++++++++ 8 files changed, 932 insertions(+), 1 deletion(-) create mode 100644 Entity/AddressbookInterface.php create mode 100644 Entity/CardInterface.php create mode 100644 Entity/Repository/AddressbookRepositoryInterface.php create mode 100644 Entity/Repository/CardRepositoryInterface.php create mode 100644 Resources/config/services/plugins/carddav.xml create mode 100644 SabreDav/CalDavBackend.php create mode 100644 SabreDav/CardDavBackend.php diff --git a/Entity/AddressbookInterface.php b/Entity/AddressbookInterface.php new file mode 100644 index 0000000..267c6c5 --- /dev/null +++ b/Entity/AddressbookInterface.php @@ -0,0 +1,80 @@ +getVCard()); + */ + public function getETag(); + +} diff --git a/Entity/Repository/AddressbookRepositoryInterface.php b/Entity/Repository/AddressbookRepositoryInterface.php new file mode 100644 index 0000000..9c86a54 --- /dev/null +++ b/Entity/Repository/AddressbookRepositoryInterface.php @@ -0,0 +1,20 @@ + + Secotrust\Bundle\SabreDavBundle\SabreDav\CalDavBackend Sabre\CalDAV\Plugin + Sabre\CalDAV\CalendarRootNode + - + + + + + + false + + + + + + diff --git a/Resources/config/services/plugins/carddav.xml b/Resources/config/services/plugins/carddav.xml new file mode 100644 index 0000000..9d2cc26 --- /dev/null +++ b/Resources/config/services/plugins/carddav.xml @@ -0,0 +1,31 @@ + + + + + + Secotrust\Bundle\SabreDavBundle\SabreDav\CardDavBackend + Sabre\CardDAV\Plugin + Sabre\CardDAV\AddressBookRoot + + + + + + + + + + false + + + + + + + + + + + diff --git a/SabreDav/CalDavBackend.php b/SabreDav/CalDavBackend.php new file mode 100644 index 0000000..bc870fb --- /dev/null +++ b/SabreDav/CalDavBackend.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Secotrust\Bundle\SabreDavBundle\SabreDav; + +use Sabre\CalDAV\Backend\BackendInterface; +use Sabre\DAV\Exception; +use Sabre\DAV\Server; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class CalDavBackend implements BackendInterface +{ + /** + * @var SecurityContextInterface + */ + private $context; + + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var ContainerInterface + */ + private $_em; + + /** + * Constructor + * + * @param SecurityContextInterface $context + * @param ContainerInterface $container + */ + public function __construct(SecurityContextInterface $context, ContainerInterface $container) + { + $this->context = $context; + $this->container = $container; + + $this->_em = $container->get('doctrine')->getManager(); + } + + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri, which the basename of the uri with which the calendar is + * accessed. + * * principaluri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * @param string $principalUri + * @return array + */ + public function getCalendarsForUser($principalUri) { + return array($principalUri); + } + + /** + * Creates a new calendar for a principal. + * + * If the creation was a success, an id must be returned that can be used to reference + * this calendar in other methods, such as updateCalendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return void + */ + public function createCalendar($principalUri,$calendarUri,array $properties){ + return; + } + + /** + * Updates properties for a calendar. + * + * The mutations array uses the propertyName in clark-notation as key, + * and the array value for the property value. In the case a property + * should be deleted, the property value will be null. + * + * This method must be atomic. If one property cannot be changed, the + * entire operation must fail. + * + * If the operation was successful, true can be returned. + * If the operation failed, false can be returned. + * + * Deletion of a non-existent property is always successful. + * + * Lastly, it is optional to return detailed information about any + * failures. In this case an array should be returned with the following + * structure: + * + * array( + * 403 => array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param mixed $calendarId + * @param array $mutations + * @return bool|array + */ + public function updateCalendar($calendarId, array $mutations){ + return true; + } + + /** + * Delete a calendar and all it's objects + * + * @param mixed $calendarId + * @return void + */ + public function deleteCalendar($calendarId){ + return; + } + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * id - unique identifier which will be used for subsequent updates + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * calendarid - The calendarid as it was passed to this function. + * * size - The size of the calendar objects, in bytes. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param mixed $calendarId + * @return array + */ + public function getCalendarObjects($calendarId){ + return array($calendarId); + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * This method must return null if the object did not exist. + * + * @param mixed $calendarId + * @param string $objectUri + * @return array|null + */ + public function getCalendarObject($calendarId,$objectUri){ + return array($calendarId); + } + + /** + * Creates a new calendar object. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + public function createCalendarObject($calendarId,$objectUri,$calendarData){ + return null; + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + public function updateCalendarObject($calendarId,$objectUri,$calendarData){ + return null; + } + + /** + * Deletes an existing calendar object. + * + * @param mixed $calendarId + * @param string $objectUri + * @return void + */ + public function deleteCalendarObject($calendarId,$objectUri){ + return null; + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by Sabre\CalDAV\CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on either VEVENT or VTODO. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interprete all these filters can also simply + * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * @param mixed $calendarId + * @param array $filters + * @return array + */ + public function calendarQuery($calendarId, array $filters){ + return array($calendarId); + } +} diff --git a/SabreDav/CardDavBackend.php b/SabreDav/CardDavBackend.php new file mode 100644 index 0000000..4094fed --- /dev/null +++ b/SabreDav/CardDavBackend.php @@ -0,0 +1,381 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Secotrust\Bundle\SabreDavBundle\SabreDav; + +use Sabre\CardDAV\Backend\BackendInterface; +use Sabre\CardDAV; +use Secotrust\Bundle\SabreDavBundle\Entity\CardInterface; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class CardDavBackend implements BackendInterface +{ + /** + * @var SecurityContextInterface + */ + private $context; + /** + * @var ContainerInterface + */ + private $container; + + /** + * @var EntityManager + */ + private $_em; + + /** + * @var type + */ + private $addressbooks_class; + + /** + * @var type + */ + private $cards_class; + + /** + * Create array with Card-Data + * + * @param type $entity + * @param type $show_id + * @return array + */ + private function getCardArray($entity, $show_id = false) { + + if (!($entity instanceof CardInterface)) { + return false; + } + + $card = array( + 'id' => $entity->getId(), + 'carddata' => $entity->getVCard(), + 'uri' => $entity->getVCardUid().'.vcf', + 'lastmodified' => $entity->getLastmodified(), + 'size' => strlen($entity->getVCard()), + 'etag' => $entity->getETag(), + ); + + if ($show_id === false){ + unset($card['id']); + } + + return $card; + } + + /** + * Constructor + * + * @param SecurityContextInterface $context + * @param ContainerInterface $container + */ + public function __construct(SecurityContextInterface $context, ContainerInterface $container) + { + $this->context = $context; + $this->container = $container; + + $this->_em = $container->get('doctrine')->getManager(); + + $this->addressbooks_class = $this->container->getParameter('secotrust.addressbooks_class'); + $this->cards_class = $this->container->getParameter('secotrust.cards_class'); + } + + /** + * Returns the list of addressbooks for a specific user. + * + * Every addressbook should have the following properties: + * id - an arbitrary unique id + * uri - the 'basename' part of the url + * principaluri - Same as the passed parameter + * + * Any additional clark-notation property may be passed besides this. Some + * common ones are : + * {DAV:}displayname + * {urn:ietf:params:xml:ns:carddav}addressbook-description + * {http://calendarserver.org/ns/}getctag + * + * @param string $principalUri + * @return array + */ + public function getAddressBooksForUser($principalUri) { + + $addressBooks = array(); + + // TODO: limit addressbook-listing for all (ACL-)Permissions + // create Service in Addressbook-Bundle (e.g. for additional permissions) + + $entities = $this->_em->getRepository($this->addressbooks_class)->findAllPrincipalAddressbooks($principalUri); + + foreach ($entities as $entity) { + + // TODO: don't list, if the user doesn't have permission!! + + $addressBooks[] = array( + 'id' => $entity->getId(), + 'uri' => $entity->getUriLabel(), + 'principaluri' => $principalUri, + '{DAV:}displayname' => $entity->getLabel(), + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $entity->getDescription(), + '{http://calendarserver.org/ns/}getctag' => $entity->getCtag(), + '{' . CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' => + new CardDAV\Property\SupportedAddressData(), + ); + } + + return $addressBooks; + } + + /** + * Updates an addressbook's properties + * + * See Sabre\DAV\IProperties for a description of the mutations array, as + * well as the return value. + * + * @param mixed $addressBookId + * @param array $mutations + * @see Sabre\DAV\IProperties::updateProperties + * @return bool|array + */ + public function updateAddressBook($addressBookId, array $mutations) { + $addressBookId = 0; + $updates = array(); + + foreach($mutations as $property=>$newValue) { + + switch($property) { + case '{DAV:}displayname' : + $updates['setLabel'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $updates['setDescription'] = $newValue; + break; + default : + // If any unsupported values were being updated, we must + // let the entire request fail. + return false; + } + + } + + // No values are being updated? + if (!$updates) { + return false; + } + + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + + foreach ($updates as $setter => $value){ + if (method_exists($addressbook, $setter)) { + $addressbook->$setter($value); + } + } + + $this->_em->flush(); + + return true; + } + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @return void + */ + public function createAddressBook($principalUri, $url, array $properties) { + + $values = array( + 'displayname' => null, + 'description' => null, + 'principaluri' => $principalUri, + 'uri' => $url, + ); + + foreach($properties as $property=>$newValue) { + + switch($property) { + case '{DAV:}displayname' : + $values['setLabel'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $values['setDescription'] = $newValue; + break; + default : + throw new DAV\Exception\BadRequest('Unknown property: ' . $property); + } + } + + $addressbook = new $this->addressbooks_class(); + + foreach ($values as $setter => $value){ + if (method_exists($addressbook, $setter)){ + $addressbook->$setter($value); + } + } + + $this->_em->persist($addressbook); + $this->_em->flush(); + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param mixed $addressBookId + * @return void + */ + public function deleteAddressBook($addressBookId) { + + //TODO: delete request for addressbook + //TODO: check if this should be done via carddav!! + + } + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param mixed $addressbookId + * @return array + */ + public function getCards($addressbookId) { + + $contactGroup = $this->_em->getRepository($this->addressbooks_class)->findOneById($addressbookId); + $entities = $contactGroup->getContactCollection(); + + $cards = array(); + foreach ($entities as $entity) { + $cards[] = $this->getCardArray($entity, true); + } + return $cards; + } + + /** + * Returns a specfic card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array + */ + public function getCard($addressBookId, $cardUri) { + $addressBookId = 0; + $vCardUid = substr($cardUri, 0, strlen('.vcf')*(-1)); + + $entity = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($vCardUid); + + return $this->getCardArray($entity, true); + } + + /** + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + public function createCard($addressBookId, $cardUri, $cardData) { + + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + + $card = new $this->cards_class(); + $card->setVCard($cardData); + $card->setVCardUid($cardUri); + + $this->_em->persist($card); + $this->_em->flush(); + + return null; + } + + /** + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressbooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + public function updateCard($addressBookId, $cardUri, $cardData) { + + $card = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($addressBookId); + $card->setVCard($cardData); + + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + $addressbook->updateCTag(); + + $this->_em->flush(); + return null; + } + + /** + * Deletes a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool + */ + public function deleteCard($addressBookId, $cardUri) { + + return $this->_em->getRepository($this->cards_class)->deleteCard($cardUri); + } + +} From b40b1bc506560218d46e1ef6b5b57ae70999c3ad Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 17 Nov 2014 14:21:47 +0100 Subject: [PATCH 05/30] removed empty parameter; simpler check for existing vars --- DependencyInjection/SecotrustSabreDavExtension.php | 2 +- Resources/config/services/plugins/caldav.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 8b25a24..8d73c3b 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -44,7 +44,7 @@ public function load(array $configs, ContainerBuilder $container) } // no root dir is set, but webdav plugin is active: throw exception - if (array_key_exists('root_dir', $config) && $config['root_dir'] !== '' && $config['plugins']['webdav'] === true ) { + if (!empty($config['root_dir']) && $config['plugins']['webdav']) { //replace argument $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); } diff --git a/Resources/config/services/plugins/caldav.xml b/Resources/config/services/plugins/caldav.xml index c626e9f..32293fd 100644 --- a/Resources/config/services/plugins/caldav.xml +++ b/Resources/config/services/plugins/caldav.xml @@ -8,7 +8,6 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\CalDavBackend Sabre\CalDAV\Plugin Sabre\CalDAV\CalendarRootNode - From b573ac8f24f227776139204798279f85989d0628 Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 17 Nov 2014 14:23:59 +0100 Subject: [PATCH 06/30] replace request_stack by request --- SabreDav/AuthBackend.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index 1f4b5e4..f40b66a 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -124,8 +124,8 @@ private function userLoginAction($userpass) $user = $userManager->findUserByUsername($userpass[0]); $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles()); - $request = $this->container->get('request'); - $session = $request->getSession(); + $requestStack = $this->container->get('request_stack'); + $session = $requestStack->getCurrentRequest()->getSession(); $session->set('_security_main', serialize($token)); } From 8a2ca23f79c49fa038c5a41c2c3e6889aea79457 Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 25 Feb 2015 15:13:30 +0100 Subject: [PATCH 07/30] implemented sabre-dav 2.*; optimized php-interfaces; added BasicAuth class --- Entity/AddressbookInterface.php | 50 ++- Entity/PrincipalInterface.php | 33 +- Entity/Repository/CardRepositoryInterface.php | 12 +- .../PrincipalRepositoryInterface.php | 27 +- Resources/config/services/plugins/auth.xml | 4 +- SabreDav/Auth/BasicAuth.php | 96 +++++ SabreDav/AuthBackend.php | 136 +++--- SabreDav/CardDavBackend.php | 394 ++++++++++++------ SabreDav/HttpRequest.php | 21 +- SabreDav/HttpResponse.php | 10 +- SabreDav/PrincipalBackend.php | 311 ++++++++------ composer.json | 8 +- 12 files changed, 758 insertions(+), 344 deletions(-) create mode 100644 SabreDav/Auth/BasicAuth.php diff --git a/Entity/AddressbookInterface.php b/Entity/AddressbookInterface.php index 267c6c5..3c480f4 100644 --- a/Entity/AddressbookInterface.php +++ b/Entity/AddressbookInterface.php @@ -43,24 +43,24 @@ public function getDescription(); /** * Set description * - * @param string $label + * @param string $description * @return $this */ public function setDescription($description); /** - * Get the value of the current CTag + * Get the synctoken of the current CTag * * @return string */ - public function getCtag(); + public function getSynctoken(); /** - * updates the cTag of the current Group + * updates the synctoken of the current Group * * @return $this */ - public function updateCTag(); + public function updateSynctoken(); /** * get the Uri @@ -68,13 +68,47 @@ public function updateCTag(); * @return type */ public function getUri(); - /** - * Get all Contacts for current Addressbook + * Get all Cars for current Addressbook * * @return array */ - public function getContactList(); + public function getCards(); + + /** + * Search Card by URI in current Addressbook + * + * @param type $uri + */ + public function findCard($uri); + /** + * Adds the given card to the current Addressbook + * + * @param \Secotrust\Bundle\SabreDavBundle\Entity\CardInterface $card + */ + public function addCard(CardInterface $card); + + /** + * Remove the given card from the current Addressbook + * + * Caution: Check the field-configuration & your field-connections
+ * and use the desired setting, if you want to delete the cards
+ * - either only from the current Addressbook
+ * - or from the cards-table too + * + * @param \Secotrust\Bundle\SabreDavBundle\Entity\CardInterface $card + * @return boolean + */ + public function removeCard(CardInterface $card); + + /** + * Remove all Cards from current Addressbook. + * + * Possible solution: use $cards = $this->getCards() and + * $this->removeCard($card) to remove all cards + */ + public function removeAllCards(); + } diff --git a/Entity/PrincipalInterface.php b/Entity/PrincipalInterface.php index d0c243f..8d4e814 100644 --- a/Entity/PrincipalInterface.php +++ b/Entity/PrincipalInterface.php @@ -10,32 +10,53 @@ * @author lduer */ interface PrincipalInterface { - + /** * Get id * * @return integer - */ + */ public function getId(); - + /** * get username * * @return string - */ + */ public function getUsername(); - + + /** + * set username + * + * @param string $username + */ + public function setUsername($username); + /** * get Email * * @return string - */ + */ public function getEmail(); + /** + * set email + * + * @param string $email + */ + public function setEmail($email); + /** * requried to define me-card as a property on the users' addressbook' * * @return string */ public function getVCardUrl(); + + /** + * set vCardUrl + * + * @param type $vCardUrl + */ + public function setVCardUrl($vCardUrl); } diff --git a/Entity/Repository/CardRepositoryInterface.php b/Entity/Repository/CardRepositoryInterface.php index 0aeb518..3f887f4 100644 --- a/Entity/Repository/CardRepositoryInterface.php +++ b/Entity/Repository/CardRepositoryInterface.php @@ -10,16 +10,10 @@ interface CardRepositoryInterface { /** - * Find one Card By vCard UID + * Find one Card By vCard-UID and Addressbook-id * * @param type $uid + * @param type $addressBookId */ - public function findSingleCardByUid($uid=null); - - /** - * delete Card by cardUri - * - * @param type $cardUri - */ - public function deleteCard($cardUri); + public function findSingleCardByUid($uid=null, $addressBookId=null); } diff --git a/Entity/Repository/PrincipalRepositoryInterface.php b/Entity/Repository/PrincipalRepositoryInterface.php index 654afb8..3b1bb8a 100644 --- a/Entity/Repository/PrincipalRepositoryInterface.php +++ b/Entity/Repository/PrincipalRepositoryInterface.php @@ -21,15 +21,36 @@ interface PrincipalRepositoryInterface { public function getPrincipalsByPrefix($prefixPath); /** - * This method should simply return an array with full principal uri's. - * * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. * + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. + * * @param type $prefixPath * @param array $searchArray + * @param string $test + */ + public function searchPrincipals($prefixPath, array $searchArray, $test='allof'); + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @return string */ - public function searchPrincipals($prefixPath, array $searchArray); + public function findByUri($uri); } diff --git a/Resources/config/services/plugins/auth.xml b/Resources/config/services/plugins/auth.xml index c368634..1413ba8 100644 --- a/Resources/config/services/plugins/auth.xml +++ b/Resources/config/services/plugins/auth.xml @@ -7,16 +7,16 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\AuthBackend Sabre\DAV\Auth\Plugin + SabreDAV - - SabreDAV + %secotrust.sabredav.auth.realm% diff --git a/SabreDav/Auth/BasicAuth.php b/SabreDav/Auth/BasicAuth.php new file mode 100644 index 0000000..7f837f9 --- /dev/null +++ b/SabreDav/Auth/BasicAuth.php @@ -0,0 +1,96 @@ +user_manager = $user_manager; + parent::__construct($realm, $request, $response); + } + + /** + * find username in the user-manager + * + * @param string $username + * @return \FOS\UserBundle\Model\UserInterface + */ + private function getUser($username) { + $user = $this->user_manager->findUserByUsername($username); + + return $user; + } + + /** + * Return user-credentials; returned password is encoded via "security.encoder_factory" + * + * @param \Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface $encoder_service + * @return array|boolean + */ + public function getCredentials(EncoderFactoryInterface $encoder_service = null) { + + if (($user = $this->request->getRawServerValue('PHP_AUTH_USER')) && ($pass = $this->request->getRawServerValue('PHP_AUTH_PW'))) { + + $credentials = array($user, $pass); + } else { + + // Most other webservers + $auth = $this->request->getHeader('Authorization'); + + // Apache could prefix environment variables with REDIRECT_ when urls + // are passed through mod_rewrite + if (!$auth) { + $auth = $this->request->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); + } + + if (!$auth) + return false; + + if (strpos(strtolower($auth), 'basic') !== 0) + return false; + + $credentials = explode(':', base64_decode(substr($auth, 6)), 2); + } + + $user = $this->getUser($credentials[0]); + + if (!$user) { + return false; + } + + if ($encoder_service === null) { + // don't return password, because it isn't encoded + return array($credentials[0], ''); + } + + $encoder = $encoder_service->getEncoder($user); + $encoded_pass = $encoder->encodePassword($credentials[1], $user->getSalt()); + + return array($credentials[0], $encoded_pass); + } +} diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index f40b66a..2c710a4 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -14,38 +14,57 @@ use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Exception; use Sabre\DAV\Server; -use Sabre\HTTP; -use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\Event; -class AuthBackend implements BackendInterface -{ - /** - * @var SecurityContextInterface - */ - private $context; +class AuthBackend implements BackendInterface { /** * @var ContainerInterface */ private $container; - + /** - * @var type + * @var \FOS\UserBundle\Model\UserManagerInterface */ - private $currentUser; - + private $user_manager; + + /** + * @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface + */ + private $token_storage; + + /** + * @var \Symfony\Component\Serializer\Encoder\EncoderInterface + */ + private $encoder_service; + + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + private $dispatcher; + + /** + * @var string + */ + protected $currentUser; + /** * Constructor * - * @param SecurityContextInterface $context + * @param ContainerInterface $container */ - public function __construct(SecurityContextInterface $context, ContainerInterface $container) - { - $this->context = $context; + public function __construct(ContainerInterface $container) { + $this->container = $container; + $this->user_manager = $this->container->get('fos_user.user_manager'); + $this->token_storage = $this->container->get('security.token_storage'); + $this->encoder_service = $this->container->get('security.encoder_factory'); + $this->dispatcher = $this->container->get('event_dispatcher'); } - + /** * Checks if username and password are valid. (Checked by the FOSUserManager) * Returns @@ -54,47 +73,36 @@ public function __construct(SecurityContextInterface $context, ContainerInterfac * @param type $password * @return boolean */ - public function validateUserPass($username, $password) - { + public function validateUserPass($username, $passwordHash) { - $userManager = $this->container->get('fos_user.user_manager'); - $user = $userManager->findUserByUsername($username); + $user = $this->user_manager->findUserByUsername($username); if (is_null($user)) { return false; } - $encoder_service = $this->container->get('security.encoder_factory'); - $encoder = $encoder_service->getEncoder($user); - $encoded_pass = $encoder->encodePassword($password, $user->getSalt()); - if ($encoded_pass === $user->getPassword()) { + if ($passwordHash === $user->getPassword()) { + + $this->userLoginAction($user, $passwordHash); + return true; - } + } return false; } - + /** * Authenticate * - * Authenticates the User with the HTTP\BasicAuth() - * if the user is not logged in via Browser in the Tool. - * + * Authenticates the User via basc-auth * * @param Server $server * @param type $realm * @return void */ - public function authenticate(Server $server, $realm) - { - if (null === $this->context->getToken()) { - throw new Exception\NotAuthenticated('The security token is NULL'); - } - - $auth = new HTTP\BasicAuth(); - $auth->setHTTPRequest($server->httpRequest); - $auth->setHTTPResponse($server->httpResponse); - $userpass = $auth->getUserPass(); + public function authenticate(Server $server, $realm) { + $auth = new Auth\BasicAuth($realm, $server->httpRequest, $server->httpResponse, $this->user_manager); + $userpass = $auth->getCredentials($this->encoder_service); if (!$userpass) { $auth->requireLogin(); @@ -102,7 +110,7 @@ public function authenticate(Server $server, $realm) } // Authenticates the user - if (!$this->validateUserPass($userpass[0],$userpass[1])) { + if (!$this->validateUserPass($userpass[0], $userpass[1])) { $auth->requireLogin(); throw new Exception\NotAuthenticated('Username or password does not match'); } @@ -113,27 +121,41 @@ public function authenticate(Server $server, $realm) } /** - * Save User-Login to session + * add the symfony-login "manually" * - * @param type $userpass - */ - private function userLoginAction($userpass) - { - //process Symfony2 Login - $userManager = $this->container->get('fos_user.user_manager'); - $user = $userManager->findUserByUsername($userpass[0]); - - $token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles()); - $requestStack = $this->container->get('request_stack'); - $session = $requestStack->getCurrentRequest()->getSession(); - $session->set('_security_main', serialize($token)); + * use the symfony token-storage for the generated UsernamePasswordToken + * to access the (logged in) user later (e.g. to check for roles or permissions) + * + * the given $passwordHash must match the encrypted password in the user-object + * + * before and after the generation/setting of the token, the events "secotrust.user_login.before" + * and "secotrust.user_login.after" are called, if some EventListeners are configured + * + * @param string $user + * @param string $passwordHash + */ + private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $passwordHash) { + // call the pre-login-event + $event = new Event(); + $this->dispatcher->dispatch('secotrust.user_login.before', $event); + + if ($user->getPassword() !== $passwordHash) { + // stop the login-action, when the password doesn't match + return; + } + + $token = new UsernamePasswordToken($user, null, 'secured_area', $user->getRoles()); + $this->token_storage->setToken($token); + + // call the post-login-event + $event = new Event(); + $this->dispatcher->dispatch('secotrust.user_login.after', $event); } - + /** * @inheritdoc */ - public function getCurrentUser() - { + public function getCurrentUser() { return $this->currentUser; } } diff --git a/SabreDav/CardDavBackend.php b/SabreDav/CardDavBackend.php index 4094fed..1329a4e 100644 --- a/SabreDav/CardDavBackend.php +++ b/SabreDav/CardDavBackend.php @@ -11,38 +11,34 @@ namespace Secotrust\Bundle\SabreDavBundle\SabreDav; -use Sabre\CardDAV\Backend\BackendInterface; +use Sabre\CardDAV\Backend\AbstractBackend; +use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV; use Secotrust\Bundle\SabreDavBundle\Entity\CardInterface; -use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -class CardDavBackend implements BackendInterface -{ - /** - * @var SecurityContextInterface - */ - private $context; +class CardDavBackend extends AbstractBackend implements SyncSupport { + /** * @var ContainerInterface */ private $container; /** - * @var EntityManager + * @var \Doctrine\ORM\EntityManager */ private $_em; - + /** - * @var type + * @var string */ private $addressbooks_class; - + /** - * @var type + * @var string */ private $cards_class; - + /** * Create array with Card-Data * @@ -55,38 +51,34 @@ private function getCardArray($entity, $show_id = false) { if (!($entity instanceof CardInterface)) { return false; } - + $card = array( 'id' => $entity->getId(), 'carddata' => $entity->getVCard(), - 'uri' => $entity->getVCardUid().'.vcf', + 'uri' => $entity->getVCardUid() . '.vcf', 'lastmodified' => $entity->getLastmodified(), 'size' => strlen($entity->getVCard()), 'etag' => $entity->getETag(), - ); + ); - if ($show_id === false){ + if ($show_id === false) { unset($card['id']); } - + return $card; - } - + } + /** * Constructor * - * @param SecurityContextInterface $context * @param ContainerInterface $container */ - public function __construct(SecurityContextInterface $context, ContainerInterface $container) - { - $this->context = $context; - $this->container = $container; - - $this->_em = $container->get('doctrine')->getManager(); + public function __construct(ContainerInterface $container) { + + $this->_em = $container->get('doctrine')->getManager(); - $this->addressbooks_class = $this->container->getParameter('secotrust.addressbooks_class'); - $this->cards_class = $this->container->getParameter('secotrust.cards_class'); + $this->addressbooks_class = $container->getParameter('secotrust.addressbooks_class'); + $this->cards_class = $container->getParameter('secotrust.cards_class'); } /** @@ -106,28 +98,22 @@ public function __construct(SecurityContextInterface $context, ContainerInterfac * @param string $principalUri * @return array */ - public function getAddressBooksForUser($principalUri) { - - $addressBooks = array(); + public function getAddressBooksForUser($principalUri) { - // TODO: limit addressbook-listing for all (ACL-)Permissions - // create Service in Addressbook-Bundle (e.g. for additional permissions) + $addressBooks = array(); $entities = $this->_em->getRepository($this->addressbooks_class)->findAllPrincipalAddressbooks($principalUri); foreach ($entities as $entity) { - // TODO: don't list, if the user doesn't have permission!! - $addressBooks[] = array( - 'id' => $entity->getId(), + 'id' => $entity->getId(), 'uri' => $entity->getUriLabel(), 'principaluri' => $principalUri, '{DAV:}displayname' => $entity->getLabel(), '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $entity->getDescription(), - '{http://calendarserver.org/ns/}getctag' => $entity->getCtag(), - '{' . CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' => - new CardDAV\Property\SupportedAddressData(), + '{http://calendarserver.org/ns/}getctag' => $entity->getSyncToken(), + '{http://sabredav.org/ns}sync-token' => $entity->getSyncToken() ); } @@ -135,55 +121,62 @@ public function getAddressBooksForUser($principalUri) { } /** - * Updates an addressbook's properties + * Updates properties for an address book. * - * See Sabre\DAV\IProperties for a description of the mutations array, as - * well as the return value. + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. * - * @param mixed $addressBookId - * @param array $mutations - * @see Sabre\DAV\IProperties::updateProperties - * @return bool|array + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documenation for more info and examples. + * + * @param string $addressBookId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void */ - public function updateAddressBook($addressBookId, array $mutations) { - $addressBookId = 0; - $updates = array(); + public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { - foreach($mutations as $property=>$newValue) { + $supportedProperties = [ + '{DAV:}displayname', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description', + ]; - switch($property) { - case '{DAV:}displayname' : - $updates['setLabel'] = $newValue; - break; - case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : - $updates['setDescription'] = $newValue; - break; - default : - // If any unsupported values were being updated, we must - // let the entire request fail. - return false; - } + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + if (!$addressbook) { + return; } - // No values are being updated? - if (!$updates) { - return false; - } + $propPatch->handle($supportedProperties, function($mutations) use ($addressbook) { - $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + $updates = []; + foreach ($mutations as $property => $newValue) { - foreach ($updates as $setter => $value){ - if (method_exists($addressbook, $setter)) { - $addressbook->$setter($value); + switch ($property) { + case '{DAV:}displayname' : + $updates['setLabel'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $updates['setDescription'] = $newValue; + break; + } } - } - $this->_em->flush(); + foreach ($updates as $setter => $value) { + if (method_exists($addressbook, $setter)) { + $addressbook->$setter($value); + } + } - return true; + $this->_em->persist($addressbook); + $this->_em->flush(); + + return true; + }); } - + /** * Creates a new address book * @@ -193,17 +186,17 @@ public function updateAddressBook($addressBookId, array $mutations) { * @return void */ public function createAddressBook($principalUri, $url, array $properties) { - + $values = array( - 'displayname' => null, - 'description' => null, + 'setLabel' => null, + 'setDescription' => null, 'principaluri' => $principalUri, 'uri' => $url, ); - foreach($properties as $property=>$newValue) { + foreach ($properties as $property => $newValue) { - switch($property) { + switch ($property) { case '{DAV:}displayname' : $values['setLabel'] = $newValue; break; @@ -213,18 +206,25 @@ public function createAddressBook($principalUri, $url, array $properties) { default : throw new DAV\Exception\BadRequest('Unknown property: ' . $property); } - } + } + + // check if current addressbooks-class can be instantiated + if ((new \ReflectionClass($this->addressbooks_class))->isAbstract()) { + return null; + } $addressbook = new $this->addressbooks_class(); - - foreach ($values as $setter => $value){ - if (method_exists($addressbook, $setter)){ + + foreach ($values as $setter => $value) { + if (method_exists($addressbook, $setter)) { $addressbook->$setter($value); } } - + $this->_em->persist($addressbook); $this->_em->flush(); + + return $addressbook->getId(); } /** @@ -234,12 +234,17 @@ public function createAddressBook($principalUri, $url, array $properties) { * @return void */ public function deleteAddressBook($addressBookId) { - - //TODO: delete request for addressbook - //TODO: check if this should be done via carddav!! - + + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + + if (!$addressbook) { + return; + } + + $addressbook->removeAllCards(); + $this->_em->delete($addressbook); } - + /** * Returns all cards for a specific addressbook id. * @@ -258,9 +263,9 @@ public function deleteAddressBook($addressBookId) { * * @param mixed $addressbookId * @return array - */ + */ public function getCards($addressbookId) { - + $contactGroup = $this->_em->getRepository($this->addressbooks_class)->findOneById($addressbookId); $entities = $contactGroup->getContactCollection(); @@ -268,9 +273,9 @@ public function getCards($addressbookId) { foreach ($entities as $entity) { $cards[] = $this->getCardArray($entity, true); } - return $cards; + return $cards; } - + /** * Returns a specfic card. * @@ -280,16 +285,14 @@ public function getCards($addressbookId) { * @param mixed $addressBookId * @param string $cardUri * @return array - */ + */ public function getCard($addressBookId, $cardUri) { - $addressBookId = 0; - $vCardUid = substr($cardUri, 0, strlen('.vcf')*(-1)); - - $entity = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($vCardUid); + $entity = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($cardUri, $addressBookId); + return $this->getCardArray($entity, true); } - + /** * Creates a new card. * @@ -314,21 +317,26 @@ public function getCard($addressBookId, $cardUri) { * @param string $cardUri * @param string $cardData * @return string|null - */ + */ public function createCard($addressBookId, $cardUri, $cardData) { - + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); - + + if ((new \ReflectionClass($this->cards_class))->isAbstract()) { + return null; + } + $card = new $this->cards_class(); $card->setVCard($cardData); $card->setVCardUid($cardUri); - + $addressbook->addCard($card); + $this->_em->persist($card); $this->_em->flush(); - - return null; + + return $card->getETag(); } - + /** * Updates a card. * @@ -353,29 +361,175 @@ public function createCard($addressBookId, $cardUri, $cardData) { * @param string $cardUri * @param string $cardData * @return string|null - */ + */ public function updateCard($addressBookId, $cardUri, $cardData) { - - $card = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($addressBookId); + + $card = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($cardUri, $addressBookId); + + if (!$card) { + return null; + } + $card->setVCard($cardData); - - $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); - $addressbook->updateCTag(); - + $this->_em->flush(); - return null; + + return $card->getEtag(); } - + /** * Deletes a card * * @param mixed $addressBookId * @param string $cardUri * @return bool - */ + */ public function deleteCard($addressBookId, $cardUri) { - - return $this->_em->getRepository($this->cards_class)->deleteCard($cardUri); + + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + + $card = $addressbook->findCard($cardUri); + + if ($card instanceof CardInterface) { + return $addressbook->removeCard($card); + } + + return false; + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken in the specified address book. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property. This is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $addressBookId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + + // the "Doctrine2 behavioral extensions" (https://github.com/Atlantic18/DoctrineExtensions) + // are used to log the addressbook-changes + $loggableClass = 'Gedmo\Loggable\Entity\LogEntry'; + + if (!class_exists($loggableClass)) { + return null; + } + + /* @var $addressbook \Secotrust\Bundle\SabreDavBundle\Entity\AddressbookInterface */ + $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); + + if ($addressbook->getSynctoken() === 0) { + return null; + } + + $result = [ + 'syncToken' => $addressbook->getSynctoken(), + 'added' => [], + 'modified' => [], + 'deleted' => [], + ]; + + if ($syncToken) { + + // Fetching all changes + $repo = $this->_em->getRepository($loggableClass); + $logs = $repo->getLogEntries($addressbook); + + $changes = []; + + // This loop ensures that any duplicates are overwritten, only the + // last change on a node is relevant. + foreach ($logs as $log) { + $changes[$addressbook->getUri()] = $log->getAction(); + } + + foreach ($changes as $uri => $operation) { + + switch ($operation) { + case 'create': + $result['added'][] = $uri; + break; + case 'update': + $result['modified'][] = $uri; + break; + case 'remove': + $result['deleted'][] = $uri; + break; + } + } + } else { + // No synctoken supplied, this is the initial sync. + $result['added'] = $addressbook->getUri(); + } + return $result; + } + + /** + * Adds a change record to the addressbookchanges table. + * + * @param mixed $addressBookId + * @param string $objectUri + * @param int $operation 1 = add, 2 = modify, 3 = delete + * @return void + */ + protected function addChange($addressBookId, $objectUri, $operation) { + + // it is suggested to use the Loggable-Extension for Doctrine to manage + // the changes in the entities + // https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/loggable.md + // + // if the extension is configured in the right way, the changes are logged automatically + // configuration-example: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/loggable.md#entity-mapping + + return; } } diff --git a/SabreDav/HttpRequest.php b/SabreDav/HttpRequest.php index 35d822e..74e1acd 100644 --- a/SabreDav/HttpRequest.php +++ b/SabreDav/HttpRequest.php @@ -17,8 +17,8 @@ /** * Class HttpRequest */ -class HttpRequest extends BaseRequest -{ +class HttpRequest extends BaseRequest { + /** * @var Request */ @@ -29,10 +29,17 @@ class HttpRequest extends BaseRequest * * @param Request $request */ - public function __construct(Request $request) - { - parent::__construct($request->server->all(), $request->request->all()); - $this->setBody($request->getContent(true), true); - $this->request = $request; // TODO needed? + public function __construct(Request $request) { + parent::__construct($request->getMethod(), $request->getRequestUri(), $request->headers->all(), $request->getContent(true)); + $this->request = $request; + } + + /** + * set the current username + * + * @param string $username + */ + public function setCurrentUsername($username) { + $this->currentUsername = $username; } } diff --git a/SabreDav/HttpResponse.php b/SabreDav/HttpResponse.php index 9a12989..f289c4b 100644 --- a/SabreDav/HttpResponse.php +++ b/SabreDav/HttpResponse.php @@ -17,8 +17,8 @@ /** * Class HttpResponse */ -class HttpResponse extends BaseResponse -{ +class HttpResponse extends BaseResponse { + /** * @var StreamedResponse */ @@ -29,8 +29,8 @@ class HttpResponse extends BaseResponse * * @param StreamedResponse $response */ - public function __construct(StreamedResponse $response) - { - $this->response = $response; // TODO needed? + public function __construct(StreamedResponse $response) { + parent::__construct($response->getStatusCode(), $response->headers->all()); + $this->response = $response; } } diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index 926fceb..5a5c298 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -11,47 +11,52 @@ namespace Secotrust\Bundle\SabreDavBundle\SabreDav; -use Sabre\DAVACL\PrincipalBackend\BackendInterface; -use Symfony\Component\Security\Core\SecurityContextInterface; +use Sabre\DAVACL\PrincipalBackend\AbstractBackend; +use FOS\UserBundle\Model\UserInterface; +use FOS\UserBundle\Model\GroupInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +class PrincipalBackend extends AbstractBackend { -class PrincipalBackend implements BackendInterface -{ /** - * @var SecurityContextInterface + * @var \Doctrine\ORM\EntityManager */ - private $context; + private $_em; /** - * @var ContainerInterface + * @var \FOS\UserBundle\Model\UserManagerInterface */ - private $container; + private $user_manager; /** - * @var ContainerInterface + * @var \FOS\UserBundle\Model\GroupManagerInterface */ - private $_em; + private $group_manager; /** - * @var type + * @var string */ private $principals_class; - - /** + + /** + * @var string + */ + private $principalgroups_class; + + /** * A list of additional fields to support * * @var array */ protected $fieldMap = array( - /** * This property can be used to display the users' real name. */ '{DAV:}displayname' => array( 'getter' => 'getUsername', + 'setter' => 'setUsername' ), - /** * This property is actually used by the CardDAV plugin, where it gets * mapped to {http://calendarserver.orgi/ns/}me-card. @@ -62,57 +67,73 @@ class PrincipalBackend implements BackendInterface */ '{http://sabredav.org/ns}vcard-url' => array( 'getter' => 'getVCardUrl', + 'setter' => 'setVCardUrl', ), /** * This is the users' primary email-address. */ '{http://sabredav.org/ns}email-address' => array( 'getter' => 'getEmail', + 'setter' => 'setEmail', ), - ); - + ); + /** * Constructor * - * @param SecurityContextInterface $context * @param ContainerInterface $container */ - public function __construct(SecurityContextInterface $context, ContainerInterface $container) - { - $this->context = $context; - $this->container = $container; - - $this->_em = $container->get('doctrine')->getManager(); - - $this->principals_class = $this->container->getParameter('secotrust.principals_class'); -// $this->cards_class = $this->container->getParameter('secotrust.cards_class'); + public function __construct(ContainerInterface $container) { + + $this->_em = $container->get('doctrine')->getManager(); + $this->principals_class = $container->getParameter('secotrust.principals_class'); + $this->principalgroups_class = $container->getParameter('secotrust.principalgroups_class'); + $this->user_manager = $container->get('fos_user.user_manager'); + + if (!$container->has('fos_user.group_manager')) { + $this->group_manager = $container->get('fos_user.group_manager'); + } } - + /** * get Array with Principal-Data from User-Object * - * @param $userObject + * @param UserInterface|GroupInterface $principalObject * @param type $show_id * @return array */ - private function getPrincipalArray($userObject, $show_id = false){ - + private function getPrincipalArray($principalObject, $show_id = false) { + + if (!($principalObject instanceof UserInterface) && !($principalObject instanceof GroupInterface)) { + throw new DAV\Exception('$principalObject must be of type UserInterface of GroupInterface'); + } + $principal = array(); - if ($show_id){ - $principal['id'] = $userObject->getId(); + if ($show_id) { + $principal['id'] = $principalObject->getId(); } - $principal['uri'] = 'principals/' . $userObject->getUsername(); + if ($principalObject instanceof UserInterface) { + $principal['uri'] = 'principals/' . $principalObject->getUsername(); + } else { + $principal['uri'] = 'principals/' . $principalObject->getName(); + } + + foreach ($this->fieldMap as $key => $value) { + if (!method_exists($principalObject, $value['getter'])) { + continue; + } + + $valueGetter = call_user_func(array($principalObject, $value['getter'])); - foreach($this->fieldMap as $key=>$value) { - if (method_exists($userObject, $value['getter']) && call_user_func(array($userObject, $value['getter']))) { - $principal[$key] = call_user_func(array($userObject, $value['getter'])); + if ($valueGetter) { + $principal[$key] = $valueGetter; } } return $principal; } - + /** * Returns a list of principals based on a prefix. * @@ -129,36 +150,59 @@ private function getPrincipalArray($userObject, $show_id = false){ * @param string $prefixPath * @return array */ - public function getPrincipalsByPrefix($prefixPath){ + public function getPrincipalsByPrefix($prefixPath) { $userlist = $this->_em->getRepository($this->principals_class)->findBy(array('enabled' => true)); $principals = array(); - foreach($userlist as $user) { + foreach ($userlist as $user) { $principals[] = $this->getPrincipalArray($user); } - return $principals; + return $principals; } - + /** * Returns a specific principal, specified by it's path. * The returned structure should be the exact same as from * getPrincipalsByPrefix. * * @param string $path - * @return array + * @return array|GroupInterface|UserInterface */ - public function getPrincipalByPath($path){ - - $username = str_replace('principals/', '', $path); - if (!(strpos($username,'/')=== false)){ - $username = substr($username, 0, strpos($username, '/')); + public function getPrincipalByPath($path, $getObject = false) { + + $name = str_replace('principals/', '', $path); + + // get username from path-string, if string contains additional slashes (e.g. admin/calendar-proxy-read) + if (!(strpos($name, '/') === false)) { + $name = substr($name, 0, strpos($name, '/')); } - $user = $this->_em->getRepository($this->principals_class)->findByUsername($username); + $user = $this->user_manager->findUserByUsername($name); + + if ($user === null) { + + if (!$this->group_manager) { + return; + } + + // search in group-manager + $group = $this->group_manager->findGroupByName($name); + + if ($group === null) { + return; + } + + if ($getObject === true) { + return $group; + } + return $this->getPrincipalArray($group, true); + } - $user = $user[0]; + if ($getObject === true) { + return $user; + } return $this->getPrincipalArray($user, true); } @@ -166,54 +210,37 @@ public function getPrincipalByPath($path){ /** * Updates one ore more webdav properties on a principal. * - * The list of mutations is supplied as an array. Each key in the array is - * a propertyname, such as {DAV:}displayname. - * - * Each value is the actual value to be updated. If a value is null, it - * must be deleted. - * - * This method should be atomic. It must either completely succeed, or - * completely fail. Success and failure can simply be returned as 'true' or - * 'false'. - * - * It is also possible to return detailed failure information. In that case - * an array such as this should be returned: + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. * - * array( - * 200 => array( - * '{DAV:}prop1' => null, - * ), - * 201 => array( - * '{DAV:}prop2' => null, - * ), - * 403 => array( - * '{DAV:}prop3' => null, - * ), - * 424 => array( - * '{DAV:}prop4' => null, - * ), - * ); - * - * In this previous example prop1 was successfully updated or deleted, and - * prop2 was succesfully created. - * - * prop3 failed to update due to '403 Forbidden' and because of this prop4 - * also could not be updated with '424 Failed dependency'. - * - * This last example was actually incorrect. While 200 and 201 could appear - * in 1 response, if there's any error (403) the other properties should - * always fail with 423 (failed dependency). - * - * But anyway, if you don't want to scratch your head over this, just - * return true or false. + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". * + * Read the PropPatch documenation for more info and examples. + * * @param string $path - * @param array $mutations - * @return array|bool + * @param \Sabre\DAV\PropPatch $propPatch */ - public function updatePrincipal($path, $mutations){ - $this->container->get('logger')->error('CardDAV update of Principal currently not possible!'); - return false; + public function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { + + $principal = $this->getPrincipalByPath($path, true); + + if (empty($principal)) { + return; + } + + $propPatch->handle(array_keys($this->fieldMap), function($properties) use ($principal) { + + foreach ($properties as $key => $value) { + + $setter = $this->fieldMap[$key]['setter']; + $principal->$setter($value); + } + + $this->_em->flush(); + return true; + }); } /** @@ -221,15 +248,15 @@ public function updatePrincipal($path, $mutations){ * properties. * * This search is specifically used by RFC3744's principal-property-search - * REPORT. You should at least allow searching on - * http://sabredav.org/ns}email-address. + * REPORT. * * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. * - * If multiple properties are being searched on, the search should be - * AND'ed. + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. * * This method should simply return an array with full principal uri's. * @@ -242,13 +269,14 @@ public function updatePrincipal($path, $mutations){ * * @param string $prefixPath * @param array $searchProperties + * @param string $test * @return array */ - public function searchPrincipals($prefixPath, array $searchProperties){ - - foreach($searchProperties as $property => $value) { + public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { - switch($property) { + foreach ($searchProperties as $property => $value) { + + switch ($property) { case '{DAV:}displayname' : $searchArray['email'] = $value; @@ -260,9 +288,9 @@ public function searchPrincipals($prefixPath, array $searchProperties){ // Unsupported property return array(); } - } + } - $principals = $this->_em->getRepository($this->principals_class)->searchPrincipals($prefixPath, $searchArray); + $principals = $this->_em->getRepository($this->principals_class)->searchPrincipals($prefixPath, $searchArray, $test); return $principals; } @@ -273,29 +301,49 @@ public function searchPrincipals($prefixPath, array $searchProperties){ * @param string $principal * @return array */ - public function getGroupMemberSet($principal){ - $principal = $this->getPrincipalByPath($principal); - - $groupMemberSet = array(); - //TODO: list group membership for all addressbooks(contactgroups): - - $groupMemberSet[] = $principal['uri']; - - return $groupMemberSet; + public function getGroupMemberSet($principal) { + + $groupMemberSet = array(); + + $principalObject = $this->getPrincipalByPath($principal, true); + + if ($principalObject instanceof UserInterface) { + // principal is a user, not a group + throw new DAV\Exception('Group-Principal not found'); + } + + $principalArray = $this->getPrincipalArray($principalObject); + $groupMemberSet[] = $principalArray['uri']; + + if ($this->principalgroups_class === '') { + return $groupMemberSet; + } + + //TODO: list all group memberships for current group (FOSUserBundle) + + return $groupMemberSet; } /** - * Returns the list of groups a principal is a member of + * Returns the list of groups a principal is a member of (each element of the list contains a URI) * * @param string $principal * @return array */ - function getGroupMembership($principal){ -// $principal = $this->getPrincipalByPath($principal); - - $groupMembership = array($principal); - - return $groupMembership; + function getGroupMembership($principal) { + + $principal_data = $this->getPrincipalByPath($principal, true); + + $groupMembership = array($principal['uri']); + + if ($this->principalgroups_class !== '') { + foreach ($principal_data->getGroups() as $group) { + $groupPrincipal = $this->getPrincipalArray($group); + $groupMembership[] = $groupPrincipal['uri']; + } + } + + return $groupMembership; } /** @@ -307,8 +355,25 @@ function getGroupMembership($principal){ * @param array $members * @return void */ - function setGroupMemberSet($principal, array $members){ - $this->container->get('logger')->error('CardDAV update of Principal-Group-Membership currently possible!'); - } + function setGroupMemberSet($principal, array $members) { + + $groupPrincipal = $this->getPrincipalByPath($principal); + + if (!$groupPrincipal || !($groupPrincipal instanceof GroupInterface)) { + throw new DAV\Exception('(Group-)Principal not found'); + } + + // check if update of user-groups is possible; break if no group-manager or principalgroups_class + if ($this->principalgroups_class === '' || !$this->group_manager) { + return; + } + + $memberObjects = array($groupPrincipal); + foreach ($members as $memberUri) { + $memberObjects[] = $this->getPrincipalByPath($memberUri); + } + + // TODO: Implement the addition/deletion of new/old members + } } diff --git a/composer.json b/composer.json index 0febc05..b948502 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,11 @@ "require": { "php": ">=5.3.3", "symfony/framework-bundle": "~2.2", - "sabre/dav": "1.8.*" + "friendsofsymfony/user-bundle": "1.3.5", + "sabre/dav": "~2.1", }, "suggest": { - "sabre/vobject": "~3.0.0", - "knplabs/knp-gaufrette-bundle": "0.2.*" + "knplabs/knp-gaufrette-bundle": "0.2.*", }, "autoload": { "psr-0": { "Secotrust\\Bundle\\SabreDavBundle": "" } @@ -35,7 +35,7 @@ "target-dir": "Secotrust/Bundle/SabreDavBundle", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } } } From b53bb0ca0da34bb733c728436d6b45281692805a Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 25 Feb 2015 15:17:19 +0100 Subject: [PATCH 08/30] updated plugin-config for carddav & principal --- Resources/config/services/plugins/carddav.xml | 10 ++++------ Resources/config/services/plugins/principal.xml | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Resources/config/services/plugins/carddav.xml b/Resources/config/services/plugins/carddav.xml index 9d2cc26..c8c33ef 100644 --- a/Resources/config/services/plugins/carddav.xml +++ b/Resources/config/services/plugins/carddav.xml @@ -7,23 +7,21 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\CardDavBackend Sabre\CardDAV\Plugin - Sabre\CardDAV\AddressBookRoot + Sabre\CardDAV\AddressBookRoot - - + - - false + - + diff --git a/Resources/config/services/plugins/principal.xml b/Resources/config/services/plugins/principal.xml index 645dff2..7e79727 100644 --- a/Resources/config/services/plugins/principal.xml +++ b/Resources/config/services/plugins/principal.xml @@ -1,23 +1,21 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> Secotrust\Bundle\SabreDavBundle\SabreDav\PrincipalBackend Sabre\DAVACL\Plugin - Sabre\CalDAV\Principal\Collection + Sabre\DAVACL\PrincipalCollection - - + - false From 334368457766da70a69b86a867f939e1f4414df5 Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 25 Feb 2015 15:31:49 +0100 Subject: [PATCH 09/30] added acl-plugin to configure the DAVACL in the security-manager extension --- DependencyInjection/Configuration.php | 22 +- .../SecotrustSabreDavExtension.php | 7 +- Resources/config/services/plugins/acl.xml | 22 +- SabreDav/Acl/SecurityManager.php | 61 +++++ SabreDav/AclPlugin.php | 208 ++++++++++++++++++ SabreDav/AuthBackend.php | 1 + SabreDav/HttpRequest.php | 15 ++ 7 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 SabreDav/Acl/SecurityManager.php create mode 100644 SabreDav/AclPlugin.php diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 658f2bb..a454d22 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -17,13 +17,12 @@ /** * Class Configuration */ -class Configuration implements ConfigurationInterface -{ +class Configuration implements ConfigurationInterface { + /** * {@inheritDoc} */ - public function getConfigTreeBuilder() - { + public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('secotrust_sabre_dav'); @@ -34,6 +33,18 @@ public function getConfigTreeBuilder() ->scalarNode('root_dir') ->example('%kernel.root_dir%/../web/dav/') ->end() + ->scalarNode('browser_logo') + ->example('%kernel.root_dir%/../web/logo/sabredav.png') + ->defaultValue('') + ->end() + ->scalarNode('favicon') + ->example('%kernel.root_dir%/../web/logo/favicon.ico') + ->defaultValue('') + ->end() + ->scalarNode('security_service') + ->example('sabredav.security_service') + ->defaultValue('') + ->end() ->scalarNode('base_uri') ->example($default_base_uri) ->end() @@ -59,7 +70,10 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('cards_class')->defaultValue('')->end() ->scalarNode('addressbooks_class')->defaultValue('')->end() + ->scalarNode('calendarobjects_class')->defaultValue('')->end() + ->scalarNode('calendar_class')->defaultValue('')->end() ->scalarNode('principals_class')->defaultValue('')->end() + ->scalarNode('principalgroups_class')->defaultValue('')->end() ->end() ->end() ->end() diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 8d73c3b..552afa4 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -44,11 +44,16 @@ public function load(array $configs, ContainerBuilder $container) } // no root dir is set, but webdav plugin is active: throw exception - if (!empty($config['root_dir']) && $config['plugins']['webdav']) { + if (!empty($config['root_dir']) && $config['plugins']['webdav']) { //replace argument $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); } + // add security-service-class + if ($config['security_service']){ + $container->setParameter('secotrust.sabredav.acl.securityService', $config['security_service']); + } + $container->setParameter('secotrust.cards_class', $config['settings']['cards_class']); $container->setParameter('secotrust.addressbooks_class', $config['settings']['addressbooks_class']); $container->setParameter('secotrust.principals_class', $config['settings']['principals_class']); diff --git a/Resources/config/services/plugins/acl.xml b/Resources/config/services/plugins/acl.xml index 6433203..a1d7d49 100644 --- a/Resources/config/services/plugins/acl.xml +++ b/Resources/config/services/plugins/acl.xml @@ -5,12 +5,32 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Sabre\DAVACL\Plugin + Secotrust\Bundle\SabreDavBundle\SabreDav\AclPlugin + Secotrust\Bundle\SabreDavBundle\SabreDav\Acl\SecurityManager + + true + true + principals + + + + + %sabredav.acl.hideNodesFromListings% + + + %sabredav.acl.accessToNodesWithoutACL% + + + %sabredav.acl.defaultUsernamePath% + + + + diff --git a/SabreDav/Acl/SecurityManager.php b/SabreDav/Acl/SecurityManager.php new file mode 100644 index 0000000..c0db9bb --- /dev/null +++ b/SabreDav/Acl/SecurityManager.php @@ -0,0 +1,61 @@ +container = $container; + $this->token = $container->get('security.token_storage')->getToken(); + $this->authorizationChecker = $container->get('security.authorization_checker'); + } + + /** + * returns the ACL list in the following format:
+ * return array('read', 'write', 'delete'); + * + * consider: + * null will be returned to tell the AclPlugin to use the default Node-Acl (e.g. if no ACL was found for this entry) + * + * an empty array will be returned, if the user has no permissions for the current object. + * + * @param type $username + * @param type $objectClass + * @param type $objectIdentifier + * @param type $groupIdentifier + * @return array|null + */ + public function getACL($username, $objectClass, $objectIdentifier, $groupIdentifier = null) { + return null; + } +} diff --git a/SabreDav/AclPlugin.php b/SabreDav/AclPlugin.php new file mode 100644 index 0000000..7d9d2dc --- /dev/null +++ b/SabreDav/AclPlugin.php @@ -0,0 +1,208 @@ +authChecker = $authChecker; + $this->container = $container; + } + + /** + * By default nodes that are inaccessible by the user, can still be seen + * in directory listings (PROPFIND on parent with Depth: 1) + * + * @param boolean $flag + */ + public function setHideNodesFromListings($flag = false) { + $this->hideNodesFromListings = (bool) $flag; + } + + /** + * By default ACL is only enforced for nodes that have ACL support (the + * ones that implement IACL). For any other node, access is + * always granted. + * + * To override this behaviour you can turn this setting off. This is useful + * if you plan to fully support ACL in the entire tree. + * + * @param boolean $flag + */ + public function setAccessToNodesWithoutACL($flag = true) { + $this->allowAccessToNodesWithoutACL = (bool) $flag; + } + + /** + * This string is prepended to the username of the currently logged in + * user. This allows the plugin to determine the principal path based on + * the username. + * + * @param string $usernamePath + */ + public function setDefaultUsernamePath($usernamePath = 'principals') { + $this->defaultUsernamePath = $usernamePath; + } + + /** + * add a principal to the admin-list to automatically receive {DAV:}all privileges + * + * @param type $principal + * @return boolean + */ + public function addAdminPrincipal($principal) { + if (strpos($principal, $this->defaultUsernamePath . '/') !== 0) { + $principal = $this->defaultUsernamePath . '/' . $principal; + } + + if (!in_array($principal, $this->adminPrincipals)) { + $this->adminPrincipals[] = $principal; + + return true; + } + + return false; + } + + /** + * remove principal from admin-list + * + * @param type $principal + * @return boolean + */ + public function removeAdminPrincipal($principal) { + if (strpos($principal, $this->defaultUsernamePath . '/') !== 0) { + $principal = $this->defaultUsernamePath . '/' . $principal; + } + + if (false !== ($key = \array_search($principal, $this->adminPrincipals))) { + unset($this->adminPrincipals[$key]); + + return true; + } + + return false; + } + + /** + * Returns the full ACL list. + * + * Either a uri or a DAV\INode may be passed. + * + * null will be returned if the node doesn't support ACLs. + * + * @param string|DAV\INode $node + * @return array + */ + public function getACL($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + if (!$node instanceof IACL) { + return null; + } + + $username = $this->server->httpRequest->getCurrentUsername(); + $acl = array(); + + $this->davSecurity = $this->container->get('secotrust.sabredav_acl_securityManager'); + + if (!is_null($this->davSecurity) && ( + $node instanceof \Sabre\CalDAV\Calendar || + $node instanceof \Sabre\CalDAV\CalendarObject || + $node instanceof \Sabre\CardDAV\AddressBook || + $node instanceof \Sabre\CardDAV\Card + )) { + + $objectClass = ''; + $objectIdentifier = array('name' => $node->getName()); + + if ($node instanceof \Sabre\CardDAV\AddressBook) { + $objectClass = $this->container->getParameter('secotrust.addressbooks_class'); + $objectIdentifier = $node->getProperties(['id']); + $this->groupNode = $objectIdentifier; + } elseif ($node instanceof \Sabre\CardDAV\Calendar) { + $objectClass = $this->container->getParameter('secotrust.calendar_class'); + $this->groupNode = $objectIdentifier; + } elseif ($node instanceof \Sabre\CardDAV\CalendarObject) { + $objectClass = $this->container->getParameter('secotrust.calendarobject_class'); + } elseif ($node instanceof \Sabre\CardDAV\Card) { + $objectClass = $this->container->getParameter('secotrust.cards_class'); + } + + // load the permission-list from the davSecurity-Service + $permissionList = $this->davSecurity->getACL( + $username, $objectClass, $objectIdentifier, $this->groupNode + ); + + if ($permissionList === null) { + // use the "default" ACL from the current node + $acl = $node->getACL(); + } else { + // write permissions to DAV-ACL + foreach ($permissionList as $permission) { + $acl[] = array( + 'privilege' => '{DAV:}' . $permission, + 'principal' => $this->defaultUsernamePath . '/' . $username, + 'protected' => true, + ); + } + } + } elseif (!($node instanceof \Sabre\DAVACL\Principal && $node->getName() !== $username)) { + // get node-acl; if node is a principal-node + // and the name is not like the current username, don't display the node-acl + $acl = $node->getACL(); + } + + // add admin-privileges for all adminPrincipals + foreach ($this->adminPrincipals as $adminPrincipal) { + $acl[] = array( + 'principal' => $adminPrincipal, + 'privilege' => '{DAV:}all', + 'protected' => true, + ); + } + + return $acl; + } +} diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index 2c710a4..278a014 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -116,6 +116,7 @@ public function authenticate(Server $server, $realm) { } $this->currentUser = $userpass[0]; + $server->httpRequest->setCurrentUsername($this->currentUser); return true; } diff --git a/SabreDav/HttpRequest.php b/SabreDav/HttpRequest.php index 74e1acd..2d6d94f 100644 --- a/SabreDav/HttpRequest.php +++ b/SabreDav/HttpRequest.php @@ -24,6 +24,11 @@ class HttpRequest extends BaseRequest { */ private $request; + /** + * @var string + */ + private $currentUsername; + /** * Constructor * @@ -42,4 +47,14 @@ public function __construct(Request $request) { public function setCurrentUsername($username) { $this->currentUsername = $username; } + + /** + * get the current username + * + * @return string + */ + public function getCurrentUsername() { + return $this->currentUsername; + } + } From 5556c5ac67603b73b2373514d632f4bb76473faf Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 25 Feb 2015 15:33:32 +0100 Subject: [PATCH 10/30] added browser-plugin class --- .../SecotrustSabreDavExtension.php | 6 ++ Resources/config/services/plugins/browser.xml | 9 ++- SabreDav/BrowserPlugin.php | 63 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 SabreDav/BrowserPlugin.php diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 552afa4..08ddff3 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -49,6 +49,12 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); } + // add logo to browser-plugin + if ($config['plugins']['browser']) { + $container->setParameter('secotrust.sabredav.browser_plugin.logo', $config['browser_logo']); + $container->setParameter('secotrust.sabredav.browser_plugin.favicon', $config['favicon']); + } + // add security-service-class if ($config['security_service']){ $container->setParameter('secotrust.sabredav.acl.securityService', $config['security_service']); diff --git a/Resources/config/services/plugins/browser.xml b/Resources/config/services/plugins/browser.xml index 8a34d5f..ead8385 100644 --- a/Resources/config/services/plugins/browser.xml +++ b/Resources/config/services/plugins/browser.xml @@ -5,12 +5,19 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Sabre\DAV\Browser\Plugin + Secotrust\Bundle\SabreDavBundle\SabreDav\BrowserPlugin + + %secotrust.sabredav.browser_plugin.logo% + %secotrust.sabredav.browser_plugin.favicon% + false + + %secotrust.sabredav.browser.config% + diff --git a/SabreDav/BrowserPlugin.php b/SabreDav/BrowserPlugin.php new file mode 100644 index 0000000..1057fde --- /dev/null +++ b/SabreDav/BrowserPlugin.php @@ -0,0 +1,63 @@ + $value) { + $this->config[$key] = $value; + } + } + + /** + * + * @param type $key + * @return type + */ + public function getBrowserConfig($key) { + if (isset($this->config[$key])) { + return $this->config[$key]; + } + return; + } + + /** + * This method returns a local pathname to an asset. + * + * The logo and favicon can be overwritten + * + * @param string $assetName + * @return string + */ + protected function getLocalAssetPath($assetName) { + // load path to logo from parameters + if ($assetName === 'sabredav.png' && $this->getBrowserConfig('browser_logo')) { + return $this->getBrowserConfig('browser_logo'); + } elseif ($assetName === 'favicon.ico' && $this->getBrowserConfig('favicon')) { + return $this->getBrowserConfig('favicon'); + } + return parent::getLocalAssetPath($assetName); + } + +} From b4a164d954ff86086fc260ac58b1704549a1f81f Mon Sep 17 00:00:00 2001 From: lduer Date: Wed, 25 Feb 2015 15:38:18 +0100 Subject: [PATCH 11/30] updated principal membership; updated controller & parameter setting; composer: suggest the installation of gedmo/doctrine-extension --- Controller/SabreDavController.php | 12 +++++------- DependencyInjection/SecotrustSabreDavExtension.php | 3 +++ SabreDav/PrincipalBackend.php | 6 +++++- composer.json | 1 + 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index ef984ea..ac289b3 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -23,8 +23,8 @@ /** * Class SabreDavController */ -class SabreDavController -{ +class SabreDavController { + /** * @var Server */ @@ -53,10 +53,9 @@ class SabreDavController * @param ContainerInterface $container * @param Router $router */ - public function __construct(Server $dav, EventDispatcherInterface $dispatcher, ContainerInterface $container, RouterInterface $router) - { + public function __construct(Server $dav, EventDispatcherInterface $dispatcher, ContainerInterface $container, RouterInterface $router) { $this->dav = $dav; - $this->dav->setBaseUri($router->generate('secotrust_sabre_dav')); + $this->dav->setBaseUri($router->generate('secotrust_sabre_dav', array())); $this->container = $container; $this->dispatcher = $dispatcher; // TODO needed? @@ -67,8 +66,7 @@ public function __construct(Server $dav, EventDispatcherInterface $dispatcher, * * @return StreamedResponse */ - public function execAction(Request $request) - { + public function execAction(Request $request) { $dav = $this->dav; $callback = function () use ($dav) { $dav->exec(); diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 08ddff3..3baabb3 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -62,6 +62,9 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('secotrust.cards_class', $config['settings']['cards_class']); $container->setParameter('secotrust.addressbooks_class', $config['settings']['addressbooks_class']); + $container->setParameter('secotrust.calendarobjects_class', $config['settings']['calendarobjects_class']); + $container->setParameter('secotrust.calendar_class', $config['settings']['calendar_class']); $container->setParameter('secotrust.principals_class', $config['settings']['principals_class']); + $container->setParameter('secotrust.principalgroups_class', $config['settings']['principalgroups_class']); } } diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index 5a5c298..e0339a4 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -334,7 +334,11 @@ function getGroupMembership($principal) { $principal_data = $this->getPrincipalByPath($principal, true); - $groupMembership = array($principal['uri']); + if (!$principal_data) { + return array(); + } + + $groupMembership = array($principal); if ($this->principalgroups_class !== '') { foreach ($principal_data->getGroups() as $group) { diff --git a/composer.json b/composer.json index b948502..d97e3d6 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ }, "suggest": { "knplabs/knp-gaufrette-bundle": "0.2.*", + "gedmo/doctrine-extensions": "~2.3" }, "autoload": { "psr-0": { "Secotrust\\Bundle\\SabreDavBundle": "" } From 970d639a72efa8535e4d0287c19e1c07f325b5a0 Mon Sep 17 00:00:00 2001 From: lduer Date: Thu, 26 Feb 2015 08:43:31 +0100 Subject: [PATCH 12/30] updated FOSUserBundle-Version --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d97e3d6..6c442d5 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "require": { "php": ">=5.3.3", "symfony/framework-bundle": "~2.2", - "friendsofsymfony/user-bundle": "1.3.5", - "sabre/dav": "~2.1", + "friendsofsymfony/user-bundle": ">=1.3.5", + "sabre/dav": "~2.1" }, "suggest": { "knplabs/knp-gaufrette-bundle": "0.2.*", From 1bf48da263924212c854419bfd6c67bccbdfcba5 Mon Sep 17 00:00:00 2001 From: lduer Date: Thu, 12 Mar 2015 09:17:36 +0100 Subject: [PATCH 13/30] bugfix: throw exception only if principal is not found! --- SabreDav/PrincipalBackend.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index e0339a4..df89eb9 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -307,15 +307,16 @@ public function getGroupMemberSet($principal) { $principalObject = $this->getPrincipalByPath($principal, true); - if ($principalObject instanceof UserInterface) { - // principal is a user, not a group - throw new DAV\Exception('Group-Principal not found'); + if (!$principalObject) { + throw new \Sabre\DAV\Exception('Principal not found'); } + // add current principal to group-list $principalArray = $this->getPrincipalArray($principalObject); $groupMemberSet[] = $principalArray['uri']; if ($this->principalgroups_class === '') { + // groups-class is not defined: return current principal as only group-member return $groupMemberSet; } From 1c17e622d4ada5d5b2a21c70adfddbf8fcddab08 Mon Sep 17 00:00:00 2001 From: lduer Date: Tue, 6 Oct 2015 10:09:42 +0200 Subject: [PATCH 14/30] implementation of sabre/dav 3.0 changes --- Controller/SabreDavController.php | 12 +- Resources/config/services/plugins/auth.xml | 1 + .../config/services/plugins/notifications.xml | 16 ++ .../config/services/plugins/schedule.xml | 16 ++ Resources/config/services/services.xml | 4 +- SabreDav/Auth/BasicAuth.php | 4 +- SabreDav/AuthBackend.php | 147 ++++++++++++++---- SabreDav/PrincipalBackend.php | 38 ++++- composer.json | 2 +- 9 files changed, 189 insertions(+), 51 deletions(-) create mode 100644 Resources/config/services/plugins/notifications.xml create mode 100644 Resources/config/services/plugins/schedule.xml diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index ac289b3..8fbdf02 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -17,7 +17,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\StreamedResponse; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouterInterface; /** @@ -35,11 +34,6 @@ class SabreDavController { */ private $dispatcher; - /** - * @var ContainerInterface - */ - private $container; - /** * @var RouterInterface */ @@ -50,14 +44,12 @@ class SabreDavController { * * @param Server $dav * @param EventDispatcherInterface $dispatcher - * @param ContainerInterface $container - * @param Router $router + * @param RouterInterface $router */ - public function __construct(Server $dav, EventDispatcherInterface $dispatcher, ContainerInterface $container, RouterInterface $router) { + public function __construct(Server $dav, EventDispatcherInterface $dispatcher, RouterInterface $router) { $this->dav = $dav; $this->dav->setBaseUri($router->generate('secotrust_sabre_dav', array())); - $this->container = $container; $this->dispatcher = $dispatcher; // TODO needed? } diff --git a/Resources/config/services/plugins/auth.xml b/Resources/config/services/plugins/auth.xml index 1413ba8..dd59029 100644 --- a/Resources/config/services/plugins/auth.xml +++ b/Resources/config/services/plugins/auth.xml @@ -13,6 +13,7 @@ + %secotrust.sabredav.auth.realm% diff --git a/Resources/config/services/plugins/notifications.xml b/Resources/config/services/plugins/notifications.xml new file mode 100644 index 0000000..a6761eb --- /dev/null +++ b/Resources/config/services/plugins/notifications.xml @@ -0,0 +1,16 @@ + + + + + + Sabre\CalDAV\Notifications\Plugin + + + + + + + + diff --git a/Resources/config/services/plugins/schedule.xml b/Resources/config/services/plugins/schedule.xml new file mode 100644 index 0000000..0a31be7 --- /dev/null +++ b/Resources/config/services/plugins/schedule.xml @@ -0,0 +1,16 @@ + + + + + + Sabre\CalDAV\Schedule\Plugin + + + + + + + + diff --git a/Resources/config/services/services.xml b/Resources/config/services/services.xml index 902cc78..5cf5923 100644 --- a/Resources/config/services/services.xml +++ b/Resources/config/services/services.xml @@ -9,7 +9,8 @@ Sabre\DAV\Server - + + @@ -17,7 +18,6 @@ - diff --git a/SabreDav/Auth/BasicAuth.php b/SabreDav/Auth/BasicAuth.php index 7f837f9..6dc7984 100644 --- a/SabreDav/Auth/BasicAuth.php +++ b/SabreDav/Auth/BasicAuth.php @@ -2,6 +2,8 @@ namespace Secotrust\Bundle\SabreDavBundle\SabreDav\Auth; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use FOS\UserBundle\Model\UserManagerInterface; use Sabre\HTTP\Auth\Basic; @@ -28,7 +30,7 @@ class BasicAuth extends Basic { * @param \Sabre\HTTP\ResponseInterface $response * @param UserManagerInterface $user_manager */ - public function __construct($realm, \Sabre\HTTP\RequestInterface $request, \Sabre\HTTP\ResponseInterface $response, UserManagerInterface $user_manager) { + public function __construct($realm, RequestInterface $request, ResponseInterface $response, UserManagerInterface $user_manager) { $this->user_manager = $user_manager; parent::__construct($realm, $request, $response); diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index 278a014..f3a1a15 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -14,6 +14,8 @@ use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Exception; use Sabre\DAV\Server; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -51,14 +53,34 @@ class AuthBackend implements BackendInterface { */ protected $currentUser; + /** + * Authentication Realm. + * + * The realm is often displayed by browser clients when showing the + * authentication dialog. + * + * @var string + */ + protected $realm = 'SabreDAV'; + + /** + * This is the prefix that will be used to generate principal urls. + * + * @var string + */ + protected $principalPrefix = 'principals/'; + /** * Constructor * * @param ContainerInterface $container + * @param $realm */ - public function __construct(ContainerInterface $container) { + public function __construct(ContainerInterface $container, $realm) { $this->container = $container; + $this->realm = $realm; + $this->user_manager = $this->container->get('fos_user.user_manager'); $this->token_storage = $this->container->get('security.token_storage'); $this->encoder_service = $this->container->get('security.encoder_factory'); @@ -82,45 +104,13 @@ public function validateUserPass($username, $passwordHash) { } if ($passwordHash === $user->getPassword()) { - - $this->userLoginAction($user, $passwordHash); - +// $this->userLoginAction($user, $passwordHash); return true; } return false; } - /** - * Authenticate - * - * Authenticates the User via basc-auth - * - * @param Server $server - * @param type $realm - * @return void - */ - public function authenticate(Server $server, $realm) { - $auth = new Auth\BasicAuth($realm, $server->httpRequest, $server->httpResponse, $this->user_manager); - $userpass = $auth->getCredentials($this->encoder_service); - - if (!$userpass) { - $auth->requireLogin(); - throw new Exception\NotAuthenticated('No authentication headers were found'); - } - - // Authenticates the user - if (!$this->validateUserPass($userpass[0], $userpass[1])) { - $auth->requireLogin(); - throw new Exception\NotAuthenticated('Username or password does not match'); - } - - $this->currentUser = $userpass[0]; - $server->httpRequest->setCurrentUsername($this->currentUser); - - return true; - } - /** * add the symfony-login "manually" * @@ -159,4 +149,93 @@ private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $pas public function getCurrentUser() { return $this->currentUser; } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + * @throws Exception + */ + public function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new Auth\BasicAuth($this->realm, $request, $response, $this->user_manager); + $userpass = $auth->getCredentials($this->encoder_service); + + // No username was given + if ($userpass === false) { + return [false, "No 'Authorization' header found. Either the client didn't send one, or the server is mis-configured"]; + } + + // Authenticates the user + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + + $this->currentUser = $userpass[0]; + $request->setCurrentUsername($this->currentUser); + + return [true, $this->principalPrefix . $userpass[0]]; + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + public function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new Auth\BasicAuth($this->realm, $request, $response, $this->user_manager); + $userpass = $auth->getCredentials($this->encoder_service); + + if (!$userpass) { + $auth->requireLogin(); + } + + // Authenticates the user + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + $auth->requireLogin(); + } + + $this->currentUser = $userpass[0]; + $request->setCurrentUsername($this->currentUser); + } } diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index df89eb9..7f5012b 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -11,13 +11,16 @@ namespace Secotrust\Bundle\SabreDavBundle\SabreDav; +use Sabre\DAV\Exception; +use Sabre\DAV\MkCol; use Sabre\DAVACL\PrincipalBackend\AbstractBackend; +use Sabre\DAVACL\PrincipalBackend\CreatePrincipalSupport; use FOS\UserBundle\Model\UserInterface; use FOS\UserBundle\Model\GroupInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -class PrincipalBackend extends AbstractBackend { +class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport { /** * @var \Doctrine\ORM\EntityManager @@ -90,7 +93,7 @@ public function __construct(ContainerInterface $container) { $this->principalgroups_class = $container->getParameter('secotrust.principalgroups_class'); $this->user_manager = $container->get('fos_user.user_manager'); - if (!$container->has('fos_user.group_manager')) { + if ($container->has('fos_user.group_manager')) { $this->group_manager = $container->get('fos_user.group_manager'); } } @@ -119,6 +122,7 @@ private function getPrincipalArray($principalObject, $show_id = false) { $principal['uri'] = 'principals/' . $principalObject->getName(); } + // get all fields from $this->fieldMap, additional to 'uri' and 'id' foreach ($this->fieldMap as $key => $value) { if (!method_exists($principalObject, $value['getter'])) { continue; @@ -154,8 +158,10 @@ public function getPrincipalsByPrefix($prefixPath) { $userlist = $this->_em->getRepository($this->principals_class)->findBy(array('enabled' => true)); $principals = array(); - + foreach ($userlist as $user) { + + // due to the lack of the implementation of prefixes, return all users $principals[] = $this->getPrincipalArray($user); } @@ -380,5 +386,31 @@ function setGroupMemberSet($principal, array $members) { } // TODO: Implement the addition/deletion of new/old members + + } + + + /** + * Creates a new principal. + * + * This method receives a full path for the new principal. The mkCol object + * contains any additional webdav properties specified during the creation + * of the principal. + * + * @param string $path + * @param MkCol $mkCol + * @return void + */ + function createPrincipal($path, MkCol $mkCol) { + + // create new user + $username = str_replace('principal/', '', $path); + + $user = $this->user_manager->createUser(); + $user->setUsername($username); + $user->setForename($username); + + $this->_em->persist($user); + $this->_em->flush(); } } diff --git a/composer.json b/composer.json index 6c442d5..96d4f23 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "php": ">=5.3.3", "symfony/framework-bundle": "~2.2", "friendsofsymfony/user-bundle": ">=1.3.5", - "sabre/dav": "~2.1" + "sabre/dav": "~3.0" }, "suggest": { "knplabs/knp-gaufrette-bundle": "0.2.*", From 0cd24bbd37c75e58e6bc0a8d513e753c53f30b9e Mon Sep 17 00:00:00 2001 From: lduer Date: Tue, 6 Oct 2015 10:30:53 +0200 Subject: [PATCH 15/30] improvements in php-vdocs & -code --- Entity/AddressbookInterface.php | 4 ++-- Entity/PrincipalInterface.php | 2 +- .../AddressbookRepositoryInterface.php | 2 +- Entity/Repository/CardRepositoryInterface.php | 4 ++-- .../Repository/PrincipalRepositoryInterface.php | 4 ++-- Resources/config/services/plugins/browser.xml | 2 +- Resources/config/services/plugins/principal.xml | 3 --- SabreDav/Acl/SecurityManager.php | 12 ++++++------ SabreDav/AclPlugin.php | 11 +++++++---- SabreDav/AuthBackend.php | 15 +++++++-------- SabreDav/BrowserPlugin.php | 4 ++-- SabreDav/CardDavBackend.php | 14 ++++++++------ SabreDav/PrincipalBackend.php | 17 +++++++++++------ 13 files changed, 50 insertions(+), 44 deletions(-) diff --git a/Entity/AddressbookInterface.php b/Entity/AddressbookInterface.php index 3c480f4..793a7ac 100644 --- a/Entity/AddressbookInterface.php +++ b/Entity/AddressbookInterface.php @@ -65,7 +65,7 @@ public function updateSynctoken(); /** * get the Uri * - * @return type + * @return string */ public function getUri(); @@ -79,7 +79,7 @@ public function getCards(); /** * Search Card by URI in current Addressbook * - * @param type $uri + * @param string $uri */ public function findCard($uri); diff --git a/Entity/PrincipalInterface.php b/Entity/PrincipalInterface.php index 8d4e814..740d664 100644 --- a/Entity/PrincipalInterface.php +++ b/Entity/PrincipalInterface.php @@ -56,7 +56,7 @@ public function getVCardUrl(); /** * set vCardUrl * - * @param type $vCardUrl + * @param string $vCardUrl */ public function setVCardUrl($vCardUrl); } diff --git a/Entity/Repository/AddressbookRepositoryInterface.php b/Entity/Repository/AddressbookRepositoryInterface.php index 9c86a54..5f9bf40 100644 --- a/Entity/Repository/AddressbookRepositoryInterface.php +++ b/Entity/Repository/AddressbookRepositoryInterface.php @@ -12,7 +12,7 @@ interface AddressbookRepositoryInterface { /** * get all Addressbooks for submitted principal * - * @param type $principalUri + * @param string $principalUri * @return array */ public function findAllPrincipalAddressbooks($principalUri); diff --git a/Entity/Repository/CardRepositoryInterface.php b/Entity/Repository/CardRepositoryInterface.php index 3f887f4..d81e573 100644 --- a/Entity/Repository/CardRepositoryInterface.php +++ b/Entity/Repository/CardRepositoryInterface.php @@ -12,8 +12,8 @@ interface CardRepositoryInterface { /** * Find one Card By vCard-UID and Addressbook-id * - * @param type $uid - * @param type $addressBookId + * @param string $uid + * @param string $addressBookId */ public function findSingleCardByUid($uid=null, $addressBookId=null); } diff --git a/Entity/Repository/PrincipalRepositoryInterface.php b/Entity/Repository/PrincipalRepositoryInterface.php index 3b1bb8a..3a0724d 100644 --- a/Entity/Repository/PrincipalRepositoryInterface.php +++ b/Entity/Repository/PrincipalRepositoryInterface.php @@ -16,7 +16,7 @@ interface PrincipalRepositoryInterface { * expected to return principals that are in this base path. * * - * @param type $uid + * @param string $prefixPath */ public function getPrincipalsByPrefix($prefixPath); @@ -29,7 +29,7 @@ public function getPrincipalsByPrefix($prefixPath); * various properties should be combined with 'AND'. If $test is set to * 'anyof', it should be combined using 'OR'. * - * @param type $prefixPath + * @param string $prefixPath * @param array $searchArray * @param string $test */ diff --git a/Resources/config/services/plugins/browser.xml b/Resources/config/services/plugins/browser.xml index ead8385..87cbd8f 100644 --- a/Resources/config/services/plugins/browser.xml +++ b/Resources/config/services/plugins/browser.xml @@ -17,7 +17,7 @@ false %secotrust.sabredav.browser.config% - + diff --git a/Resources/config/services/plugins/principal.xml b/Resources/config/services/plugins/principal.xml index 7e79727..1d7feb3 100644 --- a/Resources/config/services/plugins/principal.xml +++ b/Resources/config/services/plugins/principal.xml @@ -14,15 +14,12 @@ - - -
diff --git a/SabreDav/Acl/SecurityManager.php b/SabreDav/Acl/SecurityManager.php index c0db9bb..2d28c4b 100644 --- a/SabreDav/Acl/SecurityManager.php +++ b/SabreDav/Acl/SecurityManager.php @@ -48,12 +48,12 @@ public function __construct(ContainerInterface $container) { * null will be returned to tell the AclPlugin to use the default Node-Acl (e.g. if no ACL was found for this entry) * * an empty array will be returned, if the user has no permissions for the current object. - * - * @param type $username - * @param type $objectClass - * @param type $objectIdentifier - * @param type $groupIdentifier - * @return array|null + * + * @param string $username + * @param $objectClass + * @param $objectIdentifier + * @param null $groupIdentifier + * @return null */ public function getACL($username, $objectClass, $objectIdentifier, $groupIdentifier = null) { return null; diff --git a/SabreDav/AclPlugin.php b/SabreDav/AclPlugin.php index 7d9d2dc..51e9b26 100644 --- a/SabreDav/AclPlugin.php +++ b/SabreDav/AclPlugin.php @@ -16,7 +16,10 @@ class AclPlugin extends Plugin { /** * the "parent" node (Addressbook for Cards, Calendar for CalendarObject) - * + * + * This is set in the addressbook- or calendar-acl-request and + * used in all sub-requests (cards & events) + * * @var IACL */ private $groupNode; @@ -85,7 +88,7 @@ public function setDefaultUsernamePath($usernamePath = 'principals') { /** * add a principal to the admin-list to automatically receive {DAV:}all privileges * - * @param type $principal + * @param string $principal * @return boolean */ public function addAdminPrincipal($principal) { @@ -105,7 +108,7 @@ public function addAdminPrincipal($principal) { /** * remove principal from admin-list * - * @param type $principal + * @param string $principal * @return boolean */ public function removeAdminPrincipal($principal) { @@ -129,7 +132,7 @@ public function removeAdminPrincipal($principal) { * * null will be returned if the node doesn't support ACLs. * - * @param string|DAV\INode $node + * @param string|\Sabre\DAV\INode $node * @return array */ public function getACL($node) { diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index f3a1a15..b138c24 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -13,7 +13,6 @@ use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Exception; -use Sabre\DAV\Server; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -90,10 +89,10 @@ public function __construct(ContainerInterface $container, $realm) { /** * Checks if username and password are valid. (Checked by the FOSUserManager) * Returns - * - * @param type $username - * @param type $password - * @return boolean + * + * @param $username + * @param $passwordHash + * @return bool */ public function validateUserPass($username, $passwordHash) { @@ -121,9 +120,9 @@ public function validateUserPass($username, $passwordHash) { * * before and after the generation/setting of the token, the events "secotrust.user_login.before" * and "secotrust.user_login.after" are called, if some EventListeners are configured - * - * @param string $user - * @param string $passwordHash + * + * @param \FOS\UserBundle\Model\UserInterface $user + * @param $passwordHash */ private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $passwordHash) { // call the pre-login-event diff --git a/SabreDav/BrowserPlugin.php b/SabreDav/BrowserPlugin.php index 1057fde..3864e6e 100644 --- a/SabreDav/BrowserPlugin.php +++ b/SabreDav/BrowserPlugin.php @@ -32,8 +32,8 @@ public function setBrowserConfig(array $config) { /** * - * @param type $key - * @return type + * @param string $key + * @return string */ public function getBrowserConfig($key) { if (isset($this->config[$key])) { diff --git a/SabreDav/CardDavBackend.php b/SabreDav/CardDavBackend.php index 1329a4e..11b4038 100644 --- a/SabreDav/CardDavBackend.php +++ b/SabreDav/CardDavBackend.php @@ -14,6 +14,7 @@ use Sabre\CardDAV\Backend\AbstractBackend; use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV; +use Sabre\DAV\Exception\BadRequest; use Secotrust\Bundle\SabreDavBundle\Entity\CardInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -41,10 +42,10 @@ class CardDavBackend extends AbstractBackend implements SyncSupport { /** * Create array with Card-Data - * - * @param type $entity - * @param type $show_id - * @return array + * + * @param CardInterface $entity + * @param bool $show_id + * @return array|bool */ private function getCardArray($entity, $show_id = false) { @@ -183,7 +184,8 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc * @param string $principalUri * @param string $url Just the 'basename' of the url. * @param array $properties - * @return void + * @return null + * @throws BadRequest */ public function createAddressBook($principalUri, $url, array $properties) { @@ -204,7 +206,7 @@ public function createAddressBook($principalUri, $url, array $properties) { $values['setDescription'] = $newValue; break; default : - throw new DAV\Exception\BadRequest('Unknown property: ' . $property); + throw new BadRequest('Unknown property: ' . $property); } } diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index 7f5012b..fd1369b 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -100,15 +100,16 @@ public function __construct(ContainerInterface $container) { /** * get Array with Principal-Data from User-Object - * + * * @param UserInterface|GroupInterface $principalObject - * @param type $show_id + * @param bool $show_id * @return array + * @throws Exception */ private function getPrincipalArray($principalObject, $show_id = false) { if (!($principalObject instanceof UserInterface) && !($principalObject instanceof GroupInterface)) { - throw new DAV\Exception('$principalObject must be of type UserInterface of GroupInterface'); + throw new Exception('$principalObject must be of type UserInterface of GroupInterface'); } $principal = array(); @@ -174,7 +175,9 @@ public function getPrincipalsByPrefix($prefixPath) { * getPrincipalsByPrefix. * * @param string $path - * @return array|GroupInterface|UserInterface + * @param bool $getObject + * @return array|GroupInterface|UserInterface|void + * @throws Exception */ public function getPrincipalByPath($path, $getObject = false) { @@ -306,6 +309,7 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' * * @param string $principal * @return array + * @throws Exception */ public function getGroupMemberSet($principal) { @@ -314,7 +318,7 @@ public function getGroupMemberSet($principal) { $principalObject = $this->getPrincipalByPath($principal, true); if (!$principalObject) { - throw new \Sabre\DAV\Exception('Principal not found'); + throw new Exception('Principal not found'); } // add current principal to group-list @@ -365,13 +369,14 @@ function getGroupMembership($principal) { * @param string $principal * @param array $members * @return void + * @throws Exception */ function setGroupMemberSet($principal, array $members) { $groupPrincipal = $this->getPrincipalByPath($principal); if (!$groupPrincipal || !($groupPrincipal instanceof GroupInterface)) { - throw new DAV\Exception('(Group-)Principal not found'); + throw new Exception('(Group-)Principal not found'); } // check if update of user-groups is possible; break if no group-manager or principalgroups_class From 56f4c9f5ed5b9f534e4af077a122d2ee3463cd9b Mon Sep 17 00:00:00 2001 From: lduer Date: Tue, 6 Oct 2015 11:45:56 +0200 Subject: [PATCH 16/30] updated code with "PHP Coding Standards Fixer"; added php_cs config --- .php_cs | 10 ++ Controller/SabreDavController.php | 22 +-- .../Compiler/CollectionPass.php | 2 +- DependencyInjection/Compiler/PluginPass.php | 2 +- DependencyInjection/Configuration.php | 27 ++-- .../SecotrustSabreDavExtension.php | 20 +-- Entity/AddressbookInterface.php | 88 +++++------ Entity/CardInterface.php | 72 +++++---- Entity/PrincipalInterface.php | 36 +++-- .../AddressbookRepositoryInterface.php | 12 +- Entity/Repository/CardRepositoryInterface.php | 12 +- .../PrincipalRepositoryInterface.php | 22 +-- Resources/config/services/plugins/acl.xml | 6 +- Resources/config/services/plugins/carddav.xml | 6 +- Resources/config/services/services.xml | 2 +- SabreDav/Acl/SecurityManager.php | 22 +-- SabreDav/AclPlugin.php | 66 +++++---- SabreDav/Auth/BasicAuth.php | 42 +++--- SabreDav/AuthBackend.php | 53 +++---- SabreDav/BrowserPlugin.php | 25 ++-- SabreDav/CardDavBackend.php | 140 +++++++++--------- SabreDav/Gaufrette/Collection.php | 16 +- SabreDav/Gaufrette/File.php | 16 +- SabreDav/HttpRequest.php | 22 +-- SabreDav/HttpResponse.php | 11 +- SabreDav/PrincipalBackend.php | 105 ++++++------- SecotrustSabreDavBundle.php | 2 +- 27 files changed, 448 insertions(+), 411 deletions(-) create mode 100644 .php_cs diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..926df8c --- /dev/null +++ b/.php_cs @@ -0,0 +1,10 @@ +in(__DIR__) +; + +return Symfony\CS\Config\Config::create() + ->fixers(array('-psr0')) + ->finder($finder) +; \ No newline at end of file diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index 8fbdf02..8055e43 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -20,10 +20,10 @@ use Symfony\Component\Routing\RouterInterface; /** - * Class SabreDavController + * Class SabreDavController. */ -class SabreDavController { - +class SabreDavController +{ /** * @var Server */ @@ -35,18 +35,19 @@ class SabreDavController { private $dispatcher; /** - * @var RouterInterface + * @var RouterInterface */ private $router; - + /** - * Constructor + * Constructor. * - * @param Server $dav + * @param Server $dav * @param EventDispatcherInterface $dispatcher - * @param RouterInterface $router + * @param RouterInterface $router */ - public function __construct(Server $dav, EventDispatcherInterface $dispatcher, RouterInterface $router) { + public function __construct(Server $dav, EventDispatcherInterface $dispatcher, RouterInterface $router) + { $this->dav = $dav; $this->dav->setBaseUri($router->generate('secotrust_sabre_dav', array())); @@ -58,7 +59,8 @@ public function __construct(Server $dav, EventDispatcherInterface $dispatcher, R * * @return StreamedResponse */ - public function execAction(Request $request) { + public function execAction(Request $request) + { $dav = $this->dav; $callback = function () use ($dav) { $dav->exec(); diff --git a/DependencyInjection/Compiler/CollectionPass.php b/DependencyInjection/Compiler/CollectionPass.php index 7812c6a..ec588b5 100644 --- a/DependencyInjection/Compiler/CollectionPass.php +++ b/DependencyInjection/Compiler/CollectionPass.php @@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\Reference; /** - * Class CollectionPass + * Class CollectionPass. */ class CollectionPass implements CompilerPassInterface { diff --git a/DependencyInjection/Compiler/PluginPass.php b/DependencyInjection/Compiler/PluginPass.php index 99ec3de..f3b09ea 100644 --- a/DependencyInjection/Compiler/PluginPass.php +++ b/DependencyInjection/Compiler/PluginPass.php @@ -17,7 +17,7 @@ use Symfony\Component\Filesystem\Filesystem; /** - * Class PluginPass + * Class PluginPass. */ class PluginPass implements CompilerPassInterface { diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index a454d22..c24515a 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -15,19 +15,20 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; /** - * Class Configuration + * Class Configuration. */ -class Configuration implements ConfigurationInterface { - +class Configuration implements ConfigurationInterface +{ /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getConfigTreeBuilder() { + public function getConfigTreeBuilder() + { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('secotrust_sabre_dav'); $default_base_uri = '/app_dev.php/remote'; - + $rootNode ->children() ->scalarNode('root_dir') @@ -68,15 +69,15 @@ public function getConfigTreeBuilder() { ->arrayNode('settings') ->addDefaultsIfNotSet() ->children() - ->scalarNode('cards_class')->defaultValue('')->end() - ->scalarNode('addressbooks_class')->defaultValue('')->end() - ->scalarNode('calendarobjects_class')->defaultValue('')->end() - ->scalarNode('calendar_class')->defaultValue('')->end() - ->scalarNode('principals_class')->defaultValue('')->end() - ->scalarNode('principalgroups_class')->defaultValue('')->end() + ->scalarNode('cards_class')->defaultValue('')->end() + ->scalarNode('addressbooks_class')->defaultValue('')->end() + ->scalarNode('calendarobjects_class')->defaultValue('')->end() + ->scalarNode('calendar_class')->defaultValue('')->end() + ->scalarNode('principals_class')->defaultValue('')->end() + ->scalarNode('principalgroups_class')->defaultValue('')->end() ->end() ->end() - ->end() + ->end() ->end(); return $treeBuilder; diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 3baabb3..323b80b 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -17,12 +17,12 @@ use Symfony\Component\DependencyInjection\Loader; /** - * Class SecotrustSabreDavExtension + * Class SecotrustSabreDavExtension. */ class SecotrustSabreDavExtension extends Extension { /** - * {@inheritDoc} + * {@inheritdoc} */ public function load(array $configs, ContainerBuilder $container) { @@ -31,20 +31,20 @@ public function load(array $configs, ContainerBuilder $container) $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services/services.xml'); - + if (isset($config['base_uri'])) { $container->getDefinition('secotrust.sabredav.server')->addMethodCall('setBaseUri', array($config['base_uri'])); } - + // load all plugins foreach ($config['plugins'] as $plugin => $enabled) { if ($enabled) { $loader->load(sprintf('services/plugins/%s.xml', $plugin)); } } - + // no root dir is set, but webdav plugin is active: throw exception - if (!empty($config['root_dir']) && $config['plugins']['webdav']) { + if (!empty($config['root_dir']) && $config['plugins']['webdav']) { //replace argument $container->getDefinition('secotrust.sabredav_root')->replaceArgument(0, $config['root_dir']); } @@ -53,13 +53,13 @@ public function load(array $configs, ContainerBuilder $container) if ($config['plugins']['browser']) { $container->setParameter('secotrust.sabredav.browser_plugin.logo', $config['browser_logo']); $container->setParameter('secotrust.sabredav.browser_plugin.favicon', $config['favicon']); - } - + } + // add security-service-class - if ($config['security_service']){ + if ($config['security_service']) { $container->setParameter('secotrust.sabredav.acl.securityService', $config['security_service']); } - + $container->setParameter('secotrust.cards_class', $config['settings']['cards_class']); $container->setParameter('secotrust.addressbooks_class', $config['settings']['addressbooks_class']); $container->setParameter('secotrust.calendarobjects_class', $config['settings']['calendarobjects_class']); diff --git a/Entity/AddressbookInterface.php b/Entity/AddressbookInterface.php index 793a7ac..365e248 100644 --- a/Entity/AddressbookInterface.php +++ b/Entity/AddressbookInterface.php @@ -2,113 +2,113 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity; -use Doctrine\ORM\Mapping as ORM; - /** - * Description of AddressbookInterface + * Class AddressbookInterface. * * @author lduer */ -interface AddressbookInterface { - +interface AddressbookInterface +{ /** - * Get id + * Get id. * - * @return integer + * @return int */ public function getId(); - + /** - * Get label + * Get label. * - * @return string + * @return string */ public function getLabel(); - + /** - * Set label + * Set label. * * @param string $label + * * @return $this */ public function setLabel($label); - + /** - * Get description + * Get description. * - * @return string + * @return string */ public function getDescription(); - + /** - * Set description + * Set description. * * @param string $description + * * @return $this */ public function setDescription($description); - + /** - * Get the synctoken of the current CTag - * + * Get the synctoken of the current CTag. + * * @return string */ public function getSynctoken(); - + /** - * updates the synctoken of the current Group - * + * updates the synctoken of the current Group. + * * @return $this */ public function updateSynctoken(); - + /** - * get the Uri - * + * get the Uri. + * * @return string - */ + */ public function getUri(); /** - * Get all Cars for current Addressbook - * + * Get all Cars for current Addressbook. + * * @return array */ public function getCards(); - + /** - * Search Card by URI in current Addressbook - * + * Search Card by URI in current Addressbook. + * * @param string $uri */ public function findCard($uri); /** - * Adds the given card to the current Addressbook - * + * Adds the given card to the current Addressbook. + * * @param \Secotrust\Bundle\SabreDavBundle\Entity\CardInterface $card */ public function addCard(CardInterface $card); - + /** - * Remove the given card from the current Addressbook - * + * Remove the given card from the current Addressbook. + * * Caution: Check the field-configuration & your field-connections
* and use the desired setting, if you want to delete the cards
* - either only from the current Addressbook
- * - or from the cards-table too - * + * - or from the cards-table too + * * @param \Secotrust\Bundle\SabreDavBundle\Entity\CardInterface $card - * @return boolean + * + * @return bool */ public function removeCard(CardInterface $card); - + /** * Remove all Cards from current Addressbook. - * - * Possible solution: use $cards = $this->getCards() and + * + * Possible solution: use $cards = $this->getCards() and * $this->removeCard($card) to remove all cards */ public function removeAllCards(); - } diff --git a/Entity/CardInterface.php b/Entity/CardInterface.php index fb4fc16..97147f2 100644 --- a/Entity/CardInterface.php +++ b/Entity/CardInterface.php @@ -2,89 +2,87 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity; -use Doctrine\ORM\Mapping as ORM; - /** - * Description of CardInterface - * + * Class CardInterface. + * * Interface for single Contact-Entity in CardDAV - * + * * use prePersist()-Action (Doctrine-Call) to call "updateCTag()" and "updateLastmodified()" actions automatically * * @author lduer */ -interface CardInterface { - +interface CardInterface +{ /** - * Get id + * Get id. * - * @return integer - */ + * @return int + */ public function getId(); - + /** - * Get vCardUid + * Get vCardUid. * - * @return string + * @return string */ public function getVCardUid(); - + /** - * Set vCardUid + * Set vCardUid. * * @param string $vCardUid + * * @return Contact - */ + */ public function setVCardUid($vCardUid); - + /** - * get the vCard - * + * get the vCard. + * * @return string */ public function getVCard(); - + /** - * updates the current vCard - * + * updates the current vCard. + * * @return $this - */ + */ public function setVCard($vCard); /** - * get lastmodified-date of the current card - * + * get lastmodified-date of the current card. + * * @return \DateTime() */ public function getLastmodified(); - + /** - * updates the lastmodified-date of the current Card - * + * updates the lastmodified-date of the current Card. + * * @return $this */ public function updateLastmodified(); - + /** - * Get the value of the current CTag - * + * Get the value of the current CTag. + * * @return string */ public function getCTag(); /** - * updates the cTag of the current Card - * + * updates the cTag of the current Card. + * * @return $this */ public function updateCTag(); - + /** - * get ETag of current Card - * + * get ETag of current Card. + * * possible method: return md5-checksum of vCard-String * return md5($this->getVCard()); */ public function getETag(); - } diff --git a/Entity/PrincipalInterface.php b/Entity/PrincipalInterface.php index 740d664..4c6d723 100644 --- a/Entity/PrincipalInterface.php +++ b/Entity/PrincipalInterface.php @@ -2,60 +2,58 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity; -use Doctrine\ORM\Mapping as ORM; - /** - * Description of PrincipalInterface + * Class PrincipalInterface. * * @author lduer */ -interface PrincipalInterface { - +interface PrincipalInterface +{ /** - * Get id + * Get id. * - * @return integer + * @return int */ public function getId(); /** - * get username - * + * get username. + * * @return string */ public function getUsername(); /** - * set username - * + * set username. + * * @param string $username */ public function setUsername($username); /** - * get Email - * + * get Email. + * * @return string */ public function getEmail(); /** - * set email - * + * set email. + * * @param string $email */ public function setEmail($email); /** - * requried to define me-card as a property on the users' addressbook' - * + * requried to define me-card as a property on the users' addressbook'. + * * @return string */ public function getVCardUrl(); /** - * set vCardUrl - * + * set vCardUrl. + * * @param string $vCardUrl */ public function setVCardUrl($vCardUrl); diff --git a/Entity/Repository/AddressbookRepositoryInterface.php b/Entity/Repository/AddressbookRepositoryInterface.php index 5f9bf40..fd705be 100644 --- a/Entity/Repository/AddressbookRepositoryInterface.php +++ b/Entity/Repository/AddressbookRepositoryInterface.php @@ -3,18 +3,18 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity\Repository; /** - * Description of AddressbookRepositoryInterface + * Class AddressbookRepositoryInterface. * * @author lduer */ -interface AddressbookRepositoryInterface { - +interface AddressbookRepositoryInterface +{ /** - * get all Addressbooks for submitted principal - * + * get all Addressbooks for submitted principal. + * * @param string $principalUri + * * @return array */ public function findAllPrincipalAddressbooks($principalUri); - } diff --git a/Entity/Repository/CardRepositoryInterface.php b/Entity/Repository/CardRepositoryInterface.php index d81e573..7b7e1d4 100644 --- a/Entity/Repository/CardRepositoryInterface.php +++ b/Entity/Repository/CardRepositoryInterface.php @@ -3,17 +3,17 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity\Repository; /** - * Description of CardRepositoryInterface + * Class CardRepositoryInterface. * * @author lduer */ -interface CardRepositoryInterface { - +interface CardRepositoryInterface +{ /** - * Find one Card By vCard-UID and Addressbook-id - * + * Find one Card By vCard-UID and Addressbook-id. + * * @param string $uid * @param string $addressBookId */ - public function findSingleCardByUid($uid=null, $addressBookId=null); + public function findSingleCardByUid($uid = null, $addressBookId = null); } diff --git a/Entity/Repository/PrincipalRepositoryInterface.php b/Entity/Repository/PrincipalRepositoryInterface.php index 3a0724d..224aa55 100644 --- a/Entity/Repository/PrincipalRepositoryInterface.php +++ b/Entity/Repository/PrincipalRepositoryInterface.php @@ -3,38 +3,38 @@ namespace Secotrust\Bundle\SabreDavBundle\Entity\Repository; /** - * Description of AddressbookRepositoryInterface + * Class AddressbookRepositoryInterface. * * @author lduer */ -interface PrincipalRepositoryInterface { - +interface PrincipalRepositoryInterface +{ /** * Returns a list of principals based on a prefix. * * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * - * + * * @param string $prefixPath */ public function getPrincipalsByPrefix($prefixPath); - + /** * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. - * + * * By default, if multiple properties are submitted to this method, the * various properties should be combined with 'AND'. If $test is set to * 'anyof', it should be combined using 'OR'. - * + * * @param string $prefixPath - * @param array $searchArray + * @param array $searchArray * @param string $test */ - public function searchPrincipals($prefixPath, array $searchArray, $test='allof'); - + public function searchPrincipals($prefixPath, array $searchArray, $test = 'allof'); + /** * Finds a principal by its URI. * @@ -49,8 +49,8 @@ public function searchPrincipals($prefixPath, array $searchArray, $test='allof') * principal was not found or you refuse to find it. * * @param string $uri + * * @return string */ public function findByUri($uri); - } diff --git a/Resources/config/services/plugins/acl.xml b/Resources/config/services/plugins/acl.xml index a1d7d49..c557111 100644 --- a/Resources/config/services/plugins/acl.xml +++ b/Resources/config/services/plugins/acl.xml @@ -7,7 +7,6 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\AclPlugin Secotrust\Bundle\SabreDavBundle\SabreDav\Acl\SecurityManager - true true principals @@ -18,7 +17,6 @@ - %sabredav.acl.hideNodesFromListings% @@ -27,10 +25,10 @@ %sabredav.acl.defaultUsernamePath% - + - +
diff --git a/Resources/config/services/plugins/carddav.xml b/Resources/config/services/plugins/carddav.xml index c8c33ef..34846da 100644 --- a/Resources/config/services/plugins/carddav.xml +++ b/Resources/config/services/plugins/carddav.xml @@ -14,16 +14,16 @@ - + - + - + diff --git a/Resources/config/services/services.xml b/Resources/config/services/services.xml index 5cf5923..ccfd5b9 100644 --- a/Resources/config/services/services.xml +++ b/Resources/config/services/services.xml @@ -22,6 +22,6 @@ - + diff --git a/SabreDav/Acl/SecurityManager.php b/SabreDav/Acl/SecurityManager.php index 2d28c4b..3f4fd40 100644 --- a/SabreDav/Acl/SecurityManager.php +++ b/SabreDav/Acl/SecurityManager.php @@ -5,12 +5,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Description of SecurityManager + * Description of SecurityManager. * * @author lduer */ -class SecurityManager { - +class SecurityManager +{ /** * @var ContainerInterface */ @@ -22,19 +22,19 @@ class SecurityManager { protected $token; /** - * authorization checker + * authorization checker. * * @var Symfony\Component\Security\Core\Authorization\AuthorizationChecker */ protected $authorizationChecker; /** - * Constructor + * Constructor. * * @param ContainerInterface $container */ - public function __construct(ContainerInterface $container) { - + public function __construct(ContainerInterface $container) + { $this->container = $container; $this->token = $container->get('security.token_storage')->getToken(); $this->authorizationChecker = $container->get('security.authorization_checker'); @@ -42,7 +42,7 @@ public function __construct(ContainerInterface $container) { /** * returns the ACL list in the following format:
- * return array('read', 'write', 'delete'); + * return array('read', 'write', 'delete');. * * consider: * null will be returned to tell the AclPlugin to use the default Node-Acl (e.g. if no ACL was found for this entry) @@ -53,9 +53,9 @@ public function __construct(ContainerInterface $container) { * @param $objectClass * @param $objectIdentifier * @param null $groupIdentifier - * @return null */ - public function getACL($username, $objectClass, $objectIdentifier, $groupIdentifier = null) { - return null; + public function getACL($username, $objectClass, $objectIdentifier, $groupIdentifier = null) + { + return; } } diff --git a/SabreDav/AclPlugin.php b/SabreDav/AclPlugin.php index 51e9b26..a523077 100644 --- a/SabreDav/AclPlugin.php +++ b/SabreDav/AclPlugin.php @@ -8,14 +8,14 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Class extends the SabreDAV ACL Plugin to provide some additional methods and setter for public variables + * Class extends the SabreDAV ACL Plugin to provide some additional methods and setter for public variables. * * @author lduer */ -class AclPlugin extends Plugin { - +class AclPlugin extends Plugin +{ /** - * the "parent" node (Addressbook for Cards, Calendar for CalendarObject) + * the "parent" node (Addressbook for Cards, Calendar for CalendarObject). * * This is set in the addressbook- or calendar-acl-request and * used in all sub-requests (cards & events) @@ -40,23 +40,25 @@ class AclPlugin extends Plugin { private $davSecurity; /** - * Constructor + * Constructor. * * @param AuthorizationCheckerInterface $authChecker - * @param ContainerInterface $container + * @param ContainerInterface $container */ - public function __construct(AuthorizationCheckerInterface $authChecker, ContainerInterface $container) { + public function __construct(AuthorizationCheckerInterface $authChecker, ContainerInterface $container) + { $this->authChecker = $authChecker; $this->container = $container; } /** * By default nodes that are inaccessible by the user, can still be seen - * in directory listings (PROPFIND on parent with Depth: 1) + * in directory listings (PROPFIND on parent with Depth: 1). * - * @param boolean $flag + * @param bool $flag */ - public function setHideNodesFromListings($flag = false) { + public function setHideNodesFromListings($flag = false) + { $this->hideNodesFromListings = (bool) $flag; } @@ -68,9 +70,10 @@ public function setHideNodesFromListings($flag = false) { * To override this behaviour you can turn this setting off. This is useful * if you plan to fully support ACL in the entire tree. * - * @param boolean $flag + * @param bool $flag */ - public function setAccessToNodesWithoutACL($flag = true) { + public function setAccessToNodesWithoutACL($flag = true) + { $this->allowAccessToNodesWithoutACL = (bool) $flag; } @@ -81,19 +84,22 @@ public function setAccessToNodesWithoutACL($flag = true) { * * @param string $usernamePath */ - public function setDefaultUsernamePath($usernamePath = 'principals') { + public function setDefaultUsernamePath($usernamePath = 'principals') + { $this->defaultUsernamePath = $usernamePath; } /** - * add a principal to the admin-list to automatically receive {DAV:}all privileges + * add a principal to the admin-list to automatically receive {DAV:}all privileges. * * @param string $principal - * @return boolean + * + * @return bool */ - public function addAdminPrincipal($principal) { - if (strpos($principal, $this->defaultUsernamePath . '/') !== 0) { - $principal = $this->defaultUsernamePath . '/' . $principal; + public function addAdminPrincipal($principal) + { + if (strpos($principal, $this->defaultUsernamePath.'/') !== 0) { + $principal = $this->defaultUsernamePath.'/'.$principal; } if (!in_array($principal, $this->adminPrincipals)) { @@ -106,14 +112,16 @@ public function addAdminPrincipal($principal) { } /** - * remove principal from admin-list + * remove principal from admin-list. * * @param string $principal - * @return boolean + * + * @return bool */ - public function removeAdminPrincipal($principal) { - if (strpos($principal, $this->defaultUsernamePath . '/') !== 0) { - $principal = $this->defaultUsernamePath . '/' . $principal; + public function removeAdminPrincipal($principal) + { + if (strpos($principal, $this->defaultUsernamePath.'/') !== 0) { + $principal = $this->defaultUsernamePath.'/'.$principal; } if (false !== ($key = \array_search($principal, $this->adminPrincipals))) { @@ -133,16 +141,17 @@ public function removeAdminPrincipal($principal) { * null will be returned if the node doesn't support ACLs. * * @param string|\Sabre\DAV\INode $node + * * @return array */ - public function getACL($node) { - + public function getACL($node) + { if (is_string($node)) { $node = $this->server->tree->getNodeForPath($node); } if (!$node instanceof IACL) { - return null; + return; } $username = $this->server->httpRequest->getCurrentUsername(); @@ -156,7 +165,6 @@ public function getACL($node) { $node instanceof \Sabre\CardDAV\AddressBook || $node instanceof \Sabre\CardDAV\Card )) { - $objectClass = ''; $objectIdentifier = array('name' => $node->getName()); @@ -185,8 +193,8 @@ public function getACL($node) { // write permissions to DAV-ACL foreach ($permissionList as $permission) { $acl[] = array( - 'privilege' => '{DAV:}' . $permission, - 'principal' => $this->defaultUsernamePath . '/' . $username, + 'privilege' => '{DAV:}'.$permission, + 'principal' => $this->defaultUsernamePath.'/'.$username, 'protected' => true, ); } diff --git a/SabreDav/Auth/BasicAuth.php b/SabreDav/Auth/BasicAuth.php index 6dc7984..a72dd81 100644 --- a/SabreDav/Auth/BasicAuth.php +++ b/SabreDav/Auth/BasicAuth.php @@ -9,55 +9,57 @@ use Sabre\HTTP\Auth\Basic; /** - * BasicAuth + * BasicAuth. * * This file extends the basic-auth from sabre-dav * * @author lduer */ -class BasicAuth extends Basic { - +class BasicAuth extends Basic +{ /** - * @var UserManagerInterface + * @var UserManagerInterface */ private $user_manager; /** - * Constructor + * Constructor. * - * @param string $realm - * @param \Sabre\HTTP\RequestInterface $request + * @param string $realm + * @param \Sabre\HTTP\RequestInterface $request * @param \Sabre\HTTP\ResponseInterface $response - * @param UserManagerInterface $user_manager + * @param UserManagerInterface $user_manager */ - public function __construct($realm, RequestInterface $request, ResponseInterface $response, UserManagerInterface $user_manager) { - + public function __construct($realm, RequestInterface $request, ResponseInterface $response, UserManagerInterface $user_manager) + { $this->user_manager = $user_manager; parent::__construct($realm, $request, $response); } /** - * find username in the user-manager + * find username in the user-manager. * * @param string $username + * * @return \FOS\UserBundle\Model\UserInterface */ - private function getUser($username) { + private function getUser($username) + { $user = $this->user_manager->findUserByUsername($username); return $user; } /** - * Return user-credentials; returned password is encoded via "security.encoder_factory" + * Return user-credentials; returned password is encoded via "security.encoder_factory". * * @param \Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface $encoder_service - * @return array|boolean + * + * @return array|bool */ - public function getCredentials(EncoderFactoryInterface $encoder_service = null) { - + public function getCredentials(EncoderFactoryInterface $encoder_service = null) + { if (($user = $this->request->getRawServerValue('PHP_AUTH_USER')) && ($pass = $this->request->getRawServerValue('PHP_AUTH_PW'))) { - $credentials = array($user, $pass); } else { @@ -70,11 +72,13 @@ public function getCredentials(EncoderFactoryInterface $encoder_service = null) $auth = $this->request->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); } - if (!$auth) + if (!$auth) { return false; + } - if (strpos(strtolower($auth), 'basic') !== 0) + if (strpos(strtolower($auth), 'basic') !== 0) { return false; + } $credentials = explode(':', base64_decode(substr($auth, 6)), 2); } diff --git a/SabreDav/AuthBackend.php b/SabreDav/AuthBackend.php index b138c24..696f1e7 100644 --- a/SabreDav/AuthBackend.php +++ b/SabreDav/AuthBackend.php @@ -15,13 +15,12 @@ use Sabre\DAV\Exception; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\Event; -class AuthBackend implements BackendInterface { - +class AuthBackend implements BackendInterface +{ /** * @var ContainerInterface */ @@ -48,7 +47,7 @@ class AuthBackend implements BackendInterface { private $dispatcher; /** - * @var string + * @var string */ protected $currentUser; @@ -70,13 +69,13 @@ class AuthBackend implements BackendInterface { protected $principalPrefix = 'principals/'; /** - * Constructor + * Constructor. * * @param ContainerInterface $container * @param $realm */ - public function __construct(ContainerInterface $container, $realm) { - + public function __construct(ContainerInterface $container, $realm) + { $this->container = $container; $this->realm = $realm; @@ -88,14 +87,15 @@ public function __construct(ContainerInterface $container, $realm) { /** * Checks if username and password are valid. (Checked by the FOSUserManager) - * Returns + * Returns. * * @param $username * @param $passwordHash + * * @return bool */ - public function validateUserPass($username, $passwordHash) { - + public function validateUserPass($username, $passwordHash) + { $user = $this->user_manager->findUserByUsername($username); if (is_null($user)) { @@ -103,7 +103,7 @@ public function validateUserPass($username, $passwordHash) { } if ($passwordHash === $user->getPassword()) { -// $this->userLoginAction($user, $passwordHash); + // $this->userLoginAction($user, $passwordHash); return true; } @@ -111,7 +111,7 @@ public function validateUserPass($username, $passwordHash) { } /** - * add the symfony-login "manually" + * add the symfony-login "manually". * * use the symfony token-storage for the generated UsernamePasswordToken * to access the (logged in) user later (e.g. to check for roles or permissions) @@ -124,7 +124,8 @@ public function validateUserPass($username, $passwordHash) { * @param \FOS\UserBundle\Model\UserInterface $user * @param $passwordHash */ - private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $passwordHash) { + private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $passwordHash) + { // call the pre-login-event $event = new Event(); $this->dispatcher->dispatch('secotrust.user_login.before', $event); @@ -143,9 +144,10 @@ private function userLoginAction(\FOS\UserBundle\Model\UserInterface $user, $pas } /** - * @inheritdoc + * {@inheritdoc} */ - public function getCurrentUser() { + public function getCurrentUser() + { return $this->currentUser; } @@ -173,30 +175,32 @@ public function getCurrentUser() { * * principals/users/[username] * - * @param RequestInterface $request + * @param RequestInterface $request * @param ResponseInterface $response + * * @return array + * * @throws Exception */ - public function check(RequestInterface $request, ResponseInterface $response) { - + public function check(RequestInterface $request, ResponseInterface $response) + { $auth = new Auth\BasicAuth($this->realm, $request, $response, $this->user_manager); $userpass = $auth->getCredentials($this->encoder_service); // No username was given - if ($userpass === false) { + if ($userpass === false) { return [false, "No 'Authorization' header found. Either the client didn't send one, or the server is mis-configured"]; } // Authenticates the user if (!$this->validateUserPass($userpass[0], $userpass[1])) { - return [false, "Username or password was incorrect"]; + return [false, 'Username or password was incorrect']; } $this->currentUser = $userpass[0]; $request->setCurrentUsername($this->currentUser); - return [true, $this->principalPrefix . $userpass[0]]; + return [true, $this->principalPrefix.$userpass[0]]; } /** @@ -216,12 +220,11 @@ public function check(RequestInterface $request, ResponseInterface $response) { * append your own WWW-Authenticate header instead of overwriting the * existing one. * - * @param RequestInterface $request + * @param RequestInterface $request * @param ResponseInterface $response - * @return void */ - public function challenge(RequestInterface $request, ResponseInterface $response) { - + public function challenge(RequestInterface $request, ResponseInterface $response) + { $auth = new Auth\BasicAuth($this->realm, $request, $response, $this->user_manager); $userpass = $auth->getCredentials($this->encoder_service); diff --git a/SabreDav/BrowserPlugin.php b/SabreDav/BrowserPlugin.php index 3864e6e..18e4227 100644 --- a/SabreDav/BrowserPlugin.php +++ b/SabreDav/BrowserPlugin.php @@ -5,40 +5,43 @@ use Sabre\DAV\Browser\Plugin; /** - * Browser Plugin + * Browser Plugin. * * This file extends the sabredav-browserplugin. * It's possible to manipulate the (original) SabreDAV html-output with this class. * * @author lduer */ -class BrowserPlugin extends Plugin { - +class BrowserPlugin extends Plugin +{ /** - * @var array + * @var array */ private $config = array(); /** - * set configuration + * set configuration. * * @param array $config */ - public function setBrowserConfig(array $config) { + public function setBrowserConfig(array $config) + { foreach ($config as $key => $value) { $this->config[$key] = $value; } } /** - * * @param string $key + * * @return string */ - public function getBrowserConfig($key) { + public function getBrowserConfig($key) + { if (isset($this->config[$key])) { return $this->config[$key]; } + return; } @@ -48,16 +51,18 @@ public function getBrowserConfig($key) { * The logo and favicon can be overwritten * * @param string $assetName + * * @return string */ - protected function getLocalAssetPath($assetName) { + protected function getLocalAssetPath($assetName) + { // load path to logo from parameters if ($assetName === 'sabredav.png' && $this->getBrowserConfig('browser_logo')) { return $this->getBrowserConfig('browser_logo'); } elseif ($assetName === 'favicon.ico' && $this->getBrowserConfig('favicon')) { return $this->getBrowserConfig('favicon'); } + return parent::getLocalAssetPath($assetName); } - } diff --git a/SabreDav/CardDavBackend.php b/SabreDav/CardDavBackend.php index 11b4038..ee23a10 100644 --- a/SabreDav/CardDavBackend.php +++ b/SabreDav/CardDavBackend.php @@ -18,8 +18,8 @@ use Secotrust\Bundle\SabreDavBundle\Entity\CardInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -class CardDavBackend extends AbstractBackend implements SyncSupport { - +class CardDavBackend extends AbstractBackend implements SyncSupport +{ /** * @var ContainerInterface */ @@ -31,24 +31,25 @@ class CardDavBackend extends AbstractBackend implements SyncSupport { private $_em; /** - * @var string + * @var string */ private $addressbooks_class; /** - * @var string + * @var string */ private $cards_class; /** - * Create array with Card-Data + * Create array with Card-Data. * * @param CardInterface $entity - * @param bool $show_id + * @param bool $show_id + * * @return array|bool */ - private function getCardArray($entity, $show_id = false) { - + private function getCardArray($entity, $show_id = false) + { if (!($entity instanceof CardInterface)) { return false; } @@ -56,7 +57,7 @@ private function getCardArray($entity, $show_id = false) { $card = array( 'id' => $entity->getId(), 'carddata' => $entity->getVCard(), - 'uri' => $entity->getVCardUid() . '.vcf', + 'uri' => $entity->getVCardUid().'.vcf', 'lastmodified' => $entity->getLastmodified(), 'size' => strlen($entity->getVCard()), 'etag' => $entity->getETag(), @@ -70,12 +71,12 @@ private function getCardArray($entity, $show_id = false) { } /** - * Constructor + * Constructor. * * @param ContainerInterface $container */ - public function __construct(ContainerInterface $container) { - + public function __construct(ContainerInterface $container) + { $this->_em = $container->get('doctrine')->getManager(); $this->addressbooks_class = $container->getParameter('secotrust.addressbooks_class'); @@ -97,24 +98,24 @@ public function __construct(ContainerInterface $container) { * {http://calendarserver.org/ns/}getctag * * @param string $principalUri + * * @return array */ - public function getAddressBooksForUser($principalUri) { - + public function getAddressBooksForUser($principalUri) + { $addressBooks = array(); $entities = $this->_em->getRepository($this->addressbooks_class)->findAllPrincipalAddressbooks($principalUri); foreach ($entities as $entity) { - $addressBooks[] = array( 'id' => $entity->getId(), 'uri' => $entity->getUriLabel(), 'principaluri' => $principalUri, '{DAV:}displayname' => $entity->getLabel(), - '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $entity->getDescription(), + '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' => $entity->getDescription(), '{http://calendarserver.org/ns/}getctag' => $entity->getSyncToken(), - '{http://sabredav.org/ns}sync-token' => $entity->getSyncToken() + '{http://sabredav.org/ns}sync-token' => $entity->getSyncToken(), ); } @@ -133,15 +134,14 @@ public function getAddressBooksForUser($principalUri) { * * Read the PropPatch documenation for more info and examples. * - * @param string $addressBookId + * @param string $addressBookId * @param \Sabre\DAV\PropPatch $propPatch - * @return void */ - public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { - + public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) + { $supportedProperties = [ '{DAV:}displayname', - '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description', + '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description', ]; $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); @@ -150,16 +150,15 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc return; } - $propPatch->handle($supportedProperties, function($mutations) use ($addressbook) { + $propPatch->handle($supportedProperties, function ($mutations) use ($addressbook) { $updates = []; foreach ($mutations as $property => $newValue) { - switch ($property) { case '{DAV:}displayname' : $updates['setLabel'] = $newValue; break; - case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' : $updates['setDescription'] = $newValue; break; } @@ -179,16 +178,16 @@ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatc } /** - * Creates a new address book + * Creates a new address book. * * @param string $principalUri - * @param string $url Just the 'basename' of the url. - * @param array $properties - * @return null + * @param string $url Just the 'basename' of the url. + * @param array $properties + * * @throws BadRequest */ - public function createAddressBook($principalUri, $url, array $properties) { - + public function createAddressBook($principalUri, $url, array $properties) + { $values = array( 'setLabel' => null, 'setDescription' => null, @@ -197,22 +196,21 @@ public function createAddressBook($principalUri, $url, array $properties) { ); foreach ($properties as $property => $newValue) { - switch ($property) { case '{DAV:}displayname' : $values['setLabel'] = $newValue; break; - case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' : $values['setDescription'] = $newValue; break; default : - throw new BadRequest('Unknown property: ' . $property); + throw new BadRequest('Unknown property: '.$property); } } // check if current addressbooks-class can be instantiated if ((new \ReflectionClass($this->addressbooks_class))->isAbstract()) { - return null; + return; } $addressbook = new $this->addressbooks_class(); @@ -230,13 +228,12 @@ public function createAddressBook($principalUri, $url, array $properties) { } /** - * Deletes an entire addressbook and all its contents + * Deletes an entire addressbook and all its contents. * * @param mixed $addressBookId - * @return void */ - public function deleteAddressBook($addressBookId) { - + public function deleteAddressBook($addressBookId) + { $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); if (!$addressbook) { @@ -264,10 +261,11 @@ public function deleteAddressBook($addressBookId) { * This may speed up certain requests, especially with large cards. * * @param mixed $addressbookId + * * @return array */ - public function getCards($addressbookId) { - + public function getCards($addressbookId) + { $contactGroup = $this->_em->getRepository($this->addressbooks_class)->findOneById($addressbookId); $entities = $contactGroup->getContactCollection(); @@ -275,6 +273,7 @@ public function getCards($addressbookId) { foreach ($entities as $entity) { $cards[] = $this->getCardArray($entity, true); } + return $cards; } @@ -284,14 +283,15 @@ public function getCards($addressbookId) { * The same set of properties must be returned as with getCards. The only * exception is that 'carddata' is absolutely required. * - * @param mixed $addressBookId + * @param mixed $addressBookId * @param string $cardUri + * * @return array */ - public function getCard($addressBookId, $cardUri) { - + public function getCard($addressBookId, $cardUri) + { $entity = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($cardUri, $addressBookId); - + return $this->getCardArray($entity, true); } @@ -315,17 +315,18 @@ public function getCard($addressBookId, $cardUri) { * * If you don't return an ETag, you can just return null. * - * @param mixed $addressBookId + * @param mixed $addressBookId * @param string $cardUri * @param string $cardData + * * @return string|null */ - public function createCard($addressBookId, $cardUri, $cardData) { - + public function createCard($addressBookId, $cardUri, $cardData) + { $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); if ((new \ReflectionClass($this->cards_class))->isAbstract()) { - return null; + return; } $card = new $this->cards_class(); @@ -359,17 +360,18 @@ public function createCard($addressBookId, $cardUri, $cardData) { * * If you don't return an ETag, you can just return null. * - * @param mixed $addressBookId + * @param mixed $addressBookId * @param string $cardUri * @param string $cardData + * * @return string|null */ - public function updateCard($addressBookId, $cardUri, $cardData) { - + public function updateCard($addressBookId, $cardUri, $cardData) + { $card = $this->_em->getRepository($this->cards_class)->findSingleCardByUid($cardUri, $addressBookId); if (!$card) { - return null; + return; } $card->setVCard($cardData); @@ -380,14 +382,15 @@ public function updateCard($addressBookId, $cardUri, $cardData) { } /** - * Deletes a card + * Deletes a card. * - * @param mixed $addressBookId + * @param mixed $addressBookId * @param string $cardUri + * * @return bool */ - public function deleteCard($addressBookId, $cardUri) { - + public function deleteCard($addressBookId, $cardUri) + { $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); $card = $addressbook->findCard($cardUri); @@ -451,25 +454,27 @@ public function deleteCard($addressBookId, $cardUri) { * * @param string $addressBookId * @param string $syncToken - * @param int $syncLevel - * @param int $limit + * @param int $syncLevel + * @param int $limit + * * @return array */ - function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) + { // the "Doctrine2 behavioral extensions" (https://github.com/Atlantic18/DoctrineExtensions) // are used to log the addressbook-changes $loggableClass = 'Gedmo\Loggable\Entity\LogEntry'; if (!class_exists($loggableClass)) { - return null; + return; } /* @var $addressbook \Secotrust\Bundle\SabreDavBundle\Entity\AddressbookInterface */ $addressbook = $this->_em->getRepository($this->addressbooks_class)->find($addressBookId); if ($addressbook->getSynctoken() === 0) { - return null; + return; } $result = [ @@ -494,7 +499,6 @@ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit } foreach ($changes as $uri => $operation) { - switch ($operation) { case 'create': $result['added'][] = $uri; @@ -511,18 +515,19 @@ function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit // No synctoken supplied, this is the initial sync. $result['added'] = $addressbook->getUri(); } + return $result; } /** * Adds a change record to the addressbookchanges table. * - * @param mixed $addressBookId + * @param mixed $addressBookId * @param string $objectUri - * @param int $operation 1 = add, 2 = modify, 3 = delete - * @return void + * @param int $operation 1 = add, 2 = modify, 3 = delete */ - protected function addChange($addressBookId, $objectUri, $operation) { + protected function addChange($addressBookId, $objectUri, $operation) + { // it is suggested to use the Loggable-Extension for Doctrine to manage // the changes in the entities @@ -533,5 +538,4 @@ protected function addChange($addressBookId, $objectUri, $operation) { return; } - } diff --git a/SabreDav/Gaufrette/Collection.php b/SabreDav/Gaufrette/Collection.php index 7bfbfdd..e71f738 100644 --- a/SabreDav/Gaufrette/Collection.php +++ b/SabreDav/Gaufrette/Collection.php @@ -29,7 +29,7 @@ class Collection extends BaseCollection /** * @param Filesystem $filesystem - * @param string $prefix + * @param string $prefix */ public function __construct(Filesystem $filesystem, $prefix = '') { @@ -38,7 +38,7 @@ public function __construct(Filesystem $filesystem, $prefix = '') } /** - * @inheritdoc + * {@inheritdoc} */ public function getChildren() { @@ -58,25 +58,25 @@ public function getChildren() } /** - * @inheritdoc + * {@inheritdoc} */ public function getChild($name) { $key = $this->prefix.$name; if (!$this->filesystem->has($key)) { - throw new Exception\NotFound('The file with name: ' . $name . ' could not be found'); + throw new Exception\NotFound('The file with name: '.$name.' could not be found'); } if ($this->filesystem->getAdapter()->isDirectory($key)) { - return new Collection($this->filesystem, $key.'/'); + return new self($this->filesystem, $key.'/'); } return new File($this->filesystem->get($key)); } /** - * @inheritdoc + * {@inheritdoc} */ public function childExists($name) { @@ -84,7 +84,7 @@ public function childExists($name) } /** - * @inheritdoc + * {@inheritdoc} */ public function getName() { @@ -92,7 +92,7 @@ public function getName() } /** - * @inheritdoc + * {@inheritdoc} */ public function getLastModified() { diff --git a/SabreDav/Gaufrette/File.php b/SabreDav/Gaufrette/File.php index b2492a6..de28812 100644 --- a/SabreDav/Gaufrette/File.php +++ b/SabreDav/Gaufrette/File.php @@ -11,9 +11,7 @@ namespace Secotrust\Bundle\SabreDavBundle\SabreDav\Gaufrette; -use Sabre\DAV\Exception; use Sabre\DAV\File as BaseFile; -use Sabre\DAV\Sabre; class File extends BaseFile { @@ -23,7 +21,7 @@ class File extends BaseFile protected $file; /** - * Constructor + * Constructor. * * @param \Gaufrette\File $file */ @@ -33,7 +31,7 @@ public function __construct(\Gaufrette\File $file) } /** - * @inheritdoc + * {@inheritdoc} */ public function getName() { @@ -41,7 +39,7 @@ public function getName() } /** - * @inheritdoc + * {@inheritdoc} */ public function getSize() { @@ -49,7 +47,7 @@ public function getSize() } /** - * @inheritdoc + * {@inheritdoc} */ public function getLastModified() { @@ -57,7 +55,7 @@ public function getLastModified() } /** - * @inheritdoc + * {@inheritdoc} */ public function put($data) { @@ -65,7 +63,7 @@ public function put($data) } /** - * @inheritdoc + * {@inheritdoc} */ public function get() { @@ -73,7 +71,7 @@ public function get() } /** - * @inheritdoc + * {@inheritdoc} */ public function delete() { diff --git a/SabreDav/HttpRequest.php b/SabreDav/HttpRequest.php index 2d6d94f..3d0f072 100644 --- a/SabreDav/HttpRequest.php +++ b/SabreDav/HttpRequest.php @@ -15,10 +15,10 @@ use Symfony\Component\HttpFoundation\Request; /** - * Class HttpRequest + * Class HttpRequest. */ -class HttpRequest extends BaseRequest { - +class HttpRequest extends BaseRequest +{ /** * @var Request */ @@ -30,31 +30,33 @@ class HttpRequest extends BaseRequest { private $currentUsername; /** - * Constructor + * Constructor. * * @param Request $request */ - public function __construct(Request $request) { + public function __construct(Request $request) + { parent::__construct($request->getMethod(), $request->getRequestUri(), $request->headers->all(), $request->getContent(true)); $this->request = $request; } /** - * set the current username + * set the current username. * * @param string $username */ - public function setCurrentUsername($username) { + public function setCurrentUsername($username) + { $this->currentUsername = $username; } /** - * get the current username + * get the current username. * * @return string */ - public function getCurrentUsername() { + public function getCurrentUsername() + { return $this->currentUsername; } - } diff --git a/SabreDav/HttpResponse.php b/SabreDav/HttpResponse.php index f289c4b..37bc79c 100644 --- a/SabreDav/HttpResponse.php +++ b/SabreDav/HttpResponse.php @@ -15,21 +15,22 @@ use Symfony\Component\HttpFoundation\StreamedResponse; /** - * Class HttpResponse + * Class HttpResponse. */ -class HttpResponse extends BaseResponse { - +class HttpResponse extends BaseResponse +{ /** * @var StreamedResponse */ private $response; /** - * Constructor + * Constructor. * * @param StreamedResponse $response */ - public function __construct(StreamedResponse $response) { + public function __construct(StreamedResponse $response) + { parent::__construct($response->getStatusCode(), $response->headers->all()); $this->response = $response; } diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index fd1369b..eee77ba 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -17,11 +17,10 @@ use Sabre\DAVACL\PrincipalBackend\CreatePrincipalSupport; use FOS\UserBundle\Model\UserInterface; use FOS\UserBundle\Model\GroupInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport { - +class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport +{ /** * @var \Doctrine\ORM\EntityManager */ @@ -38,7 +37,7 @@ class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport private $group_manager; /** - * @var string + * @var string */ private $principals_class; @@ -48,19 +47,19 @@ class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport private $principalgroups_class; /** - * A list of additional fields to support + * A list of additional fields to support. * * @var array */ protected $fieldMap = array( - /** + /* * This property can be used to display the users' real name. */ '{DAV:}displayname' => array( 'getter' => 'getUsername', - 'setter' => 'setUsername' + 'setter' => 'setUsername', ), - /** + /* * This property is actually used by the CardDAV plugin, where it gets * mapped to {http://calendarserver.orgi/ns/}me-card. * @@ -72,7 +71,7 @@ class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport 'getter' => 'getVCardUrl', 'setter' => 'setVCardUrl', ), - /** + /* * This is the users' primary email-address. */ '{http://sabredav.org/ns}email-address' => array( @@ -82,12 +81,12 @@ class PrincipalBackend extends AbstractBackend implements CreatePrincipalSupport ); /** - * Constructor + * Constructor. * * @param ContainerInterface $container */ - public function __construct(ContainerInterface $container) { - + public function __construct(ContainerInterface $container) + { $this->_em = $container->get('doctrine')->getManager(); $this->principals_class = $container->getParameter('secotrust.principals_class'); $this->principalgroups_class = $container->getParameter('secotrust.principalgroups_class'); @@ -99,15 +98,17 @@ public function __construct(ContainerInterface $container) { } /** - * get Array with Principal-Data from User-Object + * get Array with Principal-Data from User-Object. * * @param UserInterface|GroupInterface $principalObject - * @param bool $show_id + * @param bool $show_id + * * @return array + * * @throws Exception */ - private function getPrincipalArray($principalObject, $show_id = false) { - + private function getPrincipalArray($principalObject, $show_id = false) + { if (!($principalObject instanceof UserInterface) && !($principalObject instanceof GroupInterface)) { throw new Exception('$principalObject must be of type UserInterface of GroupInterface'); } @@ -118,9 +119,9 @@ private function getPrincipalArray($principalObject, $show_id = false) { } if ($principalObject instanceof UserInterface) { - $principal['uri'] = 'principals/' . $principalObject->getUsername(); + $principal['uri'] = 'principals/'.$principalObject->getUsername(); } else { - $principal['uri'] = 'principals/' . $principalObject->getName(); + $principal['uri'] = 'principals/'.$principalObject->getName(); } // get all fields from $this->fieldMap, additional to 'uri' and 'id' @@ -153,13 +154,14 @@ private function getPrincipalArray($principalObject, $show_id = false) { * you have an email address, use this property. * * @param string $prefixPath + * * @return array */ - public function getPrincipalsByPrefix($prefixPath) { - + public function getPrincipalsByPrefix($prefixPath) + { $userlist = $this->_em->getRepository($this->principals_class)->findBy(array('enabled' => true)); $principals = array(); - + foreach ($userlist as $user) { // due to the lack of the implementation of prefixes, return all users @@ -175,12 +177,14 @@ public function getPrincipalsByPrefix($prefixPath) { * getPrincipalsByPrefix. * * @param string $path - * @param bool $getObject + * @param bool $getObject + * * @return array|GroupInterface|UserInterface|void + * * @throws Exception */ - public function getPrincipalByPath($path, $getObject = false) { - + public function getPrincipalByPath($path, $getObject = false) + { $name = str_replace('principals/', '', $path); // get username from path-string, if string contains additional slashes (e.g. admin/calendar-proxy-read) @@ -191,7 +195,6 @@ public function getPrincipalByPath($path, $getObject = false) { $user = $this->user_manager->findUserByUsername($name); if ($user === null) { - if (!$this->group_manager) { return; } @@ -206,6 +209,7 @@ public function getPrincipalByPath($path, $getObject = false) { if ($getObject === true) { return $group; } + return $this->getPrincipalArray($group, true); } @@ -228,26 +232,26 @@ public function getPrincipalByPath($path, $getObject = false) { * * Read the PropPatch documenation for more info and examples. * - * @param string $path + * @param string $path * @param \Sabre\DAV\PropPatch $propPatch */ - public function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { - + public function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) + { $principal = $this->getPrincipalByPath($path, true); if (empty($principal)) { return; } - $propPatch->handle(array_keys($this->fieldMap), function($properties) use ($principal) { + $propPatch->handle(array_keys($this->fieldMap), function ($properties) use ($principal) { foreach ($properties as $key => $value) { - $setter = $this->fieldMap[$key]['setter']; $principal->$setter($value); } $this->_em->flush(); + return true; }); } @@ -277,14 +281,14 @@ public function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { * from working. * * @param string $prefixPath - * @param array $searchProperties + * @param array $searchProperties * @param string $test + * * @return array */ - public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { - + public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') + { foreach ($searchProperties as $property => $value) { - switch ($property) { case '{DAV:}displayname' : @@ -305,14 +309,16 @@ public function searchPrincipals($prefixPath, array $searchProperties, $test = ' } /** - * Returns the list of members for a group-principal + * Returns the list of members for a group-principal. * * @param string $principal + * * @return array + * * @throws Exception */ - public function getGroupMemberSet($principal) { - + public function getGroupMemberSet($principal) + { $groupMemberSet = array(); $principalObject = $this->getPrincipalByPath($principal, true); @@ -336,19 +342,20 @@ public function getGroupMemberSet($principal) { } /** - * Returns the list of groups a principal is a member of (each element of the list contains a URI) + * Returns the list of groups a principal is a member of (each element of the list contains a URI). * * @param string $principal + * * @return array */ - function getGroupMembership($principal) { - + public function getGroupMembership($principal) + { $principal_data = $this->getPrincipalByPath($principal, true); if (!$principal_data) { return array(); } - + $groupMembership = array($principal); if ($this->principalgroups_class !== '') { @@ -367,12 +374,12 @@ function getGroupMembership($principal) { * The principals should be passed as a list of uri's. * * @param string $principal - * @param array $members - * @return void + * @param array $members + * * @throws Exception */ - function setGroupMemberSet($principal, array $members) { - + public function setGroupMemberSet($principal, array $members) + { $groupPrincipal = $this->getPrincipalByPath($principal); if (!$groupPrincipal || !($groupPrincipal instanceof GroupInterface)) { @@ -391,10 +398,8 @@ function setGroupMemberSet($principal, array $members) { } // TODO: Implement the addition/deletion of new/old members - } - /** * Creates a new principal. * @@ -403,10 +408,10 @@ function setGroupMemberSet($principal, array $members) { * of the principal. * * @param string $path - * @param MkCol $mkCol - * @return void + * @param MkCol $mkCol */ - function createPrincipal($path, MkCol $mkCol) { + public function createPrincipal($path, MkCol $mkCol) + { // create new user $username = str_replace('principal/', '', $path); diff --git a/SecotrustSabreDavBundle.php b/SecotrustSabreDavBundle.php index 0cbc778..eb3c0ec 100644 --- a/SecotrustSabreDavBundle.php +++ b/SecotrustSabreDavBundle.php @@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; /** - * Class SecotrustSabreDavBundle + * Class SecotrustSabreDavBundle. */ class SecotrustSabreDavBundle extends Bundle { From 10e12c1189388d629b35f7e2eafaa0e2af0e12bd Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Thu, 15 Oct 2015 18:26:54 +0200 Subject: [PATCH 17/30] Add basic config description --- Resources/doc/index.md | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 212f0a0..5892b4c 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -1,3 +1,50 @@ # SecotrustSabreDavBundle # This bundle is still WIP. + +## Setup +First add this bundle to your composer dependencies: + +`> composer require secotrust/sabredav-bundle dev-master` + +Then register it in your AppKernel.php. + +```php +class AppKernel extends Kernel +{ + public function registerBundles() + { + $bundles = array( + new Secotrust\Bundle\SabreDavBundle\SecotrustSabreDavBundle(), + // ... +``` + +Add DAV routes. + +```yaml +# app/config/routing.yml +dav: +resource: "@SecotrustSabreDavBundle/Resources/config/routing.xml" +prefix: dav +``` + +Define a service to use for file access tagged with `secotrust.sabredav.collection`. + +```yaml +# app/config/services.yml +services: + gaufrette.adapter: + class: Gaufrette\Adapter\Local + arguments: + - "%kernel.root_dir%/../var/uploads" + gaufrette.filesystem: + class: Gaufrette\Filesystem + arguments: + - @gaufrette.adapter + gaufrette.dav.collection: + class: Secotrust\Bundle\SabreDavBundle\SabreDav\Gaufrette\Collection + arguments: + - @gaufrette.filesystem + tags: + - { name: secotrust.sabredav.collection } +``` \ No newline at end of file From 68207c22710202646ae34e9b86b2e8cdcd28efff Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 11:39:23 +0200 Subject: [PATCH 18/30] Replace deprecated 'pattern' in routing.xml --- Resources/config/routing.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/config/routing.xml b/Resources/config/routing.xml index 2e7060c..1fb66d7 100644 --- a/Resources/config/routing.xml +++ b/Resources/config/routing.xml @@ -4,7 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + secotrust.sabredav.controller:execAction /?.* From c9d4c49ad2abbcdcf99ed0cd3e74142e95cae216 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 11:40:30 +0200 Subject: [PATCH 19/30] Change secotrust.sabredav.caldav_collection.class to new name --- Resources/config/services/plugins/caldav.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/config/services/plugins/caldav.xml b/Resources/config/services/plugins/caldav.xml index 32293fd..22660e0 100644 --- a/Resources/config/services/plugins/caldav.xml +++ b/Resources/config/services/plugins/caldav.xml @@ -7,7 +7,7 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\CalDavBackend Sabre\CalDAV\Plugin - Sabre\CalDAV\CalendarRootNode + Sabre\CalDAV\CalendarRoot From d1afe0b6bd5fd6fed81478404630997628c5277d Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 11:41:08 +0200 Subject: [PATCH 20/30] Update CalDavBackend to new version --- SabreDav/CalDavBackend.php | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/SabreDav/CalDavBackend.php b/SabreDav/CalDavBackend.php index bc870fb..200482e 100644 --- a/SabreDav/CalDavBackend.php +++ b/SabreDav/CalDavBackend.php @@ -121,7 +121,7 @@ public function createCalendar($principalUri,$calendarUri,array $properties){ * @param array $mutations * @return bool|array */ - public function updateCalendar($calendarId, array $mutations){ + public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch){ return true; } @@ -184,6 +184,23 @@ public function getCalendarObject($calendarId,$objectUri){ return array($calendarId); } + /** + * Returns a list of calendar objects. + * + * This method should work identical to getCalendarObject, but instead + * return all the calendar objects in the list as an array. + * + * If the backend supports this, it may allow for some speed-ups. + * + * @param mixed $calendarId + * @param array $uris + * @return array + */ + function getMultipleCalendarObjects($calendarId, array $uris) + { + return array($calendarId); + } + /** * Creates a new calendar object. * @@ -287,4 +304,28 @@ public function deleteCalendarObject($calendarId,$objectUri){ public function calendarQuery($calendarId, array $filters){ return array($calendarId); } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $principalUri + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($principalUri, $uid) + { + return null; + } } From 3e0fd51a1229e8b0172648465db931b291c46a6a Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 12:20:09 +0200 Subject: [PATCH 21/30] Fix indent --- Resources/doc/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 5892b4c..4f1b2eb 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -24,8 +24,8 @@ Add DAV routes. ```yaml # app/config/routing.yml dav: -resource: "@SecotrustSabreDavBundle/Resources/config/routing.xml" -prefix: dav + resource: "@SecotrustSabreDavBundle/Resources/config/routing.xml" + prefix: dav ``` Define a service to use for file access tagged with `secotrust.sabredav.collection`. From 0cbcddfebc8fb9877bb15204e56dcb6de93c801a Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 12:20:27 +0200 Subject: [PATCH 22/30] Document using principal backend --- Resources/doc/index.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 4f1b2eb..467d0ea 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -47,4 +47,16 @@ services: - @gaufrette.filesystem tags: - { name: secotrust.sabredav.collection } -``` \ No newline at end of file +``` + +## Add principal Collection + +```yaml +# app/config/config.yml +secotrust_sabre_dav: + plugins: + #... + principal: true + settings: + principals_class: Symfony\Component\Security\Core\User\User +``` From 452e2fd28ec9ea65b6dde8052afcdfd60ed03a0c Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Fri, 16 Oct 2015 12:21:05 +0200 Subject: [PATCH 23/30] Use UserManager to find all users --- SabreDav/PrincipalBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SabreDav/PrincipalBackend.php b/SabreDav/PrincipalBackend.php index eee77ba..953ccf1 100644 --- a/SabreDav/PrincipalBackend.php +++ b/SabreDav/PrincipalBackend.php @@ -159,7 +159,7 @@ private function getPrincipalArray($principalObject, $show_id = false) */ public function getPrincipalsByPrefix($prefixPath) { - $userlist = $this->_em->getRepository($this->principals_class)->findBy(array('enabled' => true)); + $userlist = $this->user_manager->findUsers(); $principals = array(); foreach ($userlist as $user) { From 1203774bf045e9fef421d341092e43174087ee39 Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 25 Jan 2016 07:30:38 +0100 Subject: [PATCH 24/30] removed unused event-dispatcher --- Controller/SabreDavController.php | 20 +++----------------- Resources/config/services/services.xml | 1 - 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index 8055e43..d8de536 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -14,7 +14,6 @@ use Sabre\DAV\Server; use Secotrust\Bundle\SabreDavBundle\SabreDav\HttpRequest; use Secotrust\Bundle\SabreDavBundle\SabreDav\HttpResponse; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\RouterInterface; @@ -29,29 +28,16 @@ class SabreDavController */ private $dav; - /** - * @var EventDispatcherInterface - */ - private $dispatcher; - - /** - * @var RouterInterface - */ - private $router; - /** * Constructor. * - * @param Server $dav - * @param EventDispatcherInterface $dispatcher - * @param RouterInterface $router + * @param Server $dav + * @param RouterInterface $router */ - public function __construct(Server $dav, EventDispatcherInterface $dispatcher, RouterInterface $router) + public function __construct(Server $dav, RouterInterface $router) { $this->dav = $dav; $this->dav->setBaseUri($router->generate('secotrust_sabre_dav', array())); - - $this->dispatcher = $dispatcher; // TODO needed? } /** diff --git a/Resources/config/services/services.xml b/Resources/config/services/services.xml index ccfd5b9..5084248 100644 --- a/Resources/config/services/services.xml +++ b/Resources/config/services/services.xml @@ -17,7 +17,6 @@ - From 599976600ad4f1cdf00c243e8447e0989d4403da Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 25 Jan 2016 07:58:04 +0100 Subject: [PATCH 25/30] updated code-style (removed tab-characters); removed unused vars --- Resources/config/routing.xml | 7 +- Resources/config/services/plugins/caldav.xml | 13 ++- SabreDav/CalDavBackend.php | 84 +++++++++++--------- SabreDav/CardDavBackend.php | 5 -- 4 files changed, 55 insertions(+), 54 deletions(-) diff --git a/Resources/config/routing.xml b/Resources/config/routing.xml index 1fb66d7..2cf0637 100644 --- a/Resources/config/routing.xml +++ b/Resources/config/routing.xml @@ -4,10 +4,11 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + secotrust.sabredav.controller:execAction - + /?.* - + diff --git a/Resources/config/services/plugins/caldav.xml b/Resources/config/services/plugins/caldav.xml index 22660e0..33aa0b2 100644 --- a/Resources/config/services/plugins/caldav.xml +++ b/Resources/config/services/plugins/caldav.xml @@ -7,23 +7,20 @@ Secotrust\Bundle\SabreDavBundle\SabreDav\CalDavBackend Sabre\CalDAV\Plugin - Sabre\CalDAV\CalendarRoot + Sabre\CalDAV\CalendarRoot - - + - - false + - + - - + diff --git a/SabreDav/CalDavBackend.php b/SabreDav/CalDavBackend.php index 200482e..c14f853 100644 --- a/SabreDav/CalDavBackend.php +++ b/SabreDav/CalDavBackend.php @@ -13,43 +13,40 @@ use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception; -use Sabre\DAV\Server; -use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; class CalDavBackend implements BackendInterface { /** - * @var SecurityContextInterface + * @var ContainerInterface */ - private $context; + private $_em; /** - * @var ContainerInterface + * @var string */ - private $container; + private $calendar_class; /** - * @var ContainerInterface + * @var string */ - private $_em; - + private $calendarobjects_class; + /** * Constructor * - * @param SecurityContextInterface $context * @param ContainerInterface $container */ - public function __construct(SecurityContextInterface $context, ContainerInterface $container) + public function __construct(ContainerInterface $container) { - $this->context = $context; - $this->container = $container; - - $this->_em = $container->get('doctrine')->getManager(); + $this->_em = $container->get('doctrine')->getManager(); + + $this->calendar_class = $container->getParameter('secotrust.calendar_class'); + $this->calendarobjects_class = $container->getParameter('secotrust.calendarobjects_class'); } - - /** + /** * Returns a list of calendars for a principal. * * Every project is an array with the following keys: @@ -66,8 +63,9 @@ public function __construct(SecurityContextInterface $context, ContainerInterfac * @param string $principalUri * @return array */ - public function getCalendarsForUser($principalUri) { - return array($principalUri); + public function getCalendarsForUser($principalUri) + { + return array($principalUri); } /** @@ -81,8 +79,9 @@ public function getCalendarsForUser($principalUri) { * @param array $properties * @return void */ - public function createCalendar($principalUri,$calendarUri,array $properties){ - return; + public function createCalendar($principalUri, $calendarUri, array $properties) + { + return; } /** @@ -118,11 +117,13 @@ public function createCalendar($principalUri,$calendarUri,array $properties){ * (424 Failed Dependency) because the request needs to be atomic. * * @param mixed $calendarId - * @param array $mutations + * @param $calendarId + * @param \Sabre\DAV\PropPatch $propPatch * @return bool|array */ - public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch){ - return true; + public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) + { + return true; } /** @@ -131,8 +132,9 @@ public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch){ * @param mixed $calendarId * @return void */ - public function deleteCalendar($calendarId){ - return; + public function deleteCalendar($calendarId) + { + return; } /** @@ -162,8 +164,9 @@ public function deleteCalendar($calendarId){ * @param mixed $calendarId * @return array */ - public function getCalendarObjects($calendarId){ - return array($calendarId); + public function getCalendarObjects($calendarId) + { + return array($calendarId); } /** @@ -180,8 +183,9 @@ public function getCalendarObjects($calendarId){ * @param string $objectUri * @return array|null */ - public function getCalendarObject($calendarId,$objectUri){ - return array($calendarId); + public function getCalendarObject($calendarId, $objectUri) + { + return array($calendarId); } /** @@ -217,8 +221,9 @@ function getMultipleCalendarObjects($calendarId, array $uris) * @param string $calendarData * @return string|null */ - public function createCalendarObject($calendarId,$objectUri,$calendarData){ - return null; + public function createCalendarObject($calendarId, $objectUri, $calendarData) + { + return null; } /** @@ -237,8 +242,9 @@ public function createCalendarObject($calendarId,$objectUri,$calendarData){ * @param string $calendarData * @return string|null */ - public function updateCalendarObject($calendarId,$objectUri,$calendarData){ - return null; + public function updateCalendarObject($calendarId, $objectUri, $calendarData) + { + return null; } /** @@ -248,8 +254,9 @@ public function updateCalendarObject($calendarId,$objectUri,$calendarData){ * @param string $objectUri * @return void */ - public function deleteCalendarObject($calendarId,$objectUri){ - return null; + public function deleteCalendarObject($calendarId, $objectUri) + { + return null; } /** @@ -301,8 +308,9 @@ public function deleteCalendarObject($calendarId,$objectUri){ * @param array $filters * @return array */ - public function calendarQuery($calendarId, array $filters){ - return array($calendarId); + public function calendarQuery($calendarId, array $filters) + { + return array($calendarId); } /** diff --git a/SabreDav/CardDavBackend.php b/SabreDav/CardDavBackend.php index ee23a10..aea5129 100644 --- a/SabreDav/CardDavBackend.php +++ b/SabreDav/CardDavBackend.php @@ -20,11 +20,6 @@ class CardDavBackend extends AbstractBackend implements SyncSupport { - /** - * @var ContainerInterface - */ - private $container; - /** * @var \Doctrine\ORM\EntityManager */ From 5fff3fa2bda513c957a1b1270c3cdd41cb0cdb4d Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 25 Jan 2016 11:32:20 +0100 Subject: [PATCH 26/30] updated base_uri configuration for symfony router-base --- Controller/SabreDavController.php | 3 ++- DependencyInjection/SecotrustSabreDavExtension.php | 1 + Resources/config/services/services.xml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Controller/SabreDavController.php b/Controller/SabreDavController.php index d8de536..0960373 100644 --- a/Controller/SabreDavController.php +++ b/Controller/SabreDavController.php @@ -34,8 +34,9 @@ class SabreDavController * @param Server $dav * @param RouterInterface $router */ - public function __construct(Server $dav, RouterInterface $router) + public function __construct(Server $dav, RouterInterface $router, $base_uri = '') { + $router->getContext()->setBaseUrl($router->getContext()->getBaseUrl() . $base_uri); $this->dav = $dav; $this->dav->setBaseUri($router->generate('secotrust_sabre_dav', array())); } diff --git a/DependencyInjection/SecotrustSabreDavExtension.php b/DependencyInjection/SecotrustSabreDavExtension.php index 323b80b..db58cbe 100644 --- a/DependencyInjection/SecotrustSabreDavExtension.php +++ b/DependencyInjection/SecotrustSabreDavExtension.php @@ -34,6 +34,7 @@ public function load(array $configs, ContainerBuilder $container) if (isset($config['base_uri'])) { $container->getDefinition('secotrust.sabredav.server')->addMethodCall('setBaseUri', array($config['base_uri'])); + $container->setParameter('secotrust.sabredav.base_uri', $config['base_uri']); } // load all plugins diff --git a/Resources/config/services/services.xml b/Resources/config/services/services.xml index 5084248..0892cf2 100644 --- a/Resources/config/services/services.xml +++ b/Resources/config/services/services.xml @@ -7,6 +7,7 @@ Secotrust\Bundle\SabreDavBundle\Controller\SabreDavController Sabre\DAV\Server + @@ -18,6 +19,7 @@ + %secotrust.sabredav.base_uri% From ef9eb3f1566fc5a3c61d55ca683eede0cf461166 Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 25 Jan 2016 11:47:39 +0100 Subject: [PATCH 27/30] added client list to docs --- Resources/doc/index.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 467d0ea..eb88f8c 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -60,3 +60,24 @@ secotrust_sabre_dav: settings: principals_class: Symfony\Component\Security\Core\User\User ``` + +## DAV-Clients + +Full List of supported SabreDav Clients: [http://sabre.io/dav/clients/](http://sabre.io/dav/clients/) + +### Thunderbird ++ SogoConnector: [http://www.sogo.nu/downloads/frontends.html](http://www.sogo.nu/downloads/frontends.html) + +### Microsoft Outlook / Windows 8 ++ [http://german.evomailserver.com/download.php](http://german.evomailserver.com/download.php): EVO Kollaborateur (Untested), ++ [https://help.atmail.com/hc/en-us/articles/200907874-Syncing-Outlook-Contacts-and-Calendars-with-Atmail-DavSync](https://help.atmail.com/hc/en-us/articles/200907874-Syncing-Outlook-Contacts-and-Calendars-with-Atmail-DavSync) ++ [http://forum.xda-developers.com/showthread.php?t=2478215](http://forum.xda-developers.com/showthread.php?t=2478215) ++ [http://www.outlookdav.com/](http://www.outlookdav.com/): no freeware + +### Mac OS X >= 10.8 / iOS >=7: ++ native implementation + +### Android Smartphones ++ [http://dmfs.org/carddav/](http://dmfs.org/carddav/) ++ [http://dmfs.org/calcav/](http://dmfs.org/caldav/) + From 751cc30fecf19bc10a813c2613a09870daba67ad Mon Sep 17 00:00:00 2001 From: lduer Date: Mon, 25 Jan 2016 12:01:50 +0100 Subject: [PATCH 28/30] updated to psr-4 --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 96d4f23..23c2daf 100644 --- a/composer.json +++ b/composer.json @@ -31,9 +31,8 @@ "gedmo/doctrine-extensions": "~2.3" }, "autoload": { - "psr-0": { "Secotrust\\Bundle\\SabreDavBundle": "" } + "psr-4": { "Secotrust\\Bundle\\SabreDavBundle\\": "" } }, - "target-dir": "Secotrust/Bundle/SabreDavBundle", "extra": { "branch-alias": { "dev-master": "2.0.x-dev" From cdc21b837882ca4e364cf47675bf6b283990f1bd Mon Sep 17 00:00:00 2001 From: lduer Date: Tue, 29 Nov 2016 16:05:09 +0100 Subject: [PATCH 29/30] updated sabre/dav version requirement --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 23c2daf..8276921 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "php": ">=5.3.3", "symfony/framework-bundle": "~2.2", "friendsofsymfony/user-bundle": ">=1.3.5", - "sabre/dav": "~3.0" + "sabre/dav": "~3.1.0" }, "suggest": { "knplabs/knp-gaufrette-bundle": "0.2.*", From 1958b89e24ee4a6c18579bbc448ab06bf19cb7fd Mon Sep 17 00:00:00 2001 From: lduer Date: Tue, 17 Jan 2017 10:35:19 +0100 Subject: [PATCH 30/30] Update symfony framework version requirement --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8276921..d2e9eac 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ ], "require": { "php": ">=5.3.3", - "symfony/framework-bundle": "~2.2", + "symfony/framework-bundle": "~2.2 || ^3.0", "friendsofsymfony/user-bundle": ">=1.3.5", "sabre/dav": "~3.1.0" },