diff --git a/apps/comments/l10n/bg_BG.js b/apps/comments/l10n/bg_BG.js index e63aedf80dbb1..c5fbba69f7b98 100644 --- a/apps/comments/l10n/bg_BG.js +++ b/apps/comments/l10n/bg_BG.js @@ -1,8 +1,19 @@ OC.L10N.register( "comments", { + "Type in a new comment..." : "Напиши нов коментар...", + "Delete comment" : "Изтрий коментар", "Cancel" : "Отказ", + "Edit comment" : "Редактирай коментра", + "[Deleted user]" : "[Изтрит потребител]", + "Comments" : "Коментари", + "No other comments available" : "Няма други коментари", + "More comments..." : "Още коментари...", "Save" : "Запазване", - "Comment" : "Коментар" + "Allowed characters {count} of {max}" : "Позволени символи {count} от {max}", + "{count} unread comments" : "{count} нечетени коментари", + "Comment" : "Коментар", + "Comments for files (always listed in stream)" : "Коментари на файлове (винаги изписвани в stream-а)", + "You commented" : "Вие коментирахте" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/comments/l10n/bg_BG.json b/apps/comments/l10n/bg_BG.json index 78ad0b57d4c13..64f516861caff 100644 --- a/apps/comments/l10n/bg_BG.json +++ b/apps/comments/l10n/bg_BG.json @@ -1,6 +1,17 @@ { "translations": { + "Type in a new comment..." : "Напиши нов коментар...", + "Delete comment" : "Изтрий коментар", "Cancel" : "Отказ", + "Edit comment" : "Редактирай коментра", + "[Deleted user]" : "[Изтрит потребител]", + "Comments" : "Коментари", + "No other comments available" : "Няма други коментари", + "More comments..." : "Още коментари...", "Save" : "Запазване", - "Comment" : "Коментар" + "Allowed characters {count} of {max}" : "Позволени символи {count} от {max}", + "{count} unread comments" : "{count} нечетени коментари", + "Comment" : "Коментар", + "Comments for files (always listed in stream)" : "Коментари на файлове (винаги изписвани в stream-а)", + "You commented" : "Вие коментирахте" },"pluralForm" :"nplurals=2; plural=(n != 1);" } \ No newline at end of file diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml index f79ea07ae76f5..9578526a7051a 100644 --- a/apps/dav/appinfo/database.xml +++ b/apps/dav/appinfo/database.xml @@ -272,6 +272,12 @@ CREATE TABLE calendarobjects ( text 255 + + 0 - public, 1 - private, 2 - confidential + classification + integer + 0 + calobjects_index true diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index ca456b0308989..26e37e6bb8610 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ ownCloud WebDAV endpoint AGPL owncloud.org - 0.2.4 + 0.2.5 @@ -20,4 +20,9 @@ OCA\DAV\CardDAV\Sync\SyncJob + + + OCA\DAV\Migration\Classification + + diff --git a/apps/dav/appinfo/v1/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php index c6c319aa36dff..261a4d4b96de5 100644 --- a/apps/dav/appinfo/v1/publicwebdav.php +++ b/apps/dav/appinfo/v1/publicwebdav.php @@ -66,7 +66,6 @@ $share = $authBackend->getShare(); $owner = $share->getShareOwner(); - $isWritable = $share->getPermissions() & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE); $isReadable = $share->getPermissions() & \OCP\Constants::PERMISSION_READ; $fileId = $share->getNodeId(); @@ -74,11 +73,9 @@ return false; } - if (!$isWritable) { - \OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, $storage) { - return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => \OCP\Constants::PERMISSION_READ + \OCP\Constants::PERMISSION_SHARE)); - }); - } + \OC\Files\Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) { + return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => $share->getPermissions() | \OCP\Constants::PERMISSION_SHARE)); + }); OC_Util::setupFS($owner); $ownerView = \OC\Files\Filesystem::getView(); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index ba0ef421f9757..9e0d2da4e17d5 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -31,10 +31,12 @@ use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\GroupPrincipalBackend; use OCA\DAV\HookManager; +use OCA\DAV\Migration\Classification; use \OCP\AppFramework\App; use OCP\AppFramework\IAppContainer; use OCP\Contacts\IManager; use OCP\IUser; +use Sabre\VObject\Reader; use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { @@ -106,6 +108,14 @@ public function __construct (array $urlParams=array()) { $g ); }); + + $container->registerService('OCA\DAV\Migration\Classification', function ($c) { + /** @var IAppContainer $c */ + return new Classification( + $c->query('CalDavBackend'), + $c->getServer()->getUserManager() + ); + }); } /** diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 64fdf0f7ebec1..ce4940829761d 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -37,10 +37,11 @@ use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; -use Sabre\VObject\RecurrenceIterator; +use Sabre\VObject\Recur\EventIterator; /** * Class CalDavBackend @@ -61,6 +62,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ const MAX_DATE = '2038-01-01'; + const CLASSIFICATION_PUBLIC = 0; + const CLASSIFICATION_PRIVATE = 1; + const CLASSIFICATION_CONFIDENTIAL = 2; + /** * List of CalDAV properties, and how they map to database field names * Add your own properties by simply adding on to this array. @@ -395,10 +400,10 @@ function createCalendar($principalUri, $calendarUri, array $properties) { * * Read the PropPatch documentation for more info and examples. * - * @param \Sabre\DAV\PropPatch $propPatch + * @param PropPatch $propPatch * @return void */ - function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { + function updateCalendar($calendarId, PropPatch $propPatch) { $supportedProperties = array_keys($this->propertyMap); $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; @@ -484,7 +489,7 @@ function deleteCalendar($calendarId) { */ function getCalendarObjects($calendarId) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $stmt = $query->execute(); @@ -499,6 +504,7 @@ function getCalendarObjects($calendarId) { 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), + 'classification'=> (int)$row['classification'] ]; } @@ -524,7 +530,7 @@ function getCalendarObjects($calendarId) { function getCalendarObject($calendarId, $objectUri) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))); @@ -542,6 +548,7 @@ function getCalendarObject($calendarId, $objectUri) { 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), + 'classification'=> (int)$row['classification'] ]; } @@ -559,7 +566,7 @@ function getCalendarObject($calendarId, $objectUri) { */ function getMultipleCalendarObjects($calendarId, array $uris) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))) @@ -579,6 +586,7 @@ function getMultipleCalendarObjects($calendarId, array $uris) { 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), + 'classification' => (int)$row['classification'] ]; } @@ -618,6 +626,7 @@ function createCalendarObject($calendarId, $objectUri, $calendarData) { 'componenttype' => $query->createNamedParameter($extraData['componentType']), 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']), 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']), + 'classification' => $query->createNamedParameter($extraData['classification']), 'uid' => $query->createNamedParameter($extraData['uid']), ]) ->execute(); @@ -657,6 +666,7 @@ function updateCalendarObject($calendarId, $objectUri, $calendarData) { ->set('componenttype', $query->createNamedParameter($extraData['componentType'])) ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence'])) ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence'])) + ->set('classification', $query->createNamedParameter($extraData['classification'])) ->set('uid', $query->createNamedParameter($extraData['uid'])) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) @@ -667,6 +677,23 @@ function updateCalendarObject($calendarId, $objectUri, $calendarData) { return '"' . $extraData['etag'] . '"'; } + /** + * @param int $calendarObjectId + * @param int $classification + */ + public function setClassification($calendarObjectId, $classification) { + if (!in_array($classification, [ + self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL + ])) { + throw new \InvalidArgumentException(); + } + $query = $this->db->getQueryBuilder(); + $query->update('calendarobjects') + ->set('classification', $query->createNamedParameter($classification)) + ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId))) + ->execute(); + } + /** * Deletes an existing calendar object. * @@ -1086,10 +1113,10 @@ function createSubscription($principalUri, $uri, array $properties) { * Read the PropPatch documentation for more info and examples. * * @param mixed $subscriptionId - * @param \Sabre\DAV\PropPatch $propPatch + * @param PropPatch $propPatch * @return void */ - function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + function updateSubscription($subscriptionId, PropPatch $propPatch) { $supportedProperties = array_keys($this->subscriptionPropertyMap); $supportedProperties[] = '{http://calendarserver.org/ns/}source'; @@ -1280,14 +1307,15 @@ protected function addChange($calendarId, $objectUri, $operation) { * @param string $calendarData * @return array */ - protected function getDenormalizedData($calendarData) { + public function getDenormalizedData($calendarData) { $vObject = Reader::read($calendarData); $componentType = null; $component = null; - $firstOccurence = null; - $lastOccurence = null; + $firstOccurrence = null; + $lastOccurrence = null; $uid = null; + $classification = self::CLASSIFICATION_PUBLIC; foreach($vObject->getComponents() as $component) { if ($component->name!=='VTIMEZONE') { $componentType = $component->name; @@ -1299,27 +1327,27 @@ protected function getDenormalizedData($calendarData) { throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } if ($componentType === 'VEVENT' && $component->DTSTART) { - $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); + $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { - $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); + $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); - $lastOccurence = $endDate->getTimeStamp(); + $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); - $lastOccurence = $endDate->getTimeStamp(); + $lastOccurrence = $endDate->getTimeStamp(); } else { - $lastOccurence = $firstOccurence; + $lastOccurrence = $firstOccurrence; } } else { - $it = new RecurrenceIterator($vObject, (string)$component->UID); + $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { - $lastOccurence = $maxDate->getTimeStamp(); + $lastOccurrence = $maxDate->getTimeStamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { @@ -1327,19 +1355,31 @@ protected function getDenormalizedData($calendarData) { $it->next(); } - $lastOccurence = $end->getTimeStamp(); + $lastOccurrence = $end->getTimeStamp(); } } } + if ($component->CLASS) { + $classification = CalDavBackend::CLASSIFICATION_PRIVATE; + switch ($component->CLASS->getValue()) { + case 'PUBLIC': + $classification = CalDavBackend::CLASSIFICATION_PUBLIC; + break; + case 'CONFIDENTIAL': + $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL; + break; + } + } return [ - 'etag' => md5($calendarData), - 'size' => strlen($calendarData), - 'componentType' => $componentType, - 'firstOccurence' => is_null($firstOccurence) ? null : max(0, $firstOccurence), - 'lastOccurence' => $lastOccurence, - 'uid' => $uid, + 'etag' => md5($calendarData), + 'size' => strlen($calendarData), + 'componentType' => $componentType, + 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence), + 'lastOccurence' => $lastOccurrence, + 'uid' => $uid, + 'classification' => $classification ]; } diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 73b3957a9b063..785bb5699e241 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -26,6 +26,7 @@ use OCP\IL10N; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { @@ -162,6 +163,78 @@ function propPatch(PropPatch $propPatch) { parent::propPatch($propPatch); } + function getChild($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + + if (!$obj) { + throw new NotFound('Calendar object not found'); + } + + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + throw new NotFound('Calendar object not found'); + } + + $obj['acl'] = $this->getChildACL(); + + return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + + } + + function getChildren() { + + $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); + $children = []; + foreach ($objs as $obj) { + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + continue; + } + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + function getMultipleChildren(array $paths) { + + $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + continue; + } + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + function childExists($name) { + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + if (!$obj) { + return false; + } + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + return false; + } + + return true; + } + + function calendarQuery(array $filters) { + + $uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); + if ($this->isShared()) { + return array_filter($uris, function ($uri) { + return $this->childExists($uri); + }); + } + + return $uris; + } + private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; @@ -169,4 +242,8 @@ private function canWrite() { return true; } + private function isShared() { + return isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']); + } + } diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php new file mode 100644 index 0000000000000..b4a58b52093f3 --- /dev/null +++ b/apps/dav/lib/CalDAV/CalendarObject.php @@ -0,0 +1,92 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + + +namespace OCA\DAV\CalDAV; + + +use Sabre\VObject\Component; +use Sabre\VObject\Property; +use Sabre\VObject\Reader; + +class CalendarObject extends \Sabre\CalDAV\CalendarObject { + + /** + * @inheritdoc + */ + function get() { + $data = parent::get(); + if ($this->isShared() && $this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL) { + return $this->createConfidentialObject($data); + } + return $data; + } + + private function isShared() { + return isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']); + } + + /** + * @param string $calData + * @return string + */ + private static function createConfidentialObject($calData) { + + $vObject = Reader::read($calData); + + /** @var Component $vElement */ + $vElement = null; + if(isset($vObject->VEVENT)) { + $vElement = $vObject->VEVENT; + } + if(isset($vObject->VJOURNAL)) { + $vElement = $vObject->VJOURNAL; + } + if(isset($vObject->VTODO)) { + $vElement = $vObject->VTODO; + } + if(!is_null($vElement)) { + foreach ($vElement->children as &$property) { + /** @var Property $property */ + switch($property->name) { + case 'CREATED': + case 'DTSTART': + case 'RRULE': + case 'DURATION': + case 'DTEND': + case 'CLASS': + case 'UID': + break; + case 'SUMMARY': + $property->setValue('Busy'); + break; + default: + $vElement->__unset($property->name); + unset($property); + break; + } + } + } + + return $vObject->serialize(); + } + +} diff --git a/apps/dav/lib/Connector/PublicAuth.php b/apps/dav/lib/Connector/PublicAuth.php index 2716ca29107c7..4e63ca1d29edb 100644 --- a/apps/dav/lib/Connector/PublicAuth.php +++ b/apps/dav/lib/Connector/PublicAuth.php @@ -31,13 +31,14 @@ use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; +use Sabre\DAV\Auth\Backend\AbstractBasic; /** * Class PublicAuth * * @package OCA\DAV\Connector */ -class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { +class PublicAuth extends AbstractBasic { /** @var \OCP\Share\IShare */ private $share; @@ -62,6 +63,10 @@ public function __construct(IRequest $request, $this->request = $request; $this->shareManager = $shareManager; $this->session = $session; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** @@ -99,7 +104,7 @@ protected function validateUserPass($username, $password) { if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup http_response_code(401); - header('WWW-Authenticate', 'DummyBasic real="ownCloud"'); + header('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } return false; diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 27900cc1cad07..653da10bc3c6b 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -74,6 +74,10 @@ public function __construct(ISession $session, $this->twoFactorManager = $twoFactorManager; $this->request = $request; $this->principalPrefix = $principalPrefix; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index dc47416cca8be..0a2e6713cb483 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -42,6 +42,7 @@ use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; +use OCP\IRequest; class FilesPlugin extends ServerPlugin { @@ -95,20 +96,29 @@ class FilesPlugin extends ServerPlugin { */ private $config; + /** + * @var IRequest + */ + private $request; + /** * @param Tree $tree * @param View $view + * @param IConfig $config + * @param IRequest $request * @param bool $isPublic * @param bool $downloadAttachment */ public function __construct(Tree $tree, View $view, IConfig $config, + IRequest $request, $isPublic = false, $downloadAttachment = true) { $this->tree = $tree; $this->fileView = $view; $this->config = $config; + $this->request = $request; $this->isPublic = $isPublic; $this->downloadAttachment = $downloadAttachment; } @@ -225,7 +235,18 @@ function httpGet(RequestInterface $request, ResponseInterface $response) { // adds a 'Content-Disposition: attachment' header if ($this->downloadAttachment) { - $response->addHeader('Content-Disposition', 'attachment'); + $filename = $node->getName(); + if ($this->request->isUserAgent( + [ + \OC\AppFramework\Http\Request::USER_AGENT_IE, + \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, + \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, + ])) { + $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"'); + } else { + $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename) + . '; filename="' . rawurlencode($filename) . '"'); + } } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index b193bfc76c78a..c5b4f6a9352d2 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -100,10 +100,9 @@ public function createServer($baseUri, $server->setBaseUri($baseUri); // Load plugins - $defaults = new \OC_Defaults(); $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); - $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); + $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend)); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); @@ -144,6 +143,7 @@ public function createServer($baseUri, $objectTree, $view, $this->config, + $this->request, false, !$this->config->getSystemValue('debug', false) ) diff --git a/apps/dav/lib/Migration/Classification.php b/apps/dav/lib/Migration/Classification.php new file mode 100644 index 0000000000000..b793f790af5e6 --- /dev/null +++ b/apps/dav/lib/Migration/Classification.php @@ -0,0 +1,93 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + + +namespace OCA\DAV\Migration; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class Classification implements IRepairStep { + + /** @var CalDavBackend */ + private $calDavBackend; + + /** @var IUserManager */ + private $userManager; + + /** + * Classification constructor. + * + * @param CalDavBackend $calDavBackend + */ + public function __construct(CalDavBackend $calDavBackend, IUserManager $userManager) { + $this->calDavBackend = $calDavBackend; + $this->userManager = $userManager; + } + + /** + * @param IUser $user + */ + public function runForUser($user) { + $principal = 'principals/users/' . $user->getUID(); + $calendars = $this->calDavBackend->getCalendarsForUser($principal); + foreach ($calendars as $calendar) { + $objects = $this->calDavBackend->getCalendarObjects($calendar['id']); + foreach ($objects as $object) { + $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['uri']); + $classification = $this->extractClassification($calObject['calendardata']); + $this->calDavBackend->setClassification($object['id'], $classification); + } + } + } + + /** + * @param $calendarData + * @return integer + * @throws \Sabre\DAV\Exception\BadRequest + */ + protected function extractClassification($calendarData) { + return $this->calDavBackend->getDenormalizedData($calendarData)['classification']; + } + + /** + * @inheritdoc + */ + public function getName() { + return 'Fix classification for calendar objects'; + } + + /** + * @inheritdoc + */ + public function run(IOutput $output) { + $output->startProgress(); + $this->userManager->callForAllUsers(function($user) use ($output) { + /** @var IUser $user */ + $output->advance(1, $user->getDisplayName()); + $this->runForUser($user); + }); + $output->finishProgress(); + } +} diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 179558e97ae5d..e150f441b8238 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -141,6 +141,7 @@ public function __construct(IRequest $request, $baseUri) { $this->server->tree, $view, \OC::$server->getConfig(), + $this->request, false, !\OC::$server->getConfig()->getSystemValue('debug', false) ) diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php new file mode 100644 index 0000000000000..49e5e5a2bcc2d --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php @@ -0,0 +1,163 @@ + + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, 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 + * + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use DateTime; +use DateTimeZone; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\Connector\Sabre\Principal; +use OCP\IL10N; +use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Xml\Property\Href; +use Sabre\DAVACL\IACL; +use Test\TestCase; + +/** + * Class CalDavBackendTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\CalDAV + */ +abstract class AbstractCalDavBackendTest extends TestCase { + + /** @var CalDavBackend */ + protected $backend; + + /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ + protected $principal; + + const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; + const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; + const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; + + public function setUp() { + parent::setUp(); + + $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') + ->disableOriginalConstructor() + ->setMethods(['getPrincipalByPath', 'getGroupMembership']) + ->getMock(); + $this->principal->expects($this->any())->method('getPrincipalByPath') + ->willReturn([ + 'uri' => 'principals/best-friend' + ]); + $this->principal->expects($this->any())->method('getGroupMembership') + ->withAnyParameters() + ->willReturn([self::UNIT_TEST_GROUP]); + + $db = \OC::$server->getDatabaseConnection(); + $this->backend = new CalDavBackend($db, $this->principal); + + $this->tearDown(); + } + + public function tearDown() { + parent::tearDown(); + + if (is_null($this->backend)) { + return; + } + $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); + foreach ($books as $book) { + $this->backend->deleteCalendar($book['id']); + } + $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER); + foreach ($subscriptions as $subscription) { + $this->backend->deleteSubscription($subscription['id']); + } + } + + protected function createTestCalendar() { + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [ + '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF' + ]); + $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); + $this->assertEquals(1, count($calendars)); + $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']); + /** @var SupportedCalendarComponentSet $components */ + $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']; + $this->assertEquals(['VEVENT','VTODO'], $components->getValue()); + $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color']; + $this->assertEquals('#1C4587FF', $color); + $this->assertEquals('Example', $calendars[0]['uri']); + $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']); + $calendarId = $calendars[0]['id']; + + return $calendarId; + } + + protected function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') { + + $calData = <<getUniqueID('event'); + $this->backend->createCalendarObject($calendarId, $uri0, $calData); + + return $uri0; + } + + protected function assertAcl($principal, $privilege, $acl) { + foreach($acl as $a) { + if ($a['principal'] === $principal && $a['privilege'] === $privilege) { + $this->assertTrue(true); + return; + } + } + $this->fail("ACL does not contain $principal / $privilege"); + } + + protected function assertNotAcl($principal, $privilege, $acl) { + foreach($acl as $a) { + if ($a['principal'] === $principal && $a['privilege'] === $privilege) { + $this->fail("ACL contains $principal / $privilege"); + return; + } + } + $this->assertTrue(true); + } + + protected function assertAccess($shouldHaveAcl, $principal, $privilege, $acl) { + if ($shouldHaveAcl) { + $this->assertAcl($principal, $privilege, $acl); + } else { + $this->assertNotAcl($principal, $privilege, $acl); + } + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index c3e32e436d9b1..977bdf15c8e3b 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -26,13 +26,10 @@ use DateTimeZone; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; -use OCA\DAV\Connector\Sabre\Principal; use OCP\IL10N; -use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV\PropPatch; use Sabre\DAV\Xml\Property\Href; use Sabre\DAVACL\IACL; -use Test\TestCase; /** * Class CalDavBackendTest @@ -41,54 +38,7 @@ * * @package OCA\DAV\Tests\unit\CalDAV */ -class CalDavBackendTest extends TestCase { - - /** @var CalDavBackend */ - private $backend; - - /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ - private $principal; - - const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; - const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; - const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; - - public function setUp() { - parent::setUp(); - - $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') - ->disableOriginalConstructor() - ->setMethods(['getPrincipalByPath', 'getGroupMembership']) - ->getMock(); - $this->principal->expects($this->any())->method('getPrincipalByPath') - ->willReturn([ - 'uri' => 'principals/best-friend' - ]); - $this->principal->expects($this->any())->method('getGroupMembership') - ->withAnyParameters() - ->willReturn([self::UNIT_TEST_GROUP]); - - $db = \OC::$server->getDatabaseConnection(); - $this->backend = new CalDavBackend($db, $this->principal); - - $this->tearDown(); - } - - public function tearDown() { - parent::tearDown(); - - if (is_null($this->backend)) { - return; - } - $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - foreach ($books as $book) { - $this->backend->deleteCalendar($book['id']); - } - $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER); - foreach ($subscriptions as $subscription) { - $this->backend->deleteSubscription($subscription['id']); - } - } +class CalDavBackendTest extends AbstractCalDavBackendTest { public function testCalendarOperations() { @@ -232,6 +182,7 @@ public function testCalendarObjectsOperations() { $calendarObjects = $this->backend->getCalendarObjects($calendarId); $this->assertEquals(1, count($calendarObjects)); $this->assertEquals($calendarId, $calendarObjects[0]['calendarid']); + $this->assertArrayHasKey('classification', $calendarObjects[0]); // get the cards $calendarObject = $this->backend->getCalendarObject($calendarId, $uri); @@ -241,6 +192,7 @@ public function testCalendarObjectsOperations() { $this->assertArrayHasKey('lastmodified', $calendarObject); $this->assertArrayHasKey('etag', $calendarObject); $this->assertArrayHasKey('size', $calendarObject); + $this->assertArrayHasKey('classification', $calendarObject); $this->assertEquals($calData, $calendarObject['calendardata']); // update the card @@ -310,6 +262,7 @@ public function testMultiCalendarObjects() { $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); + $this->assertArrayHasKey('classification', $card); $this->assertEquals($calData, $card['calendardata']); } @@ -363,49 +316,6 @@ public function providesCalendarQueryParameters() { ]; } - private function createTestCalendar() { - $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [ - '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF' - ]); - $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - $this->assertEquals(1, count($calendars)); - $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']); - /** @var SupportedCalendarComponentSet $components */ - $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']; - $this->assertEquals(['VEVENT','VTODO'], $components->getValue()); - $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color']; - $this->assertEquals('#1C4587FF', $color); - $this->assertEquals('Example', $calendars[0]['uri']); - $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']); - $calendarId = $calendars[0]['id']; - - return $calendarId; - } - - private function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') { - - $calData = <<getUniqueID('event'); - $this->backend->createCalendarObject($calendarId, $uri0, $calData); - - return $uri0; - } - public function testSyncSupport() { $calendarId = $this->createTestCalendar(); @@ -464,43 +374,20 @@ public function testScheduling() { /** * @dataProvider providesCalDataForGetDenormalizedData */ - public function testGetDenormalizedData($expectedFirstOccurance, $calData) { - $actual = $this->invokePrivate($this->backend, 'getDenormalizedData', [$calData]); - $this->assertEquals($expectedFirstOccurance, $actual['firstOccurence']); + public function testGetDenormalizedData($expected, $key, $calData) { + $actual = $this->backend->getDenormalizedData($calData); + $this->assertEquals($expected, $actual[$key]); } public function providesCalDataForGetDenormalizedData() { return [ - [0, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], - [null, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"] + 'first occurrence before unix epoch starts' => [0, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], + 'no first occurrence because yearly' => [null, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], + 'CLASS:PRIVATE' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PRIVATE\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'CLASS:PUBLIC' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PUBLIC\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'CLASS:CONFIDENTIAL' => [CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:CONFIDENTIAL\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'no class set -> public' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nTRANSP:OPAQUE\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'unknown class -> private' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:VERTRAULICH\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], ]; } - - private function assertAcl($principal, $privilege, $acl) { - foreach($acl as $a) { - if ($a['principal'] === $principal && $a['privilege'] === $privilege) { - $this->assertTrue(true); - return; - } - } - $this->fail("ACL does not contain $principal / $privilege"); - } - - private function assertNotAcl($principal, $privilege, $acl) { - foreach($acl as $a) { - if ($a['principal'] === $principal && $a['privilege'] === $privilege) { - $this->fail("ACL contains $principal / $privilege"); - return; - } - } - $this->assertTrue(true); - } - - private function assertAccess($shouldHaveAcl, $principal, $privilege, $acl) { - if ($shouldHaveAcl) { - $this->assertAcl($principal, $privilege, $acl); - } else { - $this->assertNotAcl($principal, $privilege, $acl); - } - } } diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index 73d85e82bbcef..56a2d4fcba76e 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -27,6 +27,7 @@ use OCA\DAV\CalDAV\Calendar; use OCP\IL10N; use Sabre\DAV\PropPatch; +use Sabre\VObject\Reader; use Test\TestCase; class CalendarTest extends TestCase { @@ -189,4 +190,153 @@ public function providesReadOnlyInfo() { 'birthday calendar' => [false, false, false, BirthdayService::BIRTHDAY_CALENDAR_URI] ]; } + + /** + * @dataProvider providesConfidentialClassificationData + * @param $expectedChildren + * @param $isShared + */ + public function testPrivateClassification($expectedChildren, $isShared) { + + $calObject0 = ['uri' => 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; + $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL]; + $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; + + /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */ + $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock(); + $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getMultipleCalendarObjects') + ->with(666, ['event-0', 'event-1', 'event-2']) + ->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getCalendarObject') + ->willReturn($calObject2)->with(666, 'event-2'); + + $calendarInfo = [ + 'principaluri' => 'user2', + 'id' => 666, + 'uri' => 'cal', + ]; + + if ($isShared) { + $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; + + } + $c = new Calendar($backend, $calendarInfo, $this->l10n); + $children = $c->getChildren(); + $this->assertEquals($expectedChildren, count($children)); + $children = $c->getMultipleChildren(['event-0', 'event-1', 'event-2']); + $this->assertEquals($expectedChildren, count($children)); + + $this->assertEquals(!$isShared, $c->childExists('event-2')); + } + + /** + * @dataProvider providesConfidentialClassificationData + * @param $expectedChildren + * @param $isShared + */ + public function testConfidentialClassification($expectedChildren, $isShared) { + $start = '20160609'; + $end = '20160610'; + + $calData = << 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; + $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'calendardata' => $calData]; + $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; + + /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */ + $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock(); + $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getMultipleCalendarObjects') + ->with(666, ['event-0', 'event-1', 'event-2']) + ->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getCalendarObject') + ->willReturn($calObject1)->with(666, 'event-1'); + + $calendarInfo = [ + 'principaluri' => 'user2', + 'id' => 666, + 'uri' => 'cal', + ]; + + if ($isShared) { + $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; + + } + $c = new Calendar($backend, $calendarInfo, $this->l10n); + + // test private event + $privateEvent = $c->getChild('event-1'); + $calData = $privateEvent->get(); + $event = Reader::read($calData); + + $this->assertEquals($start, $event->VEVENT->DTSTART->getValue()); + $this->assertEquals($end, $event->VEVENT->DTEND->getValue()); + + if ($isShared) { + $this->assertEquals('Busy', $event->VEVENT->SUMMARY->getValue()); + $this->assertArrayNotHasKey('ATTENDEE', $event->VEVENT); + $this->assertArrayNotHasKey('LOCATION', $event->VEVENT); + $this->assertArrayNotHasKey('DESCRIPTION', $event->VEVENT); + $this->assertArrayNotHasKey('ORGANIZER', $event->VEVENT); + } else { + $this->assertEquals('Test Event', $event->VEVENT->SUMMARY->getValue()); + } + } + + public function providesConfidentialClassificationData() { + return [ + [3, false], + [2, true] + ]; + } } diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php index 80f284e470e65..2b3f3e15d1a71 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -73,6 +73,11 @@ class FilesPluginTest extends TestCase { */ private $config; + /** + * @var \OCP\IRequest | \PHPUnit_Framework_MockObject_MockObject + */ + private $request; + public function setUp() { parent::setUp(); $this->server = $this->getMockBuilder('\Sabre\DAV\Server') @@ -88,11 +93,13 @@ public function setUp() { $this->config->expects($this->any())->method('getSystemValue') ->with($this->equalTo('data-fingerprint'), $this->equalTo('')) ->willReturn('my_fingerprint'); + $this->request = $this->getMock('\OCP\IRequest'); $this->plugin = new FilesPlugin( $this->tree, $this->view, - $this->config + $this->config, + $this->request ); $this->plugin->initialize($this->server); } @@ -268,6 +275,7 @@ public function testGetPublicPermissions() { $this->tree, $this->view, $this->config, + $this->getMock('\OCP\IRequest'), true); $this->plugin->initialize($this->server); @@ -484,4 +492,60 @@ public function testMoveSrcNotExist() { $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } + + public function downloadHeadersProvider() { + return [ + [ + false, + 'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"' + ], + [ + true, + 'attachment; filename="somefile.xml"' + ], + ]; + } + + /** + * @dataProvider downloadHeadersProvider + */ + public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader) { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request + ->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('test/somefile.xml')); + + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('getName') + ->will($this->returnValue('somefile.xml')); + + $this->tree + ->expects($this->once()) + ->method('getNodeForPath') + ->with('test/somefile.xml') + ->will($this->returnValue($node)); + + $this->request + ->expects($this->once()) + ->method('isUserAgent') + ->will($this->returnValue($isClumsyAgent)); + + $response + ->expects($this->once()) + ->method('addHeader') + ->with('Content-Disposition', $contentDispositionHeader); + + $this->plugin->httpGet($request, $response); + } } diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index 41d44efd89cae..baf4259b2158d 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -343,7 +343,8 @@ public function testPrepareResponses() { new \OCA\DAV\Connector\Sabre\FilesPlugin( $this->tree, $this->view, - $config + $config, + $this->getMock('\OCP\IRequest') ) ); $this->plugin->initialize($this->server); diff --git a/apps/dav/tests/unit/Migration/ClassificationTest.php b/apps/dav/tests/unit/Migration/ClassificationTest.php new file mode 100644 index 0000000000000..5c7fa62722675 --- /dev/null +++ b/apps/dav/tests/unit/Migration/ClassificationTest.php @@ -0,0 +1,75 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +namespace OCA\DAV\Tests\unit\DAV\Migration; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Migration\Classification; +use OCA\DAV\Tests\unit\CalDAV\AbstractCalDavBackendTest; +use OCP\IUser; + +/** + * Class ClassificationTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\DAV + */ +class ClassificationTest extends AbstractCalDavBackendTest { + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IUserManager */ + private $userManager; + + public function setUp() { + parent::setUp(); + + $this->userManager = $this->getMockBuilder('OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + } + + public function test() { + // setup data + $calendarId = $this->createTestCalendar(); + $eventUri = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z'); + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + + // assert proper classification + $this->assertEquals(CalDavBackend::CLASSIFICATION_PUBLIC, $object['classification']); + $this->backend->setClassification($object['id'], CalDavBackend::CLASSIFICATION_CONFIDENTIAL); + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + $this->assertEquals(CalDavBackend::CLASSIFICATION_CONFIDENTIAL, $object['classification']); + + // run migration + $c = new Classification($this->backend, $this->userManager); + + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ + $user = $this->getMockBuilder('OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once())->method('getUID')->willReturn('caldav-unit-test'); + + $c->runForUser($user); + + // assert classification after migration + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + $this->assertEquals(CalDavBackend::CLASSIFICATION_PUBLIC, $object['classification']); + } +} diff --git a/apps/encryption/l10n/pl.js b/apps/encryption/l10n/pl.js index 164a4a36d57d9..17f73e4fdb733 100644 --- a/apps/encryption/l10n/pl.js +++ b/apps/encryption/l10n/pl.js @@ -27,6 +27,8 @@ OC.L10N.register( "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nie można odszyfrować tego pliku, prawdopodobnie jest to plik udostępniony. Poproś właściciela pliku o ponowne udostępnianie pliku Tobie.", "The share will expire on %s." : "Ten zasób wygaśnie %s", "Cheers!" : "Dzięki!", + "Enable recovery key" : "Włącz klucz odzyskiwania", + "Disable recovery key" : "Wyłącz klucz odzyskiwania", "Recovery key password" : "Hasło klucza odzyskiwania", "Repeat recovery key password" : "Powtórz hasło klucza odzyskiwania", "Change recovery key password:" : "Zmień hasło klucza odzyskiwania", diff --git a/apps/encryption/l10n/pl.json b/apps/encryption/l10n/pl.json index 2bd108ba4c6f3..f1ef3faf44dc0 100644 --- a/apps/encryption/l10n/pl.json +++ b/apps/encryption/l10n/pl.json @@ -25,6 +25,8 @@ "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nie można odszyfrować tego pliku, prawdopodobnie jest to plik udostępniony. Poproś właściciela pliku o ponowne udostępnianie pliku Tobie.", "The share will expire on %s." : "Ten zasób wygaśnie %s", "Cheers!" : "Dzięki!", + "Enable recovery key" : "Włącz klucz odzyskiwania", + "Disable recovery key" : "Wyłącz klucz odzyskiwania", "Recovery key password" : "Hasło klucza odzyskiwania", "Repeat recovery key password" : "Powtórz hasło klucza odzyskiwania", "Change recovery key password:" : "Zmień hasło klucza odzyskiwania", diff --git a/apps/federatedfilesharing/l10n/hu_HU.js b/apps/federatedfilesharing/l10n/hu_HU.js index 0f0ffdd129597..0a4f26cb20dda 100644 --- a/apps/federatedfilesharing/l10n/hu_HU.js +++ b/apps/federatedfilesharing/l10n/hu_HU.js @@ -1,10 +1,14 @@ OC.L10N.register( "federatedfilesharing", { + "Federated sharing" : "Egyesített megosztás", "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", "Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva vele: %s", "Not allowed to create a federated share with the same user" : "Azonos felhasználóval nem lehet létrehozni egyesített megosztást", + "File is already shared with %s" : "Fájl már megosztva vele: %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található; talán a szerver jelenleg nem elérhető.", + "You received \"/%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s (%2$s nevében)", + "You received \"/%3$s\" as a remote share from %1$s" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s", "Accept" : "Elfogadás", "Decline" : "Elutasítás", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ossza meg velem az #ownCloud Egyesített Felhő Azonosító segítségével, lásd %s", diff --git a/apps/federatedfilesharing/l10n/hu_HU.json b/apps/federatedfilesharing/l10n/hu_HU.json index 24e25b6aa4c16..77a12ece1a803 100644 --- a/apps/federatedfilesharing/l10n/hu_HU.json +++ b/apps/federatedfilesharing/l10n/hu_HU.json @@ -1,8 +1,12 @@ { "translations": { + "Federated sharing" : "Egyesített megosztás", "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", "Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva vele: %s", "Not allowed to create a federated share with the same user" : "Azonos felhasználóval nem lehet létrehozni egyesített megosztást", + "File is already shared with %s" : "Fájl már megosztva vele: %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található; talán a szerver jelenleg nem elérhető.", + "You received \"/%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s (%2$s nevében)", + "You received \"/%3$s\" as a remote share from %1$s" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s", "Accept" : "Elfogadás", "Decline" : "Elutasítás", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ossza meg velem az #ownCloud Egyesített Felhő Azonosító segítségével, lásd %s", diff --git a/apps/federation/l10n/pt_PT.js b/apps/federation/l10n/pt_PT.js index 38ec142b68948..c227ab490b789 100644 --- a/apps/federation/l10n/pt_PT.js +++ b/apps/federation/l10n/pt_PT.js @@ -6,7 +6,7 @@ OC.L10N.register( "No ownCloud server found" : "Nenhum servidor ownCloud encontrado", "Could not add server" : "Não foi possível adicionar servidor", "Federation" : "Federação", - "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federação ownCloud permite-lhe conectar-se com outros ownClouds de confiança para partilhar directórios. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "A Federação ownCloud permite-lhe conectar-se com outras ownClouds de confiança para partilhar directorias. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", "Add server automatically once a federated share was created successfully" : "Adicionar o servidor automaticamente assim que uma partilha federada tenha sido criada com sucesso", "Trusted ownCloud Servers" : "Servidores ownCloud de confiança", "+ Add ownCloud server" : "+ Adicionar servidor ownCloud", diff --git a/apps/federation/l10n/pt_PT.json b/apps/federation/l10n/pt_PT.json index 796a21271707a..2316af9c253f3 100644 --- a/apps/federation/l10n/pt_PT.json +++ b/apps/federation/l10n/pt_PT.json @@ -4,7 +4,7 @@ "No ownCloud server found" : "Nenhum servidor ownCloud encontrado", "Could not add server" : "Não foi possível adicionar servidor", "Federation" : "Federação", - "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federação ownCloud permite-lhe conectar-se com outros ownClouds de confiança para partilhar directórios. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "A Federação ownCloud permite-lhe conectar-se com outras ownClouds de confiança para partilhar directorias. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", "Add server automatically once a federated share was created successfully" : "Adicionar o servidor automaticamente assim que uma partilha federada tenha sido criada com sucesso", "Trusted ownCloud Servers" : "Servidores ownCloud de confiança", "+ Add ownCloud server" : "+ Adicionar servidor ownCloud", diff --git a/apps/federation/l10n/ro.js b/apps/federation/l10n/ro.js index e303595514363..19f37f329a118 100644 --- a/apps/federation/l10n/ro.js +++ b/apps/federation/l10n/ro.js @@ -4,6 +4,12 @@ OC.L10N.register( "Server added to the list of trusted ownClouds" : "Server adăugat la lista serverelor ownCloud de încredere", "Server is already in the list of trusted servers." : "Serverul este deja pe lista celor de încredere.", "No ownCloud server found" : "Nu s-a găsit niciun server ownCloud", - "Could not add server" : "Nu s-a putut adăuga serverul" + "Could not add server" : "Nu s-a putut adăuga serverul", + "Federation" : "Federare", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federarea ownCloud îți permite să te conectezi la alte servere ownCloud de încredere pentru a partaja baza de utilizatori. De exemplu, va permite completarea automată a numelor utilizatorilor externi pentru partajarea federată.", + "Add server automatically once a federated share was created successfully" : "Adaugă serverul automat odată ce elementul partajat federat a fost creat cu succes", + "Trusted ownCloud Servers" : "Servere ownCloud de încredere", + "+ Add ownCloud server" : "+ Adaugă server ownCloud", + "ownCloud Server" : "Server ownCloud" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/apps/federation/l10n/ro.json b/apps/federation/l10n/ro.json index 75c514fd8c893..0acb4ebd96272 100644 --- a/apps/federation/l10n/ro.json +++ b/apps/federation/l10n/ro.json @@ -2,6 +2,12 @@ "Server added to the list of trusted ownClouds" : "Server adăugat la lista serverelor ownCloud de încredere", "Server is already in the list of trusted servers." : "Serverul este deja pe lista celor de încredere.", "No ownCloud server found" : "Nu s-a găsit niciun server ownCloud", - "Could not add server" : "Nu s-a putut adăuga serverul" + "Could not add server" : "Nu s-a putut adăuga serverul", + "Federation" : "Federare", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federarea ownCloud îți permite să te conectezi la alte servere ownCloud de încredere pentru a partaja baza de utilizatori. De exemplu, va permite completarea automată a numelor utilizatorilor externi pentru partajarea federată.", + "Add server automatically once a federated share was created successfully" : "Adaugă serverul automat odată ce elementul partajat federat a fost creat cu succes", + "Trusted ownCloud Servers" : "Servere ownCloud de încredere", + "+ Add ownCloud server" : "+ Adaugă server ownCloud", + "ownCloud Server" : "Server ownCloud" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/apps/federation/lib/DAV/FedAuth.php b/apps/federation/lib/DAV/FedAuth.php index bb1041adcdfd1..21c0d61487c09 100644 --- a/apps/federation/lib/DAV/FedAuth.php +++ b/apps/federation/lib/DAV/FedAuth.php @@ -36,6 +36,10 @@ class FedAuth extends AbstractBasic { public function __construct(DbHandler $db) { $this->db = $db; $this->principalPrefix = 'principals/system/'; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** diff --git a/apps/files/l10n/ar.js b/apps/files/l10n/ar.js index a9093c45a96c1..ae180fca23534 100644 --- a/apps/files/l10n/ar.js +++ b/apps/files/l10n/ar.js @@ -41,6 +41,12 @@ OC.L10N.register( "Pending" : "قيد الانتظار", "Unable to determine date" : "تعذر تحديد التاريخ", "This operation is forbidden" : "هذة العملية ممنوعة ", + "Could not rename \"{fileName}\"" : "إعادة تسمية الملف \"{fileName}\" لم تنجح", + "Could not create file \"{file}\"" : "لا يمكن إنشاء الملف\"{file}\"", + "Could not create file \"{file}\" because it already exists" : "لا يمكن إنشاء الملف \"{file}\" فهو موجود بالفعل", + "Could not create folder \"{dir}\"" : "لا يمكن إنشاء المجلد \"{dir}\"", + "Could not create folder \"{dir}\" because it already exists" : "لا يمكن إنشاء المجلد \"{dir}\" فهو موجود بالفعل", + "Error deleting file \"{fileName}\"." : "خطأ أثناء حذف الملف \"{fileName}\".", "No entries in this folder match '{filter}'" : "لا يوجد مدخلات في هذا المجلد تتوافق مع '{filter}'", "Name" : "اسم", "Size" : "حجم", diff --git a/apps/files/l10n/ar.json b/apps/files/l10n/ar.json index fe07f30c03380..257aa4f1bb463 100644 --- a/apps/files/l10n/ar.json +++ b/apps/files/l10n/ar.json @@ -39,6 +39,12 @@ "Pending" : "قيد الانتظار", "Unable to determine date" : "تعذر تحديد التاريخ", "This operation is forbidden" : "هذة العملية ممنوعة ", + "Could not rename \"{fileName}\"" : "إعادة تسمية الملف \"{fileName}\" لم تنجح", + "Could not create file \"{file}\"" : "لا يمكن إنشاء الملف\"{file}\"", + "Could not create file \"{file}\" because it already exists" : "لا يمكن إنشاء الملف \"{file}\" فهو موجود بالفعل", + "Could not create folder \"{dir}\"" : "لا يمكن إنشاء المجلد \"{dir}\"", + "Could not create folder \"{dir}\" because it already exists" : "لا يمكن إنشاء المجلد \"{dir}\" فهو موجود بالفعل", + "Error deleting file \"{fileName}\"." : "خطأ أثناء حذف الملف \"{fileName}\".", "No entries in this folder match '{filter}'" : "لا يوجد مدخلات في هذا المجلد تتوافق مع '{filter}'", "Name" : "اسم", "Size" : "حجم", diff --git a/apps/files/l10n/de.js b/apps/files/l10n/de.js index 068cbe8537658..0866279a435f3 100644 --- a/apps/files/l10n/de.js +++ b/apps/files/l10n/de.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Home", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de.json b/apps/files/l10n/de.json index 2169d2282955e..0f2939217d967 100644 --- a/apps/files/l10n/de.json +++ b/apps/files/l10n/de.json @@ -19,6 +19,7 @@ "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Home", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de_DE.js b/apps/files/l10n/de_DE.js index 9a349357a9970..6a2f0ab1f59a8 100644 --- a/apps/files/l10n/de_DE.js +++ b/apps/files/l10n/de_DE.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Zuhause", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de_DE.json b/apps/files/l10n/de_DE.json index 9df557e8fa4b9..047299f9510d5 100644 --- a/apps/files/l10n/de_DE.json +++ b/apps/files/l10n/de_DE.json @@ -19,6 +19,7 @@ "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Zuhause", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/en_GB.js b/apps/files/l10n/en_GB.js index ec8b821c3e71e..82ed2364dd6ba 100644 --- a/apps/files/l10n/en_GB.js +++ b/apps/files/l10n/en_GB.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Invalid directory.", "Files" : "Files", "All files" : "All files", + "File could not be found" : "File could not be found", "Home" : "Home", "Close" : "Close", "Favorites" : "Favourites", diff --git a/apps/files/l10n/en_GB.json b/apps/files/l10n/en_GB.json index 6dae8ba41b723..98b3d2000cb94 100644 --- a/apps/files/l10n/en_GB.json +++ b/apps/files/l10n/en_GB.json @@ -19,6 +19,7 @@ "Invalid directory." : "Invalid directory.", "Files" : "Files", "All files" : "All files", + "File could not be found" : "File could not be found", "Home" : "Home", "Close" : "Close", "Favorites" : "Favourites", diff --git a/apps/files/l10n/fi_FI.js b/apps/files/l10n/fi_FI.js index 325955a549210..8bf1c47612a98 100644 --- a/apps/files/l10n/fi_FI.js +++ b/apps/files/l10n/fi_FI.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Virheellinen kansio.", "Files" : "Tiedostot", "All files" : "Kaikki tiedostot", + "File could not be found" : "TIedostoa ei löytynyt", "Home" : "Koti", "Close" : "Sulje", "Favorites" : "Suosikit", diff --git a/apps/files/l10n/fi_FI.json b/apps/files/l10n/fi_FI.json index 966ef7074e4eb..dc8d168ffb5a6 100644 --- a/apps/files/l10n/fi_FI.json +++ b/apps/files/l10n/fi_FI.json @@ -19,6 +19,7 @@ "Invalid directory." : "Virheellinen kansio.", "Files" : "Tiedostot", "All files" : "Kaikki tiedostot", + "File could not be found" : "TIedostoa ei löytynyt", "Home" : "Koti", "Close" : "Sulje", "Favorites" : "Suosikit", diff --git a/apps/files/l10n/it.js b/apps/files/l10n/it.js index 7d85f952ed4c7..b560e6623cab6 100644 --- a/apps/files/l10n/it.js +++ b/apps/files/l10n/it.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Cartella non valida.", "Files" : "File", "All files" : "Tutti i file", + "File could not be found" : "Il file non può essere trovato", "Home" : "Home", "Close" : "Chiudi", "Favorites" : "Preferiti", diff --git a/apps/files/l10n/it.json b/apps/files/l10n/it.json index ec7003f738230..253fc5198aa96 100644 --- a/apps/files/l10n/it.json +++ b/apps/files/l10n/it.json @@ -19,6 +19,7 @@ "Invalid directory." : "Cartella non valida.", "Files" : "File", "All files" : "Tutti i file", + "File could not be found" : "Il file non può essere trovato", "Home" : "Home", "Close" : "Chiudi", "Favorites" : "Preferiti", diff --git a/apps/files/l10n/pl.js b/apps/files/l10n/pl.js index cb243f6bf4d2e..a366c6a967cf7 100644 --- a/apps/files/l10n/pl.js +++ b/apps/files/l10n/pl.js @@ -32,6 +32,8 @@ OC.L10N.register( "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", + "{seconds}s" : "{seconds} s", "File upload is in progress. Leaving the page now will cancel the upload." : "Wysyłanie pliku jest w toku. Jeśli opuścisz tę stronę, wysyłanie zostanie przerwane.", "Actions" : "Akcje", "Download" : "Pobierz", diff --git a/apps/files/l10n/pl.json b/apps/files/l10n/pl.json index 60323d0e757e2..05b65b2f84a4c 100644 --- a/apps/files/l10n/pl.json +++ b/apps/files/l10n/pl.json @@ -30,6 +30,8 @@ "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", + "{seconds}s" : "{seconds} s", "File upload is in progress. Leaving the page now will cancel the upload." : "Wysyłanie pliku jest w toku. Jeśli opuścisz tę stronę, wysyłanie zostanie przerwane.", "Actions" : "Akcje", "Download" : "Pobierz", diff --git a/apps/files/l10n/pt_BR.js b/apps/files/l10n/pt_BR.js index d8a7097161d8d..7bc2a94deb102 100644 --- a/apps/files/l10n/pt_BR.js +++ b/apps/files/l10n/pt_BR.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Diretório inválido.", "Files" : "Arquivos", "All files" : "Todos os arquivos", + "File could not be found" : "O arquivo não foi encontrado", "Home" : "Home", "Close" : "Fechar", "Favorites" : "Favoritos", diff --git a/apps/files/l10n/pt_BR.json b/apps/files/l10n/pt_BR.json index f5fa97a94afaa..f16c8c374a643 100644 --- a/apps/files/l10n/pt_BR.json +++ b/apps/files/l10n/pt_BR.json @@ -19,6 +19,7 @@ "Invalid directory." : "Diretório inválido.", "Files" : "Arquivos", "All files" : "Todos os arquivos", + "File could not be found" : "O arquivo não foi encontrado", "Home" : "Home", "Close" : "Fechar", "Favorites" : "Favoritos", diff --git a/apps/files/l10n/sq.js b/apps/files/l10n/sq.js index c1485c154d76a..b104afdd72331 100644 --- a/apps/files/l10n/sq.js +++ b/apps/files/l10n/sq.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Drejtori e pavlefshme.", "Files" : "Kartela", "All files" : "Krejt kartelat", + "File could not be found" : "Kartela s’u gjet dot", "Home" : "Kreu", "Close" : "Mbylle", "Favorites" : "Të parapëlqyera", diff --git a/apps/files/l10n/sq.json b/apps/files/l10n/sq.json index 9b40e9f7c9df5..80e5f104fa646 100644 --- a/apps/files/l10n/sq.json +++ b/apps/files/l10n/sq.json @@ -19,6 +19,7 @@ "Invalid directory." : "Drejtori e pavlefshme.", "Files" : "Kartela", "All files" : "Krejt kartelat", + "File could not be found" : "Kartela s’u gjet dot", "Home" : "Kreu", "Close" : "Mbylle", "Favorites" : "Të parapëlqyera", diff --git a/apps/files_external/l10n/pl.js b/apps/files_external/l10n/pl.js index 8c3e394627e77..00ae59b27c6ae 100644 --- a/apps/files_external/l10n/pl.js +++ b/apps/files_external/l10n/pl.js @@ -16,6 +16,7 @@ OC.L10N.register( "Saved" : "Zapisano", "Username" : "Nazwa użytkownika", "Password" : "Hasło", + "Credentials required" : "Wymagane poświadczenia", "Save" : "Zapisz", "Storage with id \"%i\" not found" : "Id magazynu nie został znaleziony", "Invalid mount point" : "Nieprawidłowy punkt montowania", diff --git a/apps/files_external/l10n/pl.json b/apps/files_external/l10n/pl.json index 8e00fc31863ed..235de7e9d1003 100644 --- a/apps/files_external/l10n/pl.json +++ b/apps/files_external/l10n/pl.json @@ -14,6 +14,7 @@ "Saved" : "Zapisano", "Username" : "Nazwa użytkownika", "Password" : "Hasło", + "Credentials required" : "Wymagane poświadczenia", "Save" : "Zapisz", "Storage with id \"%i\" not found" : "Id magazynu nie został znaleziony", "Invalid mount point" : "Nieprawidłowy punkt montowania", diff --git a/apps/files_sharing/l10n/pl.js b/apps/files_sharing/l10n/pl.js index ebdfe2aa738a0..e1bec669533fd 100644 --- a/apps/files_sharing/l10n/pl.js +++ b/apps/files_sharing/l10n/pl.js @@ -25,6 +25,9 @@ OC.L10N.register( "Invalid ownCloud url" : "Błędny adres URL", "Shared by" : "Udostępniane przez", "Sharing" : "Udostępnianie", + "Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora", + "Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora", + "Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych", "A file or folder has been shared" : "Plik lub folder stał się współdzielony", "You shared %1$s with %2$s" : "Współdzielisz %1$s z %2$s", "You shared %1$s with group %2$s" : "Współdzielisz %1$s z grupą %2$s", diff --git a/apps/files_sharing/l10n/pl.json b/apps/files_sharing/l10n/pl.json index f5de5dd368c8d..6190871b16f0c 100644 --- a/apps/files_sharing/l10n/pl.json +++ b/apps/files_sharing/l10n/pl.json @@ -23,6 +23,9 @@ "Invalid ownCloud url" : "Błędny adres URL", "Shared by" : "Udostępniane przez", "Sharing" : "Udostępnianie", + "Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora", + "Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora", + "Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych", "A file or folder has been shared" : "Plik lub folder stał się współdzielony", "You shared %1$s with %2$s" : "Współdzielisz %1$s z %2$s", "You shared %1$s with group %2$s" : "Współdzielisz %1$s z grupą %2$s", diff --git a/apps/files_sharing/l10n/ro.js b/apps/files_sharing/l10n/ro.js index 2481f77bcc670..5cddb6e8cdffd 100644 --- a/apps/files_sharing/l10n/ro.js +++ b/apps/files_sharing/l10n/ro.js @@ -12,12 +12,28 @@ OC.L10N.register( "Shared with others" : "Partajat cu alții", "Shared by link" : "Partajat prin link", "Nothing shared with you yet" : "Nimic nu e partajat cu tine încă", + "Files and folders others share with you will show up here" : "Fișierele și directoarele partajate cu tine vor apărea aici", "Nothing shared yet" : "Nimic partajat încă", + "Files and folders you share will show up here" : "Fișierele și directoarele pe care le partajezi vor apărea aici", + "No shared links" : "Nicio legătură partajată", + "Files and folders you share by link will show up here" : "Fișierele și directoarele pe care le partajezi prin legături vor apărea aici", + "Remote share" : "Element partajat la distanță", + "Remote share password" : "Parolă element partajat la distanță", "Cancel" : "Anulare", + "Add remote share" : "Adaugă element partajat la distanță", + "You can upload into this folder" : "Poți încărca în acest director", "No ownCloud installation (7 or higher) found at {remote}" : "Nu s-a găsit nicio instanță ownCloud (versiunea 7 sau mai mare) la {remote}", "Invalid ownCloud url" : "URL ownCloud invalid", "Shared by" : "impartite in ", "Sharing" : "Partajare", + "Share API is disabled" : "API-ul de partajare este dezactivat", + "Wrong share ID, share doesn't exist" : "ID greșit al elementului partajat, acesta nu există", + "Could not delete share" : "Nu s-a putut șterge elementul partajat", + "Please specify a file or folder path" : "Specifică un fișier sau o cale către un director", + "Wrong path, file/folder doesn't exist" : "Cale greșită, fișierul/directorul nu există", + "Please specify a valid user" : "Specifică un utilizator valid", + "Please specify a valid group" : "Specifică un grup valid", + "Invalid date, date format must be YYYY-MM-DD" : "Dată invalidă, formatul trebuie să fie AAAA-LL-ZZ", "Not a directory" : "Nu este un director", "Could not lock path" : "Calea nu a putut fi blocată", "Cannot increase permissions" : "Nu se pot extinde permisiunile", @@ -26,6 +42,9 @@ OC.L10N.register( "You shared %1$s with group %2$s" : "Ai partajat %1$s cu grupul %2$s", "You shared %1$s via link" : "Ai partajat %1$s prin legătură", "%2$s shared %1$s with you" : "%2$s a partajat %1$s cu tine", + "Shared with %2$s" : "Partajat cu %2$s", + "Shared with %3$s by %2$s" : "Partajat de %2$s cu %3$s", + "Shared with group %2$s" : "Partajat cu grupul %2$s", "Shares" : "Partajări", "This share is password-protected" : "Această partajare este protejată cu parolă", "The password is wrong. Try again." : "Parola este incorectă. Încercaţi din nou.", @@ -38,6 +57,7 @@ OC.L10N.register( "sharing is disabled" : "Partajare este oprită", "Add to your ownCloud" : "Adaugă propriul tău ownCloud", "Download" : "Descarcă", - "Download %s" : "Descarcă %s" + "Download %s" : "Descarcă %s", + "Direct link" : "Legătură directă" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/apps/files_sharing/l10n/ro.json b/apps/files_sharing/l10n/ro.json index e3f9450bcb526..6d0d87037db56 100644 --- a/apps/files_sharing/l10n/ro.json +++ b/apps/files_sharing/l10n/ro.json @@ -10,12 +10,28 @@ "Shared with others" : "Partajat cu alții", "Shared by link" : "Partajat prin link", "Nothing shared with you yet" : "Nimic nu e partajat cu tine încă", + "Files and folders others share with you will show up here" : "Fișierele și directoarele partajate cu tine vor apărea aici", "Nothing shared yet" : "Nimic partajat încă", + "Files and folders you share will show up here" : "Fișierele și directoarele pe care le partajezi vor apărea aici", + "No shared links" : "Nicio legătură partajată", + "Files and folders you share by link will show up here" : "Fișierele și directoarele pe care le partajezi prin legături vor apărea aici", + "Remote share" : "Element partajat la distanță", + "Remote share password" : "Parolă element partajat la distanță", "Cancel" : "Anulare", + "Add remote share" : "Adaugă element partajat la distanță", + "You can upload into this folder" : "Poți încărca în acest director", "No ownCloud installation (7 or higher) found at {remote}" : "Nu s-a găsit nicio instanță ownCloud (versiunea 7 sau mai mare) la {remote}", "Invalid ownCloud url" : "URL ownCloud invalid", "Shared by" : "impartite in ", "Sharing" : "Partajare", + "Share API is disabled" : "API-ul de partajare este dezactivat", + "Wrong share ID, share doesn't exist" : "ID greșit al elementului partajat, acesta nu există", + "Could not delete share" : "Nu s-a putut șterge elementul partajat", + "Please specify a file or folder path" : "Specifică un fișier sau o cale către un director", + "Wrong path, file/folder doesn't exist" : "Cale greșită, fișierul/directorul nu există", + "Please specify a valid user" : "Specifică un utilizator valid", + "Please specify a valid group" : "Specifică un grup valid", + "Invalid date, date format must be YYYY-MM-DD" : "Dată invalidă, formatul trebuie să fie AAAA-LL-ZZ", "Not a directory" : "Nu este un director", "Could not lock path" : "Calea nu a putut fi blocată", "Cannot increase permissions" : "Nu se pot extinde permisiunile", @@ -24,6 +40,9 @@ "You shared %1$s with group %2$s" : "Ai partajat %1$s cu grupul %2$s", "You shared %1$s via link" : "Ai partajat %1$s prin legătură", "%2$s shared %1$s with you" : "%2$s a partajat %1$s cu tine", + "Shared with %2$s" : "Partajat cu %2$s", + "Shared with %3$s by %2$s" : "Partajat de %2$s cu %3$s", + "Shared with group %2$s" : "Partajat cu grupul %2$s", "Shares" : "Partajări", "This share is password-protected" : "Această partajare este protejată cu parolă", "The password is wrong. Try again." : "Parola este incorectă. Încercaţi din nou.", @@ -36,6 +55,7 @@ "sharing is disabled" : "Partajare este oprită", "Add to your ownCloud" : "Adaugă propriul tău ownCloud", "Download" : "Descarcă", - "Download %s" : "Descarcă %s" + "Download %s" : "Descarcă %s", + "Direct link" : "Legătură directă" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/apps/files_sharing/lib/API/Share20OCS.php b/apps/files_sharing/lib/API/Share20OCS.php index b10c51ce2c9ff..3d6a715be9954 100644 --- a/apps/files_sharing/lib/API/Share20OCS.php +++ b/apps/files_sharing/lib/API/Share20OCS.php @@ -100,15 +100,8 @@ public function __construct( */ protected function formatShare(\OCP\Share\IShare $share) { $sharedBy = $this->userManager->get($share->getSharedBy()); - // for federated shares the owner can be a remote user, in this - // case we use the initiator - if ($this->userManager->userExists($share->getShareOwner())) { - $shareOwner = $this->userManager->get($share->getShareOwner()); - $localUser = $share->getShareOwner(); - } else { - $shareOwner = $this->userManager->get($share->getSharedBy()); - $localUser = $share->getSharedBy(); - } + $shareOwner = $this->userManager->get($share->getShareOwner()); + $result = [ 'id' => $share->getId(), 'share_type' => $share->getShareType(), @@ -123,8 +116,16 @@ protected function formatShare(\OCP\Share\IShare $share) { 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(), ]; - $node = $share->getNode(); - $result['path'] = $this->rootFolder->getUserFolder($localUser)->getRelativePath($node->getPath()); + $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); + $nodes = $userFolder->getById($share->getNodeId()); + + if (empty($nodes)) { + throw new NotFoundException(); + } + + $node = $nodes[0]; + + $result['path'] = $userFolder->getRelativePath($node->getPath()); if ($node instanceOf \OCP\Files\Folder) { $result['item_type'] = 'folder'; } else { @@ -536,7 +537,6 @@ public function getShares() { $shares = array_merge($shares, $federatedShares); } - $formatted = []; foreach ($shares as $share) { try { diff --git a/apps/files_sharing/tests/API/Share20OCSTest.php b/apps/files_sharing/tests/API/Share20OCSTest.php index 02b16d7bf884a..b760a0f47a014 100644 --- a/apps/files_sharing/tests/API/Share20OCSTest.php +++ b/apps/files_sharing/tests/API/Share20OCSTest.php @@ -433,8 +433,12 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { ->method('getRelativePath') ->will($this->returnArgument(0)); + $userFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$share->getNode()]); + $this->rootFolder->method('getUserFolder') - ->with($share->getShareOwner()) + ->with($this->currentUser->getUID()) ->willReturn($userFolder); $this->urlGenerator @@ -2006,8 +2010,19 @@ public function testFormatShare(array $expects, \OCP\Share\IShare $share, array ->willReturn('myLink'); - $this->rootFolder->method('getUserFolder')->with($share->getShareOwner())->will($this->returnSelf()); - $this->rootFolder->method('getRelativePath')->will($this->returnArgument(0)); + $this->rootFolder->method('getUserFolder') + ->with($this->currentUser->getUID()) + ->will($this->returnSelf()); + + if (!$exception) { + $this->rootFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$share->getNode()]); + + $this->rootFolder->method('getRelativePath') + ->with($share->getNode()->getPath()) + ->will($this->returnArgument(0)); + } try { $result = $this->invokePrivate($this->ocs, 'formatShare', [$share]); diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index f44c346236e63..058b0c4758c46 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -764,8 +764,7 @@ function testGetShareFromSubFolderReShares() { // we should get exactly one result $this->assertCount(1, $data); - $expectedPath = $this->folder . $this->subfolder; - $this->assertEquals($expectedPath, $data[0]['path']); + $this->assertEquals($this->subfolder, $data[0]['path']); $this->shareManager->deleteShare($share2); $this->shareManager->deleteShare($share1); @@ -801,6 +800,9 @@ function testGetShareFromFolderReReShares() { ->setPermissions(1); $share3 = $this->shareManager->createShare($share3); + /* + * Test as recipient + */ $request = $this->createRequest(['path' => '/', 'subfiles' => 'true']); $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER3); $result = $ocs->getShares(); @@ -811,9 +813,37 @@ function testGetShareFromFolderReReShares() { // we should get exactly one result $this->assertCount(1, $data); + $this->assertEquals($this->subsubfolder, $data[0]['path']); - $expectedPath = $this->folder . $this->subfolder . $this->subsubfolder; - $this->assertEquals($expectedPath, $data[0]['path']); + /* + * Test for first owner/initiator + */ + $request = $this->createRequest([]); + $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER1); + $result = $ocs->getShares(); + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $data = $result->getData(); + + // we should get exactly one result + $this->assertCount(1, $data); + $this->assertEquals($this->folder . $this->subfolder, $data[0]['path']); + + /* + * Test for second initiator + */ + $request = $this->createRequest([]); + $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER2); + $result = $ocs->getShares(); + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $data = $result->getData(); + + // we should get exactly one result + $this->assertCount(1, $data); + $this->assertEquals($this->subfolder . $this->subsubfolder, $data[0]['path']); $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); @@ -922,8 +952,7 @@ function testGetShareFromFileReReShares() { // we should get exactly one result $this->assertCount(1, $data); - $expectedPath = $this->folder.$this->subfolder.$this->filename; - $this->assertEquals($expectedPath, $data[0]['path']); + $this->assertEquals($this->filename, $data[0]['path']); $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); diff --git a/apps/systemtags/l10n/hu_HU.js b/apps/systemtags/l10n/hu_HU.js index 13d1705ff1ca4..783b90ece8a4c 100644 --- a/apps/systemtags/l10n/hu_HU.js +++ b/apps/systemtags/l10n/hu_HU.js @@ -21,6 +21,7 @@ OC.L10N.register( "%1$s assigned system tag %3$s to %2$s" : "%1$s hozzárendelte ezt a rendszer címkét: %3$s neki: %2$s", "You unassigned system tag %3$s from %2$s" : "%3$s rendszer címke hozzárendelést elvette tőle: %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s elvette ezt a rendszer címkét %3$s tőle: %2$s", + "%s (restricted)" : "%s (korlátozott)", "%s (invisible)" : "%s (láthatatlan)", "No files in here" : "Itt nincsenek fájlok", "No entries found in this folder" : "Nincsenek bejegyzések ebben a könyvtárban", diff --git a/apps/systemtags/l10n/hu_HU.json b/apps/systemtags/l10n/hu_HU.json index 6408b3a531445..f89da5d3dfd22 100644 --- a/apps/systemtags/l10n/hu_HU.json +++ b/apps/systemtags/l10n/hu_HU.json @@ -19,6 +19,7 @@ "%1$s assigned system tag %3$s to %2$s" : "%1$s hozzárendelte ezt a rendszer címkét: %3$s neki: %2$s", "You unassigned system tag %3$s from %2$s" : "%3$s rendszer címke hozzárendelést elvette tőle: %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s elvette ezt a rendszer címkét %3$s tőle: %2$s", + "%s (restricted)" : "%s (korlátozott)", "%s (invisible)" : "%s (láthatatlan)", "No files in here" : "Itt nincsenek fájlok", "No entries found in this folder" : "Nincsenek bejegyzések ebben a könyvtárban", diff --git a/apps/updatenotification/l10n/hu_HU.js b/apps/updatenotification/l10n/hu_HU.js index 1df38a6746f77..cce4549f01e90 100644 --- a/apps/updatenotification/l10n/hu_HU.js +++ b/apps/updatenotification/l10n/hu_HU.js @@ -1,8 +1,11 @@ OC.L10N.register( "updatenotification", { + "Update notifications" : "Frissítési értesítés", "{version} is available. Get more information on how to update." : "{version} rendelkezésre áll. További információ a frissítéshez.", "Updated channel" : "Frissített csatorna", + "ownCloud core" : "ownCloud mag", + "Update for %1$s to version %2$s is available." : "%1$s frissíthető %2$s verzióra.", "Updater" : "Frissítéskezelő", "A new version is available: %s" : "Új verzió érhető el: %s", "Open updater" : "Frissítő megnyitása", diff --git a/apps/updatenotification/l10n/hu_HU.json b/apps/updatenotification/l10n/hu_HU.json index eb953669ab110..952b499d52a6d 100644 --- a/apps/updatenotification/l10n/hu_HU.json +++ b/apps/updatenotification/l10n/hu_HU.json @@ -1,6 +1,9 @@ { "translations": { + "Update notifications" : "Frissítési értesítés", "{version} is available. Get more information on how to update." : "{version} rendelkezésre áll. További információ a frissítéshez.", "Updated channel" : "Frissített csatorna", + "ownCloud core" : "ownCloud mag", + "Update for %1$s to version %2$s is available." : "%1$s frissíthető %2$s verzióra.", "Updater" : "Frissítéskezelő", "A new version is available: %s" : "Új verzió érhető el: %s", "Open updater" : "Frissítő megnyitása", diff --git a/apps/user_ldap/l10n/pl.js b/apps/user_ldap/l10n/pl.js index de8f1f49ad350..a75b38fc12fe3 100644 --- a/apps/user_ldap/l10n/pl.js +++ b/apps/user_ldap/l10n/pl.js @@ -31,6 +31,7 @@ OC.L10N.register( "Confirm Deletion" : "Potwierdź usunięcie", "Mappings cleared successfully!" : "Mapowanie wyczyszczone!", "Error while clearing the mappings." : "Błąd podczas czyszczenia mapowania.", + "Mode switch" : "Przełącznik trybów", "Select attributes" : "Wybierz atrybuty", "_%s group found_::_%s groups found_" : ["%s znaleziona grupa","%s znalezionych grup","%s znalezionych grup"], "_%s user found_::_%s users found_" : ["%s znaleziony użytkownik","%s znalezionych użytkowników","%s znalezionych użytkowników"], diff --git a/apps/user_ldap/l10n/pl.json b/apps/user_ldap/l10n/pl.json index 5a853c3abed48..472148fa3b006 100644 --- a/apps/user_ldap/l10n/pl.json +++ b/apps/user_ldap/l10n/pl.json @@ -29,6 +29,7 @@ "Confirm Deletion" : "Potwierdź usunięcie", "Mappings cleared successfully!" : "Mapowanie wyczyszczone!", "Error while clearing the mappings." : "Błąd podczas czyszczenia mapowania.", + "Mode switch" : "Przełącznik trybów", "Select attributes" : "Wybierz atrybuty", "_%s group found_::_%s groups found_" : ["%s znaleziona grupa","%s znalezionych grup","%s znalezionych grup"], "_%s user found_::_%s users found_" : ["%s znaleziony użytkownik","%s znalezionych użytkowników","%s znalezionych użytkowników"], diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 7c12613f34d23..14d86fb0619fb 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -535,7 +535,7 @@ public function getUserGroups($uid) { } if(isset($this->cachedGroupsByMember[$uid])) { - $groups[] = $this->cachedGroupsByMember[$uid]; + $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]); } else { $groupsByMember = array_values($this->getGroupsByMember($uid)); $groupsByMember = $this->access->ownCloudGroupNames($groupsByMember); diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index 556c4b0b39486..35d525068a642 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -454,4 +454,57 @@ public function testGetUserGroupsMemberOfDisabled() { $groupBackend->getUserGroups('userX'); } + public function testGetGroupsByMember() { + $access = $this->getAccessMock(); + + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'useMemberOfToDetectMembership') { + return 0; + } else if($name === 'ldapDynamicGroupMemberURL') { + return ''; + } else if($name === 'ldapNestedGroups') { + return false; + } + return 1; + })); + + $dn = 'cn=userX,dc=foobar'; + + $access->connection->hasPrimaryGroups = false; + + $access->expects($this->exactly(2)) + ->method('username2dn') + ->will($this->returnValue($dn)); + + $access->expects($this->never()) + ->method('readAttribute') + ->with($dn, 'memberOf'); + + $group1 = [ + 'cn' => 'group1', + 'dn' => ['cn=group1,ou=groups,dc=domain,dc=com'], + ]; + $group2 = [ + 'cn' => 'group2', + 'dn' => ['cn=group2,ou=groups,dc=domain,dc=com'], + ]; + + $access->expects($this->once()) + ->method('ownCloudGroupNames') + ->with([$group1, $group2]) + ->will($this->returnValue(['group1', 'group2'])); + + $access->expects($this->once()) + ->method('fetchListOfGroups') + ->will($this->returnValue([$group1, $group2])); + + $groupBackend = new GroupLDAP($access); + $groups = $groupBackend->getUserGroups('userX'); + $this->assertEquals(['group1', 'group2'], $groups); + + $groupsAgain = $groupBackend->getUserGroups('userX'); + $this->assertEquals(['group1', 'group2'], $groupsAgain); + } } diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index f4d40615fa772..14ff505463cbb 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -82,7 +82,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'none';| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| @@ -97,7 +97,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'none';| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| diff --git a/core/Command/Maintenance/Install.php b/core/Command/Maintenance/Install.php index b1b63b9b3bd6c..12a61d6341a09 100644 --- a/core/Command/Maintenance/Install.php +++ b/core/Command/Maintenance/Install.php @@ -106,7 +106,12 @@ protected function validateInput(InputInterface $input, OutputInterface $output, $dbUser = $input->getOption('database-user'); $dbPass = $input->getOption('database-pass'); $dbName = $input->getOption('database-name'); - $dbHost = $input->getOption('database-host'); + if ($db === 'oci') { + // an empty hostname needs to be read from the raw parameters + $dbHost = $input->getParameterOption('--database-host', ''); + } else { + $dbHost = $input->getOption('database-host'); + } $dbTablePrefix = 'oc_'; if ($input->hasParameterOption('--database-table-prefix')) { $dbTablePrefix = (string) $input->getOption('database-table-prefix'); diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index c64f58ae2cc22..7806e1de904dd 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -171,6 +171,7 @@ public function showLoginForm($user, $redirect_url, $remember_login) { * @return RedirectResponse */ public function tryLogin($user, $password, $redirect_url) { + $originalUser = $user; // TODO: Add all the insane error handling /* @var $loginResult IUser */ $loginResult = $this->userManager->checkPassword($user, $password); @@ -186,8 +187,8 @@ public function tryLogin($user, $password, $redirect_url) { $this->session->set('loginMessages', [ ['invalidpassword'] ]); - // Read current user and append if possible - $args = !is_null($user) ? ['user' => $user] : []; + // Read current user and append if possible - we need to return the unmodified user otherwise we will leak the login name + $args = !is_null($user) ? ['user' => $originalUser] : []; return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)); } // TODO: remove password checks from above and let the user session handle failures diff --git a/core/l10n/de.js b/core/l10n/de.js index 177376c380285..52e1639efff4c 100644 --- a/core/l10n/de.js +++ b/core/l10n/de.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Vielen Dank für Deine Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für dich Konto aktiviert. Bitte authentifiziere dich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifiziere dich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Du greifst von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de.json b/core/l10n/de.json index a66abe7016200..d713c507248a7 100644 --- a/core/l10n/de.json +++ b/core/l10n/de.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Vielen Dank für Deine Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für dich Konto aktiviert. Bitte authentifiziere dich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifiziere dich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Du greifst von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de_DE.js b/core/l10n/de_DE.js index f10a73798d6d7..9e379c2c0258c 100644 --- a/core/l10n/de_DE.js +++ b/core/l10n/de_DE.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Vielen Dank für Ihre Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für Ihr Konto aktiviert. Bitte authentifizieren Sie sich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifizieren Sie sich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Sie greifen von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de_DE.json b/core/l10n/de_DE.json index 3b6267dca62be..bcdf1f5279527 100644 --- a/core/l10n/de_DE.json +++ b/core/l10n/de_DE.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Vielen Dank für Ihre Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für Ihr Konto aktiviert. Bitte authentifizieren Sie sich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifizieren Sie sich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Sie greifen von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/en_GB.js b/core/l10n/en_GB.js index 6f5859aae3ea1..76045cf033ddf 100644 --- a/core/l10n/en_GB.js +++ b/core/l10n/en_GB.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Thank you for your patience.", "Two-step verification" : "Two-step verification", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Enhanced security has been enabled for your account. Please authenticate using a second factor.", + "Cancel login" : "Cancel login", "Please authenticate using the selected factor." : "Please authenticate using the selected factor.", "An error occured while verifying the token" : "An error occured while verifying the token", "You are accessing the server from an untrusted domain." : "You are accessing the server from an untrusted domain.", diff --git a/core/l10n/en_GB.json b/core/l10n/en_GB.json index 7aedc2a730f39..d71ff3f8ada2b 100644 --- a/core/l10n/en_GB.json +++ b/core/l10n/en_GB.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Thank you for your patience.", "Two-step verification" : "Two-step verification", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Enhanced security has been enabled for your account. Please authenticate using a second factor.", + "Cancel login" : "Cancel login", "Please authenticate using the selected factor." : "Please authenticate using the selected factor.", "An error occured while verifying the token" : "An error occured while verifying the token", "You are accessing the server from an untrusted domain." : "You are accessing the server from an untrusted domain.", diff --git a/core/l10n/fi_FI.js b/core/l10n/fi_FI.js index d15457022a210..d491cf213fc9f 100644 --- a/core/l10n/fi_FI.js +++ b/core/l10n/fi_FI.js @@ -187,6 +187,7 @@ OC.L10N.register( "Warning" : "Varoitus", "Error while sending notification" : "Virhe ilmoitusta lähettäessä", "Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}", + "restricted" : "rajoitettu", "invisible" : "näkymätön", "({scope})" : "({scope})", "Delete" : "Poista", @@ -289,6 +290,7 @@ OC.L10N.register( "Thank you for your patience." : "Kiitos kärsivällisyydestäsi.", "Two-step verification" : "Kaksivaiheinen vahvistus", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Tililläsi on käytössä lisäturvatoimia. Tunnistaudu käyttäen kaksivaiheista vahvistusta.", + "Cancel login" : "Peru kirjautuminen", "Please authenticate using the selected factor." : "Tunnistaudu käyttäen valittua vahvistusta.", "You are accessing the server from an untrusted domain." : "Olet yhteydessä palvelimeen epäluotettavasta verkko-osoitteesta.", "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Ota yhteys ylläpitoon. Jos olet tämän asennuksen ylläpitäjä, määritä \"trusted_domains\"-asetus config/config.php-tiedostossa. Esimerkkimääritys on tarjolla tiedostossa config/config.sample.php.", diff --git a/core/l10n/fi_FI.json b/core/l10n/fi_FI.json index 14565b0b46eb4..e3f7fb28a8c11 100644 --- a/core/l10n/fi_FI.json +++ b/core/l10n/fi_FI.json @@ -185,6 +185,7 @@ "Warning" : "Varoitus", "Error while sending notification" : "Virhe ilmoitusta lähettäessä", "Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}", + "restricted" : "rajoitettu", "invisible" : "näkymätön", "({scope})" : "({scope})", "Delete" : "Poista", @@ -287,6 +288,7 @@ "Thank you for your patience." : "Kiitos kärsivällisyydestäsi.", "Two-step verification" : "Kaksivaiheinen vahvistus", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Tililläsi on käytössä lisäturvatoimia. Tunnistaudu käyttäen kaksivaiheista vahvistusta.", + "Cancel login" : "Peru kirjautuminen", "Please authenticate using the selected factor." : "Tunnistaudu käyttäen valittua vahvistusta.", "You are accessing the server from an untrusted domain." : "Olet yhteydessä palvelimeen epäluotettavasta verkko-osoitteesta.", "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Ota yhteys ylläpitoon. Jos olet tämän asennuksen ylläpitäjä, määritä \"trusted_domains\"-asetus config/config.php-tiedostossa. Esimerkkimääritys on tarjolla tiedostossa config/config.sample.php.", diff --git a/core/l10n/it.js b/core/l10n/it.js index 7daff3e0dad73..6769455e6699d 100644 --- a/core/l10n/it.js +++ b/core/l10n/it.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Grazie per la pazienza.", "Two-step verification" : "Verifica in due fasi", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "La sicurezza migliorata è stata abilitata per il tuo account. Autenticati utilizzando un secondo fattore.", + "Cancel login" : "Annulla l'accesso", "Please authenticate using the selected factor." : "Autentica utilizzando il fattore selezionato.", "An error occured while verifying the token" : "Si è verificato un errore durante la verifica del token", "You are accessing the server from an untrusted domain." : "Stai accedendo al server da un dominio non attendibile.", diff --git a/core/l10n/it.json b/core/l10n/it.json index 9d403e4f72494..ac6d16d90b902 100644 --- a/core/l10n/it.json +++ b/core/l10n/it.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Grazie per la pazienza.", "Two-step verification" : "Verifica in due fasi", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "La sicurezza migliorata è stata abilitata per il tuo account. Autenticati utilizzando un secondo fattore.", + "Cancel login" : "Annulla l'accesso", "Please authenticate using the selected factor." : "Autentica utilizzando il fattore selezionato.", "An error occured while verifying the token" : "Si è verificato un errore durante la verifica del token", "You are accessing the server from an untrusted domain." : "Stai accedendo al server da un dominio non attendibile.", diff --git a/core/l10n/pt_BR.js b/core/l10n/pt_BR.js index a0ed6ee2aba93..e793cab7836ec 100644 --- a/core/l10n/pt_BR.js +++ b/core/l10n/pt_BR.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Obrigado pela sua paciência.", "Two-step verification" : "Verificação em dois passos", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Segurança reforçada foi ativada para sua conta. Por favor autenticar usando um segundo fator.", + "Cancel login" : "Cancelar o login", "Please authenticate using the selected factor." : "Por favor autenticar usando o fator selecionado.", "An error occured while verifying the token" : "Ocorreu um erro ao verificar o token", "You are accessing the server from an untrusted domain." : "Você está acessando o servidor a partir de um domínio não confiável.", diff --git a/core/l10n/pt_BR.json b/core/l10n/pt_BR.json index 8642980d76616..c6a944e17754d 100644 --- a/core/l10n/pt_BR.json +++ b/core/l10n/pt_BR.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Obrigado pela sua paciência.", "Two-step verification" : "Verificação em dois passos", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Segurança reforçada foi ativada para sua conta. Por favor autenticar usando um segundo fator.", + "Cancel login" : "Cancelar o login", "Please authenticate using the selected factor." : "Por favor autenticar usando o fator selecionado.", "An error occured while verifying the token" : "Ocorreu um erro ao verificar o token", "You are accessing the server from an untrusted domain." : "Você está acessando o servidor a partir de um domínio não confiável.", diff --git a/core/l10n/ro.js b/core/l10n/ro.js index 7b4cf2c895750..e73ec5cf79f23 100644 --- a/core/l10n/ro.js +++ b/core/l10n/ro.js @@ -8,23 +8,37 @@ OC.L10N.register( "Unknown filetype" : "Tip fișier necunoscut", "Invalid image" : "Imagine invalidă", "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.", + "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou", "%s password reset" : "%s resetare parola", "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.", "Error loading tags" : "Eroare la încărcarea etichetelor", "Tag already exists" : "Eticheta deja există", + "Error deleting tag(s)" : "Eroare la ștergerea etichetei(-lor)", "Error tagging" : "Eroare la etichetare", "Error untagging" : "Eroare la scoaterea etichetei", + "Error favoriting" : "Eroare la adăugarea la favorite", + "Error unfavoriting" : "Eroare la scoaterea de la favorite", "Couldn't send mail to following users: %s " : "Nu s-a putut trimite mesajul către următorii utilizatori: %s", "Preparing update" : "Se pregătește actualizarea", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Alerte reparare:", "Repair error: " : "Eroare de reparare:", + "[%d / %d]: Checking table %s" : "[%d / %d]: Se verifică tabela %s", "Turned on maintenance mode" : "Modul mentenanță a fost activat", "Turned off maintenance mode" : "Modul mentenanță a fost dezactivat", + "Maintenance mode is kept active" : "Modul de mentenanță este menținut activ", "Updating database schema" : "Se actualizează schema bazei de date", "Updated database" : "Bază de date actualizată", "Checking whether the database schema can be updated (this can take a long time depending on the database size)" : "Se verifică dacă schema bazei de date poate fi actualizată (această verificare poate lua mult timp în funcție de mărimea bazei de date)", + "Checking updates of apps" : "Se verifică actualizările aplicațiilor", "Updated \"%s\" to %s" : "\"%s\" a fost actualizat până la %s", + "Set log level to debug" : "Setează nivelul de logare la \"debug\"", + "Reset log level" : "Resetează nivelul de logare", + "Starting code integrity check" : "Începe verificarea integrității codului", + "Finished code integrity check" : "Verificarea integrității codului a fost finalizată", + "%s (3rdparty)" : "%s (terță parte)", "%s (incompatible)" : "%s (incompatibil)", + "Following apps have been disabled: %s" : "Următoarele aplicații au fost dezactivate: %s", "Already up to date" : "Deja actualizat", "Sunday" : "Duminică", "Monday" : "Luni", @@ -71,9 +85,11 @@ OC.L10N.register( "Oct." : "Oct.", "Nov." : "Noi.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Au apărut probleme la verificarea integrității codului. Mai multe informații…", "Settings" : "Setări", "Problem loading page, reloading in 5 seconds" : "A apărut o problemă la încărcarea paginii, se reîncearcă în 5 secunde", "Saving..." : "Se salvează...", + "Dismiss" : "Înlătură", "seconds ago" : "secunde în urmă", "I know what I'm doing" : "Eu știu ce fac", "Password can not be changed. Please contact your administrator." : "Parola nu poate fi modificată. Vă rugăm să contactați administratorul dvs.", @@ -135,24 +151,39 @@ OC.L10N.register( "access control" : "control acces", "Could not unshare" : "Nu s-a putut elimina partajarea", "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.", + "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou", "Share" : "Partajează", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud", "Share with users…" : "Partajează cu utilizatori...", + "Share with users or groups…" : "Partajează cu utilizatori sau grupuri...", + "Error removing share" : "Eroare la înlăturarea elementului partajat", "Warning" : "Atenție", + "Error while sending notification" : "Eroare la trimiterea notificării", + "Non-existing tag #{tag}" : "Etichetă inexistentă #{tag}", + "restricted" : "restricționat", "invisible" : "invizibil", "Delete" : "Șterge", "Rename" : "Redenumește", + "Collaborative tags" : "Etichete colaborative", "The object type is not specified." : "Tipul obiectului nu este specificat.", "Enter new" : "Introducere nou", "Add" : "Adaugă", "Edit tags" : "Editează etichete", + "No tags selected for deletion." : "Nu au fost selectate etichete pentru ștergere.", "unknown text" : "text necunoscut", + "Hello world!" : "Hello world!", "sunny" : "însorit", + "Hello {name}" : "Salut {name}", "new" : "nou", + "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.", + "Updating to {version}" : "Actualizare la {version}", "An error occurred." : "A apărut o eroare.", "Please reload the page." : "Te rugăm să reîncarci pagina.", "The update was unsuccessful. Please report this issue to the ownCloud community." : "Actualizarea a eșuat! Raportați problema către comunitatea ownCloud.", + "The update was successful. There were warnings." : "Actualizarea nu a avut loc cu succes. Au existat avertismente.", "The update was successful. Redirecting you to ownCloud now." : "Actualizare reușită. Ești redirecționat către ownCloud.", + "Searching other places" : "Se caută în alte locuri", + "No search results in other folders" : "Nu există rezultate ale căutării în alte directoare", "Personal" : "Personal", "Users" : "Utilizatori", "Apps" : "Aplicații", @@ -160,8 +191,12 @@ OC.L10N.register( "Help" : "Ajutor", "Access forbidden" : "Acces restricționat", "File not found" : "Fișierul nu a fost găsit", + "The specified document has not been found on the server." : "Documentul specificat nu a fost găsit pe server.", + "You can click here to return to %s." : "Poți da click aici pentru a te întoarce la %s.", "The share will expire on %s." : "Partajarea va expira în data de %s.", "Cheers!" : "Noroc!", + "Internal Server Error" : "Eroare internă a serverului", + "The server encountered an internal error and was unable to complete your request." : "Serverul a întâmpinat o eroare și nu îți poate îndeplini cererea.", "Technical details" : "Detalii tehnice", "Type: %s" : "Tip: %s", "Code: %s" : "Cod: %s", @@ -190,16 +225,35 @@ OC.L10N.register( "See the documentation" : "Vezi documentația", "Log out" : "Ieșire", "Search" : "Căutare", + "Please contact your administrator." : "Contactează-ți administratorul.", "An internal error occurred." : "A apărut o eroare internă.", + "Please try again or contact your administrator." : "Încearcă din nou sau contactează-ți administratorul.", "Username or email" : "Nume de utilizator sau adresă email", "Log in" : "Autentificare", + "Wrong password. Reset it?" : "Parolă greșită. O resetezi?", + "Wrong password." : "Parolă greșită.", + "Stay logged in" : "Rămâi autentificat", "Alternative Logins" : "Conectări alternative", "Use the following link to reset your password: {link}" : "Folosește următorul link pentru a reseta parola: {link}", "New password" : "Noua parolă", "New Password" : "Noua parolă", "Reset password" : "Resetează parola", "Thank you for your patience." : "Îți mulțumim pentru răbdare.", + "Two-step verification" : "Verificare în doi pași", + "Cancel login" : "Anulează autentificarea", + "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.", + "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului", "You are accessing the server from an untrusted domain." : "Accesezi serverul dintr-un domeniu care nu a fost configurat ca fiind de încredere.", - "Start update" : "Începe actualizarea" + "Add \"%s\" as trusted domain" : "Adaugă \"%s\" ca domeniu de încredere", + "App update required" : "E necesară o actualizare a aplicației", + "%s will be updated to version %s" : "%s va fi actualizat la versiunea %s", + "These apps will be updated:" : "Aceste aplicații vor fi actualizate:", + "These incompatible apps will be disabled:" : "Aceste aplicații incompatibile vor fi dezactivate:", + "The theme %s has been disabled." : "Tema %s a fost dezactivată.", + "Start update" : "Începe actualizarea", + "Detailed logs" : "Loguri detaliate", + "Update needed" : "E necesară actualizarea", + "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.", + "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme." }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/core/l10n/ro.json b/core/l10n/ro.json index cea43ae3142be..c154312dab6df 100644 --- a/core/l10n/ro.json +++ b/core/l10n/ro.json @@ -6,23 +6,37 @@ "Unknown filetype" : "Tip fișier necunoscut", "Invalid image" : "Imagine invalidă", "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.", + "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou", "%s password reset" : "%s resetare parola", "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.", "Error loading tags" : "Eroare la încărcarea etichetelor", "Tag already exists" : "Eticheta deja există", + "Error deleting tag(s)" : "Eroare la ștergerea etichetei(-lor)", "Error tagging" : "Eroare la etichetare", "Error untagging" : "Eroare la scoaterea etichetei", + "Error favoriting" : "Eroare la adăugarea la favorite", + "Error unfavoriting" : "Eroare la scoaterea de la favorite", "Couldn't send mail to following users: %s " : "Nu s-a putut trimite mesajul către următorii utilizatori: %s", "Preparing update" : "Se pregătește actualizarea", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Alerte reparare:", "Repair error: " : "Eroare de reparare:", + "[%d / %d]: Checking table %s" : "[%d / %d]: Se verifică tabela %s", "Turned on maintenance mode" : "Modul mentenanță a fost activat", "Turned off maintenance mode" : "Modul mentenanță a fost dezactivat", + "Maintenance mode is kept active" : "Modul de mentenanță este menținut activ", "Updating database schema" : "Se actualizează schema bazei de date", "Updated database" : "Bază de date actualizată", "Checking whether the database schema can be updated (this can take a long time depending on the database size)" : "Se verifică dacă schema bazei de date poate fi actualizată (această verificare poate lua mult timp în funcție de mărimea bazei de date)", + "Checking updates of apps" : "Se verifică actualizările aplicațiilor", "Updated \"%s\" to %s" : "\"%s\" a fost actualizat până la %s", + "Set log level to debug" : "Setează nivelul de logare la \"debug\"", + "Reset log level" : "Resetează nivelul de logare", + "Starting code integrity check" : "Începe verificarea integrității codului", + "Finished code integrity check" : "Verificarea integrității codului a fost finalizată", + "%s (3rdparty)" : "%s (terță parte)", "%s (incompatible)" : "%s (incompatibil)", + "Following apps have been disabled: %s" : "Următoarele aplicații au fost dezactivate: %s", "Already up to date" : "Deja actualizat", "Sunday" : "Duminică", "Monday" : "Luni", @@ -69,9 +83,11 @@ "Oct." : "Oct.", "Nov." : "Noi.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Au apărut probleme la verificarea integrității codului. Mai multe informații…", "Settings" : "Setări", "Problem loading page, reloading in 5 seconds" : "A apărut o problemă la încărcarea paginii, se reîncearcă în 5 secunde", "Saving..." : "Se salvează...", + "Dismiss" : "Înlătură", "seconds ago" : "secunde în urmă", "I know what I'm doing" : "Eu știu ce fac", "Password can not be changed. Please contact your administrator." : "Parola nu poate fi modificată. Vă rugăm să contactați administratorul dvs.", @@ -133,24 +149,39 @@ "access control" : "control acces", "Could not unshare" : "Nu s-a putut elimina partajarea", "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.", + "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou", "Share" : "Partajează", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud", "Share with users…" : "Partajează cu utilizatori...", + "Share with users or groups…" : "Partajează cu utilizatori sau grupuri...", + "Error removing share" : "Eroare la înlăturarea elementului partajat", "Warning" : "Atenție", + "Error while sending notification" : "Eroare la trimiterea notificării", + "Non-existing tag #{tag}" : "Etichetă inexistentă #{tag}", + "restricted" : "restricționat", "invisible" : "invizibil", "Delete" : "Șterge", "Rename" : "Redenumește", + "Collaborative tags" : "Etichete colaborative", "The object type is not specified." : "Tipul obiectului nu este specificat.", "Enter new" : "Introducere nou", "Add" : "Adaugă", "Edit tags" : "Editează etichete", + "No tags selected for deletion." : "Nu au fost selectate etichete pentru ștergere.", "unknown text" : "text necunoscut", + "Hello world!" : "Hello world!", "sunny" : "însorit", + "Hello {name}" : "Salut {name}", "new" : "nou", + "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.", + "Updating to {version}" : "Actualizare la {version}", "An error occurred." : "A apărut o eroare.", "Please reload the page." : "Te rugăm să reîncarci pagina.", "The update was unsuccessful. Please report this issue to the ownCloud community." : "Actualizarea a eșuat! Raportați problema către comunitatea ownCloud.", + "The update was successful. There were warnings." : "Actualizarea nu a avut loc cu succes. Au existat avertismente.", "The update was successful. Redirecting you to ownCloud now." : "Actualizare reușită. Ești redirecționat către ownCloud.", + "Searching other places" : "Se caută în alte locuri", + "No search results in other folders" : "Nu există rezultate ale căutării în alte directoare", "Personal" : "Personal", "Users" : "Utilizatori", "Apps" : "Aplicații", @@ -158,8 +189,12 @@ "Help" : "Ajutor", "Access forbidden" : "Acces restricționat", "File not found" : "Fișierul nu a fost găsit", + "The specified document has not been found on the server." : "Documentul specificat nu a fost găsit pe server.", + "You can click here to return to %s." : "Poți da click aici pentru a te întoarce la %s.", "The share will expire on %s." : "Partajarea va expira în data de %s.", "Cheers!" : "Noroc!", + "Internal Server Error" : "Eroare internă a serverului", + "The server encountered an internal error and was unable to complete your request." : "Serverul a întâmpinat o eroare și nu îți poate îndeplini cererea.", "Technical details" : "Detalii tehnice", "Type: %s" : "Tip: %s", "Code: %s" : "Cod: %s", @@ -188,16 +223,35 @@ "See the documentation" : "Vezi documentația", "Log out" : "Ieșire", "Search" : "Căutare", + "Please contact your administrator." : "Contactează-ți administratorul.", "An internal error occurred." : "A apărut o eroare internă.", + "Please try again or contact your administrator." : "Încearcă din nou sau contactează-ți administratorul.", "Username or email" : "Nume de utilizator sau adresă email", "Log in" : "Autentificare", + "Wrong password. Reset it?" : "Parolă greșită. O resetezi?", + "Wrong password." : "Parolă greșită.", + "Stay logged in" : "Rămâi autentificat", "Alternative Logins" : "Conectări alternative", "Use the following link to reset your password: {link}" : "Folosește următorul link pentru a reseta parola: {link}", "New password" : "Noua parolă", "New Password" : "Noua parolă", "Reset password" : "Resetează parola", "Thank you for your patience." : "Îți mulțumim pentru răbdare.", + "Two-step verification" : "Verificare în doi pași", + "Cancel login" : "Anulează autentificarea", + "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.", + "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului", "You are accessing the server from an untrusted domain." : "Accesezi serverul dintr-un domeniu care nu a fost configurat ca fiind de încredere.", - "Start update" : "Începe actualizarea" + "Add \"%s\" as trusted domain" : "Adaugă \"%s\" ca domeniu de încredere", + "App update required" : "E necesară o actualizare a aplicației", + "%s will be updated to version %s" : "%s va fi actualizat la versiunea %s", + "These apps will be updated:" : "Aceste aplicații vor fi actualizate:", + "These incompatible apps will be disabled:" : "Aceste aplicații incompatibile vor fi dezactivate:", + "The theme %s has been disabled." : "Tema %s a fost dezactivată.", + "Start update" : "Începe actualizarea", + "Detailed logs" : "Loguri detaliate", + "Update needed" : "E necesară actualizarea", + "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.", + "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme." },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/core/l10n/ru.js b/core/l10n/ru.js index 398fe84160fd9..619d007c85116 100644 --- a/core/l10n/ru.js +++ b/core/l10n/ru.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Спасибо за терпение.", "Two-step verification" : "Двухшаговая проверка", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Для вашей учётной записи включена повышенная безопасность. Пожалуйста, аутентифицируйтесь через второй фактор.", + "Cancel login" : "Отменить вход", "Please authenticate using the selected factor." : "Пожалуйста, аутентифицируйтесь выбранным фактором.", "An error occured while verifying the token" : "При проверке токена возникла ошибка.", "You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с недоверенного домена.", diff --git a/core/l10n/ru.json b/core/l10n/ru.json index fb4bf2a1052a5..9fe8d22feae37 100644 --- a/core/l10n/ru.json +++ b/core/l10n/ru.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Спасибо за терпение.", "Two-step verification" : "Двухшаговая проверка", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Для вашей учётной записи включена повышенная безопасность. Пожалуйста, аутентифицируйтесь через второй фактор.", + "Cancel login" : "Отменить вход", "Please authenticate using the selected factor." : "Пожалуйста, аутентифицируйтесь выбранным фактором.", "An error occured while verifying the token" : "При проверке токена возникла ошибка.", "You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с недоверенного домена.", diff --git a/core/l10n/sq.js b/core/l10n/sq.js index 06da819b5a076..049f33d335544 100644 --- a/core/l10n/sq.js +++ b/core/l10n/sq.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Ju faleminderit për durimin.", "Two-step verification" : "Verifikim dyhapësh", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Siguria e zgjeruar është aktivizuar për llogarinë tuaj. Ju lutemi, bëni mirëfilltësimin duke përdorur një faktor të dytë.", + "Cancel login" : "Anuloje hyrjen", "Please authenticate using the selected factor." : "Ju lutemi, bëni mirëfilltësimin duke përdorur faktorin e përzgjedhur.", "An error occured while verifying the token" : "Ndodhi një gabim gjatë verifikimit të token-it", "You are accessing the server from an untrusted domain." : "Po hyni në shërbyes nga një përkatësi jo e besuar.", diff --git a/core/l10n/sq.json b/core/l10n/sq.json index 91d40b34e971e..d6dee599bf7de 100644 --- a/core/l10n/sq.json +++ b/core/l10n/sq.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Ju faleminderit për durimin.", "Two-step verification" : "Verifikim dyhapësh", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Siguria e zgjeruar është aktivizuar për llogarinë tuaj. Ju lutemi, bëni mirëfilltësimin duke përdorur një faktor të dytë.", + "Cancel login" : "Anuloje hyrjen", "Please authenticate using the selected factor." : "Ju lutemi, bëni mirëfilltësimin duke përdorur faktorin e përzgjedhur.", "An error occured while verifying the token" : "Ndodhi një gabim gjatë verifikimit të token-it", "You are accessing the server from an untrusted domain." : "Po hyni në shërbyes nga një përkatësi jo e besuar.", diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js index 3124a3352612b..1a36e960779b7 100644 --- a/lib/l10n/pl.js +++ b/lib/l10n/pl.js @@ -30,6 +30,7 @@ OC.L10N.register( "_%n minute ago_::_%n minutes ago_" : ["%n minute temu","%n minut temu","%n minut temu"], "seconds ago" : "sekund temu", "Empty filename is not allowed" : "Pusta nazwa nie jest dozwolona.", + "4-byte characters are not supported in file names" : "Znaki 4-bajtowe są niedozwolone w nazwach plików", "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", "File name is too long" : "Nazwa pliku zbyt długa", "App directory already exists" : "Katalog aplikacji już isnieje", diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json index bbb4cc7b5ea96..048530779e1ad 100644 --- a/lib/l10n/pl.json +++ b/lib/l10n/pl.json @@ -28,6 +28,7 @@ "_%n minute ago_::_%n minutes ago_" : ["%n minute temu","%n minut temu","%n minut temu"], "seconds ago" : "sekund temu", "Empty filename is not allowed" : "Pusta nazwa nie jest dozwolona.", + "4-byte characters are not supported in file names" : "Znaki 4-bajtowe są niedozwolone w nazwach plików", "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", "File name is too long" : "Nazwa pliku zbyt długa", "App directory already exists" : "Katalog aplikacji już isnieje", diff --git a/lib/l10n/pt_PT.js b/lib/l10n/pt_PT.js index 196009d71d862..f069531895d19 100644 --- a/lib/l10n/pt_PT.js +++ b/lib/l10n/pt_PT.js @@ -18,12 +18,12 @@ OC.L10N.register( "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", "ownCloud %s or higher is required." : "É necessário ownCloud %s ou superior.", "ownCloud %s or lower is required." : "É necessário ownCloud %s ou inferior.", - "Unknown filetype" : "Ficheiro desconhecido", + "Unknown filetype" : "Tipo de ficheiro desconhecido", "Invalid image" : "Imagem inválida", "today" : "hoje", "yesterday" : "ontem", "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], - "last month" : "ultímo mês", + "last month" : "ultimo mês", "last year" : "ano passado", "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], "seconds ago" : "Minutos atrás", diff --git a/lib/l10n/pt_PT.json b/lib/l10n/pt_PT.json index 5dc215f41b832..55492d0f14c45 100644 --- a/lib/l10n/pt_PT.json +++ b/lib/l10n/pt_PT.json @@ -16,12 +16,12 @@ "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", "ownCloud %s or higher is required." : "É necessário ownCloud %s ou superior.", "ownCloud %s or lower is required." : "É necessário ownCloud %s ou inferior.", - "Unknown filetype" : "Ficheiro desconhecido", + "Unknown filetype" : "Tipo de ficheiro desconhecido", "Invalid image" : "Imagem inválida", "today" : "hoje", "yesterday" : "ontem", "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], - "last month" : "ultímo mês", + "last month" : "ultimo mês", "last year" : "ano passado", "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], "seconds ago" : "Minutos atrás", diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index f84c8a41f17be..4e48ee149ef80 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -273,7 +273,9 @@ protected function loadConfigValues() { ->from('appconfig'); $result = $sql->execute(); - while ($row = $result->fetch()) { + // we are going to store the result in memory anyway + $rows = $result->fetchAll(); + foreach ($rows as $row) { if (!isset($this->cache[$row['appid']])) { $this->cache[$row['appid']] = []; } diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index 0856d8d19c091..6a096e504d80d 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -104,6 +104,10 @@ public function getConnection($type, $additionalConnectionParams) { break; case 'oci': $eventManager->addEventSubscriber(new OracleSessionInit); + // the driverOptions are unused in dbal and need to be mapped to the parameters + if (isset($additionalConnectionParams['driverOptions'])) { + $additionalConnectionParams = array_merge($additionalConnectionParams, $additionalConnectionParams['driverOptions']); + } break; case 'sqlite3': $journalMode = $additionalConnectionParams['sqlite.journal_mode']; diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index 2bd9fb0210077..3573ff19c6e8d 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -22,6 +22,8 @@ namespace OC\Encryption; +use OC\Cache\CappedMemoryCache; + class File implements \OCP\Encryption\IFile { /** @var Util */ @@ -36,6 +38,7 @@ class File implements \OCP\Encryption\IFile { public function __construct(Util $util) { $this->util = $util; + $this->cache = new CappedMemoryCache(); } diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index b998c6bcfaee9..52bb4dfdfca1c 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -29,6 +29,10 @@ * Propagate etags and mtimes within the storage */ class Propagator implements IPropagator { + private $inBatch = false; + + private $batch = []; + /** * @var \OC\Files\Storage\Storage */ @@ -59,6 +63,13 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) { $parents = $this->getParents($internalPath); + if ($this->inBatch) { + foreach ($parents as $parent) { + $this->addToBatch($parent, $time, $sizeDifference); + } + return; + } + $parentHashes = array_map('md5', $parents); $etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag @@ -68,7 +79,7 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) { }, $parentHashes); $builder->update('filecache') - ->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter($time) . ')')) + ->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT) . ')')) ->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR)) ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))) ->andWhere($builder->expr()->in('path_hash', $hashParams)); @@ -98,4 +109,79 @@ protected function getParents($path) { } return $parents; } + + /** + * Mark the beginning of a propagation batch + * + * Note that not all cache setups support propagation in which case this will be a noop + * + * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent + * before the batch is committed. + */ + public function beginBatch() { + $this->inBatch = true; + } + + private function addToBatch($internalPath, $time, $sizeDifference) { + if (!isset($this->batch[$internalPath])) { + $this->batch[$internalPath] = [ + 'hash' => md5($internalPath), + 'time' => $time, + 'size' => $sizeDifference + ]; + } else { + $this->batch[$internalPath]['size'] += $sizeDifference; + if ($time > $this->batch[$internalPath]['time']) { + $this->batch[$internalPath]['time'] = $time; + } + } + } + + /** + * Commit the active propagation batch + */ + public function commitBatch() { + if (!$this->inBatch) { + throw new \BadMethodCallException('Not in batch'); + } + $this->inBatch = false; + + $this->connection->beginTransaction(); + + $query = $this->connection->getQueryBuilder(); + $storageId = (int)$this->storage->getStorageCache()->getNumericId(); + + $query->update('filecache') + ->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')')) + ->set('etag', $query->expr()->literal(uniqid())) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))); + + $sizeQuery = $this->connection->getQueryBuilder(); + $sizeQuery->update('filecache') + ->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size'))) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))) + ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + + foreach ($this->batch as $item) { + $query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT); + $query->setParameter('hash', $item['hash']); + + $query->execute(); + + if ($item['size']) { + $sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT); + $sizeQuery->setParameter('hash', $item['hash']); + + $sizeQuery->execute(); + } + } + + $this->batch = []; + + $this->connection->commit(); + } + + } diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index 8beba116fe14f..e4e5e353f9f53 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -138,7 +138,9 @@ public function backgroundScan($dir) { $this->triggerPropagator($storage, $path); }); + $storage->getPropagator()->beginBatch(); $scanner->backgroundScan(); + $storage->getPropagator()->commitBatch(); } } @@ -187,12 +189,14 @@ public function scan($dir = '') { $this->db->beginTransaction(); } try { + $storage->getPropagator()->beginBatch(); $scanner->scan($relativePath, \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); $cache = $storage->getCache(); if ($cache instanceof Cache) { // only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner $cache->correctFolderSize($relativePath); } + $storage->getPropagator()->commitBatch(); } catch (StorageNotAvailableException $e) { $this->logger->error('Storage ' . $storage->getId() . ' not available'); $this->logger->logException($e); diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 4869db7749799..bb2967d7e6e2a 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -29,11 +29,13 @@ namespace OC; use OC\Repair\AssetCache; +use OC\Repair\AvatarPermissions; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\DropOldJobs; use OC\Repair\OldGroupMembershipShares; use OC\Repair\RemoveGetETagEntries; +use OC\Repair\RemoveOldShares; use OC\Repair\SharePropagation; use OC\Repair\SqliteAutoincrement; use OC\Repair\DropOldTables; @@ -132,6 +134,8 @@ public static function getRepairSteps() { new UpdateOutdatedOcsIds(\OC::$server->getConfig()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new SharePropagation(\OC::$server->getConfig()), + new RemoveOldShares(\OC::$server->getDatabaseConnection()), + new AvatarPermissions(\OC::$server->getDatabaseConnection()), ]; } diff --git a/lib/private/Repair/AvatarPermissions.php b/lib/private/Repair/AvatarPermissions.php new file mode 100644 index 0000000000000..d23479f5ba8f9 --- /dev/null +++ b/lib/private/Repair/AvatarPermissions.php @@ -0,0 +1,115 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ +namespace OC\Repair; + +use Doctrine\DBAL\Platforms\OraclePlatform; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class AvatarPermissions + * + * @package OC\Repair + */ +class AvatarPermissions implements IRepairStep { + /** @var IDBConnection */ + private $connection; + + /** + * AvatarPermissions constructor. + * + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() { + return 'Fix permissions so avatars can be stored again'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + $output->startProgress(2); + $this->fixUserRootPermissions(); + $output->advance(); + $this->fixAvatarPermissions(); + $output->finishProgress(); + } + + /** + * Make sure all user roots have permissions 23 (all but share) + */ + protected function fixUserRootPermissions() { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb->select('numeric_id') + ->from('storages') + ->where($qb->expr()->like('id', $qb2->createParameter('like'))); + + if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + // '' is null on oracle + $path = $qb2->expr()->isNull('path'); + } else { + $path = $qb2->expr()->eq('path', $qb2->createNamedParameter('')); + } + + $qb2->update('filecache') + ->set('permissions', $qb2->createNamedParameter(23)) + ->where($path) + ->andWhere($qb2->expr()->in('storage', $qb2->createFunction($qb->getSQL()))) + ->andWhere($qb2->expr()->neq('permissions', $qb2->createNamedParameter(23))) + ->setParameter('like', 'home::%'); + + + $qb2->execute(); + } + + /** + * Make sure all avatar files in the user roots have permission 27 + */ + protected function fixAvatarPermissions() { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb->select('numeric_id') + ->from('storages') + ->where($qb->expr()->like('id', $qb2->createParameter('like'))); + + $qb2->update('filecache') + ->set('permissions', $qb2->createNamedParameter(27)) + ->where($qb2->expr()->like('path', $qb2->createNamedParameter('avatar.%'))) + ->andWhere($qb2->expr()->in('storage', $qb2->createFunction($qb->getSQL()))) + ->andWhere($qb2->expr()->neq('permissions', $qb2->createNamedParameter(27))) + ->setParameter('like', 'home::%'); + + $qb2->execute(); + } + +} + diff --git a/lib/private/Repair/RemoveOldShares.php b/lib/private/Repair/RemoveOldShares.php new file mode 100644 index 0000000000000..2c05d97b15c13 --- /dev/null +++ b/lib/private/Repair/RemoveOldShares.php @@ -0,0 +1,103 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ +namespace OC\Repair; + +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class RemoveOldShares + * + * @package OC\Repair + */ +class RemoveOldShares implements IRepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** + * RemoveOldCalendarShares constructor. + * + * @param IDBConnection $db + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() { + return 'Remove old (< 9.0) calendar/contact shares'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + $output->startProgress(4); + + $this->removeCalendarShares($output); + $this->removeContactShares($output); + + $output->finishProgress(); + } + + /** + * @param IOutput $output + */ + private function removeCalendarShares(IOutput $output) { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('calendar'))); + $qb->execute(); + + $output->advance(); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('event'))); + $qb->execute(); + + $output->advance(); + } + + /** + * @param IOutput $output + */ + private function removeContactShares(IOutput $output) { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('contact'))); + $qb->execute(); + + $output->advance(); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('addressbook'))); + $qb->execute(); + + $output->advance(); + } +} + diff --git a/lib/private/legacy/json.php b/lib/private/legacy/json.php index d201d69723e80..1dde63602b1f2 100644 --- a/lib/private/legacy/json.php +++ b/lib/private/legacy/json.php @@ -64,7 +64,9 @@ public static function checkAppEnabled($app) { * @deprecated Use annotation based ACLs from the AppFramework instead */ public static function checkLoggedIn() { - if( !OC_User::isLoggedIn()) { + $twoFactorAuthManger = \OC::$server->getTwoFactorAuthManager(); + if( !OC_User::isLoggedIn() + || $twoFactorAuthManger->needsSecondFactor()) { $l = \OC::$server->getL10N('lib'); http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index a863348566e60..65d00c16388da 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -970,6 +970,11 @@ public static function checkLoggedIn() { ); exit(); } + // Redirect to index page if 2FA challenge was not solved yet + if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor()) { + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); + exit(); + } } /** diff --git a/lib/public/Files/Cache/IPropagator.php b/lib/public/Files/Cache/IPropagator.php index 5494ec9a54ecd..541135b9e6006 100644 --- a/lib/public/Files/Cache/IPropagator.php +++ b/lib/public/Files/Cache/IPropagator.php @@ -27,6 +27,25 @@ * @since 9.0.0 */ interface IPropagator { + /** + * Mark the beginning of a propagation batch + * + * Note that not all cache setups support propagation in which case this will be a noop + * + * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent + * before the batch is committed. + * + * @since 9.1.0 + */ + public function beginBatch(); + + /** + * Commit the active propagation batch + * + * @since 9.1.0 + */ + public function commitBatch(); + /** * @param string $internalPath * @param int $time diff --git a/lib/public/Migration/IRepairStep.php b/lib/public/Migration/IRepairStep.php index 3394b70a6ff87..84619128b4e7f 100644 --- a/lib/public/Migration/IRepairStep.php +++ b/lib/public/Migration/IRepairStep.php @@ -39,8 +39,9 @@ public function getName(); * Run repair step. * Must throw exception on error. * - * @since 9.1.0 + * @param IOutput $output * @throws \Exception in case of failure + * @since 9.1.0 */ public function run(IOutput $output); diff --git a/settings/l10n/en_GB.js b/settings/l10n/en_GB.js index 443ca4adc38fa..bf3bf387fa629 100644 --- a/settings/l10n/en_GB.js +++ b/settings/l10n/en_GB.js @@ -137,6 +137,7 @@ OC.L10N.register( "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.", "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel.", "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience." : "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience.", "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version." : "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version.", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "The PHP module 'fileinfo' is missing. We strongly recommend enabling this module to get best results with mime-type detection.", @@ -266,6 +267,7 @@ OC.L10N.register( "Current password" : "Current password", "New password" : "New password", "Change password" : "Change password", + "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.", "Browser" : "Browser", "Most recent activity" : "Most recent activity", "You've linked these devices." : "You've linked these devices.", diff --git a/settings/l10n/en_GB.json b/settings/l10n/en_GB.json index 838710e407386..9ab2f0e5a962b 100644 --- a/settings/l10n/en_GB.json +++ b/settings/l10n/en_GB.json @@ -135,6 +135,7 @@ "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.", "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel.", "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience." : "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience.", "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version." : "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version.", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "The PHP module 'fileinfo' is missing. We strongly recommend enabling this module to get best results with mime-type detection.", @@ -264,6 +265,7 @@ "Current password" : "Current password", "New password" : "New password", "Change password" : "Change password", + "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.", "Browser" : "Browser", "Most recent activity" : "Most recent activity", "You've linked these devices." : "You've linked these devices.", diff --git a/settings/templates/apps.php b/settings/templates/apps.php index ecb00fb27c38b..d3c1433269319 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -91,8 +91,22 @@ t('Admin documentation'));?> ↗ {{/if}} + + {{#if documentation.developer}} + + t('Developer documentation'));?> ↗ + + {{/if}}

{{/if}} + + {{#if website}} + t('Visit website'));?> ↗ + {{/if}} + + {{#if bugs}} + t('Report a bug'));?> ↗ + {{/if}}
t("Show description …"));?>
diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php index ea9d6a44148ae..d6fa772d38bee 100644 --- a/tests/Core/Controller/LoginControllerTest.php +++ b/tests/Core/Controller/LoginControllerTest.php @@ -29,6 +29,7 @@ use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; +use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use Test\TestCase; @@ -36,19 +37,19 @@ class LoginControllerTest extends TestCase { /** @var LoginController */ private $loginController; - /** @var IRequest */ + /** @var IRequest | \PHPUnit_Framework_MockObject_MockObject */ private $request; - /** @var IUserManager */ + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ private $userManager; - /** @var IConfig */ + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ private $config; - /** @var ISession */ + /** @var ISession | \PHPUnit_Framework_MockObject_MockObject */ private $session; - /** @var IUserSession */ + /** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */ private $userSession; - /** @var IURLGenerator */ + /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */ private $urlGenerator; - /** @var Manager */ + /** @var Manager | \PHPUnit_Framework_MockObject_MockObject */ private $twoFactorManager; public function setUp() { @@ -296,6 +297,7 @@ public function testLoginWithInvalidCredentials() { } public function testLoginWithValidCredentials() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $password = 'secret'; $indexPageUrl = 'some url'; @@ -323,6 +325,7 @@ public function testLoginWithValidCredentials() { } public function testLoginWithValidCredentialsAndRedirectUrl() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $user->expects($this->any()) ->method('getUID') @@ -352,6 +355,7 @@ public function testLoginWithValidCredentialsAndRedirectUrl() { } public function testLoginWithTwoFactorEnforced() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $user->expects($this->any()) ->method('getUID') @@ -380,8 +384,36 @@ public function testLoginWithTwoFactorEnforced() { ->with('core.TwoFactorChallenge.selectChallenge') ->will($this->returnValue($challengeUrl)); - $expected = new \OCP\AppFramework\Http\RedirectResponse($challengeUrl); + $expected = new RedirectResponse($challengeUrl); $this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null)); } + public function testToNotLeakLoginName() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ + $user = $this->getMock('\OCP\IUser'); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('john')); + + $this->userManager->expects($this->exactly(2)) + ->method('checkPassword') + ->withConsecutive( + ['john@doe.com', 'just wrong'], + ['john', 'just wrong'] + ) + ->willReturn(false); + + $this->userManager->expects($this->once()) + ->method('getByEmail') + ->with('john@doe.com') + ->willReturn([$user]); + + $this->urlGenerator->expects($this->once()) + ->method('linkToRoute') + ->with('core.login.showLoginForm', ['user' => 'john@doe.com']) + ->will($this->returnValue('')); + + $expected = new RedirectResponse(''); + $this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', 'just wrong', null)); + } } diff --git a/tests/lib/Files/Cache/PropagatorTest.php b/tests/lib/Files/Cache/PropagatorTest.php new file mode 100644 index 0000000000000..402b29c8c3efd --- /dev/null +++ b/tests/lib/Files/Cache/PropagatorTest.php @@ -0,0 +1,125 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use OC\Files\Storage\Temporary; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Storage\IStorage; +use Test\TestCase; + +/** + * @group DB + */ +class PropagatorTest extends TestCase { + /** @var IStorage */ + private $storage; + + public function setUp() { + parent::setUp(); + $this->storage = new Temporary(); + $this->storage->mkdir('foo/bar'); + $this->storage->file_put_contents('foo/bar/file.txt', 'bar'); + $this->storage->getScanner()->scan(''); + } + + /** + * @param $paths + * @return ICacheEntry[] + */ + private function getFileInfos($paths) { + $values = array_map(function ($path) { + return $this->storage->getCache()->get($path); + }, $paths); + return array_combine($paths, $values); + } + + public function testEtagPropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldInfos = $this->getFileInfos($paths); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', time()); + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + $this->assertNotEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + } + } + + public function testTimePropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldTime = time() - 200; + $targetTime = time() - 100; + $now = time(); + $cache = $this->storage->getCache(); + $cache->put('', ['mtime' => $now]); + $cache->put('foo', ['mtime' => $now]); + $cache->put('foo/bar', ['mtime' => $oldTime]); + $cache->put('foo/bar/file.txt', ['mtime' => $oldTime]); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', $targetTime); + $newInfos = $this->getFileInfos($paths); + + $this->assertEquals($targetTime, $newInfos['foo/bar']->getMTime()); + + // dont lower mtimes + $this->assertEquals($now, $newInfos['foo']->getMTime()); + $this->assertEquals($now, $newInfos['']->getMTime()); + } + + public function testSizePropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldInfos = $this->getFileInfos($paths); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', time(), 10); + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + $this->assertEquals($oldInfo->getSize() + 10, $newInfos[$i]->getSize()); + } + } + + public function testBatchedPropagation() { + $this->storage->mkdir('foo/baz'); + $this->storage->mkdir('asd'); + $this->storage->file_put_contents('asd/file.txt', 'bar'); + $this->storage->file_put_contents('foo/baz/file.txt', 'bar'); + $this->storage->getScanner()->scan(''); + + $paths = ['', 'foo', 'foo/bar', 'asd', 'foo/baz']; + + $oldInfos = $this->getFileInfos($paths); + $propagator = $this->storage->getPropagator(); + + $propagator->beginBatch(); + $propagator->propagateChange('asd/file.txt', time(), 10); + $propagator->propagateChange('foo/bar/file.txt', time(), 2); + + $newInfos = $this->getFileInfos($paths); + + // no changes until we finish the batch + foreach ($oldInfos as $i => $oldInfo) { + $this->assertEquals($oldInfo->getSize(), $newInfos[$i]->getSize()); + $this->assertEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + $this->assertEquals($oldInfo->getMTime(), $newInfos[$i]->getMTime()); + } + + $propagator->commitBatch(); + + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + if ($oldInfo->getPath() !== 'foo/baz') { + $this->assertNotEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + } + } + + $this->assertEquals($oldInfos['']->getSize() + 12, $newInfos['']->getSize()); + $this->assertEquals($oldInfos['asd']->getSize() + 10, $newInfos['asd']->getSize()); + $this->assertEquals($oldInfos['foo']->getSize() + 2, $newInfos['foo']->getSize()); + $this->assertEquals($oldInfos['foo/bar']->getSize() + 2, $newInfos['foo/bar']->getSize()); + $this->assertEquals($oldInfos['foo/baz']->getSize(), $newInfos['foo/baz']->getSize()); + } +} diff --git a/tests/lib/Repair/AvatarPermissionsTest.php b/tests/lib/Repair/AvatarPermissionsTest.php new file mode 100644 index 0000000000000..e3f582dc51253 --- /dev/null +++ b/tests/lib/Repair/AvatarPermissionsTest.php @@ -0,0 +1,189 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ +namespace Test\Repair; + +/** + * Test for fixing the userRoot and avatar permissions + * + * @group DB + * + * @see \OC\Repair\AvatarPermissionsTest + */ +class AvatarPermissionsTest extends \Test\TestCase { + + /** @var \OC\Repair\AvatarPermissions */ + protected $repair; + + /** @var \OCP\IDBConnection */ + protected $connection; + + protected function setUp() { + parent::setUp(); + + $this->connection = \OC::$server->getDatabaseConnection(); + $this->repair = new \OC\Repair\AvatarPermissions($this->connection); + $this->cleanUpTables(); + } + + protected function tearDown() { + $this->cleanUpTables(); + + parent::tearDown(); + } + + protected function cleanUpTables() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('filecache')->execute(); + $qb->delete('storages')->execute(); + } + + public function dataFixUserRootPermissions() { + return [ + ['home::user', '', 0, 23], + ['home::user', 'foo', 0, 0], + ['home::user', 'avatar.jpg', 0, 0], + ['ABC::user', '', 0, 0], + ['ABC::user', 'foo', 0, 0], + ]; + } + + /** + * @dataProvider dataFixUserRootPermissions + * + * @param string $storageId + * @param string $path + * @param int $permissionsBefore + * @param int $permissionsAfter + */ + public function testFixUserRootPermissions($storageId, $path, $permissionsBefore, $permissionsAfter) { + $userStorage = $this->addStorage($storageId); + $userHome = $this->addFileCacheEntry($userStorage, $path, $permissionsBefore); + + $this->invokePrivate($this->repair, 'fixUserRootPermissions', []); + + $this->verifyPermissions($userHome, $permissionsAfter); + } + + public function dataFixAvatarPermissions() { + return [ + ['home::user', '', 0, 0], + ['home::user', 'avatar.jpg', 0, 27], + ['home::user', 'avatar.png', 0, 27], + ['home::user', 'avatar.32.png', 0, 27], + ['home::user', 'mine.txt', 0, 0], + ['ABC::user', '', 0, 0], + ['ABC::user', 'avatar.jpg', 0, 0], + ['ABC::user', 'avatar.png', 0, 0], + ['ABC::user', 'avatar.32.png', 0, 0], + ['ABC::user', 'mine.txt', 0, 0], + ]; + } + + /** + * @dataProvider dataFixAvatarPermissions + * + * @param string $storageId + * @param string $path + * @param int $permissionsBefore + * @param int $permissionsAfter + */ + public function testFixAvatarPermissions($storageId, $path, $permissionsBefore, $permissionsAfter) { + $userStorage = $this->addStorage($storageId); + $userHome = $this->addFileCacheEntry($userStorage, $path, $permissionsBefore); + + $this->invokePrivate($this->repair, 'fixAvatarPermissions', []); + + $this->verifyPermissions($userHome, $permissionsAfter); + } + + /** + * Add a new storage + * + * @param string $id + * @return int The numeric id + */ + protected function addStorage($id) { + $qb = $this->connection->getQueryBuilder(); + + $qb->insert('storages') + ->values([ + 'id' => $qb->createNamedParameter($id) + ]); + + $qb->execute(); + + return $qb->getLastInsertId(); + } + + /** + * Add a filecache entry + * + * @param int $storage + * @param string $path + * @param int $permissions + * + * @return int The fileid + */ + protected function addFileCacheEntry($storage, $path, $permissions) { + $qb = $this->connection->getQueryBuilder(); + + $qb->insert('filecache') + ->values([ + 'path' => $qb->createNamedParameter($path), + 'path_hash' => $qb->createNamedParameter(md5($path)), + 'parent' => $qb->createNamedParameter(42), + 'mimetype' => $qb->createNamedParameter(23), + 'mimepart' => $qb->createNamedParameter(32), + 'size' => $qb->createNamedParameter(16), + 'mtime' => $qb->createNamedParameter(1), + 'storage_mtime' => $qb->createNamedParameter(2), + 'encrypted' => $qb->createNamedParameter(0), + 'unencrypted_size' => $qb->createNamedParameter(0), + 'storage' => $qb->createNamedParameter($storage), + 'permissions' => $qb->createNamedParameter($permissions), + ]); + + $qb->execute(); + + return $qb->getLastInsertId(); + } + + /** + * @param int $fileId + * @param int $permissions + */ + protected function verifyPermissions($fileId, $permissions) { + $qb = $this->connection->getQueryBuilder(); + + $qb->select('permissions') + ->from('filecache') + ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($fileId))); + + $cursor = $qb->execute(); + + $data = $cursor->fetch(); + $cursor->closeCursor(); + + $this->assertSame($permissions, (int)$data['permissions']); + } + + +} diff --git a/tests/lib/Repair/RemoveOldSharesTest.php b/tests/lib/Repair/RemoveOldSharesTest.php new file mode 100644 index 0000000000000..ac30585bdc5b2 --- /dev/null +++ b/tests/lib/Repair/RemoveOldSharesTest.php @@ -0,0 +1,160 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ +namespace Test\Repair; + +use OC\Repair\RemoveOldShares; +use OCP\IDBConnection; +use OCP\Migration\IOutput; + +/** + * Class RemoveOldSharesTest + * + * @package Test\Repair + * @group DB + */ +class RemoveOldSharesTest extends \Test\TestCase { + + /** @var RemoveOldShares */ + protected $repair; + + /** @var IDBConnection */ + protected $connection; + + /** @var IOutput */ + private $outputMock; + + protected function setUp() { + parent::setUp(); + + $this->outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') + ->disableOriginalConstructor() + ->getMock(); + + $this->connection = \OC::$server->getDatabaseConnection(); + $this->repair = new RemoveOldShares($this->connection); + } + + protected function tearDown() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share'); + $qb->execute(); + + return parent::tearDown(); + } + + public function testRun() { + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('calendar'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('event'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('contact'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('addressbook'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count') + ->from('share'); + + $cursor = $qb->execute(); + $data = $cursor->fetchAll(); + $cursor->closeCursor(); + $this->assertEquals(5, $data[0]['count']); + + $this->repair->run($this->outputMock); + + $qb = $this->connection->getQueryBuilder(); + $qb->select('*') + ->from('share'); + + $cursor = $qb->execute(); + $data = $cursor->fetchAll(); + $cursor->closeCursor(); + $this->assertCount(1, $data); + $this->assertEquals('file', $data[0]['item_type']); + } +}