diff options
Diffstat (limited to 'apps/dav/lib')
-rw-r--r-- | apps/dav/lib/carddav/addressbook.php | 36 | ||||
-rw-r--r-- | apps/dav/lib/carddav/addressbookroot.php | 12 | ||||
-rw-r--r-- | apps/dav/lib/carddav/card.php | 39 | ||||
-rw-r--r-- | apps/dav/lib/carddav/carddavbackend.php | 6 | ||||
-rw-r--r-- | apps/dav/lib/carddav/converter.php | 158 | ||||
-rw-r--r-- | apps/dav/lib/carddav/plugin.php | 47 | ||||
-rw-r--r-- | apps/dav/lib/carddav/useraddressbooks.php | 36 | ||||
-rw-r--r-- | apps/dav/lib/dav/systemprincipalbackend.php | 183 | ||||
-rw-r--r-- | apps/dav/lib/rootcollection.php | 24 | ||||
-rw-r--r-- | apps/dav/lib/server.php | 2 |
10 files changed, 526 insertions, 17 deletions
diff --git a/apps/dav/lib/carddav/addressbook.php b/apps/dav/lib/carddav/addressbook.php index eff1ad321e5..507657e9682 100644 --- a/apps/dav/lib/carddav/addressbook.php +++ b/apps/dav/lib/carddav/addressbook.php @@ -3,6 +3,7 @@ namespace OCA\DAV\CardDAV; use OCA\DAV\CardDAV\Sharing\IShareableAddressBook; +use Sabre\DAV\Exception\NotFound; class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareableAddressBook { @@ -51,4 +52,39 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareableAddres $carddavBackend = $this->carddavBackend; $carddavBackend->getShares($this->getName()); } + + function getACL() { + $acl = parent::getACL(); + if ($this->getOwner() === 'principals/system/system') { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ]; + } + + return $acl; + } + + function getChildACL() { + $acl = parent::getChildACL(); + if ($this->getOwner() === 'principals/system/system') { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ]; + } + + return $acl; + } + + function getChild($name) { + $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); + if (!$obj) { + throw new NotFound('Card not found'); + } + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + } diff --git a/apps/dav/lib/carddav/addressbookroot.php b/apps/dav/lib/carddav/addressbookroot.php index ee99ac8d798..8c78d024556 100644 --- a/apps/dav/lib/carddav/addressbookroot.php +++ b/apps/dav/lib/carddav/addressbookroot.php @@ -20,4 +20,14 @@ class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot { } -}
\ No newline at end of file + function getName() { + + // Grabbing all the components of the principal path. + $parts = explode('/', $this->principalPrefix); + + // We are only interested in the second part. + return $parts[1]; + + } + +} diff --git a/apps/dav/lib/carddav/card.php b/apps/dav/lib/carddav/card.php new file mode 100644 index 00000000000..cea0b1e41c7 --- /dev/null +++ b/apps/dav/lib/carddav/card.php @@ -0,0 +1,39 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\CardDAV; + +class Card extends \Sabre\CardDAV\Card { + + function getACL() { + $acl = parent::getACL(); + if ($this->getOwner() === 'principals/system/system') { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ]; + } + + return $acl; + } + +} diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php index daa31725fa1..29b056672b4 100644 --- a/apps/dav/lib/carddav/carddavbackend.php +++ b/apps/dav/lib/carddav/carddavbackend.php @@ -108,7 +108,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { return $addressBooks; } - private function getAddressBooksByUri($addressBookUri) { + public function getAddressBooksByUri($addressBookUri) { $query = $this->db->getQueryBuilder(); $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken']) ->from('addressbooks') @@ -117,10 +117,10 @@ class CardDavBackend implements BackendInterface, SyncSupport { ->execute(); $row = $result->fetch(); - if (is_null($row)) { + $result->closeCursor(); + if ($row === false) { return null; } - $result->closeCursor(); return [ 'id' => $row['id'], diff --git a/apps/dav/lib/carddav/converter.php b/apps/dav/lib/carddav/converter.php new file mode 100644 index 00000000000..56b73eba4c0 --- /dev/null +++ b/apps/dav/lib/carddav/converter.php @@ -0,0 +1,158 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\CardDAV; + +use OCP\IImage; +use OCP\IUser; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Property\Text; + +class Converter { + + /** + * @param IUser $user + * @return VCard + */ + public function createCardFromUser(IUser $user) { + + $uid = $user->getUID(); + $displayName = $user->getDisplayName(); + $displayName = empty($displayName ) ? $uid : $displayName; + $emailAddress = $user->getEMailAddress(); + $cloudId = $user->getCloudId(); + $image = $user->getAvatarImage(-1); + + $vCard = new VCard(); + $vCard->add(new Text($vCard, 'UID', $uid)); + if (!empty($displayName)) { + $vCard->add(new Text($vCard, 'FN', $displayName)); + $vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName))); + } + if (!empty($emailAddress)) { + $vCard->add(new Text($vCard, 'EMAIL', $emailAddress, ['TYPE' => 'OTHER'])); + } + if (!empty($cloudId)) { + $vCard->add(new Text($vCard, 'CLOUD', $cloudId)); + } + if ($image) { + $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); + } + $vCard->validate(); + + return $vCard; + } + + /** + * @param VCard $vCard + * @param IUser $user + * @return bool + */ + public function updateCard(VCard $vCard, IUser $user) { + $uid = $user->getUID(); + $displayName = $user->getDisplayName(); + $displayName = empty($displayName ) ? $uid : $displayName; + $emailAddress = $user->getEMailAddress(); + $cloudId = $user->getCloudId(); + $image = $user->getAvatarImage(-1); + + $updated = false; + if($this->propertyNeedsUpdate($vCard, 'FN', $displayName)) { + $vCard->FN = new Text($vCard, 'FN', $displayName); + unset($vCard->N); + $vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName))); + $updated = true; + } + if($this->propertyNeedsUpdate($vCard, 'EMAIL', $emailAddress)) { + $vCard->EMAIL = new Text($vCard, 'EMAIL', $emailAddress); + $updated = true; + } + if($this->propertyNeedsUpdate($vCard, 'CLOUD', $cloudId)) { + $vCard->CLOUD = new Text($vCard, 'CLOUD', $cloudId); + $updated = true; + } + + if($this->propertyNeedsUpdate($vCard, 'PHOTO', $image)) { + $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); + $updated = true; + } + + if (empty($emailAddress) && !is_null($vCard->EMAIL)) { + unset($vCard->EMAIL); + $updated = true; + } + if (empty($cloudId) && !is_null($vCard->CLOUD)) { + unset($vCard->CLOUD); + $updated = true; + } + if (empty($image) && !is_null($vCard->PHOTO)) { + unset($vCard->PHOTO); + $updated = true; + } + + return $updated; + } + + /** + * @param VCard $vCard + * @param string $name + * @param string|IImage $newValue + * @return bool + */ + private function propertyNeedsUpdate(VCard $vCard, $name, $newValue) { + if (is_null($newValue)) { + return false; + } + $value = $vCard->__get($name); + if (!is_null($value)) { + $value = $value->getValue(); + $newValue = $newValue instanceof IImage ? $newValue->data() : $newValue; + + return $value !== $newValue; + } + return true; + } + + /** + * @param string $fullName + * @return string[] + */ + public function splitFullName($fullName) { + // Very basic western style parsing. I'm not gonna implement + // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;) + + $elements = explode(' ', $fullName); + $result = ['', '', '', '', '']; + if (count($elements) > 2) { + $result[0] = implode(' ', array_slice($elements, count($elements)-1)); + $result[1] = $elements[0]; + $result[2] = implode(' ', array_slice($elements, 1, count($elements)-2)); + } elseif (count($elements) === 2) { + $result[0] = $elements[1]; + $result[1] = $elements[0]; + } else { + $result[0] = $elements[0]; + } + + return $result; + } + +} diff --git a/apps/dav/lib/carddav/plugin.php b/apps/dav/lib/carddav/plugin.php new file mode 100644 index 00000000000..f2b3dcbfda9 --- /dev/null +++ b/apps/dav/lib/carddav/plugin.php @@ -0,0 +1,47 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\CardDAV; + +use Sabre\HTTP\URLUtil; + +class Plugin extends \Sabre\CardDAV\Plugin { + + /** + * Returns the addressbook home for a given principal + * + * @param string $principal + * @return string + */ + protected function getAddressbookHomeForPrincipal($principal) { + + if (strrpos($principal, 'principals/users', -strlen($principal)) !== FALSE) { + list(, $principalId) = URLUtil::splitPath($principal); + return self::ADDRESSBOOK_ROOT . '/users/' . $principalId; + } + if (strrpos($principal, 'principals/system', -strlen($principal)) !== FALSE) { + list(, $principalId) = URLUtil::splitPath($principal); + return self::ADDRESSBOOK_ROOT . '/system/' . $principalId; + } + + throw new \LogicException('This is not supposed to happen'); + } +} diff --git a/apps/dav/lib/carddav/useraddressbooks.php b/apps/dav/lib/carddav/useraddressbooks.php index 5f618a0ece3..093cee0e1b2 100644 --- a/apps/dav/lib/carddav/useraddressbooks.php +++ b/apps/dav/lib/carddav/useraddressbooks.php @@ -11,13 +11,39 @@ class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome { */ function getChildren() { - $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); - $objs = []; - foreach($addressbooks as $addressbook) { - $objs[] = new AddressBook($this->carddavBackend, $addressbook); + $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); + $objects = []; + foreach($addressBooks as $addressBook) { + $objects[] = new AddressBook($this->carddavBackend, $addressBook); } - return $objs; + return $objects; } + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = parent::getACL(); + if ($this->principalUri === 'principals/system/system') { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ]; + } + + return $acl; + } + } diff --git a/apps/dav/lib/dav/systemprincipalbackend.php b/apps/dav/lib/dav/systemprincipalbackend.php new file mode 100644 index 00000000000..2c2049ace60 --- /dev/null +++ b/apps/dav/lib/dav/systemprincipalbackend.php @@ -0,0 +1,183 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\DAV; + +use Sabre\DAVACL\PrincipalBackend\AbstractBackend; +use Sabre\HTTP\URLUtil; + +class SystemPrincipalBackend extends AbstractBackend { + + /** + * 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 + */ + function getPrincipalsByPrefix($prefixPath) { + $principals = []; + + if ($prefixPath === 'principals/system') { + $principals[] = [ + 'uri' => 'principals/system/system', + '{DAV:}displayname' => 'system', + ]; + } + + 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 + */ + function getPrincipalByPath($path) { + + $elements = explode('/', $path); + if ($elements[0] !== 'principals') { + return null; + } + if ($elements[1] === 'system') { + $principal = [ + 'uri' => 'principals/system/system', + '{DAV:}displayname' => 'system', + ]; + return $principal; + } + + return null; + } + + /** + * Updates one ore more webdav properties on a principal. + * + * 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. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documentation for more info and examples. + * + * @param string $path + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { + } + + /** + * 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. + * + * 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'. + * + * 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 + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + return []; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + function getGroupMemberSet($principal) { + // TODO: for now the group principal has only one member, the user itself + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new \Sabre\DAV\Exception('Principal not found'); + } + + return [$principal['uri']]; + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + function getGroupMembership($principal) { + list($prefix, $name) = URLUtil::splitPath($principal); + + if ($prefix === 'principals/system') { + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new \Sabre\DAV\Exception('Principal not found'); + } + + return []; + } + return []; + } + + /** + * 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) { + throw new \Sabre\DAV\Exception('Setting members of the group is not supported yet'); + } +} diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php index 3e349fa31c9..c1635c9cde5 100644 --- a/apps/dav/lib/rootcollection.php +++ b/apps/dav/lib/rootcollection.php @@ -6,6 +6,7 @@ use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\Sabre\Principal; +use OCA\DAV\DAV\SystemPrincipalBackend; use Sabre\CalDAV\CalendarRoot; use Sabre\CalDAV\Principal\Collection; use Sabre\DAV\SimpleCollection; @@ -23,24 +24,33 @@ class RootCollection extends SimpleCollection { $disableListing = !$config->getSystemValue('debug', false); // setup the first level of the dav tree - $principalCollection = new Collection($principalBackend, 'principals/users'); - $principalCollection->disableListing = $disableListing; + $userPrincipals = new Collection($principalBackend, 'principals/users'); + $userPrincipals->disableListing = $disableListing; + $systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system'); + $systemPrincipals->disableListing = $disableListing; $filesCollection = new Files\RootCollection($principalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; $caldavBackend = new CalDavBackend($db); $calendarRoot = new CalendarRoot($principalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; - $cardDavBackend = new CardDavBackend(\OC::$server->getDatabaseConnection(), $principalBackend); + $usersCardDavBackend = new CardDavBackend($db, $principalBackend); + $usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users'); + $usersAddressBookRoot->disableListing = $disableListing; - $addressBookRoot = new AddressBookRoot($principalBackend, $cardDavBackend, 'principals/users'); - $addressBookRoot->disableListing = $disableListing; + $systemCardDavBackend = new CardDavBackend($db, $principalBackend); + $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system'); + $systemAddressBookRoot->disableListing = $disableListing; $children = [ - new SimpleCollection('principals', [$principalCollection]), + new SimpleCollection('principals', [ + $userPrincipals, + $systemPrincipals]), $filesCollection, $calendarRoot, - $addressBookRoot, + new SimpleCollection('addressbooks', [ + $usersAddressBookRoot, + $systemAddressBookRoot]), ]; parent::__construct('root', $children); diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index ffdb917085e..a031f2c442b 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -58,7 +58,7 @@ class Server { $this->server->addPlugin(new CardDAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); // addressbook plugins - $this->server->addPlugin(new \Sabre\CardDAV\Plugin()); + $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); // Finder on OS X requires Class 2 WebDAV support (locking), since we do // not provide locking we emulate it using a fake locking plugin. |