diff options
Diffstat (limited to 'apps')
34 files changed, 1176 insertions, 88 deletions
diff --git a/apps/dav/lib/caldav/schedule/imipplugin.php b/apps/dav/lib/caldav/schedule/imipplugin.php new file mode 100644 index 00000000000..eafdbe9c1bd --- /dev/null +++ b/apps/dav/lib/caldav/schedule/imipplugin.php @@ -0,0 +1,110 @@ +<?php + +namespace OCA\DAV\CalDAV\Schedule; + +use OCP\ILogger; +use OCP\Mail\IMailer; +use Sabre\DAV; +use Sabre\VObject\ITip; +use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin; +/** + * iMIP handler. + * + * This class is responsible for sending out iMIP messages. iMIP is the + * email-based transport for iTIP. iTIP deals with scheduling operations for + * iCalendar objects. + * + * If you want to customize the email that gets sent out, you can do so by + * extending this class and overriding the sendMessage method. + * + * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class IMipPlugin extends SabreIMipPlugin { + + /** @var IMailer */ + private $mailer; + + /** @var ILogger */ + private $logger; + + /** + * Creates the email handler. + * + * @param IMailer $mailer + */ + function __construct(IMailer $mailer, ILogger $logger) { + parent::__construct(''); + $this->mailer = $mailer; + $this->logger = $logger; + } + + /** + * Event handler for the 'schedule' event. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function schedule(ITip\Message $iTipMessage) { + + // Not sending any emails if the system considers the update + // insignificant. + if (!$iTipMessage->significantChange) { + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + } + return; + } + + $summary = $iTipMessage->message->VEVENT->SUMMARY; + + if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') { + return; + } + + if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') { + return; + } + + $sender = substr($iTipMessage->sender, 7); + $recipient = substr($iTipMessage->recipient, 7); + + $senderName = ($iTipMessage->senderName) ? $iTipMessage->senderName : null; + $recipientName = ($iTipMessage->recipientName) ? $iTipMessage->recipientName : null; + + $subject = 'SabreDAV iTIP message'; + switch (strtoupper($iTipMessage->method)) { + case 'REPLY' : + $subject = 'Re: ' . $summary; + break; + case 'REQUEST' : + $subject = $summary; + break; + case 'CANCEL' : + $subject = 'Cancelled: ' . $summary; + break; + } + + $contentType = 'text/calendar; charset=UTF-8; method=' . $iTipMessage->method; + + $message = $this->mailer->createMessage(); + + $message->setReplyTo([$sender => $senderName]) + ->setTo([$recipient => $recipientName]) + ->setSubject($subject) + ->setBody($iTipMessage->message->serialize(), $contentType); + try { + $failed = $this->mailer->send($message); + if ($failed) { + $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); + $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; + } + $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; + } catch(\Exception $ex) { + $this->logger->logException($ex, ['app' => 'dav']); + $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; + } + } + +} diff --git a/apps/dav/lib/connector/sabre/auth.php b/apps/dav/lib/connector/sabre/auth.php index 7f4f4a531b1..02b88390bad 100644 --- a/apps/dav/lib/connector/sabre/auth.php +++ b/apps/dav/lib/connector/sabre/auth.php @@ -151,7 +151,10 @@ class Auth extends AbstractBasic { */ private function auth(RequestInterface $request, ResponseInterface $response) { if (\OC_User::handleApacheAuth() || - ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) + //Fix for broken webdav clients + ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || + //Well behaved clients that only send the cookie are allowed + ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ) { $user = $this->userSession->getUser()->getUID(); \OC_Util::setupFS($user); diff --git a/apps/dav/lib/connector/sabre/principal.php b/apps/dav/lib/connector/sabre/principal.php index cc9c1c40d59..c917b11225a 100644 --- a/apps/dav/lib/connector/sabre/principal.php +++ b/apps/dav/lib/connector/sabre/principal.php @@ -33,22 +33,20 @@ namespace OCA\DAV\Connector\Sabre; use OCP\IUser; use OCP\IUserManager; use OCP\IConfig; +use Sabre\DAV\Exception; use \Sabre\DAV\PropPatch; +use Sabre\DAVACL\PrincipalBackend\BackendInterface; use Sabre\HTTP\URLUtil; -class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { - /** @var IConfig */ - private $config; +class Principal implements BackendInterface { + /** @var IUserManager */ private $userManager; /** - * @param IConfig $config * @param IUserManager $userManager */ - public function __construct(IConfig $config, - IUserManager $userManager) { - $this->config = $config; + public function __construct(IUserManager $userManager) { $this->userManager = $userManager; } @@ -108,13 +106,13 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { * * @param string $principal * @return string[] - * @throws \Sabre\DAV\Exception + * @throws Exception */ public 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'); + throw new Exception('Principal not found'); } return [$principal['uri']]; @@ -125,7 +123,7 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { * * @param string $principal * @return array - * @throws \Sabre\DAV\Exception + * @throws Exception */ public function getGroupMembership($principal) { list($prefix, $name) = URLUtil::splitPath($principal); @@ -134,7 +132,7 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { if ($prefix === 'principals/users') { $principal = $this->getPrincipalByPath($principal); if (!$principal) { - throw new \Sabre\DAV\Exception('Principal not found'); + throw new Exception('Principal not found'); } // TODO: for now the user principal has only its own groups @@ -157,10 +155,10 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { * * @param string $principal * @param string[] $members - * @throws \Sabre\DAV\Exception + * @throws Exception */ public function setGroupMemberSet($principal, array $members) { - throw new \Sabre\DAV\Exception('Setting members of the group is not supported yet'); + throw new Exception('Setting members of the group is not supported yet'); } /** diff --git a/apps/dav/lib/dav/groupprincipalbackend.php b/apps/dav/lib/dav/groupprincipalbackend.php new file mode 100644 index 00000000000..156dc2c1285 --- /dev/null +++ b/apps/dav/lib/dav/groupprincipalbackend.php @@ -0,0 +1,153 @@ +<?php + +namespace OCA\DAV\DAV; + +use OCP\IGroup; +use OCP\IGroupManager; +use Sabre\DAV\Exception; +use \Sabre\DAV\PropPatch; +use Sabre\DAVACL\PrincipalBackend\BackendInterface; + +class GroupPrincipalBackend implements BackendInterface { + + const PRINCIPAL_PREFIX = 'principals/groups'; + + /** @var IGroupManager */ + private $groupManager; + + /** + * @param IGroupManager $IGroupManager + */ + public function __construct(IGroupManager $IGroupManager) { + $this->groupManager = $IGroupManager; + } + + /** + * 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 + * + * @param string $prefixPath + * @return string[] + */ + public function getPrincipalsByPrefix($prefixPath) { + $principals = []; + + if ($prefixPath === self::PRINCIPAL_PREFIX) { + foreach($this->groupManager->search('') as $user) { + $principals[] = $this->groupToPrincipal($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) { + $elements = explode('/', $path); + if ($elements[0] !== 'principals') { + return null; + } + if ($elements[1] !== 'groups') { + return null; + } + $name = $elements[2]; + $user = $this->groupManager->get($name); + + if (!is_null($user)) { + return $this->groupToPrincipal($user); + } + + return null; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return string[] + * @throws Exception + */ + public function getGroupMemberSet($principal) { + // TODO: implement if we want that + return []; + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + * @throws Exception + */ + public function getGroupMembership($principal) { + 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 string[] $members + * @throws Exception + */ + public function setGroupMemberSet($principal, array $members) { + throw new Exception('Setting members of the group is not supported yet'); + } + + /** + * @param string $path + * @param PropPatch $propPatch + * @return int + */ + function updatePrincipal($path, PropPatch $propPatch) { + return 0; + } + + /** + * @param string $prefixPath + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + return []; + } + + /** + * @param string $uri + * @param string $principalPrefix + * @return string + */ + function findByUri($uri, $principalPrefix) { + return ''; + } + + /** + * @param IGroup $group + * @return array + */ + protected function groupToPrincipal($group) { + $groupId = $group->getGID(); + $principal = [ + 'uri' => "principals/groups/$groupId", + '{DAV:}displayname' => $groupId, + ]; + + return $principal; + } +} diff --git a/apps/dav/lib/dav/systemprincipalbackend.php b/apps/dav/lib/dav/systemprincipalbackend.php index 2c2049ace60..f65cd8bfc36 100644 --- a/apps/dav/lib/dav/systemprincipalbackend.php +++ b/apps/dav/lib/dav/systemprincipalbackend.php @@ -65,11 +65,7 @@ class SystemPrincipalBackend extends AbstractBackend { */ function getPrincipalByPath($path) { - $elements = explode('/', $path); - if ($elements[0] !== 'principals') { - return null; - } - if ($elements[1] === 'system') { + if ($path === 'principals/system/system') { $principal = [ 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php index 96cc2bbc46a..2261712200a 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\GroupPrincipalBackend; use OCA\DAV\DAV\SystemPrincipalBackend; use Sabre\CalDAV\CalendarRoot; use Sabre\CalDAV\Principal\Collection; @@ -16,22 +17,26 @@ class RootCollection extends SimpleCollection { public function __construct() { $config = \OC::$server->getConfig(); $db = \OC::$server->getDatabaseConnection(); - $principalBackend = new Principal( - $config, - \OC::$server->getUserManager() + $userPrincipalBackend = new Principal( + \OC::$server->getUserManager() + ); + $groupPrincipalBackend = new GroupPrincipalBackend( + \OC::$server->getGroupManager() ); // as soon as debug mode is enabled we allow listing of principals $disableListing = !$config->getSystemValue('debug', false); // setup the first level of the dav tree - $userPrincipals = new Collection($principalBackend, 'principals/users'); + $userPrincipals = new Collection($userPrincipalBackend, 'principals/users'); $userPrincipals->disableListing = $disableListing; + $groupPrincipals = new Collection($groupPrincipalBackend, 'principals/groups'); + $groupPrincipals->disableListing = $disableListing; $systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system'); $systemPrincipals->disableListing = $disableListing; - $filesCollection = new Files\RootCollection($principalBackend, 'principals/users'); + $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; $caldavBackend = new CalDavBackend($db); - $calendarRoot = new CalendarRoot($principalBackend, $caldavBackend, 'principals/users'); + $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; $systemTagCollection = new SystemTag\SystemTagsByIdCollection( \OC::$server->getSystemTagManager() @@ -41,17 +46,18 @@ class RootCollection extends SimpleCollection { \OC::$server->getSystemTagObjectMapper() ); - $usersCardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getLogger()); - $usersAddressBookRoot = new AddressBookRoot($principalBackend, $usersCardDavBackend, 'principals/users'); + $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getLogger()); + $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users'); $usersAddressBookRoot->disableListing = $disableListing; - $systemCardDavBackend = new CardDavBackend($db, $principalBackend, \OC::$server->getLogger()); + $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, \OC::$server->getLogger()); $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, 'principals/system'); $systemAddressBookRoot->disableListing = $disableListing; $children = [ new SimpleCollection('principals', [ $userPrincipals, + $groupPrincipals, $systemPrincipals]), $filesCollection, $calendarRoot, diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index ffcbb02db70..a6ad878d29f 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -2,12 +2,12 @@ namespace OCA\DAV; +use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; use OCA\DAV\Files\CustomPropertiesBackend; use OCP\IRequest; use Sabre\DAV\Auth\Plugin; -use Sabre\HTTP\Util; class Server { @@ -19,6 +19,7 @@ class Server { $this->baseUri = $baseUri; $logger = \OC::$server->getLogger(); $dispatcher = \OC::$server->getEventDispatcher(); + $mailer = \OC::$server->getMailer(); $root = new RootCollection(); $this->server = new \OCA\DAV\Connector\Sabre\Server($root); @@ -49,9 +50,8 @@ class Server { // calendar plugins $this->server->addPlugin(new \Sabre\CalDAV\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); - $senderEmail = \OCP\Util::getDefaultEmailAddress('no-reply'); $this->server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin()); - $this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($senderEmail)); + $this->server->addPlugin(new IMipPlugin($mailer, $logger)); $this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin()); $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); diff --git a/apps/dav/tests/unit/caldav/schedule/imipplugintest.php b/apps/dav/tests/unit/caldav/schedule/imipplugintest.php new file mode 100644 index 00000000000..49059e93aef --- /dev/null +++ b/apps/dav/tests/unit/caldav/schedule/imipplugintest.php @@ -0,0 +1,91 @@ +<?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\Tests\Unit\CalDAV\Schedule; + +use OC\Mail\Mailer; +use OCA\DAV\CalDAV\Schedule\IMipPlugin; +use OCP\ILogger; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\ITip\Message; +use Test\TestCase; + +class IMipPluginTest extends TestCase { + + public function testDelivery() { + $mailMessage = new \OC\Mail\Message(new \Swift_Message()); + /** @var Mailer | \PHPUnit_Framework_MockObject_MockObject $mailer */ + $mailer = $this->getMockBuilder('OC\Mail\Mailer')->disableOriginalConstructor()->getMock(); + $mailer->method('createMessage')->willReturn($mailMessage); + $mailer->expects($this->once())->method('send'); + /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject $logger */ + $logger = $this->getMockBuilder('OC\Log')->disableOriginalConstructor()->getMock(); + + $plugin = new IMipPlugin($mailer, $logger); + $message = new Message(); + $message->method = 'REQUEST'; + $message->message = new VCalendar(); + $message->message->add('VEVENT', [ + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + 'SUMMARY' => 'Fellowship meeting', + ]); + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->recipient = 'mailto:frodo@hobb.it'; + + $plugin->schedule($message); + $this->assertEquals('1.1', $message->getScheduleStatus()); + $this->assertEquals('Fellowship meeting', $mailMessage->getSubject()); + $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); + $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); + $this->assertEquals('text/calendar; charset=UTF-8; method=REQUEST', $mailMessage->getSwiftMessage()->getContentType()); + } + + public function testFailedDelivery() { + $mailMessage = new \OC\Mail\Message(new \Swift_Message()); + /** @var Mailer | \PHPUnit_Framework_MockObject_MockObject $mailer */ + $mailer = $this->getMockBuilder('OC\Mail\Mailer')->disableOriginalConstructor()->getMock(); + $mailer->method('createMessage')->willReturn($mailMessage); + $mailer->method('send')->willThrowException(new \Exception()); + /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject $logger */ + $logger = $this->getMockBuilder('OC\Log')->disableOriginalConstructor()->getMock(); + + $plugin = new IMipPlugin($mailer, $logger); + $message = new Message(); + $message->method = 'REQUEST'; + $message->message = new VCalendar(); + $message->message->add('VEVENT', [ + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + 'SUMMARY' => 'Fellowship meeting', + ]); + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->recipient = 'mailto:frodo@hobb.it'; + + $plugin->schedule($message); + $this->assertEquals('5.0', $message->getScheduleStatus()); + $this->assertEquals('Fellowship meeting', $mailMessage->getSubject()); + $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); + $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); + $this->assertEquals('text/calendar; charset=UTF-8; method=REQUEST', $mailMessage->getSwiftMessage()->getContentType()); + } + +} diff --git a/apps/dav/tests/unit/carddav/convertertest.php b/apps/dav/tests/unit/carddav/convertertest.php index f4e2ea3f002..bae7e383f9a 100644 --- a/apps/dav/tests/unit/carddav/convertertest.php +++ b/apps/dav/tests/unit/carddav/convertertest.php @@ -41,10 +41,10 @@ class ConverterTests extends TestCase { public function providesNewUsers() { return [ - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n"], - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar"], - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nEMAIL;TYPE=OTHER:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"], - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nCLOUD:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nEMAIL;TYPE=OTHER:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nCLOUD:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"], ]; } @@ -88,9 +88,9 @@ class ConverterTests extends TestCase { public function providesUsersForUpdateOfRemovedElement() { return [ - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"], - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"], - ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.7//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"], + ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"], ]; } diff --git a/apps/dav/tests/unit/connector/sabre/auth.php b/apps/dav/tests/unit/connector/sabre/auth.php index 217ff5fc3fa..5e1cdfb03d8 100644 --- a/apps/dav/tests/unit/connector/sabre/auth.php +++ b/apps/dav/tests/unit/connector/sabre/auth.php @@ -21,6 +21,7 @@ namespace OCA\DAV\Tests\Unit\Connector\Sabre; +use OCP\IUser; use Test\TestCase; use OCP\ISession; use OCP\IUserSession; @@ -29,6 +30,7 @@ use OCP\IUserSession; * Class Auth * * @package OCA\DAV\Connector\Sabre + * @group DB */ class Auth extends TestCase { /** @var ISession */ @@ -330,21 +332,31 @@ class Auth extends TestCase { $httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') ->disableOriginalConstructor() ->getMock(); + /** @var IUser */ + $user = $this->getMock('OCP\IUser'); + $user->method('getUID')->willReturn('MyTestUser'); $this->userSession ->expects($this->any()) ->method('isLoggedIn') ->will($this->returnValue(true)); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->willReturn($user); $this->session - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->will($this->returnValue('MyTestUser')); $httpRequest - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('getHeader') ->with('Authorization') ->will($this->returnValue(null)); - $this->auth->check($httpRequest, $httpResponse); + $this->assertEquals( + [true, 'principals/users/MyTestUser'], + $this->auth->check($httpRequest, $httpResponse) + ); } public function testAuthenticateValidCredentials() { diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php index 9a6ae545b58..160d5744260 100644 --- a/apps/dav/tests/unit/connector/sabre/principal.php +++ b/apps/dav/tests/unit/connector/sabre/principal.php @@ -17,18 +17,14 @@ use OCP\IConfig; class Principal extends \Test\TestCase { /** @var IUserManager */ private $userManager; - /** @var IConfig */ - private $config; /** @var \OCA\DAV\Connector\Sabre\Principal */ private $connector; public function setUp() { $this->userManager = $this->getMockBuilder('\OCP\IUserManager') ->disableOriginalConstructor()->getMock(); - $this->config = $this->getMockBuilder('\OCP\IConfig') - ->disableOriginalConstructor()->getMock(); - $this->connector = new \OCA\DAV\Connector\Sabre\Principal($this->config, $this->userManager); + $this->connector = new \OCA\DAV\Connector\Sabre\Principal($this->userManager); parent::setUp(); } diff --git a/apps/dav/tests/unit/dav/groupprincipaltest.php b/apps/dav/tests/unit/dav/groupprincipaltest.php new file mode 100644 index 00000000000..bb1bd2526ed --- /dev/null +++ b/apps/dav/tests/unit/dav/groupprincipaltest.php @@ -0,0 +1,146 @@ +<?php + +namespace OCA\DAV\Tests\Unit\DAV; + +use OCA\DAV\DAV\GroupPrincipalBackend; +use OCP\IGroupManager; +use PHPUnit_Framework_MockObject_MockObject; +use \Sabre\DAV\PropPatch; + +class GroupPrincipalTest extends \Test\TestCase { + + /** @var IGroupManager | PHPUnit_Framework_MockObject_MockObject */ + private $groupManager; + + /** @var GroupPrincipalBackend */ + private $connector; + + public function setUp() { + $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager') + ->disableOriginalConstructor()->getMock(); + + $this->connector = new GroupPrincipalBackend($this->groupManager); + parent::setUp(); + } + + public function testGetPrincipalsByPrefixWithoutPrefix() { + $response = $this->connector->getPrincipalsByPrefix(''); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPrefixWithUsers() { + $group1 = $this->mockGroup('foo'); + $group2 = $this->mockGroup('bar'); + $this->groupManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([$group1, $group2])); + + $expectedResponse = [ + 0 => [ + 'uri' => 'principals/groups/foo', + '{DAV:}displayname' => 'foo' + ], + 1 => [ + 'uri' => 'principals/groups/bar', + '{DAV:}displayname' => 'bar', + ] + ]; + $response = $this->connector->getPrincipalsByPrefix('principals/groups'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPrefixEmpty() { + $this->groupManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([])); + + $response = $this->connector->getPrincipalsByPrefix('principals/groups'); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPathWithoutMail() { + $group1 = $this->mockGroup('foo'); + $this->groupManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($group1)); + + $expectedResponse = [ + 'uri' => 'principals/groups/foo', + '{DAV:}displayname' => 'foo' + ]; + $response = $this->connector->getPrincipalByPath('principals/groups/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathWithMail() { + $fooUser = $this->mockGroup('foo'); + $this->groupManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + + $expectedResponse = [ + 'uri' => 'principals/groups/foo', + '{DAV:}displayname' => 'foo', + ]; + $response = $this->connector->getPrincipalByPath('principals/groups/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathEmpty() { + $this->groupManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $response = $this->connector->getPrincipalByPath('principals/groups/foo'); + $this->assertSame(null, $response); + } + + public function testGetGroupMemberSet() { + $response = $this->connector->getGroupMemberSet('principals/groups/foo'); + $this->assertSame([], $response); + } + + public function testGetGroupMembership() { + $response = $this->connector->getGroupMembership('principals/groups/foo'); + $this->assertSame([], $response); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Setting members of the group is not supported yet + */ + public function testSetGroupMembership() { + $this->connector->setGroupMemberSet('principals/groups/foo', ['foo']); + } + + public function testUpdatePrincipal() { + $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array()))); + } + + public function testSearchPrincipals() { + $this->assertSame([], $this->connector->searchPrincipals('principals/groups', [])); + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject + */ + private function mockGroup($gid) { + $fooUser = $this->getMockBuilder('\OC\Group\Group') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(1)) + ->method('getGID') + ->will($this->returnValue($gid)); + return $fooUser; + } +} diff --git a/apps/dav/tests/unit/dav/systemprincipalbackendtest.php b/apps/dav/tests/unit/dav/systemprincipalbackendtest.php new file mode 100644 index 00000000000..96b1a8592c2 --- /dev/null +++ b/apps/dav/tests/unit/dav/systemprincipalbackendtest.php @@ -0,0 +1,131 @@ +<?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\Tests\DAV; + +use OCA\DAV\DAV\SystemPrincipalBackend; +use Test\TestCase; + +class SystemPrincipalBackendTest extends TestCase { + + /** + * @dataProvider providesPrefix + * @param $expected + * @param $prefix + */ + public function testGetPrincipalsByPrefix($expected, $prefix) { + $backend = new SystemPrincipalBackend(); + $result = $backend->getPrincipalsByPrefix($prefix); + $this->assertEquals($expected, $result); + } + + public function providesPrefix() { + return [ + [[], ''], + [[[ + 'uri' => 'principals/system/system', + '{DAV:}displayname' => 'system', + ]], 'principals/system'], + ]; + } + + /** + * @dataProvider providesPath + * @param $expected + * @param $path + */ + public function testGetPrincipalByPath($expected, $path) { + $backend = new SystemPrincipalBackend(); + $result = $backend->getPrincipalByPath($path); + $this->assertEquals($expected, $result); + } + + public function providesPath() { + return [ + [null, ''], + [null, 'principals'], + [null, 'principals/system'], + [[ + 'uri' => 'principals/system/system', + '{DAV:}displayname' => 'system', + ], 'principals/system/system'], + ]; + } + + /** + * @dataProvider providesPrincipalForGetGroupMemberSet + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + * + * @param string $principal + * @throws \Sabre\DAV\Exception + */ + public function testGetGroupMemberSetExceptional($principal) { + $backend = new SystemPrincipalBackend(); + $backend->getGroupMemberSet($principal); + } + + public function providesPrincipalForGetGroupMemberSet() { + return [ + [null], + ['principals/system'], + ]; + } + + /** + * @throws \Sabre\DAV\Exception + */ + public function testGetGroupMemberSet() { + $backend = new SystemPrincipalBackend(); + $result = $backend->getGroupMemberSet('principals/system/system'); + $this->assertEquals(['principals/system/system'], $result); + } + + /** + * @dataProvider providesPrincipalForGetGroupMembership + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + * + * @param string $principal + * @throws \Sabre\DAV\Exception + */ + public function testGetGroupMembershipExceptional($principal) { + $backend = new SystemPrincipalBackend(); + $backend->getGroupMembership($principal); + } + + public function providesPrincipalForGetGroupMembership() { + return [ + ['principals/system/a'], + ]; + } + + /** + * @throws \Sabre\DAV\Exception + */ + public function testGetGroupMembership() { + $backend = new SystemPrincipalBackend(); + $result = $backend->getGroupMembership('principals/system/system'); + $this->assertEquals([], $result); + } + + +} diff --git a/apps/dav/tests/unit/phpunit.xml b/apps/dav/tests/unit/phpunit.xml index 46c3cdfb345..314855d863b 100644 --- a/apps/dav/tests/unit/phpunit.xml +++ b/apps/dav/tests/unit/phpunit.xml @@ -6,7 +6,7 @@ timeoutForLargeTests="900" > <testsuite name='unit'> - <directory suffix='test.php'>.</directory> + <directory suffix='.php'>.</directory> </testsuite> <!-- filters for code coverage --> <filter> diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php index 0c8418c67a8..8fa42be27fc 100644 --- a/apps/encryption/lib/keymanager.php +++ b/apps/encryption/lib/keymanager.php @@ -213,7 +213,7 @@ class KeyManager { } /** - * @param $password + * @param string $password * @return bool */ public function checkRecoveryPassword($password) { diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php index cffa641f517..98d8c79a40a 100644 --- a/apps/encryption/lib/recovery.php +++ b/apps/encryption/lib/recovery.php @@ -68,10 +68,6 @@ class Recovery { * @var IFile */ private $file; - /** - * @var string - */ - private $recoveryKeyId; /** * @param IUserSession $user @@ -102,7 +98,6 @@ class Recovery { } /** - * @param $recoveryKeyId * @param string $password * @return bool */ @@ -112,6 +107,9 @@ class Recovery { if (!$keyManager->recoveryKeyExists()) { $keyPair = $this->crypt->createKeyPair(); + if(!is_array($keyPair)) { + return false; + } $this->keyManager->setRecoveryKey($password, $keyPair); } @@ -134,6 +132,9 @@ class Recovery { public function changeRecoveryKeyPassword($newPassword, $oldPassword) { $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword); + if($decryptedRecoveryKey === false) { + return false; + } $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword); $header = $this->crypt->generateHeader(); if ($encryptedRecoveryKey) { @@ -264,8 +265,9 @@ class Recovery { $encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, $recoveryPassword); - - $this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user); + if($privateKey !== false) { + $this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user); + } } /** diff --git a/apps/encryption/tests/lib/RecoveryTest.php b/apps/encryption/tests/lib/RecoveryTest.php index 0cdd76d6b44..82f5341960e 100644 --- a/apps/encryption/tests/lib/RecoveryTest.php +++ b/apps/encryption/tests/lib/RecoveryTest.php @@ -59,14 +59,44 @@ class RecoveryTest extends TestCase { */ private $instance; - public function testEnableAdminRecovery() { + public function testEnableAdminRecoverySuccessful() { $this->keyManagerMock->expects($this->exactly(2)) ->method('recoveryKeyExists') ->willReturnOnConsecutiveCalls(false, true); $this->cryptMock->expects($this->once()) ->method('createKeyPair') - ->willReturn(true); + ->willReturn([ + 'publicKey' => 'privateKey', + 'privateKey' => 'publicKey', + ]); + + $this->keyManagerMock->expects($this->once()) + ->method('setRecoveryKey') + ->willReturn(false); + + $this->keyManagerMock->expects($this->exactly(2)) + ->method('checkRecoveryPassword') + ->willReturnOnConsecutiveCalls(true, true); + + $this->assertTrue($this->instance->enableAdminRecovery('password')); + $this->assertArrayHasKey('recoveryAdminEnabled', self::$tempStorage); + $this->assertEquals(1, self::$tempStorage['recoveryAdminEnabled']); + + $this->assertTrue($this->instance->enableAdminRecovery('password')); + } + + public function testEnableAdminRecoveryCouldNotCheckPassword() { + $this->keyManagerMock->expects($this->exactly(2)) + ->method('recoveryKeyExists') + ->willReturnOnConsecutiveCalls(false, true); + + $this->cryptMock->expects($this->once()) + ->method('createKeyPair') + ->willReturn([ + 'publicKey' => 'privateKey', + 'privateKey' => 'publicKey', + ]); $this->keyManagerMock->expects($this->once()) ->method('setRecoveryKey') @@ -83,7 +113,19 @@ class RecoveryTest extends TestCase { $this->assertFalse($this->instance->enableAdminRecovery('password')); } - public function testChangeRecoveryKeyPassword() { + public function testEnableAdminRecoveryCouldNotCreateKey() { + $this->keyManagerMock->expects($this->once()) + ->method('recoveryKeyExists') + ->willReturn(false); + + $this->cryptMock->expects($this->once()) + ->method('createKeyPair') + ->willReturn(false); + + $this->assertFalse($this->instance->enableAdminRecovery('password')); + } + + public function testChangeRecoveryKeyPasswordSuccessful() { $this->assertFalse($this->instance->changeRecoveryKeyPassword('password', 'passwordOld')); @@ -101,6 +143,19 @@ class RecoveryTest extends TestCase { 'passwordOld')); } + public function testChangeRecoveryKeyPasswordCouldNotDecryptPrivateRecoveryKey() { + $this->assertFalse($this->instance->changeRecoveryKeyPassword('password', 'passwordOld')); + + $this->keyManagerMock->expects($this->once()) + ->method('getSystemPrivateKey'); + + $this->cryptMock->expects($this->once()) + ->method('decryptPrivateKey') + ->will($this->returnValue(false)); + + $this->assertFalse($this->instance->changeRecoveryKeyPassword('password', 'passwordOld')); + } + public function testDisableAdminRecovery() { $this->keyManagerMock->expects($this->exactly(2)) @@ -145,8 +200,7 @@ class RecoveryTest extends TestCase { $this->cryptMock->expects($this->once()) ->method('decryptPrivateKey'); - $this->assertNull($this->instance->recoverUsersFiles('password', - 'admin')); + $this->assertNull($this->instance->recoverUsersFiles('password', 'admin')); } public function testRecoverFile() { diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js index 4048bfc31bc..6137b7d89e6 100644 --- a/apps/files_external/js/statusmanager.js +++ b/apps/files_external/js/statusmanager.js @@ -77,7 +77,7 @@ OCA.External.StatusManager = { } else { defObj = $.ajax({ type : 'GET', - url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'globalstorages') + '/' + mountData.id, + url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'userglobalstorages') + '/' + mountData.id, success : function(response) { if (response && response.status === 0) { self.mountStatus[mountData.mount_point] = response; diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 49a40a9a5d7..b369a45f407 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -29,6 +29,8 @@ use \OCA\Files_External\Lib\Auth\AuthMechanism; * External storage configuration */ class StorageConfig implements \JsonSerializable { + const MOUNT_TYPE_ADMIN = 1; + const MOUNT_TYPE_PERSONAl = 2; /** * Storage config id @@ -108,6 +110,13 @@ class StorageConfig implements \JsonSerializable { private $mountOptions = []; /** + * Whether it's a personal or admin mount + * + * @var int + */ + private $type; + + /** * Creates a storage config * * @param int|null $id config id or null for a new config @@ -350,6 +359,20 @@ class StorageConfig implements \JsonSerializable { } /** + * @return int self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAl + */ + public function getType() { + return $this->type; + } + + /** + * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAl + */ + public function setType($type) { + $this->type = $type; + } + + /** * Serialize config to JSON * * @return array diff --git a/apps/files_external/service/dbconfigservice.php b/apps/files_external/service/dbconfigservice.php index 76f7052c4ed..810a57963f8 100644 --- a/apps/files_external/service/dbconfigservice.php +++ b/apps/files_external/service/dbconfigservice.php @@ -214,6 +214,34 @@ class DBConfigService { /** * @param int $mountId + * @param string $newMountPoint + */ + public function setMountPoint($mountId, $newMountPoint) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('external_mounts') + ->set('mount_point', $builder->createNamedParameter($newMountPoint)) + ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, \PDO::PARAM_INT))); + + $query->execute(); + } + + /** + * @param int $mountId + * @param string $newAuthBackend + */ + public function setAuthBackend($mountId, $newAuthBackend) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('external_mounts') + ->set('auth_backend', $builder->createNamedParameter($newAuthBackend)) + ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, \PDO::PARAM_INT))); + + $query->execute(); + } + + /** + * @param int $mountId * @param string $key * @param string $value */ diff --git a/apps/files_external/service/globalstoragesservice.php b/apps/files_external/service/globalstoragesservice.php index 2d25288e7bc..c524020e025 100644 --- a/apps/files_external/service/globalstoragesservice.php +++ b/apps/files_external/service/globalstoragesservice.php @@ -157,4 +157,8 @@ class GlobalStoragesService extends StoragesService { public function getVisibilityType() { return BackendService::VISIBILITY_ADMIN; } + + protected function isApplicable(StorageConfig $config) { + return true; + } } diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php index 97f79c13324..26ff956d27e 100644 --- a/apps/files_external/service/storagesservice.php +++ b/apps/files_external/service/storagesservice.php @@ -23,12 +23,9 @@ namespace OCA\Files_external\Service; -use \OCP\IUserSession; use \OC\Files\Filesystem; - use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_external\NotFoundException; -use \OCA\Files_External\Service\BackendService; use \OCA\Files_External\Lib\Backend\Backend; use \OCA\Files_External\Lib\Auth\AuthMechanism; use \OCP\Files\StorageNotAvailableException; @@ -85,6 +82,7 @@ abstract class StoragesService { array_values($applicableGroups), $mount['priority'] ); + $config->setType($mount['type']); $config->setId((int)$mount['mount_id']); return $config; } catch (\UnexpectedValueException $e) { @@ -132,10 +130,23 @@ abstract class StoragesService { throw new NotFoundException('Storage with id "' . $id . '" not found'); } - return $this->getStorageConfigFromDBMount($mount); + $config = $this->getStorageConfigFromDBMount($mount); + if ($this->isApplicable($config)) { + return $config; + } else { + throw new NotFoundException('Storage with id "' . $id . '" not found'); + } } /** + * Check whether this storage service should provide access to a storage + * + * @param StorageConfig $config + * @return bool + */ + abstract protected function isApplicable(StorageConfig $config); + + /** * Gets all storages, valid or not * * @return StorageConfig[] array of storage configs @@ -392,6 +403,14 @@ abstract class StoragesService { $this->dbConfig->setOption($id, $key, $value); } + if ($updatedStorage->getMountPoint() !== $oldStorage->getMountPoint()) { + $this->dbConfig->setMountPoint($id, $updatedStorage->getMountPoint()); + } + + if ($updatedStorage->getAuthMechanism()->getIdentifier() !== $oldStorage->getAuthMechanism()->getIdentifier()) { + $this->dbConfig->setAuthBackend($id, $updatedStorage->getAuthMechanism()->getIdentifier()); + } + $this->triggerChangeHooks($oldStorage, $updatedStorage); return $this->getStorage($id); diff --git a/apps/files_external/service/userglobalstoragesservice.php b/apps/files_external/service/userglobalstoragesservice.php index e58815f8a79..50973883563 100644 --- a/apps/files_external/service/userglobalstoragesservice.php +++ b/apps/files_external/service/userglobalstoragesservice.php @@ -152,4 +152,22 @@ class UserGlobalStoragesService extends GlobalStoragesService { return 0; } + protected function isApplicable(StorageConfig $config) { + $applicableUsers = $config->getApplicableUsers(); + $applicableGroups = $config->getApplicableGroups(); + + if (count($applicableUsers) === 0 && count($applicableGroups) === 0) { + return true; + } + if (in_array($this->getUser()->getUID(), $applicableUsers, true)) { + return true; + } + $groupIds = $this->groupManager->getUserGroupIds($this->getUser()); + foreach ($groupIds as $groupId) { + if (in_array($groupId, $applicableGroups, true)) { + return true; + } + } + return false; + } } diff --git a/apps/files_external/service/userstoragesservice.php b/apps/files_external/service/userstoragesservice.php index 5cf04578caf..19981dd0137 100644 --- a/apps/files_external/service/userstoragesservice.php +++ b/apps/files_external/service/userstoragesservice.php @@ -104,12 +104,25 @@ class UserStoragesService extends StoragesService { * @return StorageConfig storage config, with added id */ public function addStorage(StorageConfig $newStorage) { + $newStorage->setApplicableUsers([$this->getUser()->getUID()]); $config = parent::addStorage($newStorage); - $this->dbConfig->addApplicable($config->getId(), DBConfigService::APPLICABLE_TYPE_USER, $this->getUser()->getUID()); return $config; } /** + * Update storage to the configuration + * + * @param StorageConfig $updatedStorage storage attributes + * + * @return StorageConfig storage config + * @throws NotFoundException if the given storage does not exist in the config + */ + public function updateStorage(StorageConfig $updatedStorage) { + $updatedStorage->setApplicableUsers([$this->getUser()->getUID()]); + return parent::updateStorage($updatedStorage); + } + + /** * Get the visibility type for this controller, used in validation * * @return string BackendService::VISIBILITY_* constants @@ -117,4 +130,8 @@ class UserStoragesService extends StoragesService { public function getVisibilityType() { return BackendService::VISIBILITY_PERSONAL; } + + protected function isApplicable(StorageConfig $config) { + return ($config->getApplicableUsers() === [$this->getUser()->getUID()]) && $config->getType() === StorageConfig::MOUNT_TYPE_PERSONAl; + } } diff --git a/apps/files_external/tests/service/dbconfigservicetest.php b/apps/files_external/tests/service/dbconfigservicetest.php index d5b4ff1585d..bfb564c6663 100644 --- a/apps/files_external/tests/service/dbconfigservicetest.php +++ b/apps/files_external/tests/service/dbconfigservicetest.php @@ -230,4 +230,32 @@ class DBConfigServiceTest extends TestCase { $this->assertEquals($id1, $mounts[0]['mount_id']); $this->assertEquals([['type' => DBConfigService::APPLICABLE_TYPE_GLOBAL, 'value' => null, 'mount_id' => $id1]], $mounts[0]['applicable']); } + + public function testSetMountPoint() { + $id1 = $this->addMount('/test', 'foo', 'bar', 100, DBConfigService::MOUNT_TYPE_ADMIN); + $id2 = $this->addMount('/foo', 'foo', 'bar', 100, DBConfigService::MOUNT_TYPE_ADMIN); + + $this->dbConfig->setMountPoint($id1, '/asd'); + + $mount = $this->dbConfig->getMountById($id1); + $this->assertEquals('/asd', $mount['mount_point']); + + // remains unchanged + $mount = $this->dbConfig->getMountById($id2); + $this->assertEquals('/foo', $mount['mount_point']); + } + + public function testSetAuthBackend() { + $id1 = $this->addMount('/test', 'foo', 'bar', 100, DBConfigService::MOUNT_TYPE_ADMIN); + $id2 = $this->addMount('/foo', 'foo', 'bar', 100, DBConfigService::MOUNT_TYPE_ADMIN); + + $this->dbConfig->setAuthBackend($id1, 'none'); + + $mount = $this->dbConfig->getMountById($id1); + $this->assertEquals('none', $mount['auth_backend']); + + // remains unchanged + $mount = $this->dbConfig->getMountById($id2); + $this->assertEquals('bar', $mount['auth_backend']); + } } diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php index 7847bd45d4a..d5430982899 100644 --- a/apps/files_external/tests/service/storagesservicetest.php +++ b/apps/files_external/tests/service/storagesservicetest.php @@ -465,4 +465,33 @@ abstract class StoragesServiceTest extends \Test\TestCase { $params[Filesystem::signal_param_users] ); } + + public function testUpdateStorageMountPoint() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $savedStorage = $this->service->addStorage($storage); + + $newAuthMechanism = $this->backendService->getAuthMechanism('identifier:\Other\Auth\Mechanism'); + + $updatedStorage = new StorageConfig($savedStorage->getId()); + $updatedStorage->setMountPoint('mountpoint2'); + $updatedStorage->setBackend($backend); + $updatedStorage->setAuthMechanism($newAuthMechanism); + $updatedStorage->setBackendOptions(['password' => 'password2']); + + $this->service->updateStorage($updatedStorage); + + $savedStorage = $this->service->getStorage($updatedStorage->getId()); + + $this->assertEquals('/mountpoint2', $savedStorage->getMountPoint()); + $this->assertEquals($newAuthMechanism, $savedStorage->getAuthMechanism()); + $this->assertEquals('password2', $savedStorage->getBackendOption('password')); + } } diff --git a/apps/files_external/tests/service/userglobalstoragesservicetest.php b/apps/files_external/tests/service/userglobalstoragesservicetest.php index b8379288d43..d4c48b3f691 100644 --- a/apps/files_external/tests/service/userglobalstoragesservicetest.php +++ b/apps/files_external/tests/service/userglobalstoragesservicetest.php @@ -21,6 +21,7 @@ */ namespace OCA\Files_External\Tests\Service; +use OCA\Files_external\NotFoundException; use OCA\Files_external\Service\StoragesService; use \OCA\Files_External\Service\UserGlobalStoragesService; use \OCP\IGroupManager; @@ -140,6 +141,13 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $this->assertEquals('/mountpoint', $retrievedStorage->getMountPoint()); } else { $this->assertEquals(0, count($storages)); + + try { + $this->service->getStorage($newStorage->getId()); + $this->fail('Failed asserting that storage can\'t be accessed by id'); + } catch (NotFoundException $e) { + + } } } @@ -349,4 +357,8 @@ class UserGlobalStoragesServiceTest extends GlobalStoragesServiceTest { $this->assertTrue(true); } + public function testUpdateStorageMountPoint() { + // we don't test this here + $this->assertTrue(true); + } } diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php index 5e984c52bfd..a2d3819427c 100644 --- a/apps/files_external/tests/service/userstoragesservicetest.php +++ b/apps/files_external/tests/service/userstoragesservicetest.php @@ -23,6 +23,8 @@ namespace OCA\Files_external\Tests\Service; use \OC\Files\Filesystem; +use OCA\Files_external\Service\GlobalStoragesService; +use OCA\Files_external\Service\StoragesService; use \OCA\Files_external\Service\UserStoragesService; use \OCA\Files_external\NotFoundException; use \OCA\Files_external\Lib\StorageConfig; @@ -38,9 +40,16 @@ class UserStoragesServiceTest extends StoragesServiceTest { private $userId; + /** + * @var StoragesService + */ + protected $globalStoragesService; + public function setUp() { parent::setUp(); + $this->globalStoragesService = new GlobalStoragesService($this->backendService, $this->dbConfig); + $this->userId = $this->getUniqueID('user_'); $this->createUser($this->userId, $this->userId); $this->user = \OC::$server->getUserManager()->get($this->userId); @@ -122,8 +131,8 @@ class UserStoragesServiceTest extends StoragesServiceTest { $newStorage = $this->service->updateStorage($newStorage); $this->assertEquals('anotherPassword', $newStorage->getBackendOptions()['password']); + $this->assertEquals([$this->userId], $newStorage->getApplicableUsers()); // these attributes are unused for user storages - $this->assertEmpty($newStorage->getApplicableUsers()); $this->assertEmpty($newStorage->getApplicableGroups()); $this->assertEquals(0, $newStorage->getStatus()); @@ -174,4 +183,25 @@ class UserStoragesServiceTest extends StoragesServiceTest { $this->userId ); } + + /** + * @expectedException \OCA\Files_external\NotFoundException + */ + public function testGetAdminStorage() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + $storage->setApplicableUsers([$this->userId]); + + $newStorage = $this->globalStoragesService->addStorage($storage); + + $this->assertInstanceOf('\OCA\Files_external\Lib\StorageConfig', $this->globalStoragesService->getStorage($newStorage->getId())); + + $this->service->getStorage($newStorage->getId()); + } } diff --git a/apps/files_sharing/tests/js/publicAppSpec.js b/apps/files_sharing/tests/js/publicAppSpec.js index cedc51f98f1..17b7ffc9798 100644 --- a/apps/files_sharing/tests/js/publicAppSpec.js +++ b/apps/files_sharing/tests/js/publicAppSpec.js @@ -21,12 +21,13 @@ describe('OCA.Sharing.PublicApp tests', function() { var App = OCA.Sharing.PublicApp; - var hostStub, protocolStub, webrootStub; + var hostStub, portStub, protocolStub, webrootStub; var $preview; beforeEach(function() { protocolStub = sinon.stub(OC, 'getProtocol').returns('https'); hostStub = sinon.stub(OC, 'getHost').returns('example.com'); + portStub = sinon.stub(OC, 'getPort').returns(9876); webrootStub = sinon.stub(OC, 'getRootPath').returns('/owncloud'); $preview = $('<div id="preview"></div>'); $('#testArea').append($preview); @@ -40,6 +41,7 @@ describe('OCA.Sharing.PublicApp tests', function() { afterEach(function() { protocolStub.restore(); hostStub.restore(); + portStub.restore(); webrootStub.restore(); }); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 693a420a74d..7d2d355f9b4 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -1193,7 +1193,7 @@ class Access extends LDAPUtility implements user\IUserTools { $searchWords = explode(' ', trim($search)); $wordFilters = array(); foreach($searchWords as $word) { - $word .= '*'; + $word = $this->prepareSearchTerm($word); //every word needs to appear at least once $wordMatchOneAttrFilters = array(); foreach($searchAttributes as $attr) { @@ -1226,7 +1226,8 @@ class Access extends LDAPUtility implements user\IUserTools { ); } } - $search = empty($search) ? '*' : $search.'*'; + + $search = $this->prepareSearchTerm($search); if(!is_array($searchAttributes) || count($searchAttributes) === 0) { if(empty($fallbackAttribute)) { return ''; @@ -1244,6 +1245,22 @@ class Access extends LDAPUtility implements user\IUserTools { } /** + * returns the search term depending on whether we are allowed + * list users found by ldap with the current input appended by + * a * + * @return string + */ + private function prepareSearchTerm($term) { + $config = \OC::$server->getConfig(); + + $allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'); + + $result = empty($term) ? '*' : + $allowEnum !== 'no' ? $term . '*' : $term; + return $result; + } + + /** * returns the filter used for counting users * @return string */ diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 3f3953bb28b..67918bca409 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -570,15 +570,12 @@ class Connection extends LDAPUtility { * @param string $host * @param string $port * @return false|void + * @throws \OC\ServerNotAvailableException */ private function doConnect($host, $port) { if(empty($host)) { return false; } - if(strpos($host, '://') !== false) { - //ldap_connect ignores port parameter when URLs are passed - $host .= ':' . $port; - } $this->ldapConnectionRes = $this->ldap->connect($host, $port); if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { @@ -586,6 +583,8 @@ class Connection extends LDAPUtility { $this->ldap->startTls($this->ldapConnectionRes); } } + } else { + throw new \OC\ServerNotAvailableException('Could not set required LDAP Protocol version.'); } } diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index 4d45db2e155..e730bff82c3 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -48,7 +48,14 @@ class LDAP implements ILDAPWrapper { * @return mixed */ public function connect($host, $port) { - return $this->invokeLDAPMethod('connect', $host, $port); + if(strpos($host, '://') === false) { + $host = 'ldap://' . $host; + } + if(strpos($host, ':', strpos($host, '://') + 1) === false) { + //ldap_connect ignores port parameter when URLs are passed + $host .= ':' . $port; + } + return $this->invokeLDAPMethod('connect', $host); } /** diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index e53ff35cfd6..20926fb06a4 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -1035,13 +1035,6 @@ class Wizard extends LDAPUtility { if(!$hostInfo) { throw new \Exception($this->l->t('Invalid Host')); } - if(isset($hostInfo['scheme'])) { - if(isset($hostInfo['port'])) { - //problem - } else { - $host .= ':' . $port; - } - } \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG); $cr = $this->ldap->connect($host, $port); if(!is_resource($cr)) { @@ -1291,12 +1284,10 @@ class Wizard extends LDAPUtility { return $this->cr; } - $host = $this->configuration->ldapHost; - if(strpos($host, '://') !== false) { - //ldap_connect ignores port parameter when URLs are passed - $host .= ':' . $this->configuration->ldapPort; - } - $cr = $this->ldap->connect($host, $this->configuration->ldapPort); + $cr = $this->ldap->connect( + $this->configuration->ldapHost, + $this->configuration->ldapPort + ); $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0); diff --git a/apps/user_ldap/tests/integration/lib/integrationtestconnect.php b/apps/user_ldap/tests/integration/lib/integrationtestconnect.php new file mode 100644 index 00000000000..878aa08f5ab --- /dev/null +++ b/apps/user_ldap/tests/integration/lib/integrationtestconnect.php @@ -0,0 +1,166 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * + * @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\user_ldap\tests\integration\lib; + +use OCA\user_ldap\lib\user\Manager as LDAPUserManager; +use OCA\user_ldap\tests\integration\AbstractIntegrationTest; +use OCA\User_LDAP\Mapping\UserMapping; +use OCA\user_ldap\USER_LDAP; + +require_once __DIR__ . '/../../../../../lib/base.php'; + +class IntegrationConnect extends AbstractIntegrationTest { + /** @var UserMapping */ + protected $mapping; + + /** @var USER_LDAP */ + protected $backend; + + /** @var string */ + protected $host; + + /** @var int */ + protected $port; + + public function __construct($host, $port, $bind, $pwd, $base) { + // make sure host is a simple host name + if(strpos($host, '://') !== false) { + $host = substr_replace($host, '', 0, strpos($host, '://') + 3); + } + if(strpos($host, ':') !== false) { + $host = substr_replace($host, '', strpos($host, ':')); + } + $this->host = $host; + $this->port = $port; + parent::__construct($host, $port, $bind, $pwd, $base); + } + + /** + * test that a faulty host will does not connect successfully + * + * @return bool + */ + protected function case1() { + // reset possible LDAP connection + $this->initConnection(); + $this->connection->setConfiguration([ + 'ldapHost' => 'qwertz.uiop', + ]); + try { + $this->connection->getConnectionResource(); + } catch (\OC\ServerNotAvailableException $e) { + return true; + } + return false; + } + + /** + * tests that a connect succeeds when only a hostname is provided + * + * @return bool + */ + protected function case2() { + // reset possible LDAP connection + $this->initConnection(); + $this->connection->setConfiguration([ + 'ldapHost' => $this->host, + ]); + try { + $this->connection->getConnectionResource(); + } catch (\OC\ServerNotAvailableException $e) { + return false; + } + return true; + } + + /** + * tests that a connect succeeds when an LDAP URL is provided + * + * @return bool + */ + protected function case3() { + // reset possible LDAP connection + $this->initConnection(); + $this->connection->setConfiguration([ + 'ldapHost' => 'ldap://' . $this->host, + ]); + try { + $this->connection->getConnectionResource(); + } catch (\OC\ServerNotAvailableException $e) { + return false; + } + return true; + } + + /** + * tests that a connect succeeds when an LDAP URL with port is provided + * + * @return bool + */ + protected function case4() { + // reset possible LDAP connection + $this->initConnection(); + $this->connection->setConfiguration([ + 'ldapHost' => 'ldap://' . $this->host . ':' . $this->port, + ]); + try { + $this->connection->getConnectionResource(); + } catch (\OC\ServerNotAvailableException $e) { + return false; + } + return true; + } + + /** + * tests that a connect succeeds when a hostname with port is provided + * + * @return bool + */ + protected function case5() { + // reset possible LDAP connection + $this->initConnection(); + $this->connection->setConfiguration([ + 'ldapHost' => $this->host . ':' . $this->port, + ]); + try { + $this->connection->getConnectionResource(); + } catch (\OC\ServerNotAvailableException $e) { + return false; + } + return true; + } + + /** + * repeat case1, only to make sure that not a connection was reused by + * accident. + * + * @return bool + */ + protected function case6() { + return $this->case1(); + } +} + +require_once(__DIR__ . '/../setup-scripts/config.php'); +$test = new IntegrationConnect($host, $port, $adn, $apwd, $bdn); +$test->init(); +$test->run(); |