diff options
108 files changed, 1737 insertions, 200 deletions
diff --git a/apps/comments/l10n/lb.js b/apps/comments/l10n/lb.js index f63640ac6f7..d7f8c5884ac 100644 --- a/apps/comments/l10n/lb.js +++ b/apps/comments/l10n/lb.js @@ -2,6 +2,7 @@ OC.L10N.register( "comments", { "Cancel" : "Ofbriechen", - "Save" : "Späicheren" + "Save" : "Späicheren", + "Comment" : "Kommentar" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/comments/l10n/lb.json b/apps/comments/l10n/lb.json index c015c4dd2a8..bfa307a7e8d 100644 --- a/apps/comments/l10n/lb.json +++ b/apps/comments/l10n/lb.json @@ -1,5 +1,6 @@ { "translations": { "Cancel" : "Ofbriechen", - "Save" : "Späicheren" + "Save" : "Späicheren", + "Comment" : "Kommentar" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/comments/l10n/pl.js b/apps/comments/l10n/pl.js index d5e3f4b3320..d4a492e1da2 100644 --- a/apps/comments/l10n/pl.js +++ b/apps/comments/l10n/pl.js @@ -14,7 +14,10 @@ OC.L10N.register( "Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}", "{count} unread comments" : "{count} nieprzeczytanych komentarzy", "Comment" : "Komentarz", + "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>", + "You commented" : "Skomentowałeś/łaś", "%1$s commented" : "%1$s skomentował", + "You commented on %2$s" : "Skomentowałeś/łaś %2$s", "%1$s commented on %2$s" : "%1$s skomentował %2$s" }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/apps/comments/l10n/pl.json b/apps/comments/l10n/pl.json index 28bbf20817f..78e9f0ff210 100644 --- a/apps/comments/l10n/pl.json +++ b/apps/comments/l10n/pl.json @@ -12,7 +12,10 @@ "Allowed characters {count} of {max}" : "Dozwolone znaki {count} z {max}", "{count} unread comments" : "{count} nieprzeczytanych komentarzy", "Comment" : "Komentarz", + "<strong>Comments</strong> for files <em>(always listed in stream)</em>" : "<strong>Komentarze</strong> dla plików <em>(zawsze wypisane w strumieniu)</em>", + "You commented" : "Skomentowałeś/łaś", "%1$s commented" : "%1$s skomentował", + "You commented on %2$s" : "Skomentowałeś/łaś %2$s", "%1$s commented on %2$s" : "%1$s skomentował %2$s" },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php index 88582d64ee5..fc7aff4a63c 100644 --- a/apps/dav/appinfo/v1/carddav.php +++ b/apps/dav/appinfo/v1/carddav.php @@ -75,6 +75,7 @@ if ($debugging) { } $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); +$server->addPlugin(new \OCA\DAV\CardDAV\ImageExportPlugin(\OC::$server->getLogger())); $server->addPlugin(new ExceptionLoggerPlugin('carddav', \OC::$server->getLogger())); // And off we go! diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index de2056ebc35..b7b24ba7632 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -136,7 +136,8 @@ class Application extends App { public function setupContactsProvider(IManager $contactsManager, $userID) { /** @var ContactsManager $cm */ $cm = $this->getContainer()->query('ContactsManager'); - $cm->setupContactsProvider($contactsManager, $userID); + $urlGenerator = $this->getContainer()->getServer()->getURLGenerator(); + $cm->setupContactsProvider($contactsManager, $userID, $urlGenerator); } public function registerHooks() { diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 310be695185..b26f81766c6 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -24,6 +24,7 @@ namespace OCA\DAV\CardDAV; use OCP\Constants; use OCP\IAddressBook; +use OCP\IURLGenerator; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property\Text; use Sabre\VObject\Reader; @@ -40,21 +41,27 @@ class AddressBookImpl implements IAddressBook { /** @var AddressBook */ private $addressBook; + /** @var IURLGenerator */ + private $urlGenerator; + /** * AddressBookImpl constructor. * * @param AddressBook $addressBook * @param array $addressBookInfo * @param CardDavBackend $backend + * @param IUrlGenerator $urlGenerator */ public function __construct( AddressBook $addressBook, array $addressBookInfo, - CardDavBackend $backend) { + CardDavBackend $backend, + IURLGenerator $urlGenerator) { $this->addressBook = $addressBook; $this->addressBookInfo = $addressBookInfo; $this->backend = $backend; + $this->urlGenerator = $urlGenerator; } /** @@ -83,11 +90,11 @@ class AddressBookImpl implements IAddressBook { * @since 5.0.0 */ public function search($pattern, $searchProperties, $options) { - $result = $this->backend->search($this->getKey(), $pattern, $searchProperties); + $results = $this->backend->search($this->getKey(), $pattern, $searchProperties); $vCards = []; - foreach ($result as $cardData) { - $vCards[] = $this->vCard2Array($this->readCard($cardData)); + foreach ($results as $result) { + $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata'])); } return $vCards; @@ -100,13 +107,12 @@ class AddressBookImpl implements IAddressBook { */ public function createOrUpdate($properties) { $update = false; - if (!isset($properties['UID'])) { // create a new contact + if (!isset($properties['URI'])) { // create a new contact $uid = $this->createUid(); $uri = $uid . '.vcf'; $vCard = $this->createEmptyVCard($uid); } else { // update existing contact - $uid = $properties['UID']; - $uri = $uid . '.vcf'; + $uri = $properties['URI']; $vCardData = $this->backend->getCard($this->getKey(), $uri); $vCard = $this->readCard($vCardData['carddata']); $update = true; @@ -122,7 +128,7 @@ class AddressBookImpl implements IAddressBook { $this->backend->createCard($this->getKey(), $uri, $vCard->serialize()); } - return $this->vCard2Array($vCard); + return $this->vCard2Array($uri, $vCard); } @@ -207,13 +213,31 @@ class AddressBookImpl implements IAddressBook { /** * create array with all vCard properties * + * @param string $uri * @param VCard $vCard * @return array */ - protected function vCard2Array(VCard $vCard) { - $result = []; + protected function vCard2Array($uri, VCard $vCard) { + $result = [ + 'URI' => $uri, + ]; + foreach ($vCard->children as $property) { $result[$property->name] = $property->getValue(); + if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') { + $url = $this->urlGenerator->getAbsoluteURL( + $this->urlGenerator->linkTo('', 'remote.php') . '/dav/'); + $url .= implode('/', [ + 'addressbooks', + substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/' + $this->addressBookInfo['uri'], + $uri + ]) . '?photo'; + + $result['PHOTO'] = 'VALUE=uri:' . $url; + } else { + $result[$property->name] = $property->getValue(); + } } if ($this->addressBookInfo['principaluri'] === 'principals/system/system' && $this->addressBookInfo['uri'] === 'system') { diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 3d2904df39c..2ad9f4778c9 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -780,7 +780,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { } $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId))); - $query->select('c.carddata')->from($this->dbCardsTable, 'c') + $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c') ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL()))); $result = $query->execute(); @@ -788,8 +788,10 @@ class CardDavBackend implements BackendInterface, SyncSupport { $result->closeCursor(); - return array_map(function($array) {return $this->readBlob($array['carddata']);}, $cards); - + return array_map(function($array) { + $array['carddata'] = $this->readBlob($array['carddata']); + return $array; + }, $cards); } /** diff --git a/apps/dav/lib/CardDAV/ContactsManager.php b/apps/dav/lib/CardDAV/ContactsManager.php index 7900c6ccae0..ad633483fdd 100644 --- a/apps/dav/lib/CardDAV/ContactsManager.php +++ b/apps/dav/lib/CardDAV/ContactsManager.php @@ -22,6 +22,7 @@ namespace OCA\DAV\CardDAV; use OCP\Contacts\IManager; +use OCP\IURLGenerator; class ContactsManager { @@ -37,26 +38,29 @@ class ContactsManager { /** * @param IManager $cm * @param string $userId + * @param IURLGenerator $urlGenerator */ - public function setupContactsProvider(IManager $cm, $userId) { + public function setupContactsProvider(IManager $cm, $userId, IURLGenerator $urlGenerator) { $addressBooks = $this->backend->getAddressBooksForUser("principals/users/$userId"); - $this->register($cm, $addressBooks); + $this->register($cm, $addressBooks, $urlGenerator); $addressBooks = $this->backend->getAddressBooksForUser("principals/system/system"); - $this->register($cm, $addressBooks); + $this->register($cm, $addressBooks, $urlGenerator); } /** * @param IManager $cm * @param $addressBooks + * @param IURLGenerator $urlGenerator */ - private function register(IManager $cm, $addressBooks) { + private function register(IManager $cm, $addressBooks, $urlGenerator) { foreach ($addressBooks as $addressBookInfo) { $addressBook = new \OCA\DAV\CardDAV\AddressBook($this->backend, $addressBookInfo); $cm->registerAddressBook( new AddressBookImpl( $addressBook, $addressBookInfo, - $this->backend + $this->backend, + $urlGenerator ) ); } diff --git a/apps/dav/lib/CardDAV/ImageExportPlugin.php b/apps/dav/lib/CardDAV/ImageExportPlugin.php new file mode 100644 index 00000000000..3f505222491 --- /dev/null +++ b/apps/dav/lib/CardDAV/ImageExportPlugin.php @@ -0,0 +1,146 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\CardDAV; + +use OCP\ILogger; +use Sabre\CardDAV\Card; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\VObject\Parameter; +use Sabre\VObject\Property\Binary; +use Sabre\VObject\Reader; + +class ImageExportPlugin extends ServerPlugin { + + /** @var Server */ + protected $server; + /** @var ILogger */ + private $logger; + + public function __construct(ILogger $logger) { + $this->logger = $logger; + } + + /** + * Initializes the plugin and registers event handlers + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + } + + /** + * Intercepts GET requests on addressbook urls ending with ?photo. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|void + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + // TODO: in addition to photo we should also add logo some point in time + if (!array_key_exists('photo', $queryParams)) { + return true; + } + + $path = $request->getPath(); + $node = $this->server->tree->getNodeForPath($path); + + if (!($node instanceof Card)) { + return true; + } + + $this->server->transactionType = 'carddav-image-export'; + + // Checking ACL, if available. + if ($aclPlugin = $this->server->getPlugin('acl')) { + /** @var \Sabre\DAVACL\Plugin $aclPlugin */ + $aclPlugin->checkPrivileges($path, '{DAV:}read'); + } + + if ($result = $this->getPhoto($node)) { + $response->setHeader('Content-Type', $result['Content-Type']); + $response->setStatus(200); + + $response->setBody($result['body']); + + // Returning false to break the event chain + return false; + } + return true; + } + + function getPhoto(Card $node) { + // TODO: this is kind of expensive - load carddav data from database and parse it + // we might want to build up a cache one day + try { + $vObject = $this->readCard($node->get()); + if (!$vObject->PHOTO) { + return false; + } + + $photo = $vObject->PHOTO; + $type = $this->getType($photo); + + $valType = $photo->getValueType(); + $val = ($valType === 'URI' ? $photo->getRawMimeDirValue() : $photo->getValue()); + return [ + 'Content-Type' => $type, + 'body' => $val + ]; + } catch(\Exception $ex) { + $this->logger->logException($ex); + } + return false; + } + + private function readCard($cardData) { + return Reader::read($cardData); + } + + /** + * @param Binary $photo + * @return Parameter + */ + private function getType($photo) { + $params = $photo->parameters(); + if (isset($params['TYPE']) || isset($params['MEDIATYPE'])) { + /** @var Parameter $typeParam */ + $typeParam = isset($params['TYPE']) ? $params['TYPE'] : $params['MEDIATYPE']; + $type = $typeParam->getValue(); + + if (strpos($type, 'image/') === 0) { + return $type; + } else { + return 'image/' . strtolower($type); + } + } + return ''; + } +} diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 51f0acbe2ee..82c2711b560 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -31,8 +31,10 @@ namespace OCA\DAV\Connector\Sabre; use Exception; use OC\AppFramework\Http\Request; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\Authentication\TwoFactorAuth\Manager; use OC\User\Session; +use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden; use OCP\IRequest; use OCP\ISession; use Sabre\DAV\Auth\Backend\AbstractBasic; @@ -115,14 +117,19 @@ class Auth extends AbstractBasic { return true; } else { \OC_Util::setupFS(); //login hooks may need early access to the filesystem - if($this->userSession->logClientIn($username, $password, $this->request)) { - \OC_Util::setupFS($this->userSession->getUser()->getUID()); - $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); + try { + if ($this->userSession->logClientIn($username, $password, $this->request)) { + \OC_Util::setupFS($this->userSession->getUser()->getUID()); + $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); + $this->session->close(); + return true; + } else { + $this->session->close(); + return false; + } + } catch (PasswordLoginForbiddenException $ex) { $this->session->close(); - return true; - } else { - $this->session->close(); - return false; + throw new PasswordLoginForbidden(); } } } diff --git a/apps/dav/lib/Connector/Sabre/Exception/PasswordLoginForbidden.php b/apps/dav/lib/Connector/Sabre/Exception/PasswordLoginForbidden.php new file mode 100644 index 00000000000..6537da3d56d --- /dev/null +++ b/apps/dav/lib/Connector/Sabre/Exception/PasswordLoginForbidden.php @@ -0,0 +1,54 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Connector\Sabre\Exception; + +use DOMElement; +use Sabre\DAV\Server; +use Sabre\DAV\Exception\NotAuthenticated; + +class PasswordLoginForbidden extends NotAuthenticated { + + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + public function getHTTPCode() { + return 401; + } + + /** + * This method allows the exception to include additional information + * into the WebDAV error response + * + * @param Server $server + * @param DOMElement $errorNode + * @return void + */ + public function serialize(Server $server, DOMElement $errorNode) { + + // set ownCloud namespace + $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); + + $error = $errorNode->ownerDocument->createElementNS('o:', 'o:hint', 'password login forbidden'); + $errorNode->appendChild($error); + } + +} diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index e150f441b82..7fa1b13783b 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -25,6 +25,7 @@ namespace OCA\DAV; use OCA\DAV\CalDAV\Schedule\IMipPlugin; +use OCA\DAV\CardDAV\ImageExportPlugin; use OCA\DAV\Connector\Sabre\Auth; use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; use OCA\DAV\Connector\Sabre\DavAclPlugin; @@ -103,6 +104,7 @@ class Server { // addressbook plugins $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); $this->server->addPlugin(new VCFExportPlugin()); + $this->server->addPlugin(new ImageExportPlugin(\OC::$server->getLogger())); // system tags plugins $this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin( diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php index ddfec3dc3ac..4ccc841157f 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php @@ -43,6 +43,9 @@ class AddressBookImplTest extends TestCase { /** @var AddressBook | \PHPUnit_Framework_MockObject_MockObject */ private $addressBook; + /** @var \OCP\IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */ + private $urlGenerator; + /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject */ private $backend; @@ -61,11 +64,13 @@ class AddressBookImplTest extends TestCase { $this->backend = $this->getMockBuilder('\OCA\DAV\CardDAV\CardDavBackend') ->disableOriginalConstructor()->getMock(); $this->vCard = $this->getMock('Sabre\VObject\Component\VCard'); + $this->urlGenerator = $this->getMock('OCP\IURLGenerator'); $this->addressBookImpl = new AddressBookImpl( $this->addressBook, $this->addressBookInfo, - $this->backend + $this->backend, + $this->urlGenerator ); } @@ -87,7 +92,8 @@ class AddressBookImplTest extends TestCase { [ $this->addressBook, $this->addressBookInfo, - $this->backend + $this->backend, + $this->urlGenerator, ] ) ->setMethods(['vCard2Array', 'readCard']) @@ -100,15 +106,18 @@ class AddressBookImplTest extends TestCase { ->with($this->addressBookInfo['id'], $pattern, $searchProperties) ->willReturn( [ - 'cardData1', - 'cardData2' + ['uri' => 'foo.vcf', 'carddata' => 'cardData1'], + ['uri' => 'bar.vcf', 'carddata' => 'cardData2'] ] ); $addressBookImpl->expects($this->exactly(2))->method('readCard') ->willReturn($this->vCard); $addressBookImpl->expects($this->exactly(2))->method('vCard2Array') - ->with($this->vCard)->willReturn('vCard'); + ->withConsecutive( + ['foo.vcf', $this->vCard], + ['bar.vcf', $this->vCard] + )->willReturn('vCard'); $result = $addressBookImpl->search($pattern, $searchProperties, []); $this->assertTrue((is_array($result))); @@ -130,7 +139,8 @@ class AddressBookImplTest extends TestCase { [ $this->addressBook, $this->addressBookInfo, - $this->backend + $this->backend, + $this->urlGenerator, ] ) ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard']) @@ -146,7 +156,7 @@ class AddressBookImplTest extends TestCase { $this->backend->expects($this->never())->method('updateCard'); $this->backend->expects($this->never())->method('getCard'); $addressBookImpl->expects($this->once())->method('vCard2Array') - ->with($this->vCard)->willReturn(true); + ->with('uid.vcf', $this->vCard)->willReturn(true); $this->assertTrue($addressBookImpl->createOrUpdate($properties)); } @@ -161,7 +171,8 @@ class AddressBookImplTest extends TestCase { public function testUpdate() { $uid = 'uid'; - $properties = ['UID' => $uid, 'FN' => 'John Doe']; + $uri = 'bla.vcf'; + $properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe']; /** @var \PHPUnit_Framework_MockObject_MockObject | AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder('OCA\DAV\CardDAV\AddressBookImpl') @@ -169,7 +180,8 @@ class AddressBookImplTest extends TestCase { [ $this->addressBook, $this->addressBookInfo, - $this->backend + $this->backend, + $this->urlGenerator, ] ) ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard']) @@ -178,7 +190,7 @@ class AddressBookImplTest extends TestCase { $addressBookImpl->expects($this->never())->method('createUid'); $addressBookImpl->expects($this->never())->method('createEmptyVCard'); $this->backend->expects($this->once())->method('getCard') - ->with($this->addressBookInfo['id'], $uid . '.vcf') + ->with($this->addressBookInfo['id'], $uri) ->willReturn(['carddata' => 'data']); $addressBookImpl->expects($this->once())->method('readCard') ->with('data')->willReturn($this->vCard); @@ -187,7 +199,7 @@ class AddressBookImplTest extends TestCase { $this->backend->expects($this->never())->method('createCard'); $this->backend->expects($this->once())->method('updateCard'); $addressBookImpl->expects($this->once())->method('vCard2Array') - ->with($this->vCard)->willReturn(true); + ->with($uri, $this->vCard)->willReturn(true); $this->assertTrue($addressBookImpl->createOrUpdate($properties)); } @@ -251,7 +263,8 @@ class AddressBookImplTest extends TestCase { [ $this->addressBook, $this->addressBookInfo, - $this->backend + $this->backend, + $this->urlGenerator, ] ) ->setMethods(['getUid']) diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index c7cd4a30052..58a93befe68 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -535,8 +535,8 @@ class CardDavBackendTest extends TestCase { $found = []; foreach ($result as $r) { foreach ($expected as $exp) { - if (strpos($r, $exp) > 0) { - $found[$exp] = true; + if ($r['uri'] === $exp[0] && strpos($r['carddata'], $exp[1]) > 0) { + $found[$exp[1]] = true; break; } } @@ -547,11 +547,11 @@ class CardDavBackendTest extends TestCase { public function dataTestSearch() { return [ - ['John', ['FN'], ['John Doe', 'John M. Doe']], - ['M. Doe', ['FN'], ['John M. Doe']], - ['Do', ['FN'], ['John Doe', 'John M. Doe']], - 'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], ['John Doe', 'John M. Doe']], - 'case insensitive' => ['john', ['FN'], ['John Doe', 'John M. Doe']] + ['John', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], + ['M. Doe', ['FN'], [['uri1', 'John M. Doe']]], + ['Do', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], + 'check if duplicates are handled correctly' => ['John', ['FN', 'CLOUD'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], + 'case insensitive' => ['john', ['FN'], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]] ]; } diff --git a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php b/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php index 2abd9da66e7..23a49a1962f 100644 --- a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php +++ b/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php @@ -32,6 +32,7 @@ class ContactsManagerTest extends TestCase { /** @var IManager | \PHPUnit_Framework_MockObject_MockObject $cm */ $cm = $this->getMockBuilder('OCP\Contacts\IManager')->disableOriginalConstructor()->getMock(); $cm->expects($this->exactly(2))->method('registerAddressBook'); + $urlGenerator = $this->getMockBuilder('OCP\IUrlGenerator')->disableOriginalConstructor()->getMock(); /** @var CardDavBackend | \PHPUnit_Framework_MockObject_MockObject $backEnd */ $backEnd = $this->getMockBuilder('OCA\DAV\CardDAV\CardDavBackend')->disableOriginalConstructor()->getMock(); $backEnd->method('getAddressBooksForUser')->willReturn([ @@ -39,6 +40,6 @@ class ContactsManagerTest extends TestCase { ]); $app = new ContactsManager($backEnd); - $app->setupContactsProvider($cm, 'user01'); + $app->setupContactsProvider($cm, 'user01', $urlGenerator); } } diff --git a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php new file mode 100644 index 00000000000..8583df0b6f9 --- /dev/null +++ b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php @@ -0,0 +1,151 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + + +namespace OCA\DAV\Tests\unit\CardDAV; + + +use OCA\DAV\CardDAV\ImageExportPlugin; +use OCP\ILogger; +use Sabre\CardDAV\Card; +use Sabre\DAV\Server; +use Sabre\DAV\Tree; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class ImageExportPluginTest extends TestCase { + + /** @var ResponseInterface | \PHPUnit_Framework_MockObject_MockObject */ + private $response; + /** @var RequestInterface | \PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var ImageExportPlugin | \PHPUnit_Framework_MockObject_MockObject */ + private $plugin; + /** @var Server */ + private $server; + /** @var Tree | \PHPUnit_Framework_MockObject_MockObject */ + private $tree; + /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + function setUp() { + parent::setUp(); + + $this->request = $this->getMockBuilder('Sabre\HTTP\RequestInterface')->getMock(); + $this->response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')->getMock(); + $this->server = $this->getMockBuilder('Sabre\DAV\Server')->getMock(); + $this->tree = $this->getMockBuilder('Sabre\DAV\Tree')->disableOriginalConstructor()->getMock(); + $this->server->tree = $this->tree; + $this->logger = $this->getMockBuilder('\OCP\ILogger')->getMock(); + + $this->plugin = $this->getMock('OCA\DAV\CardDAV\ImageExportPlugin', ['getPhoto'], [$this->logger]); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider providesQueryParams + * @param $param + */ + public function testQueryParams($param) { + $this->request->expects($this->once())->method('getQueryParameters')->willReturn($param); + $result = $this->plugin->httpGet($this->request, $this->response); + $this->assertTrue($result); + } + + public function providesQueryParams() { + return [ + [[]], + [['1']], + [['foo' => 'bar']], + ]; + } + + public function testNotACard() { + $this->request->expects($this->once())->method('getQueryParameters')->willReturn(['photo' => true]); + $this->request->expects($this->once())->method('getPath')->willReturn('/files/welcome.txt'); + $this->tree->expects($this->once())->method('getNodeForPath')->with('/files/welcome.txt')->willReturn(null); + $result = $this->plugin->httpGet($this->request, $this->response); + $this->assertTrue($result); + } + + /** + * @dataProvider providesCardWithOrWithoutPhoto + * @param bool $expected + * @param array $getPhotoResult + */ + public function testCardWithOrWithoutPhoto($expected, $getPhotoResult) { + $this->request->expects($this->once())->method('getQueryParameters')->willReturn(['photo' => true]); + $this->request->expects($this->once())->method('getPath')->willReturn('/files/welcome.txt'); + + $card = $this->getMockBuilder('Sabre\CardDAV\Card')->disableOriginalConstructor()->getMock(); + $this->tree->expects($this->once())->method('getNodeForPath')->with('/files/welcome.txt')->willReturn($card); + + $this->plugin->expects($this->once())->method('getPhoto')->willReturn($getPhotoResult); + + if (!$expected) { + $this->response->expects($this->once())->method('setHeader'); + $this->response->expects($this->once())->method('setStatus'); + $this->response->expects($this->once())->method('setBody'); + } + + $result = $this->plugin->httpGet($this->request, $this->response); + $this->assertEquals($expected, $result); + } + + public function providesCardWithOrWithoutPhoto() { + return [ + [true, null], + [false, ['Content-Type' => 'image/jpeg', 'body' => '1234']], + ]; + } + + /** + * @dataProvider providesPhotoData + * @param $expected + * @param $cardData + */ + public function testGetPhoto($expected, $cardData) { + /** @var Card | \PHPUnit_Framework_MockObject_MockObject $card */ + $card = $this->getMockBuilder('Sabre\CardDAV\Card')->disableOriginalConstructor()->getMock(); + $card->expects($this->once())->method('get')->willReturn($cardData); + + $this->plugin = new ImageExportPlugin($this->logger); + $this->plugin->initialize($this->server); + + $result = $this->plugin->getPhoto($card); + $this->assertEquals($expected, $result); + } + + public function providesPhotoData() { + return [ + 'empty vcard' => [false, ''], + 'vcard without PHOTO' => [false, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n"], + 'vcard 3 with PHOTO' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU=\r\nEND:VCARD\r\n"], + // + // TODO: these three below are not working - needs debugging + // + //'vcard 3 with PHOTO URL' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;TYPE=JPEG:http://example.org/photo.jpg\r\nEND:VCARD\r\n"], + //'vcard 4 with PHOTO' => [['Content-Type' => 'image/jpeg', 'body' => '12345'], "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO:data:image/jpeg;MTIzNDU=\r\nEND:VCARD\r\n"], + 'vcard 4 with PHOTO URL' => [['Content-Type' => 'image/jpeg', 'body' => 'http://example.org/photo.jpg'], "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;MEDIATYPE=image/jpeg:http://example.org/photo.jpg\r\nEND:VCARD\r\n"], + ]; + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php index 147a0c2b8c5..9564298f23a 100644 --- a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php @@ -208,6 +208,25 @@ class AuthTest extends TestCase { $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); } + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden + */ + public function testValidateUserPassWithPasswordLoginForbidden() { + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('MyTestUser', 'MyTestPassword') + ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException())); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword']); + } public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGet() { $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') diff --git a/apps/encryption/l10n/sv.js b/apps/encryption/l10n/sv.js index b8541cfbb25..2e00f5d35ff 100644 --- a/apps/encryption/l10n/sv.js +++ b/apps/encryption/l10n/sv.js @@ -20,7 +20,7 @@ OC.L10N.register( "Could not update the private key password." : "Kunde inte uppdatera lösenord för den privata nyckeln", "The old password was not correct, please try again." : "Det gamla lösenordet var inte korrekt. Vänligen försök igen.", "The current log-in password was not correct, please try again." : "Det nuvarande inloggningslösenordet var inte korrekt. Vänligen försök igen.", - "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades utan problem.", + "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades.", "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Du behöver migrera dina krypteringsnycklar från den gamla krypteringen (ownCloud <= 8.0) till den nya. Kör 'occ encryption:migrate' eller kontakta din administratör", "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ogiltig privat nyckel i krypteringsprogrammet. Vänligen uppdatera lösenordet till din privata nyckel under dina personliga inställningar för att återfå tillgång till dina krypterade filer.", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Krypteringsprogrammet är aktiverat men dina nycklar är inte initierade. Vänligen logga ut och in igen", @@ -30,8 +30,11 @@ OC.L10N.register( "one-time password for server-side-encryption" : "engångslösenord för kryptering på serversidan", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan ej dekryptera denna fil, förmodligen är det en delad fil. Be ägaren av filen att dela den med dig.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Filen kan inte läsas, troligtvis är det en delad fil. Be ägaren av filen att dela den med dig igen.", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Hej,\n\nadministratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet '%s'.\n\nVänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.\n\n", "The share will expire on %s." : "Utdelningen kommer att upphöra %s.", "Cheers!" : "Ha de fint!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Hej,<br><br>administratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet <strong>%s</strong>.<br><br>Vänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.<br><br>", + "Encrypt the home storage" : "Kryptera hemmalagringen", "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Aktivering av det här alternativet krypterar alla filer som är lagrade på huvudlagringsplatsen, annars kommer bara filer på extern lagringsplats att krypteras", "Enable recovery key" : "Aktivera återställningsnyckel", "Disable recovery key" : "Inaktivera återställningsnyckel", @@ -43,6 +46,7 @@ OC.L10N.register( "New recovery key password" : "Nytt lösenord för återställningsnyckeln", "Repeat new recovery key password" : "Upprepa nytt lösenord för återställningsnyckeln", "Change Password" : "Byt lösenord", + "ownCloud basic encryption module" : "ownCloud baskrypteringsmodul", "Your private key password no longer matches your log-in password." : "Ditt lösenord för din privata nyckel matchar inte längre ditt inloggningslösenord.", "Set your old private key password to your current log-in password:" : "Sätt ditt gamla privatnyckellösenord till ditt aktuella inloggningslösenord:", " If you don't remember your old password you can ask your administrator to recover your files." : "Om du inte kommer ihåg ditt gamla lösenord kan du be din administratör att återställa dina filer.", diff --git a/apps/encryption/l10n/sv.json b/apps/encryption/l10n/sv.json index 9a6919b4888..b06c1bc9189 100644 --- a/apps/encryption/l10n/sv.json +++ b/apps/encryption/l10n/sv.json @@ -18,7 +18,7 @@ "Could not update the private key password." : "Kunde inte uppdatera lösenord för den privata nyckeln", "The old password was not correct, please try again." : "Det gamla lösenordet var inte korrekt. Vänligen försök igen.", "The current log-in password was not correct, please try again." : "Det nuvarande inloggningslösenordet var inte korrekt. Vänligen försök igen.", - "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades utan problem.", + "Private key password successfully updated." : "Den privata nyckelns lösenord uppdaterades.", "You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run 'occ encryption:migrate' or contact your administrator" : "Du behöver migrera dina krypteringsnycklar från den gamla krypteringen (ownCloud <= 8.0) till den nya. Kör 'occ encryption:migrate' eller kontakta din administratör", "Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files." : "Ogiltig privat nyckel i krypteringsprogrammet. Vänligen uppdatera lösenordet till din privata nyckel under dina personliga inställningar för att återfå tillgång till dina krypterade filer.", "Encryption App is enabled but your keys are not initialized, please log-out and log-in again" : "Krypteringsprogrammet är aktiverat men dina nycklar är inte initierade. Vänligen logga ut och in igen", @@ -28,8 +28,11 @@ "one-time password for server-side-encryption" : "engångslösenord för kryptering på serversidan", "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Kan ej dekryptera denna fil, förmodligen är det en delad fil. Be ägaren av filen att dela den med dig.", "Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Filen kan inte läsas, troligtvis är det en delad fil. Be ägaren av filen att dela den med dig igen.", + "Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n" : "Hej,\n\nadministratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet '%s'.\n\nVänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.\n\n", "The share will expire on %s." : "Utdelningen kommer att upphöra %s.", "Cheers!" : "Ha de fint!", + "Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section \"ownCloud basic encryption module\" of your personal settings and update your encryption password by entering this password into the \"old log-in password\" field and your current login-password.<br><br>" : "Hej,<br><br>administratören har aktiverat kryptering på servern. Dina filer har krypterats med lösenordet <strong>%s</strong>.<br><br>Vänligen logga in i webbgränssnittet, gå till \"ownCloud baskrypteringsmodul\" i dina personliga inställningar och uppdatera ditt krypteringslösenord genom att mata in det här lösenordet i fältet \"gamla inloggningslösenordet\" och ditt nuvarande inloggningslösenord.<br><br>", + "Encrypt the home storage" : "Kryptera hemmalagringen", "Enabling this option encrypts all files stored on the main storage, otherwise only files on external storage will be encrypted" : "Aktivering av det här alternativet krypterar alla filer som är lagrade på huvudlagringsplatsen, annars kommer bara filer på extern lagringsplats att krypteras", "Enable recovery key" : "Aktivera återställningsnyckel", "Disable recovery key" : "Inaktivera återställningsnyckel", @@ -41,6 +44,7 @@ "New recovery key password" : "Nytt lösenord för återställningsnyckeln", "Repeat new recovery key password" : "Upprepa nytt lösenord för återställningsnyckeln", "Change Password" : "Byt lösenord", + "ownCloud basic encryption module" : "ownCloud baskrypteringsmodul", "Your private key password no longer matches your log-in password." : "Ditt lösenord för din privata nyckel matchar inte längre ditt inloggningslösenord.", "Set your old private key password to your current log-in password:" : "Sätt ditt gamla privatnyckellösenord till ditt aktuella inloggningslösenord:", " If you don't remember your old password you can ask your administrator to recover your files." : "Om du inte kommer ihåg ditt gamla lösenord kan du be din administratör att återställa dina filer.", diff --git a/apps/federatedfilesharing/l10n/pl.js b/apps/federatedfilesharing/l10n/pl.js index 1d8b949c74a..4f65f44553e 100644 --- a/apps/federatedfilesharing/l10n/pl.js +++ b/apps/federatedfilesharing/l10n/pl.js @@ -1,6 +1,7 @@ OC.L10N.register( "federatedfilesharing", { + "Federated sharing" : "Sfederowane udostępnianie", "Sharing %s failed, because this item is already shared with %s" : "Współdzielenie %s nie powiodło się, ponieważ element jest już współdzielony z %s", "File is already shared with %s" : "Plik jest już współdzielony z %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Współdzielenie %s nie powiodło się, nie można odnaleźć %s. Prawdopobnie serwer nie jest teraz osiągalny.", diff --git a/apps/federatedfilesharing/l10n/pl.json b/apps/federatedfilesharing/l10n/pl.json index c44eecbeeb7..7b43ed3fce1 100644 --- a/apps/federatedfilesharing/l10n/pl.json +++ b/apps/federatedfilesharing/l10n/pl.json @@ -1,4 +1,5 @@ { "translations": { + "Federated sharing" : "Sfederowane udostępnianie", "Sharing %s failed, because this item is already shared with %s" : "Współdzielenie %s nie powiodło się, ponieważ element jest już współdzielony z %s", "File is already shared with %s" : "Plik jest już współdzielony z %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "Współdzielenie %s nie powiodło się, nie można odnaleźć %s. Prawdopobnie serwer nie jest teraz osiągalny.", diff --git a/apps/files/l10n/lb.js b/apps/files/l10n/lb.js index 30a86b000bb..d7888f9f197 100644 --- a/apps/files/l10n/lb.js +++ b/apps/files/l10n/lb.js @@ -9,11 +9,13 @@ OC.L10N.register( "Missing a temporary folder" : "Et feelt en temporären Dossier", "Failed to write to disk" : "Konnt net op den Disk schreiwen", "Files" : "Dateien", + "All files" : "All d'Fichieren", "Home" : "Doheem", "Close" : "Zoumaachen", "Favorites" : "Favoriten", "Upload cancelled." : "Upload ofgebrach.", "Uploading..." : "Lueden erop...", + "..." : "...", "File upload is in progress. Leaving the page now will cancel the upload." : "File Upload am gaang. Wann's de des Säit verléiss gëtt den Upload ofgebrach.", "Download" : "Download", "Rename" : "Ëmbenennen", diff --git a/apps/files/l10n/lb.json b/apps/files/l10n/lb.json index 99a6b4845da..a736e06570b 100644 --- a/apps/files/l10n/lb.json +++ b/apps/files/l10n/lb.json @@ -7,11 +7,13 @@ "Missing a temporary folder" : "Et feelt en temporären Dossier", "Failed to write to disk" : "Konnt net op den Disk schreiwen", "Files" : "Dateien", + "All files" : "All d'Fichieren", "Home" : "Doheem", "Close" : "Zoumaachen", "Favorites" : "Favoriten", "Upload cancelled." : "Upload ofgebrach.", "Uploading..." : "Lueden erop...", + "..." : "...", "File upload is in progress. Leaving the page now will cancel the upload." : "File Upload am gaang. Wann's de des Säit verléiss gëtt den Upload ofgebrach.", "Download" : "Download", "Rename" : "Ëmbenennen", diff --git a/apps/files/l10n/pl.js b/apps/files/l10n/pl.js index a366c6a967c..c5273a36ebd 100644 --- a/apps/files/l10n/pl.js +++ b/apps/files/l10n/pl.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Zła ścieżka.", "Files" : "Pliki", "All files" : "Wszystkie pliki", + "File could not be found" : "Nie można odnaleźć pliku", "Home" : "Dom", "Close" : "Zamknij", "Favorites" : "Ulubione", @@ -32,8 +33,15 @@ OC.L10N.register( "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{hours}:{minutes}:{seconds} hour{plural_s} left" : "Pozostało {hours}:{minutes}:{seconds} hour{plural_s} ", + "{hours}:{minutes}h" : "{hours}:{minutes}godz.", + "{minutes}:{seconds} minute{plural_s} left" : "Pozostało {minutes}:{seconds} minute{plural_s}", + "{minutes}:{seconds}m" : "{minutes}:{seconds}min.", "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", "{seconds}s" : "{seconds} s", + "Any moment now..." : "Jeszcze chwilę...", + "Soon..." : "Wkrótce...", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} z {totalSize} ({bitrate})", "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", @@ -49,6 +57,10 @@ OC.L10N.register( "This directory is unavailable, please check the logs or contact the administrator" : "Ten folder jest niedostępny, proszę sprawdzić logi lub skontaktować się z administratorem.", "Could not move \"{file}\", target exists" : "Nie można było przenieść „{file}” – plik o takiej nazwie już istnieje", "Could not move \"{file}\"" : "Nie można było przenieść \"{file}\"", + "{newName} already exists" : "{newName} już istnieje", + "Could not rename \"{fileName}\", it does not exist any more" : "Nie można zmienić nazwy \"{fileName}\", plik nie istnieje", + "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Nazwa \"{targetName}\" jest juz używana w folderze \"{dir}\". Proszę wybrać inną nazwę.", + "Could not rename \"{fileName}\"" : "Nie można zmienić nazwy \"{fileName}\"", "Could not create file \"{file}\"" : "Nie można było utworzyć pliku \"{file}\"", "Could not create file \"{file}\" because it already exists" : "Nie można było utworzyć pliku \"{file}\", ponieważ ten plik już istnieje.", "Could not create folder \"{dir}\"" : "Nie można utworzyć folderu „{dir}”", @@ -71,8 +83,10 @@ OC.L10N.register( "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Miejsce dla {owner} jest na wyczerpaniu ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "Twój magazyn jest prawie pełny ({usedSpacePercent}%)", "Path" : "Ścieżka", + "_%n byte_::_%n bytes_" : ["%n bajt","%n bajty","%n bajtów"], "Favorited" : "Ulubione", "Favorite" : "Ulubione", + "Local link" : "Lokalny odnośnik", "Folder" : "Folder", "New folder" : "Nowy folder", "{newname} already exists" : "{newname} już istnieje", @@ -80,6 +94,7 @@ OC.L10N.register( "An error occurred while trying to update the tags" : "Wystąpił błąd podczas aktualizacji tagów", "A new file or folder has been <strong>created</strong>" : "Nowy plik lub folder został <strong>utworzony</strong>", "A file or folder has been <strong>changed</strong>" : "Plik lub folder został <strong>zmieniony</strong>", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "Ogranicz powiadomienia o utworzeniu i zmianach do swoich <strong>ulubionych plkow</strong> <em>(Tylko w strumieniu aktywności)</em>", "A file or folder has been <strong>deleted</strong>" : "Plik lub folder został <strong>usunięty</strong>", "A file or folder has been <strong>restored</strong>" : "Plik lub folder został <strong>przywrócy</strong>", "You created %1$s" : "Utworzyłeś %1$s", @@ -99,15 +114,20 @@ OC.L10N.register( "Maximum upload size" : "Maksymalny rozmiar wysyłanego pliku", "max. possible: " : "maks. możliwy:", "Save" : "Zapisz", + "With PHP-FPM it might take 5 minutes for changes to be applied." : "Z PHP-FPM zastosowanie zmian może zająć 5 minut.", + "Missing permissions to edit from here." : "Brakuje uprawnień do edycji.", "Settings" : "Ustawienia", "Show hidden files" : "Pokaż ukryte pliki", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">access your Files via WebDAV</a>" : "Użyj tego adresu aby uzyskać <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">dostęp do swoich plików poprzez WebDAV</a>", "No files in here" : "Brak plików", + "Upload some content or sync with your devices!" : "Wgraj coś, albo wykonaj synchronizację ze swoimi urządzeniami.", "No entries found in this folder" : "Brak wpisów w tym folderze", "Select all" : "Wybierz wszystko", "Upload too large" : "Ładowany plik jest za duży", "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Pliki, które próbujesz przesłać, przekraczają maksymalną dopuszczalną wielkość.", "No favorites" : "Brak ulubionych", + "Files and folders you mark as favorite will show up here" : "Pliki i katalogi, które oznaczysz jako ulubione wyświetlą się tutaj", "Text file" : "Plik tekstowy", "New text file.txt" : "Nowy plik tekstowy.txt" }, diff --git a/apps/files/l10n/pl.json b/apps/files/l10n/pl.json index 05b65b2f84a..c0be569faf6 100644 --- a/apps/files/l10n/pl.json +++ b/apps/files/l10n/pl.json @@ -19,6 +19,7 @@ "Invalid directory." : "Zła ścieżka.", "Files" : "Pliki", "All files" : "Wszystkie pliki", + "File could not be found" : "Nie można odnaleźć pliku", "Home" : "Dom", "Close" : "Zamknij", "Favorites" : "Ulubione", @@ -30,8 +31,15 @@ "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{hours}:{minutes}:{seconds} hour{plural_s} left" : "Pozostało {hours}:{minutes}:{seconds} hour{plural_s} ", + "{hours}:{minutes}h" : "{hours}:{minutes}godz.", + "{minutes}:{seconds} minute{plural_s} left" : "Pozostało {minutes}:{seconds} minute{plural_s}", + "{minutes}:{seconds}m" : "{minutes}:{seconds}min.", "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", "{seconds}s" : "{seconds} s", + "Any moment now..." : "Jeszcze chwilę...", + "Soon..." : "Wkrótce...", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} z {totalSize} ({bitrate})", "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", @@ -47,6 +55,10 @@ "This directory is unavailable, please check the logs or contact the administrator" : "Ten folder jest niedostępny, proszę sprawdzić logi lub skontaktować się z administratorem.", "Could not move \"{file}\", target exists" : "Nie można było przenieść „{file}” – plik o takiej nazwie już istnieje", "Could not move \"{file}\"" : "Nie można było przenieść \"{file}\"", + "{newName} already exists" : "{newName} już istnieje", + "Could not rename \"{fileName}\", it does not exist any more" : "Nie można zmienić nazwy \"{fileName}\", plik nie istnieje", + "The name \"{targetName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Nazwa \"{targetName}\" jest juz używana w folderze \"{dir}\". Proszę wybrać inną nazwę.", + "Could not rename \"{fileName}\"" : "Nie można zmienić nazwy \"{fileName}\"", "Could not create file \"{file}\"" : "Nie można było utworzyć pliku \"{file}\"", "Could not create file \"{file}\" because it already exists" : "Nie można było utworzyć pliku \"{file}\", ponieważ ten plik już istnieje.", "Could not create folder \"{dir}\"" : "Nie można utworzyć folderu „{dir}”", @@ -69,8 +81,10 @@ "Storage of {owner} is almost full ({usedSpacePercent}%)" : "Miejsce dla {owner} jest na wyczerpaniu ({usedSpacePercent}%)", "Your storage is almost full ({usedSpacePercent}%)" : "Twój magazyn jest prawie pełny ({usedSpacePercent}%)", "Path" : "Ścieżka", + "_%n byte_::_%n bytes_" : ["%n bajt","%n bajty","%n bajtów"], "Favorited" : "Ulubione", "Favorite" : "Ulubione", + "Local link" : "Lokalny odnośnik", "Folder" : "Folder", "New folder" : "Nowy folder", "{newname} already exists" : "{newname} już istnieje", @@ -78,6 +92,7 @@ "An error occurred while trying to update the tags" : "Wystąpił błąd podczas aktualizacji tagów", "A new file or folder has been <strong>created</strong>" : "Nowy plik lub folder został <strong>utworzony</strong>", "A file or folder has been <strong>changed</strong>" : "Plik lub folder został <strong>zmieniony</strong>", + "Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>" : "Ogranicz powiadomienia o utworzeniu i zmianach do swoich <strong>ulubionych plkow</strong> <em>(Tylko w strumieniu aktywności)</em>", "A file or folder has been <strong>deleted</strong>" : "Plik lub folder został <strong>usunięty</strong>", "A file or folder has been <strong>restored</strong>" : "Plik lub folder został <strong>przywrócy</strong>", "You created %1$s" : "Utworzyłeś %1$s", @@ -97,15 +112,20 @@ "Maximum upload size" : "Maksymalny rozmiar wysyłanego pliku", "max. possible: " : "maks. możliwy:", "Save" : "Zapisz", + "With PHP-FPM it might take 5 minutes for changes to be applied." : "Z PHP-FPM zastosowanie zmian może zająć 5 minut.", + "Missing permissions to edit from here." : "Brakuje uprawnień do edycji.", "Settings" : "Ustawienia", "Show hidden files" : "Pokaż ukryte pliki", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">access your Files via WebDAV</a>" : "Użyj tego adresu aby uzyskać <a href=\"%s\" target=\"_blank\" rel=\"noreferrer\">dostęp do swoich plików poprzez WebDAV</a>", "No files in here" : "Brak plików", + "Upload some content or sync with your devices!" : "Wgraj coś, albo wykonaj synchronizację ze swoimi urządzeniami.", "No entries found in this folder" : "Brak wpisów w tym folderze", "Select all" : "Wybierz wszystko", "Upload too large" : "Ładowany plik jest za duży", "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Pliki, które próbujesz przesłać, przekraczają maksymalną dopuszczalną wielkość.", "No favorites" : "Brak ulubionych", + "Files and folders you mark as favorite will show up here" : "Pliki i katalogi, które oznaczysz jako ulubione wyświetlą się tutaj", "Text file" : "Plik tekstowy", "New text file.txt" : "Nowy plik tekstowy.txt" },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" diff --git a/apps/files/l10n/sv.js b/apps/files/l10n/sv.js index d2f7df4a3a4..b07bdf8b59b 100644 --- a/apps/files/l10n/sv.js +++ b/apps/files/l10n/sv.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Felaktig mapp.", "Files" : "Filer", "All files" : "Alla filer", + "File could not be found" : "Fil kunde inte hittas", "Home" : "Hem", "Close" : "Stäng", "Favorites" : "Favoriter", @@ -32,6 +33,15 @@ OC.L10N.register( "Could not get result from server." : "Gick inte att hämta resultat från server.", "Uploading..." : "Laddar upp...", "..." : "...", + "{hours}:{minutes}:{seconds} hour{plural_s} left" : "{hours}:{minutes}:{seconds} timme/ar kvar", + "{hours}:{minutes}h" : "{hours}:{minutes}", + "{minutes}:{seconds} minute{plural_s} left" : "{minutes}:{seconds} minut(er) kvar", + "{minutes}:{seconds}m" : "{minutes}:{seconds}", + "{seconds} second{plural_s} left" : "{seconds} sekund(er) kvar", + "{seconds}s" : "{seconds}s", + "Any moment now..." : "Alldeles strax...", + "Soon..." : "Snart...", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})", "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.", "Actions" : "Åtgärder", "Download" : "Ladda ner", @@ -77,6 +87,7 @@ OC.L10N.register( "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"], "Favorited" : "Favoriserad", "Favorite" : "Favorit", + "Local link" : "Lokal länk", "Folder" : "Mapp", "New folder" : "Ny mapp", "{newname} already exists" : "{newname} existerar redan", diff --git a/apps/files/l10n/sv.json b/apps/files/l10n/sv.json index c2d1d464bc9..957088d80fc 100644 --- a/apps/files/l10n/sv.json +++ b/apps/files/l10n/sv.json @@ -19,6 +19,7 @@ "Invalid directory." : "Felaktig mapp.", "Files" : "Filer", "All files" : "Alla filer", + "File could not be found" : "Fil kunde inte hittas", "Home" : "Hem", "Close" : "Stäng", "Favorites" : "Favoriter", @@ -30,6 +31,15 @@ "Could not get result from server." : "Gick inte att hämta resultat från server.", "Uploading..." : "Laddar upp...", "..." : "...", + "{hours}:{minutes}:{seconds} hour{plural_s} left" : "{hours}:{minutes}:{seconds} timme/ar kvar", + "{hours}:{minutes}h" : "{hours}:{minutes}", + "{minutes}:{seconds} minute{plural_s} left" : "{minutes}:{seconds} minut(er) kvar", + "{minutes}:{seconds}m" : "{minutes}:{seconds}", + "{seconds} second{plural_s} left" : "{seconds} sekund(er) kvar", + "{seconds}s" : "{seconds}s", + "Any moment now..." : "Alldeles strax...", + "Soon..." : "Snart...", + "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})", "File upload is in progress. Leaving the page now will cancel the upload." : "Filuppladdning pågår. Lämnar du sidan så avbryts uppladdningen.", "Actions" : "Åtgärder", "Download" : "Ladda ner", @@ -75,6 +85,7 @@ "_%n byte_::_%n bytes_" : ["%n bytes","%n bytes"], "Favorited" : "Favoriserad", "Favorite" : "Favorit", + "Local link" : "Lokal länk", "Folder" : "Mapp", "New folder" : "Ny mapp", "{newname} already exists" : "{newname} existerar redan", diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 2477f513db3..d210c158ec1 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -854,7 +854,7 @@ MountConfigListView.prototype = _.extend({ this.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion); if (storageConfig.backendOptions) { - $td.children().each(function() { + $td.find('input, select').each(function() { var input = $(this); var val = storageConfig.backendOptions[input.data('parameter')]; if (val !== undefined) { @@ -1001,7 +1001,7 @@ MountConfigListView.prototype = _.extend({ newElement = $('<input type="password" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" placeholder="'+ trimmedPlaceholder+'" />'); } else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) { var checkboxId = _.uniqueId('checkbox_'); - newElement = $('<input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" /><label for="'+checkboxId+'">'+ trimmedPlaceholder+'</label>'); + newElement = $('<div><label><input type="checkbox" id="'+checkboxId+'" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'+ trimmedPlaceholder+'</label></div>'); } else if (placeholder.type === MountConfigListView.ParameterTypes.HIDDEN) { newElement = $('<input type="hidden" class="'+classes.join(' ')+'" data-parameter="'+parameter+'" />'); } else { diff --git a/apps/files_external/l10n/lb.js b/apps/files_external/l10n/lb.js index 598458c731f..50bb06ff7f9 100644 --- a/apps/files_external/l10n/lb.js +++ b/apps/files_external/l10n/lb.js @@ -6,6 +6,7 @@ OC.L10N.register( "Username" : "Benotzernumm", "Password" : "Passwuert", "Save" : "Späicheren", + "None" : "Keng", "Port" : "Port", "Region" : "Regioun", "URL" : "URL", diff --git a/apps/files_external/l10n/lb.json b/apps/files_external/l10n/lb.json index 265d8ffda96..9f7aa84bb1a 100644 --- a/apps/files_external/l10n/lb.json +++ b/apps/files_external/l10n/lb.json @@ -4,6 +4,7 @@ "Username" : "Benotzernumm", "Password" : "Passwuert", "Save" : "Späicheren", + "None" : "Keng", "Port" : "Port", "Region" : "Regioun", "URL" : "URL", diff --git a/apps/files_external/l10n/sv.js b/apps/files_external/l10n/sv.js index dfa978f202a..3df7763a5fe 100644 --- a/apps/files_external/l10n/sv.js +++ b/apps/files_external/l10n/sv.js @@ -1,6 +1,7 @@ OC.L10N.register( "files_external", { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fel vid hämtning av åtkomst-token. Verifiera att din app-nyckel och hemlighet stämmer.", "Step 1 failed. Exception: %s" : "Steg 1 flaerade. Undantag: %s", "Step 2 failed. Exception: %s" : "Steg 2 falerade. Undantag: %s", "External storage" : "Extern lagring", diff --git a/apps/files_external/l10n/sv.json b/apps/files_external/l10n/sv.json index 528588bd702..1070a322e92 100644 --- a/apps/files_external/l10n/sv.json +++ b/apps/files_external/l10n/sv.json @@ -1,4 +1,5 @@ { "translations": { + "Fetching request tokens failed. Verify that your app key and secret are correct." : "Fel vid hämtning av åtkomst-token. Verifiera att din app-nyckel och hemlighet stämmer.", "Step 1 failed. Exception: %s" : "Steg 1 flaerade. Undantag: %s", "Step 2 failed. Exception: %s" : "Steg 2 falerade. Undantag: %s", "External storage" : "Extern lagring", diff --git a/apps/files_external/lib/Controller/GlobalStoragesController.php b/apps/files_external/lib/Controller/GlobalStoragesController.php index 471e3b51593..71bef7dc714 100644 --- a/apps/files_external/lib/Controller/GlobalStoragesController.php +++ b/apps/files_external/lib/Controller/GlobalStoragesController.php @@ -127,6 +127,7 @@ class GlobalStoragesController extends StoragesController { * @param array $applicableUsers users for which to mount the storage * @param array $applicableGroups groups for which to mount the storage * @param int $priority priority + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ diff --git a/apps/files_external/lib/Controller/StoragesController.php b/apps/files_external/lib/Controller/StoragesController.php index e50426f4888..c1216008ffa 100644 --- a/apps/files_external/lib/Controller/StoragesController.php +++ b/apps/files_external/lib/Controller/StoragesController.php @@ -237,6 +237,7 @@ abstract class StoragesController extends Controller { * on whether the remote storage is available or not. * * @param StorageConfig $storage storage configuration + * @param bool $testOnly whether to storage should only test the connection or do more things */ protected function updateStorageStatus(StorageConfig &$storage, $testOnly = true) { try { @@ -291,6 +292,7 @@ abstract class StoragesController extends Controller { * Get an external storage entry. * * @param int $id storage id + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ diff --git a/apps/files_external/lib/Controller/UserGlobalStoragesController.php b/apps/files_external/lib/Controller/UserGlobalStoragesController.php index f65e578507d..3006e72666d 100644 --- a/apps/files_external/lib/Controller/UserGlobalStoragesController.php +++ b/apps/files_external/lib/Controller/UserGlobalStoragesController.php @@ -107,6 +107,7 @@ class UserGlobalStoragesController extends StoragesController { * Get an external storage entry. * * @param int $id storage id + * @param bool $testOnly whether to storage should only test the connection or do more things * @return DataResponse * * @NoAdminRequired @@ -139,6 +140,7 @@ class UserGlobalStoragesController extends StoragesController { * * @param int $id storage id * @param array $backendOptions backend-specific options + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse * diff --git a/apps/files_external/lib/Controller/UserStoragesController.php b/apps/files_external/lib/Controller/UserStoragesController.php index 28663090e89..5dc23aad769 100644 --- a/apps/files_external/lib/Controller/UserStoragesController.php +++ b/apps/files_external/lib/Controller/UserStoragesController.php @@ -159,6 +159,7 @@ class UserStoragesController extends StoragesController { * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions backend-specific mount options + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse * diff --git a/apps/files_external/lib/Lib/Storage/Google.php b/apps/files_external/lib/Lib/Storage/Google.php index 96f12800c10..0b617aafe9d 100644 --- a/apps/files_external/lib/Lib/Storage/Google.php +++ b/apps/files_external/lib/Lib/Storage/Google.php @@ -168,11 +168,11 @@ class Google extends \OC\Files\Storage\Common { $path = trim($path, '/'); $this->driveFiles[$path] = $file; if ($file === false) { - // Set all child paths as false + // Remove all children $len = strlen($path); foreach ($this->driveFiles as $key => $file) { if (substr($key, 0, $len) === $path) { - $this->driveFiles[$key] = false; + unset($this->driveFiles[$key]); } } } diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index c9cc40b0ba0..6662f637039 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -51,13 +51,17 @@ break; case DefinitionParameter::VALUE_BOOLEAN: ?> <?php $checkboxId = uniqid("checkbox_"); ?> + <div> + <label> <input type="checkbox" id="<?php p($checkboxId); ?>" <?php if (!empty($classes)): ?> class="checkbox <?php p(implode(' ', $classes)); ?>"<?php endif; ?> data-parameter="<?php p($parameter->getName()); ?>" <?php if ($value === true): ?> checked="checked"<?php endif; ?> /> - <label for="<?php p($checkboxId); ?>"><?php p($placeholder); ?></label> + <?php p($placeholder); ?> + </label> + </div> <?php break; case DefinitionParameter::VALUE_HIDDEN: ?> diff --git a/apps/files_external/tests/env/start-swift-ceph.sh b/apps/files_external/tests/env/start-swift-ceph.sh index ba17b8f42dd..b73fa899a6d 100755 --- a/apps/files_external/tests/env/start-swift-ceph.sh +++ b/apps/files_external/tests/env/start-swift-ceph.sh @@ -74,6 +74,11 @@ if [[ $ready != 'READY=1' ]]; then docker logs $container exit 1 fi +if ! "$thisFolder"/env/wait-for-connection ${host} 80 600; then + echo "[ERROR] Waited 600 seconds, no response" >&2 + docker logs $container + exit 1 +fi echo "Waiting another 15 seconds" sleep 15 diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 5740574ec4c..c6ae6903eec 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -32,14 +32,14 @@ $l = \OC::$server->getL10N('files_sharing'); \OC::$CLASSPATH['OC_Share_Backend_Folder'] = 'files_sharing/lib/share/folder.php'; \OC::$CLASSPATH['OC\Files\Storage\Shared'] = 'files_sharing/lib/sharedstorage.php'; -$application = new \OCA\Files_Sharing\AppInfo\Application(); -$application->registerMountProviders(); - \OCA\Files_Sharing\Helper::registerHooks(); \OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); \OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); +$application = new \OCA\Files_Sharing\AppInfo\Application(); +$application->registerMountProviders(); + $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( 'OCA\Files::loadAdditionalScripts', diff --git a/apps/files_sharing/l10n/lb.js b/apps/files_sharing/l10n/lb.js index fcbfd04c64f..0679858d818 100644 --- a/apps/files_sharing/l10n/lb.js +++ b/apps/files_sharing/l10n/lb.js @@ -5,6 +5,7 @@ OC.L10N.register( "No shared links" : "Keng gedeelte Linken", "Cancel" : "Ofbriechen", "Shared by" : "Gedeelt vun", + "Sharing" : "Gedeelt", "The password is wrong. Try again." : "Den Passwuert ass incorrect. Probeier ed nach eng keier.", "Password" : "Passwuert", "No entries found in this folder" : "Keng Elementer an dësem Dossier fonnt", diff --git a/apps/files_sharing/l10n/lb.json b/apps/files_sharing/l10n/lb.json index 5a466e560c6..9355d70a6fb 100644 --- a/apps/files_sharing/l10n/lb.json +++ b/apps/files_sharing/l10n/lb.json @@ -3,6 +3,7 @@ "No shared links" : "Keng gedeelte Linken", "Cancel" : "Ofbriechen", "Shared by" : "Gedeelt vun", + "Sharing" : "Gedeelt", "The password is wrong. Try again." : "Den Passwuert ass incorrect. Probeier ed nach eng keier.", "Password" : "Passwuert", "No entries found in this folder" : "Keng Elementer an dësem Dossier fonnt", diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 2907ceaaea2..fda16c7acac 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -115,7 +115,8 @@ class Application extends App { $server = $c->query('ServerContainer'); return new MountProvider( $server->getConfig(), - $server->getShareManager() + $server->getShareManager(), + $server->getLogger() ); }); diff --git a/apps/files_sharing/lib/Helper.php b/apps/files_sharing/lib/Helper.php index e4640f82eb6..2353a281b7e 100644 --- a/apps/files_sharing/lib/Helper.php +++ b/apps/files_sharing/lib/Helper.php @@ -277,19 +277,23 @@ class Helper { /** * get default share folder * + * @param \OC\Files\View * @return string */ - public static function getShareFolder() { + public static function getShareFolder($view = null) { + if ($view === null) { + $view = Filesystem::getView(); + } $shareFolder = \OC::$server->getConfig()->getSystemValue('share_folder', '/'); $shareFolder = Filesystem::normalizePath($shareFolder); - if (!Filesystem::file_exists($shareFolder)) { + if (!$view->file_exists($shareFolder)) { $dir = ''; $subdirs = explode('/', $shareFolder); foreach ($subdirs as $subdir) { $dir = $dir . '/' . $subdir; - if (!Filesystem::is_dir($dir)) { - Filesystem::mkdir($dir); + if (!$view->is_dir($dir)) { + $view->mkdir($dir); } } } diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index d8f355f2fd3..c71c0e8ddea 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -26,6 +26,7 @@ namespace OCA\Files_Sharing; use OCP\Files\Config\IMountProvider; use OCP\Files\Storage\IStorageFactory; use OCP\IConfig; +use OCP\ILogger; use OCP\IUser; use OCP\Share\IManager; @@ -41,12 +42,19 @@ class MountProvider implements IMountProvider { protected $shareManager; /** + * @var ILogger + */ + protected $logger; + + /** * @param \OCP\IConfig $config * @param IManager $shareManager + * @param ILogger $logger */ - public function __construct(IConfig $config, IManager $shareManager) { + public function __construct(IConfig $config, IManager $shareManager, ILogger $logger) { $this->config = $config; $this->shareManager = $shareManager; + $this->logger = $logger; } @@ -60,22 +68,28 @@ class MountProvider implements IMountProvider { public function getMountsForUser(IUser $user, IStorageFactory $storageFactory) { $shares = $this->shareManager->getSharedWith($user->getUID(), \OCP\Share::SHARE_TYPE_USER, null, -1); $shares = array_merge($shares, $this->shareManager->getSharedWith($user->getUID(), \OCP\Share::SHARE_TYPE_GROUP, null, -1)); - $shares = array_filter($shares, function (\OCP\Share\IShare $share) { - return $share->getPermissions() > 0; + // filter out excluded shares and group shares that includes self + $shares = array_filter($shares, function (\OCP\Share\IShare $share) use ($user) { + return $share->getPermissions() > 0 && $share->getShareOwner() !== $user->getUID(); }); $mounts = []; foreach ($shares as $share) { - $mounts[] = new SharedMount( - '\OC\Files\Storage\Shared', - $mounts, - [ - 'user' => $user->getUID(), - 'newShare' => $share, - ], - $storageFactory - ); + try { + $mounts[] = new SharedMount( + '\OC\Files\Storage\Shared', + $mounts, + [ + 'user' => $user->getUID(), + 'newShare' => $share, + ], + $storageFactory + ); + } catch (\Exception $e) { + $this->logger->logException($e); + $this->logger->error('Error while trying to create shared mount'); + } } // array_filter removes the null values from the array diff --git a/apps/files_sharing/lib/SharedMount.php b/apps/files_sharing/lib/SharedMount.php index 83527053f43..2b066bd2d94 100644 --- a/apps/files_sharing/lib/SharedMount.php +++ b/apps/files_sharing/lib/SharedMount.php @@ -81,7 +81,7 @@ class SharedMount extends MountPoint implements MoveableMount { $parent = dirname($share->getTarget()); if (!$this->recipientView->is_dir($parent)) { - $parent = Helper::getShareFolder(); + $parent = Helper::getShareFolder($this->recipientView); } $newMountPoint = $this->generateUniqueTarget( diff --git a/apps/files_sharing/tests/MountProviderTest.php b/apps/files_sharing/tests/MountProviderTest.php new file mode 100644 index 00000000000..f69098cde7b --- /dev/null +++ b/apps/files_sharing/tests/MountProviderTest.php @@ -0,0 +1,136 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\Files_Sharing\Tests; + +use OCA\Files_Sharing\MountProvider; +use OCP\Files\Storage\IStorageFactory; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use OCP\Share\IShare; +use OCP\Share\IManager; +use OCP\Files\Mount\IMountPoint; + +class MountProviderTest extends \Test\TestCase { + + /** @var MountProvider */ + private $provider; + + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var IUser|\PHPUnit_Framework_MockObject_MockObject */ + private $user; + + /** @var IStorageFactory|\PHPUnit_Framework_MockObject_MockObject */ + private $loader; + + /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ + private $shareManager; + + /** @var ILogger | \PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('OCP\IConfig'); + $this->user = $this->getMock('OCP\IUser'); + $this->loader = $this->getMock('OCP\Files\Storage\IStorageFactory'); + $this->shareManager = $this->getMock('\OCP\Share\IManager'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->provider = new MountProvider($this->config, $this->shareManager, $this->logger); + } + + public function testExcludeShares() { + /** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share1 */ + $share1 = $this->getMock('\OCP\Share\IShare'); + $share1->expects($this->once()) + ->method('getPermissions') + ->will($this->returnValue(0)); + + $share2 = $this->getMock('\OCP\Share\IShare'); + $share2->expects($this->once()) + ->method('getPermissions') + ->will($this->returnValue(31)); + $share2->expects($this->any()) + ->method('getShareOwner') + ->will($this->returnValue('user2')); + $share2->expects($this->any()) + ->method('getTarget') + ->will($this->returnValue('/share2')); + + $share3 = $this->getMock('\OCP\Share\IShare'); + $share3->expects($this->once()) + ->method('getPermissions') + ->will($this->returnValue(0)); + + /** @var IShare | \PHPUnit_Framework_MockObject_MockObject $share4 */ + $share4 = $this->getMock('\OCP\Share\IShare'); + $share4->expects($this->once()) + ->method('getPermissions') + ->will($this->returnValue(31)); + $share4->expects($this->any()) + ->method('getShareOwner') + ->will($this->returnValue('user2')); + $share4->expects($this->any()) + ->method('getTarget') + ->will($this->returnValue('/share4')); + + $share5 = $this->getMock('\OCP\Share\IShare'); + $share5->expects($this->once()) + ->method('getPermissions') + ->will($this->returnValue(31)); + $share5->expects($this->any()) + ->method('getShareOwner') + ->will($this->returnValue('user1')); + + $userShares = [$share1, $share2]; + $groupShares = [$share3, $share4, $share5]; + + $this->user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('user1')); + + $this->shareManager->expects($this->at(0)) + ->method('getSharedWith') + ->with('user1', \OCP\Share::SHARE_TYPE_USER) + ->will($this->returnValue($userShares)); + $this->shareManager->expects($this->at(1)) + ->method('getSharedWith') + ->with('user1', \OCP\Share::SHARE_TYPE_GROUP, null, -1) + ->will($this->returnValue($groupShares)); + + $mounts = $this->provider->getMountsForUser($this->user, $this->loader); + + $this->assertCount(2, $mounts); + $this->assertSharedMount($share1, $mounts[0]); + $this->assertSharedMount($share4, $mounts[1]); + } + + private function assertSharedMount(IShare $share, IMountPoint $mount) { + $this->assertInstanceOf('OCA\Files_Sharing\SharedMount', $mount); + $this->assertEquals($share, $mount->getShare()); + } +} + diff --git a/apps/systemtags/l10n/ca.js b/apps/systemtags/l10n/ca.js index 20ec2d4edb1..46d9d409662 100644 --- a/apps/systemtags/l10n/ca.js +++ b/apps/systemtags/l10n/ca.js @@ -2,6 +2,27 @@ OC.L10N.register( "systemtags", { "Tags" : "Etiquetes", + "Tagged files" : "Fitxers marcats", + "Select tags to filter by" : "Selecciona les marques per filtrar-ne", + "Please select tags to filter by" : "Si us plau selecciona les marques per filtrar-ne", + "No files found for the selected tags" : "No s'han trobat fitxers per les marques sel·leccionades", + "<strong>System tags</strong> for a file have been modified" : "Les <strong>Marques de Sistema</strong> d'un fitxer s'han modificat", + "You assigned system tag %3$s" : "Has assignat la marca de sistema %3$s", + "%1$s assigned system tag %3$s" : "%1$s ha assignat la marca de sistema %3$s", + "You unassigned system tag %3$s" : "Has des-assignat la marca de sistema %3$s", + "%1$s unassigned system tag %3$s" : "%1$s ha des-assignat la marca de sistema %3$s", + "You created system tag %2$s" : "Has creat la marca de sistema %2$s", + "%1$s created system tag %2$s" : "%1$s ha creat la marca de sistema %2$s", + "You deleted system tag %2$s" : "Has esborrat la marca de sistema %2$s", + "%1$s deleted system tag %2$s" : "%1$s ha esborrat la marca de sistema %2$s", + "You updated system tag %3$s to %2$s" : "Has actualitzat les marques de sistema de la %3$s a la %2$s", + "%1$s updated system tag %3$s to %2$s" : "%1$s ha actualitzat les marques de sistema de la %3$s a la %2$s", + "You assigned system tag %3$s to %2$s" : "Has assignat les marques de sistema de la %3$s a la %2$s", + "%1$s assigned system tag %3$s to %2$s" : "%1$s ha assignat les marques de sistema de la %3$s a la %2$s", + "You unassigned system tag %3$s from %2$s" : "Has des-assignat les marques de sistema de la %3$s a la %2$s", + "%1$s unassigned system tag %3$s from %2$s" : "%1$s ha des-assignat les marques de sistema de la %3$s a la %2$s", + "%s (restricted)" : "%s (restringit)", + "%s (invisible)" : "%s (invisible)", "No files in here" : "No hi ha arxius", "No entries found in this folder" : "No hi ha entrades en aquesta carpeta", "Name" : "Nom", diff --git a/apps/systemtags/l10n/ca.json b/apps/systemtags/l10n/ca.json index 87c8c678761..9bb1d6fba83 100644 --- a/apps/systemtags/l10n/ca.json +++ b/apps/systemtags/l10n/ca.json @@ -1,5 +1,26 @@ { "translations": { "Tags" : "Etiquetes", + "Tagged files" : "Fitxers marcats", + "Select tags to filter by" : "Selecciona les marques per filtrar-ne", + "Please select tags to filter by" : "Si us plau selecciona les marques per filtrar-ne", + "No files found for the selected tags" : "No s'han trobat fitxers per les marques sel·leccionades", + "<strong>System tags</strong> for a file have been modified" : "Les <strong>Marques de Sistema</strong> d'un fitxer s'han modificat", + "You assigned system tag %3$s" : "Has assignat la marca de sistema %3$s", + "%1$s assigned system tag %3$s" : "%1$s ha assignat la marca de sistema %3$s", + "You unassigned system tag %3$s" : "Has des-assignat la marca de sistema %3$s", + "%1$s unassigned system tag %3$s" : "%1$s ha des-assignat la marca de sistema %3$s", + "You created system tag %2$s" : "Has creat la marca de sistema %2$s", + "%1$s created system tag %2$s" : "%1$s ha creat la marca de sistema %2$s", + "You deleted system tag %2$s" : "Has esborrat la marca de sistema %2$s", + "%1$s deleted system tag %2$s" : "%1$s ha esborrat la marca de sistema %2$s", + "You updated system tag %3$s to %2$s" : "Has actualitzat les marques de sistema de la %3$s a la %2$s", + "%1$s updated system tag %3$s to %2$s" : "%1$s ha actualitzat les marques de sistema de la %3$s a la %2$s", + "You assigned system tag %3$s to %2$s" : "Has assignat les marques de sistema de la %3$s a la %2$s", + "%1$s assigned system tag %3$s to %2$s" : "%1$s ha assignat les marques de sistema de la %3$s a la %2$s", + "You unassigned system tag %3$s from %2$s" : "Has des-assignat les marques de sistema de la %3$s a la %2$s", + "%1$s unassigned system tag %3$s from %2$s" : "%1$s ha des-assignat les marques de sistema de la %3$s a la %2$s", + "%s (restricted)" : "%s (restringit)", + "%s (invisible)" : "%s (invisible)", "No files in here" : "No hi ha arxius", "No entries found in this folder" : "No hi ha entrades en aquesta carpeta", "Name" : "Nom", diff --git a/apps/systemtags/lib/Activity/Listener.php b/apps/systemtags/lib/Activity/Listener.php index 9b6597119c6..435109053bd 100644 --- a/apps/systemtags/lib/Activity/Listener.php +++ b/apps/systemtags/lib/Activity/Listener.php @@ -188,6 +188,10 @@ class Listener { $activity->setAffectedUser($user); foreach ($tags as $tag) { + // don't publish activity for non-admins if tag is invisible + if (!$tag->isUserVisible() && !$this->groupManager->isAdmin($user)) { + continue; + } if ($event->getEvent() === MapperEvent::EVENT_ASSIGN) { $activity->setSubject(Extension::ASSIGN_TAG, [ $actor, diff --git a/apps/updatenotification/l10n/ca.js b/apps/updatenotification/l10n/ca.js index 5f6db3199a6..10e7328cb07 100644 --- a/apps/updatenotification/l10n/ca.js +++ b/apps/updatenotification/l10n/ca.js @@ -1,7 +1,19 @@ OC.L10N.register( "updatenotification", { + "Update notifications" : "Notificacions d'actualització", "{version} is available. Get more information on how to update." : "Hi ha disponible la versió {version}. Obtingueu més informació sobre com actualitzar.", - "Updater" : "Actualitzador" + "Updated channel" : "Canal actualitzat", + "ownCloud core" : "Nucli d'ownCloud", + "Update for %1$s to version %2$s is available." : "L'actualització per %1$s a la versió %2$s està disponible.", + "Updater" : "Actualitzador", + "A new version is available: %s" : "Una nova versió està disponible: %s", + "Open updater" : "Obrir actualitzador", + "Your version is up to date." : "La teva versió està actualitzada.", + "Checked on %s" : "Comprovat en %s", + "Update channel:" : "Actualitzar canal:", + "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Sempre podràs actualitzar a una versió més recent / canal experimental. Però mai es pot fer un \"downgrade\" a un canal més estable.", + "Notify members of the following groups about available updates:" : "Notificar als membres dels següents grups sobre les actualitzacions disponibles:", + "Only notification for app updates are available, because the selected update channel for ownCloud itself does not allow notifications." : "Només notificació d'actualitzacions d'aplicacions estan disponibles, degut a que el canal d'actualització per ownCloud seleccionat no permet les notificacions." }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/updatenotification/l10n/ca.json b/apps/updatenotification/l10n/ca.json index 74b1a731e90..673db0accd8 100644 --- a/apps/updatenotification/l10n/ca.json +++ b/apps/updatenotification/l10n/ca.json @@ -1,5 +1,17 @@ { "translations": { + "Update notifications" : "Notificacions d'actualització", "{version} is available. Get more information on how to update." : "Hi ha disponible la versió {version}. Obtingueu més informació sobre com actualitzar.", - "Updater" : "Actualitzador" + "Updated channel" : "Canal actualitzat", + "ownCloud core" : "Nucli d'ownCloud", + "Update for %1$s to version %2$s is available." : "L'actualització per %1$s a la versió %2$s està disponible.", + "Updater" : "Actualitzador", + "A new version is available: %s" : "Una nova versió està disponible: %s", + "Open updater" : "Obrir actualitzador", + "Your version is up to date." : "La teva versió està actualitzada.", + "Checked on %s" : "Comprovat en %s", + "Update channel:" : "Actualitzar canal:", + "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Sempre podràs actualitzar a una versió més recent / canal experimental. Però mai es pot fer un \"downgrade\" a un canal més estable.", + "Notify members of the following groups about available updates:" : "Notificar als membres dels següents grups sobre les actualitzacions disponibles:", + "Only notification for app updates are available, because the selected update channel for ownCloud itself does not allow notifications." : "Només notificació d'actualitzacions d'aplicacions estan disponibles, degut a que el canal d'actualització per ownCloud seleccionat no permet les notificacions." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/updatenotification/l10n/lb.js b/apps/updatenotification/l10n/lb.js new file mode 100644 index 00000000000..e877f169fc4 --- /dev/null +++ b/apps/updatenotification/l10n/lb.js @@ -0,0 +1,15 @@ +OC.L10N.register( + "updatenotification", + { + "Update notifications" : "Notifikatiounen aktualiséieren", + "{version} is available. Get more information on how to update." : "{Versioun} ass verfügbar. Kréi méi Informatiounen doriwwer wéi d'Aktualiséierung ofleeft.", + "Updated channel" : "Aktualiséierte Kanal", + "ownCloud core" : "ownCloud Kär", + "Update for %1$s to version %2$s is available." : "D'Aktualiséierung fir %1$s op d'Versioun %2$s ass verfügbar.", + "A new version is available: %s" : "Eng nei Versioun ass verfügbar: %s", + "Open updater" : "Den Aktualiséierungsprogramm opmaachen", + "Your version is up to date." : "Déng Versioun ass aktualiséiert.", + "Checked on %s" : "Gepréift um %s", + "Update channel:" : "Kanal updaten:" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/apps/updatenotification/l10n/lb.json b/apps/updatenotification/l10n/lb.json new file mode 100644 index 00000000000..a43883f6c3e --- /dev/null +++ b/apps/updatenotification/l10n/lb.json @@ -0,0 +1,13 @@ +{ "translations": { + "Update notifications" : "Notifikatiounen aktualiséieren", + "{version} is available. Get more information on how to update." : "{Versioun} ass verfügbar. Kréi méi Informatiounen doriwwer wéi d'Aktualiséierung ofleeft.", + "Updated channel" : "Aktualiséierte Kanal", + "ownCloud core" : "ownCloud Kär", + "Update for %1$s to version %2$s is available." : "D'Aktualiséierung fir %1$s op d'Versioun %2$s ass verfügbar.", + "A new version is available: %s" : "Eng nei Versioun ass verfügbar: %s", + "Open updater" : "Den Aktualiséierungsprogramm opmaachen", + "Your version is up to date." : "Déng Versioun ass aktualiséiert.", + "Checked on %s" : "Gepréift um %s", + "Update channel:" : "Kanal updaten:" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +}
\ No newline at end of file diff --git a/apps/updatenotification/l10n/pl.js b/apps/updatenotification/l10n/pl.js index d86fdf3c243..ffeb1b0601f 100644 --- a/apps/updatenotification/l10n/pl.js +++ b/apps/updatenotification/l10n/pl.js @@ -12,6 +12,8 @@ OC.L10N.register( "Your version is up to date." : "Posiadasz aktualną wersję.", "Checked on %s" : "Sprawdzone na %s", "Update channel:" : "Kanał aktualizacji:", - "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Możesz zawsze zaktualizować swoją wersję do nowego / ekperymentalnego kanału. Jednakże nie możesz powrócić do poprzedniej stabilniejszej wersji. " + "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Możesz zawsze zaktualizować swoją wersję do nowego / ekperymentalnego kanału. Jednakże nie możesz powrócić do poprzedniej stabilniejszej wersji. ", + "Notify members of the following groups about available updates:" : "Powiadom członków następujących grup o dostępnych aktualizacjach: ", + "Only notification for app updates are available, because the selected update channel for ownCloud itself does not allow notifications." : "Tylko powiadomienia o aktualizacjach aplikacji są dostępne, gdyż wybrany kanał aktualizacji ownCloud nie zezwala na powiadomienia. " }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/apps/updatenotification/l10n/pl.json b/apps/updatenotification/l10n/pl.json index b5d7132d9f0..6f03b0105bf 100644 --- a/apps/updatenotification/l10n/pl.json +++ b/apps/updatenotification/l10n/pl.json @@ -10,6 +10,8 @@ "Your version is up to date." : "Posiadasz aktualną wersję.", "Checked on %s" : "Sprawdzone na %s", "Update channel:" : "Kanał aktualizacji:", - "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Możesz zawsze zaktualizować swoją wersję do nowego / ekperymentalnego kanału. Jednakże nie możesz powrócić do poprzedniej stabilniejszej wersji. " + "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Możesz zawsze zaktualizować swoją wersję do nowego / ekperymentalnego kanału. Jednakże nie możesz powrócić do poprzedniej stabilniejszej wersji. ", + "Notify members of the following groups about available updates:" : "Powiadom członków następujących grup o dostępnych aktualizacjach: ", + "Only notification for app updates are available, because the selected update channel for ownCloud itself does not allow notifications." : "Tylko powiadomienia o aktualizacjach aplikacji są dostępne, gdyż wybrany kanał aktualizacji ownCloud nie zezwala na powiadomienia. " },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/apps/updatenotification/l10n/sl.js b/apps/updatenotification/l10n/sl.js index 12ba787e5e4..a59a58731fb 100644 --- a/apps/updatenotification/l10n/sl.js +++ b/apps/updatenotification/l10n/sl.js @@ -4,6 +4,7 @@ OC.L10N.register( "Update notifications" : "Posodobi obvestila", "{version} is available. Get more information on how to update." : "Na voljo je nova različica {version}. Na voljo je več podrobnosti o nadgradnji.", "Updated channel" : "Posodobljen kanal", + "ownCloud core" : "Jedro ownCloud", "Update for %1$s to version %2$s is available." : "Posodobitev %1$s na različico %2$s je na voljo.", "Updater" : "Posodabljalnik", "A new version is available: %s" : "Na voljo je nova različica: %s", diff --git a/apps/updatenotification/l10n/sl.json b/apps/updatenotification/l10n/sl.json index ecc0e3519d4..c96c9666fb1 100644 --- a/apps/updatenotification/l10n/sl.json +++ b/apps/updatenotification/l10n/sl.json @@ -2,6 +2,7 @@ "Update notifications" : "Posodobi obvestila", "{version} is available. Get more information on how to update." : "Na voljo je nova različica {version}. Na voljo je več podrobnosti o nadgradnji.", "Updated channel" : "Posodobljen kanal", + "ownCloud core" : "Jedro ownCloud", "Update for %1$s to version %2$s is available." : "Posodobitev %1$s na različico %2$s je na voljo.", "Updater" : "Posodabljalnik", "A new version is available: %s" : "Na voljo je nova različica: %s", diff --git a/apps/user_ldap/l10n/lb.js b/apps/user_ldap/l10n/lb.js index b340887548e..f62d2924488 100644 --- a/apps/user_ldap/l10n/lb.js +++ b/apps/user_ldap/l10n/lb.js @@ -1,14 +1,51 @@ OC.L10N.register( "user_ldap", { + "Failed to delete the server configuration" : "D'Server-Konfiguratioun konnt net geläscht ginn", + "The configuration is invalid: anonymous bind is not allowed." : "Dës Konfiguratioun ass ongëlteg: eng anonym Bindung ass net erlaabt.", + "Action does not exist" : "Dës Aktioun gëtt et net", + "Testing configuration…" : "D'Konfiguratioun gëtt getest...", + "Configuration incorrect" : "D'Konfiguratioun ass net korrekt", + "Configuration incomplete" : "D'Konfiguratioun ass net komplett", + "Configuration OK" : "Konfiguratioun OK", + "Select groups" : "Wiel Gruppen äus", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "D'Späicheren huet net geklappt. W.e.g. géi sécher dass Datebank an der Operatioun ass. Lued nach emol éiers de weider fiers.", + "Select attributes" : "Wiel Attributer aus", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "De Benotzer konnt net fonnt ginn. W.e.g. kuck deng Login Attributer a Benotzernumm no. \n ", + "_%s group found_::_%s groups found_" : ["%s Grupp fonnt","%s Gruppe fonnt"], + "_%s user found_::_%s users found_" : ["%s Benotzer fonnt","%s Benotzere fonnt"], + "Could not find the desired feature" : "Déi gewënschte Funktioun konnt net fonnt ginn", + "Server" : "Server", "Users" : "Benotzer", "Groups" : "Gruppen", + "Test Configuration" : "Konfiguratiounstest", "Help" : "Hëllef", + "Groups meeting these criteria are available in %s:" : "D'Gruppen, déi dës Critèren erfëllen sinn am %s:", + "Only these object classes:" : "Nëmmen des Klass vun Objeten:", + "Only from these groups:" : "Nëmme vun dëse Gruppen:", + "Search groups" : "Sich Gruppen", + "Available groups" : "Disponibel Gruppen", + "Selected groups" : "Ausgewielte Gruppen", + "Test Loginname" : "Test Benotzernumm", + "Verify settings" : "Astellungen iwwerpréiwen", + "1. Server" : "1. Server", + "%s. Server:" : "%s. Server", + "Delete the current configuration" : "Läsch déi aktuell Konfiguratioun", "Host" : "Host", "Port" : "Port", + "User DN" : "Benotzer DN", "Password" : "Passwuert", + "Saving" : "Speicheren...", "Back" : "Zeréck", "Continue" : "Weider", - "Advanced" : "Avancéiert" + "Advanced" : "Erweidert", + "Connection Settings" : "D'Astellunge vun der Verbindung", + "Configuration Active" : "D'Konfiguratioun ass aktiv", + "When unchecked, this configuration will be skipped." : "Ouni Iwwerpréiwung wäert dës Konfiguratioun iwwergaange ginn.", + "Directory Settings" : "Dossier's Astellungen", + "in bytes" : "A Bytes", + "Email Field" : "Email Feld", + "Internal Username" : "Interne Benotzernumm", + "Internal Username Attribute:" : "Interne Benotzernumm Attribut:" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/user_ldap/l10n/lb.json b/apps/user_ldap/l10n/lb.json index 4b4d46427b8..e869a5821b1 100644 --- a/apps/user_ldap/l10n/lb.json +++ b/apps/user_ldap/l10n/lb.json @@ -1,12 +1,49 @@ { "translations": { + "Failed to delete the server configuration" : "D'Server-Konfiguratioun konnt net geläscht ginn", + "The configuration is invalid: anonymous bind is not allowed." : "Dës Konfiguratioun ass ongëlteg: eng anonym Bindung ass net erlaabt.", + "Action does not exist" : "Dës Aktioun gëtt et net", + "Testing configuration…" : "D'Konfiguratioun gëtt getest...", + "Configuration incorrect" : "D'Konfiguratioun ass net korrekt", + "Configuration incomplete" : "D'Konfiguratioun ass net komplett", + "Configuration OK" : "Konfiguratioun OK", + "Select groups" : "Wiel Gruppen äus", + "Saving failed. Please make sure the database is in Operation. Reload before continuing." : "D'Späicheren huet net geklappt. W.e.g. géi sécher dass Datebank an der Operatioun ass. Lued nach emol éiers de weider fiers.", + "Select attributes" : "Wiel Attributer aus", + "User not found. Please check your login attributes and username. Effective filter (to copy-and-paste for command line validation): <br/>" : "De Benotzer konnt net fonnt ginn. W.e.g. kuck deng Login Attributer a Benotzernumm no. \n ", + "_%s group found_::_%s groups found_" : ["%s Grupp fonnt","%s Gruppe fonnt"], + "_%s user found_::_%s users found_" : ["%s Benotzer fonnt","%s Benotzere fonnt"], + "Could not find the desired feature" : "Déi gewënschte Funktioun konnt net fonnt ginn", + "Server" : "Server", "Users" : "Benotzer", "Groups" : "Gruppen", + "Test Configuration" : "Konfiguratiounstest", "Help" : "Hëllef", + "Groups meeting these criteria are available in %s:" : "D'Gruppen, déi dës Critèren erfëllen sinn am %s:", + "Only these object classes:" : "Nëmmen des Klass vun Objeten:", + "Only from these groups:" : "Nëmme vun dëse Gruppen:", + "Search groups" : "Sich Gruppen", + "Available groups" : "Disponibel Gruppen", + "Selected groups" : "Ausgewielte Gruppen", + "Test Loginname" : "Test Benotzernumm", + "Verify settings" : "Astellungen iwwerpréiwen", + "1. Server" : "1. Server", + "%s. Server:" : "%s. Server", + "Delete the current configuration" : "Läsch déi aktuell Konfiguratioun", "Host" : "Host", "Port" : "Port", + "User DN" : "Benotzer DN", "Password" : "Passwuert", + "Saving" : "Speicheren...", "Back" : "Zeréck", "Continue" : "Weider", - "Advanced" : "Avancéiert" + "Advanced" : "Erweidert", + "Connection Settings" : "D'Astellunge vun der Verbindung", + "Configuration Active" : "D'Konfiguratioun ass aktiv", + "When unchecked, this configuration will be skipped." : "Ouni Iwwerpréiwung wäert dës Konfiguratioun iwwergaange ginn.", + "Directory Settings" : "Dossier's Astellungen", + "in bytes" : "A Bytes", + "Email Field" : "Email Feld", + "Internal Username" : "Interne Benotzernumm", + "Internal Username Attribute:" : "Interne Benotzernumm Attribut:" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index daeb7d942a4..4d0753696ff 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -732,7 +732,14 @@ class Access extends LDAPUtility implements IUserTools { $user->unmark(); $user = $this->userManager->get($ocName); } - $user->processAttributes($userRecord); + if ($user !== null) { + $user->processAttributes($userRecord); + } else { + \OC::$server->getLogger()->debug( + "The ldap user manager returned null for $ocName", + ['app'=>'user_ldap'] + ); + } } } diff --git a/autotest.sh b/autotest.sh index e798157fe64..40c54102eae 100755 --- a/autotest.sh +++ b/autotest.sh @@ -192,8 +192,8 @@ function execute_tests { DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") echo "Waiting for MySQL initialisation ..." - if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 60; then - echo "[ERROR] Waited 60 seconds, no response" >&2 + if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 600; then + echo "[ERROR] Waited 600 seconds, no response" >&2 exit 1 fi @@ -221,8 +221,8 @@ function execute_tests { DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") echo "Waiting for MariaDB initialisation ..." - if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 60; then - echo "[ERROR] Waited 60 seconds, no response" >&2 + if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 600; then + echo "[ERROR] Waited 600 seconds, no response" >&2 exit 1 fi diff --git a/core/Application.php b/core/Application.php index a87917b626a..8ea2672e54e 100644 --- a/core/Application.php +++ b/core/Application.php @@ -32,6 +32,7 @@ use OC\AppFramework\Utility\TimeFactory; use OC\Core\Controller\AvatarController; use OC\Core\Controller\LoginController; use OC\Core\Controller\LostController; +use OC\Core\Controller\OccController; use OC\Core\Controller\TokenController; use OC\Core\Controller\TwoFactorChallengeController; use OC\Core\Controller\UserController; @@ -125,6 +126,18 @@ class Application extends App { $c->query('SecureRandom') ); }); + $container->registerService('OccController', function(SimpleContainer $c) { + return new OccController( + $c->query('AppName'), + $c->query('Request'), + $c->query('Config'), + new \OC\Console\Application( + $c->query('Config'), + $c->query('ServerContainer')->getEventDispatcher(), + $c->query('Request') + ) + ); + }); /** * Core class wrappers diff --git a/core/Controller/OccController.php b/core/Controller/OccController.php new file mode 100644 index 00000000000..917d02f37f1 --- /dev/null +++ b/core/Controller/OccController.php @@ -0,0 +1,147 @@ +<?php +/** + * @author Victor Dubiniuk <dubiniuk@owncloud.com> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Core\Controller; + +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OC\Console\Application; +use OCP\IConfig; +use OCP\IRequest; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; + +class OccController extends Controller { + + /** @var array */ + private $allowedCommands = [ + 'app:disable', + 'app:enable', + 'app:getpath', + 'app:list', + 'check', + 'config:list', + 'maintenance:mode', + 'status', + 'upgrade' + ]; + + /** @var IConfig */ + private $config; + /** @var Application */ + private $console; + + /** + * OccController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IConfig $config + * @param Application $console + */ + public function __construct($appName, IRequest $request, + IConfig $config, Application $console) { + parent::__construct($appName, $request); + $this->config = $config; + $this->console = $console; + } + + /** + * @PublicPage + * @NoCSRFRequired + * + * Execute occ command + * Sample request + * POST http://domain.tld/index.php/occ/status', + * { + * 'params': { + * '--no-warnings':'1', + * '--output':'json' + * }, + * 'token': 'someToken' + * } + * + * @param string $command + * @param string $token + * @param array $params + * + * @return JSONResponse + * @throws \Exception + */ + public function execute($command, $token, $params = []) { + try { + $this->validateRequest($command, $token); + + $output = new BufferedOutput(); + $formatter = $output->getFormatter(); + $formatter->setDecorated(false); + $this->console->setAutoExit(false); + $this->console->loadCommands(new ArrayInput([]), $output); + + $inputArray = array_merge(['command' => $command], $params); + $input = new ArrayInput($inputArray); + + $exitCode = $this->console->run($input, $output); + $response = $output->fetch(); + + $json = [ + 'exitCode' => $exitCode, + 'response' => $response + ]; + + } catch (\UnexpectedValueException $e){ + $json = [ + 'exitCode' => 126, + 'response' => 'Not allowed', + 'details' => $e->getMessage() + ]; + } + return new JSONResponse($json); + } + + /** + * Check if command is allowed and has a valid security token + * @param $command + * @param $token + */ + protected function validateRequest($command, $token){ + if (!in_array($this->request->getRemoteAddress(), ['::1', '127.0.0.1', 'localhost'])) { + throw new \UnexpectedValueException('Web executor is not allowed to run from a different host'); + } + + if (!in_array($command, $this->allowedCommands)) { + throw new \UnexpectedValueException(sprintf('Command "%s" is not allowed to run via web request', $command)); + } + + $coreToken = $this->config->getSystemValue('updater.secret', ''); + if ($coreToken === '') { + throw new \UnexpectedValueException( + 'updater.secret is undefined in config/config.php. Either browse the admin settings in your ownCloud and click "Open updater" or define a strong secret using <pre>php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'</pre> and set this in the config.php.' + ); + } + + if (!password_verify($token, $coreToken)) { + throw new \UnexpectedValueException( + 'updater.secret does not match the provided token' + ); + } + } +} diff --git a/core/l10n/lb.js b/core/l10n/lb.js index 92d10ed3661..dfd8e7a0f62 100644 --- a/core/l10n/lb.js +++ b/core/l10n/lb.js @@ -83,6 +83,7 @@ OC.L10N.register( "can share" : "kann deelen", "can edit" : "kann änneren", "create" : "erstellen", + "change" : "änneren", "delete" : "läschen", "access control" : "Zougrëffskontroll", "Share" : "Deelen", diff --git a/core/l10n/lb.json b/core/l10n/lb.json index 382da7f58d2..e1cfc10d33c 100644 --- a/core/l10n/lb.json +++ b/core/l10n/lb.json @@ -81,6 +81,7 @@ "can share" : "kann deelen", "can edit" : "kann änneren", "create" : "erstellen", + "change" : "änneren", "delete" : "läschen", "access control" : "Zougrëffskontroll", "Share" : "Deelen", diff --git a/core/l10n/pl.js b/core/l10n/pl.js index 0a83da4f4c9..3b284163b4f 100644 --- a/core/l10n/pl.js +++ b/core/l10n/pl.js @@ -13,6 +13,7 @@ OC.L10N.register( "No valid crop data provided" : "Brak danych do przycięcia", "Crop is not square" : "Przycięcie nie jest prostokątem", "Couldn't reset password because the token is invalid" : "Nie można zresetować hasła, ponieważ token jest niepoprawny", + "Couldn't reset password because the token is expired" : "Nie można zresetować hasła, ponieważ token wygasł", "Couldn't send reset email. Please make sure your username is correct." : "Nie mogę wysłać maila resetującego. Sprawdź czy nazwa użytkownika jest poprawna.", "%s password reset" : "%s reset hasła", "Couldn't send reset email. Please contact your administrator." : "Nie mogę wysłać maila resetującego. Skontaktuj się z administratorem.", @@ -25,8 +26,11 @@ OC.L10N.register( "Error unfavoriting" : "Błąd przy usuwaniu z ulubionych", "Couldn't send mail to following users: %s " : "Nie można było wysłać wiadomości do następujących użytkowników: %s", "Preparing update" : "Przygotowuję aktualizację", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Ostrzeżenie naprawiania:", "Repair error: " : "Błąd naprawiania:", + "Please use the command line updater because automatic updating is disabled in the config.php." : "Użyj aktualizatora z linii poleceń, ponieważ automatyczna aktualizacja jest zablokowana w config.php.", + "[%d / %d]: Checking table %s" : "[%d / %d]: Sprawdzanie tabeli %s", "Turned on maintenance mode" : "Włączony tryb konserwacji", "Turned off maintenance mode" : "Wyłączony tryb konserwacji", "Maintenance mode is kept active" : "Tryb konserwacji pozostaje aktywny", @@ -91,6 +95,7 @@ OC.L10N.register( "Oct." : "Paź.", "Nov." : "Lis.", "Dec." : "Gru.", + "<a href=\"{docUrl}\">There were problems with the code integrity check. More information…</a>" : "<a href=\"{docUrl}\">Sprawdzenie spójności kodu nie wykazało problemów. Więcej informacji…</a>", "Settings" : "Ustawienia", "Problem loading page, reloading in 5 seconds" : "Błąd podczas ładowania strony, odświeżanie w ciągu 5 sekund.", "Saving..." : "Zapisywanie...", @@ -123,6 +128,8 @@ OC.L10N.register( "So-so password" : "Mało skomplikowane hasło", "Good password" : "Dobre hasło", "Strong password" : "Mocne hasło", + "Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken." : "Serwer WWW nie jest jeszcze na tyle poprawnie skonfigurowany, aby umożliwić synchronizację plików, ponieważ interfejs WebDAV wydaje się być uszkodzony.", + "Your web server is not set up properly to resolve \"{url}\". Further information can be found in our <a target=\"_blank\" rel=\"noreferrer\" href=\"{docLink}\">documentation</a>." : "Serwer WWW nie jest poprawnie skonfigurowany, aby wyświetlić \"{url}\". Więcej informacji można znaleźć w naszej <a target=\"_blank\" rel=\"noreferrer\" href=\"{docLink}\">dokumentacji</a>.", "Error occurred while checking server setup" : "Pojawił się błąd podczas sprawdzania ustawień serwera", "Shared" : "Udostępniono", "Shared with {recipients}" : "Współdzielony z {recipients}", @@ -158,6 +165,8 @@ OC.L10N.register( "change" : "zmiany", "delete" : "usuń", "access control" : "kontrola dostępu", + "Could not unshare" : "Nie udało się usunąć udostępnienia", + "No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}", "No users found for {search}" : "Nie znaleziono użytkowników dla {search}", "An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.", "Share" : "Udostępnij", @@ -165,9 +174,12 @@ OC.L10N.register( "Share with users…" : "Współdziel z użytkownikami...", "Share with users, groups or remote users…" : "Współdziel z użytkownikami, grupami lub zdalnym użytkownikiem...", "Share with users or groups…" : "Współdziel z użytkownikami lub grupami...", + "Share with users or remote users…" : "Współdziel z użytkownikami lub zdalnymi użytkownikami...", "Error removing share" : "Błąd podczas usuwania współdzielenia", "Warning" : "Ostrzeżenie", "Error while sending notification" : "Błąd podczas wysyłania powiadomienia", + "Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje", + "restricted" : "ograniczone", "invisible" : "niewidoczny", "Delete" : "Usuń", "Rename" : "Zmień nazwę", @@ -259,6 +271,11 @@ OC.L10N.register( "This means only administrators can use the instance." : "To oznacza, że tylko administratorzy mogą w tej chwili używać aplikacji.", "Contact your system administrator if this message persists or appeared unexpectedly." : "Skontaktuj się z administratorem, jeśli ten komunikat pojawił się nieoczekiwanie lub wyświetla się ciągle.", "Thank you for your patience." : "Dziękuję za cierpliwość.", + "Two-step verification" : "Weryfikacja dwuskładnikowa", + "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Dla Twojego konta uruchomiono wzmocnioną ochronę. Uwierzytelnij przy pomocy drugiego składnika.", + "Cancel login" : "Anuluj logowanie", + "Please authenticate using the selected factor." : "Uwierzytelnij przy pomocy wybranego składnika", + "An error occured while verifying the token" : "Wystąpił błąd podczas weryfikacji tokena", "You are accessing the server from an untrusted domain." : "Dostajesz się do serwera z niezaufanej domeny.", "Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "W zależności od konfiguracji, jako administrator możesz także użyć poniższego przycisku aby zaufać tej domenie.", "Add \"%s\" as trusted domain" : "Dodaj \"%s\" jako domenę zaufaną", @@ -272,6 +289,7 @@ OC.L10N.register( "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:", "Detailed logs" : "Szczegółowe logi", "Update needed" : "Wymagana aktualizacja", + "For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.", "This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna." }, "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/core/l10n/pl.json b/core/l10n/pl.json index d839ef91846..6833611713d 100644 --- a/core/l10n/pl.json +++ b/core/l10n/pl.json @@ -11,6 +11,7 @@ "No valid crop data provided" : "Brak danych do przycięcia", "Crop is not square" : "Przycięcie nie jest prostokątem", "Couldn't reset password because the token is invalid" : "Nie można zresetować hasła, ponieważ token jest niepoprawny", + "Couldn't reset password because the token is expired" : "Nie można zresetować hasła, ponieważ token wygasł", "Couldn't send reset email. Please make sure your username is correct." : "Nie mogę wysłać maila resetującego. Sprawdź czy nazwa użytkownika jest poprawna.", "%s password reset" : "%s reset hasła", "Couldn't send reset email. Please contact your administrator." : "Nie mogę wysłać maila resetującego. Skontaktuj się z administratorem.", @@ -23,8 +24,11 @@ "Error unfavoriting" : "Błąd przy usuwaniu z ulubionych", "Couldn't send mail to following users: %s " : "Nie można było wysłać wiadomości do następujących użytkowników: %s", "Preparing update" : "Przygotowuję aktualizację", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Ostrzeżenie naprawiania:", "Repair error: " : "Błąd naprawiania:", + "Please use the command line updater because automatic updating is disabled in the config.php." : "Użyj aktualizatora z linii poleceń, ponieważ automatyczna aktualizacja jest zablokowana w config.php.", + "[%d / %d]: Checking table %s" : "[%d / %d]: Sprawdzanie tabeli %s", "Turned on maintenance mode" : "Włączony tryb konserwacji", "Turned off maintenance mode" : "Wyłączony tryb konserwacji", "Maintenance mode is kept active" : "Tryb konserwacji pozostaje aktywny", @@ -89,6 +93,7 @@ "Oct." : "Paź.", "Nov." : "Lis.", "Dec." : "Gru.", + "<a href=\"{docUrl}\">There were problems with the code integrity check. More information…</a>" : "<a href=\"{docUrl}\">Sprawdzenie spójności kodu nie wykazało problemów. Więcej informacji…</a>", "Settings" : "Ustawienia", "Problem loading page, reloading in 5 seconds" : "Błąd podczas ładowania strony, odświeżanie w ciągu 5 sekund.", "Saving..." : "Zapisywanie...", @@ -121,6 +126,8 @@ "So-so password" : "Mało skomplikowane hasło", "Good password" : "Dobre hasło", "Strong password" : "Mocne hasło", + "Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken." : "Serwer WWW nie jest jeszcze na tyle poprawnie skonfigurowany, aby umożliwić synchronizację plików, ponieważ interfejs WebDAV wydaje się być uszkodzony.", + "Your web server is not set up properly to resolve \"{url}\". Further information can be found in our <a target=\"_blank\" rel=\"noreferrer\" href=\"{docLink}\">documentation</a>." : "Serwer WWW nie jest poprawnie skonfigurowany, aby wyświetlić \"{url}\". Więcej informacji można znaleźć w naszej <a target=\"_blank\" rel=\"noreferrer\" href=\"{docLink}\">dokumentacji</a>.", "Error occurred while checking server setup" : "Pojawił się błąd podczas sprawdzania ustawień serwera", "Shared" : "Udostępniono", "Shared with {recipients}" : "Współdzielony z {recipients}", @@ -156,6 +163,8 @@ "change" : "zmiany", "delete" : "usuń", "access control" : "kontrola dostępu", + "Could not unshare" : "Nie udało się usunąć udostępnienia", + "No users or groups found for {search}" : "Nie znaleziono użytkowników lub grup dla {search}", "No users found for {search}" : "Nie znaleziono użytkowników dla {search}", "An error occurred. Please try again" : "Wystąpił błąd. Proszę spróbować ponownie.", "Share" : "Udostępnij", @@ -163,9 +172,12 @@ "Share with users…" : "Współdziel z użytkownikami...", "Share with users, groups or remote users…" : "Współdziel z użytkownikami, grupami lub zdalnym użytkownikiem...", "Share with users or groups…" : "Współdziel z użytkownikami lub grupami...", + "Share with users or remote users…" : "Współdziel z użytkownikami lub zdalnymi użytkownikami...", "Error removing share" : "Błąd podczas usuwania współdzielenia", "Warning" : "Ostrzeżenie", "Error while sending notification" : "Błąd podczas wysyłania powiadomienia", + "Non-existing tag #{tag}" : "Znacznik #{tag} nie istnieje", + "restricted" : "ograniczone", "invisible" : "niewidoczny", "Delete" : "Usuń", "Rename" : "Zmień nazwę", @@ -257,6 +269,11 @@ "This means only administrators can use the instance." : "To oznacza, że tylko administratorzy mogą w tej chwili używać aplikacji.", "Contact your system administrator if this message persists or appeared unexpectedly." : "Skontaktuj się z administratorem, jeśli ten komunikat pojawił się nieoczekiwanie lub wyświetla się ciągle.", "Thank you for your patience." : "Dziękuję za cierpliwość.", + "Two-step verification" : "Weryfikacja dwuskładnikowa", + "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Dla Twojego konta uruchomiono wzmocnioną ochronę. Uwierzytelnij przy pomocy drugiego składnika.", + "Cancel login" : "Anuluj logowanie", + "Please authenticate using the selected factor." : "Uwierzytelnij przy pomocy wybranego składnika", + "An error occured while verifying the token" : "Wystąpił błąd podczas weryfikacji tokena", "You are accessing the server from an untrusted domain." : "Dostajesz się do serwera z niezaufanej domeny.", "Depending on your configuration, as an administrator you might also be able to use the button below to trust this domain." : "W zależności od konfiguracji, jako administrator możesz także użyć poniższego przycisku aby zaufać tej domenie.", "Add \"%s\" as trusted domain" : "Dodaj \"%s\" jako domenę zaufaną", @@ -270,6 +287,7 @@ "To avoid timeouts with larger installations, you can instead run the following command from your installation directory:" : "Aby uniknąć timeout-ów przy większych instalacjach, możesz zamiast tego uruchomić następującą komendę w katalogu Twojej instalacji:", "Detailed logs" : "Szczegółowe logi", "Update needed" : "Wymagana aktualizacja", + "For help, see the <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">documentation</a>." : "Aby uzyskać pomoc, zajrzyj do <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">dokumentacji</a>.", "This page will refresh itself when the %s instance is available again." : "Strona odświeży się gdy instancja %s będzie ponownie dostępna." },"pluralForm" :"nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/core/l10n/sv.js b/core/l10n/sv.js index d74a441a70e..e7cb586306b 100644 --- a/core/l10n/sv.js +++ b/core/l10n/sv.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Tack för ditt tålamod.", "Two-step verification" : "Tvåfaktorsautentisering", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Utökad säkerhet har aktiverats på ditt konto. Vänligen autentisera med en andra faktor.", + "Cancel login" : "Avbryt inloggning", "Please authenticate using the selected factor." : "Vänligen autentisera med vald faktor.", "An error occured while verifying the token" : "Ett fel uppstod vid verifiering av token.", "You are accessing the server from an untrusted domain." : "Du ansluter till servern från en osäker domän.", diff --git a/core/l10n/sv.json b/core/l10n/sv.json index b2b2d282ad2..352bea7de70 100644 --- a/core/l10n/sv.json +++ b/core/l10n/sv.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Tack för ditt tålamod.", "Two-step verification" : "Tvåfaktorsautentisering", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Utökad säkerhet har aktiverats på ditt konto. Vänligen autentisera med en andra faktor.", + "Cancel login" : "Avbryt inloggning", "Please authenticate using the selected factor." : "Vänligen autentisera med vald faktor.", "An error occured while verifying the token" : "Ett fel uppstod vid verifiering av token.", "You are accessing the server from an untrusted domain." : "Du ansluter till servern från en osäker domän.", diff --git a/core/routes.php b/core/routes.php index 402277d8f3e..c473408e2e9 100644 --- a/core/routes.php +++ b/core/routes.php @@ -48,6 +48,7 @@ $application->registerRoutes($this, [ ['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'], ['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'], ['name' => 'token#generateToken', 'url' => '/token/generate', 'verb' => 'POST'], + ['name' => 'occ#execute', 'url' => '/occ/{command}', 'verb' => 'POST'], ['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'], ['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'], ['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'], diff --git a/lib/base.php b/lib/base.php index b33687dbab7..45f291e5cb7 100644 --- a/lib/base.php +++ b/lib/base.php @@ -49,6 +49,8 @@ * */ +use OCP\IRequest; + require_once 'public/Constants.php'; /** @@ -271,9 +273,20 @@ class OC { } } - public static function checkMaintenanceMode() { + /** + * Limit maintenance mode access + * @param IRequest $request + */ + public static function checkMaintenanceMode(IRequest $request) { + // Check if requested URL matches 'index.php/occ' + $isOccControllerRequested = preg_match('|/index\.php$|', $request->getScriptName()) === 1 + && strpos($request->getPathInfo(), '/occ/') === 0; // Allow ajax update script to execute without being stopped - if (\OC::$server->getSystemConfig()->getValue('maintenance', false) && OC::$SUBURI != '/core/ajax/update.php') { + if ( + \OC::$server->getSystemConfig()->getValue('maintenance', false) + && OC::$SUBURI != '/core/ajax/update.php' + && !$isOccControllerRequested + ) { // send http status 503 header('HTTP/1.1 503 Service Temporarily Unavailable'); header('Status: 503 Service Temporarily Unavailable'); @@ -820,7 +833,7 @@ class OC { $request = \OC::$server->getRequest(); $requestPath = $request->getRawPathInfo(); if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade - self::checkMaintenanceMode(); + self::checkMaintenanceMode($request); self::checkUpgrade(); } diff --git a/lib/l10n/ca.js b/lib/l10n/ca.js index 2d6b88768f0..d84d7284275 100644 --- a/lib/l10n/ca.js +++ b/lib/l10n/ca.js @@ -44,6 +44,7 @@ OC.L10N.register( "For the best results, please consider using a GNU/Linux server instead." : "Per millors resultats, millor considereu utilitzar un servidor GNU/Linux.", "Set an admin username." : "Establiu un nom d'usuari per l'administrador.", "Set an admin password." : "Establiu una contrasenya per l'administrador.", + "Invalid Federated Cloud ID" : "ID de núvol federat invàlid", "%s shared »%s« with you" : "%s ha compartit »%s« amb tu", "Sharing %s failed, because the file does not exist" : "Ha fallat en compartir %s, perquè el fitxer no existeix", "You are not allowed to share %s" : "No se us permet compartir %s", @@ -54,6 +55,8 @@ OC.L10N.register( "Sharing %s failed, because %s is not a member of the group %s" : "Ha fallat en compartir %s, perquè %s no és membre del grup %s", "You need to provide a password to create a public link, only protected links are allowed" : "Heu de proporcionar una contrasenya per crear un enllaç públic. Només es permeten enllaços segurs.", "Sharing %s failed, because sharing with links is not allowed" : "Ha fallat en compartir %s, perquè no es permet compartir amb enllaços", + "Not allowed to create a federated share with the same user" : "No està permés crear una compartició federada amb el mateix usuari", + "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "La compartició de %s ha fallat, no es pot trobar %s, potser el servidor està actualment innacessible.", "Share type %s is not valid for %s" : "La compartició tipus %s no és vàlida per %s", "Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Ha fallat en establir els permisos per %s perquè aquests excedeixen els permesos per a %s", "Setting permissions for %s failed, because the item was not found" : "Ha fallat en establir els permisos per %s, perquè no s'ha trobat l'element", diff --git a/lib/l10n/ca.json b/lib/l10n/ca.json index 2291b637c51..ac6dd35ea1d 100644 --- a/lib/l10n/ca.json +++ b/lib/l10n/ca.json @@ -42,6 +42,7 @@ "For the best results, please consider using a GNU/Linux server instead." : "Per millors resultats, millor considereu utilitzar un servidor GNU/Linux.", "Set an admin username." : "Establiu un nom d'usuari per l'administrador.", "Set an admin password." : "Establiu una contrasenya per l'administrador.", + "Invalid Federated Cloud ID" : "ID de núvol federat invàlid", "%s shared »%s« with you" : "%s ha compartit »%s« amb tu", "Sharing %s failed, because the file does not exist" : "Ha fallat en compartir %s, perquè el fitxer no existeix", "You are not allowed to share %s" : "No se us permet compartir %s", @@ -52,6 +53,8 @@ "Sharing %s failed, because %s is not a member of the group %s" : "Ha fallat en compartir %s, perquè %s no és membre del grup %s", "You need to provide a password to create a public link, only protected links are allowed" : "Heu de proporcionar una contrasenya per crear un enllaç públic. Només es permeten enllaços segurs.", "Sharing %s failed, because sharing with links is not allowed" : "Ha fallat en compartir %s, perquè no es permet compartir amb enllaços", + "Not allowed to create a federated share with the same user" : "No està permés crear una compartició federada amb el mateix usuari", + "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "La compartició de %s ha fallat, no es pot trobar %s, potser el servidor està actualment innacessible.", "Share type %s is not valid for %s" : "La compartició tipus %s no és vàlida per %s", "Setting permissions for %s failed, because the permissions exceed permissions granted to %s" : "Ha fallat en establir els permisos per %s perquè aquests excedeixen els permesos per a %s", "Setting permissions for %s failed, because the item was not found" : "Ha fallat en establir els permisos per %s, perquè no s'ha trobat l'element", diff --git a/lib/l10n/sl.js b/lib/l10n/sl.js index e8e861af3c4..5e0e1fbcdbd 100644 --- a/lib/l10n/sl.js +++ b/lib/l10n/sl.js @@ -157,7 +157,7 @@ OC.L10N.register( "Please check that the data directory contains a file \".ocdata\" in its root." : "Preverite, ali je v korenu podatkovne mape datoteka \".ocdata\".", "Could not obtain lock type %d on \"%s\"." : "Ni mogoče pridobiti zaklepa %d na \"%s\".", "Storage unauthorized. %s" : "Dostop do shrambe ni overjen. %s", - "Storage incomplete configuration. %s" : "Nepopolna konfiguracija shrambe. %s", + "Storage incomplete configuration. %s" : "Nepopolna nastavitev shrambe. %s", "Storage connection error. %s" : "Napaka povezave do shrambe. %s", "Storage not available" : "Shramba ni na voljo", "Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s" diff --git a/lib/l10n/sl.json b/lib/l10n/sl.json index a05d62b1c39..48c9f69a86d 100644 --- a/lib/l10n/sl.json +++ b/lib/l10n/sl.json @@ -155,7 +155,7 @@ "Please check that the data directory contains a file \".ocdata\" in its root." : "Preverite, ali je v korenu podatkovne mape datoteka \".ocdata\".", "Could not obtain lock type %d on \"%s\"." : "Ni mogoče pridobiti zaklepa %d na \"%s\".", "Storage unauthorized. %s" : "Dostop do shrambe ni overjen. %s", - "Storage incomplete configuration. %s" : "Nepopolna konfiguracija shrambe. %s", + "Storage incomplete configuration. %s" : "Nepopolna nastavitev shrambe. %s", "Storage connection error. %s" : "Napaka povezave do shrambe. %s", "Storage not available" : "Shramba ni na voljo", "Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s" diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index e082cea3305..c8b2009fcc7 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -27,6 +27,7 @@ */ namespace OC; +use OC\Cache\CappedMemoryCache; use OCP\IDBConnection; use OCP\PreConditionNotMetException; @@ -58,14 +59,15 @@ class AllConfig implements \OCP\IConfig { * - deleteAllUserValues * - deleteAppFromAllUsers * - * @var array $userCache + * @var CappedMemoryCache $userCache */ - private $userCache = array(); + private $userCache; /** * @param SystemConfig $systemConfig */ function __construct(SystemConfig $systemConfig) { + $this->userCache = new CappedMemoryCache(); $this->systemConfig = $systemConfig; } diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index 69bfeb5e9bb..32a507623e3 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -26,6 +26,7 @@ namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\User\Session; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -89,8 +90,12 @@ class CORSMiddleware extends Middleware { $pass = $this->request->server['PHP_AUTH_PW']; $this->session->logout(); - if(!$this->session->logClientIn($user, $pass, $this->request)) { - throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED); + try { + if (!$this->session->logClientIn($user, $pass, $this->request)) { + throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED); + } + } catch (PasswordLoginForbiddenException $ex) { + throw new SecurityException('Password login forbidden, use token instead', Http::STATUS_UNAUTHORIZED); } } } diff --git a/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php b/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php new file mode 100644 index 00000000000..2e9f9534dbd --- /dev/null +++ b/lib/private/Authentication/Exceptions/PasswordLoginForbiddenException.php @@ -0,0 +1,29 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Authentication\Exceptions; + +use Exception; + +class PasswordLoginForbiddenException extends Exception { + +} diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index b02220976cf..2e105dd4a5d 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -77,6 +77,7 @@ class DefaultTokenMapper extends Mapper { ->execute(); $data = $result->fetch(); + $result->closeCursor(); if ($data === false) { throw new DoesNotExistException('token does not exist'); } diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 2467b8d836f..b9d06829572 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -166,6 +166,23 @@ class DefaultTokenProvider implements IProvider { } /** + * Encrypt and set the password of the given token + * + * @param IToken $token + * @param string $tokenId + * @param string $password + * @throws InvalidTokenException + */ + public function setPassword(IToken $token, $tokenId, $password) { + if (!($token instanceof DefaultToken)) { + throw new InvalidTokenException(); + } + /** @var DefaultToken $token */ + $token->setPassword($this->encryptPassword($password, $tokenId)); + $this->mapper->update($token); + } + + /** * Invalidate (delete) the given session token * * @param string $token diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index 97f8ababbbe..d4bbe158e0a 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -99,4 +99,14 @@ interface IProvider { * @return string */ public function getPassword(IToken $token, $tokenId); + + /** + * Encrypt and set the password of the given token + * + * @param IToken $token + * @param string $tokenId + * @param string $password + * @throws InvalidTokenException + */ + public function setPassword(IToken $token, $tokenId, $password); } diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index ec91064278e..8a9191a4c53 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -138,9 +138,10 @@ class Application { * @throws \Exception */ public function run(InputInterface $input = null, OutputInterface $output = null) { + $args = isset($this->request->server['argv']) ? $this->request->server['argv'] : []; $this->dispatcher->dispatch(ConsoleEvent::EVENT_RUN, new ConsoleEvent( ConsoleEvent::EVENT_RUN, - $this->request->server['argv'] + $args )); return $this->application->run($input, $output); } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index e9daa123470..31549c93cb2 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -998,7 +998,10 @@ class View { // Create the directories if any if (!$this->file_exists($filePath)) { - $this->mkdir($filePath); + $result = $this->createParentDirectories($filePath); + if($result === false) { + return false; + } } $source = fopen($tmpFile, 'r'); @@ -2107,4 +2110,22 @@ class View { } return [$uid, $filename]; } + + /** + * Creates parent non-existing folders + * + * @param string $filePath + * @return bool + */ + private function createParentDirectories($filePath) { + $parentDirectory = dirname($filePath); + while(!$this->file_exists($parentDirectory)) { + $result = $this->createParentDirectories($parentDirectory); + if($result === false) { + return false; + } + } + $this->mkdir($filePath); + return true; + } } diff --git a/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php b/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php index 1f31e849446..5c72bfaa57c 100644 --- a/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php +++ b/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php @@ -39,6 +39,7 @@ class ExcludeFileByNameFilterIterator extends \RecursiveFilterIterator { '.DS_Store', // Mac OS X 'Thumbs.db', // Microsoft Windows '.directory', // Dolphin (KDE) + '.webapp', // Gentoo/Funtoo & derivatives use a tool known as webapp-config to manager wep-apps. ]; /** diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 96731fa9dfd..2b33dc413b4 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -137,7 +137,7 @@ class Repair implements IOutput{ new SharePropagation(\OC::$server->getConfig()), new RemoveOldShares(\OC::$server->getDatabaseConnection()), new AvatarPermissions(\OC::$server->getDatabaseConnection()), - new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getRootFolder()), + new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()), ]; } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index aedb308539a..2b65f31af28 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -33,6 +33,7 @@ namespace OC\User; use OC; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC\Hooks\Emitter; @@ -333,17 +334,16 @@ class Session implements IUserSession, Emitter { * @param string $password * @param IRequest $request * @throws LoginException + * @throws PasswordLoginForbiddenException * @return boolean */ public function logClientIn($user, $password, IRequest $request) { $isTokenPassword = $this->isTokenPassword($password); if (!$isTokenPassword && $this->isTokenAuthEnforced()) { - // TODO: throw LoginException instead (https://github.com/owncloud/core/pull/24616) - return false; + throw new PasswordLoginForbiddenException(); } if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) { - // TODO: throw LoginException instead (https://github.com/owncloud/core/pull/24616) - return false; + throw new PasswordLoginForbiddenException(); } if (!$this->login($user, $password) ) { $users = $this->manager->getByEmail($user); @@ -428,19 +428,22 @@ class Session implements IUserSession, Emitter { */ public function tryBasicAuthLogin(IRequest $request) { if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { - $result = $this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request); - if ($result === true) { - /** - * Add DAV authenticated. This should in an ideal world not be - * necessary but the iOS App reads cookies from anywhere instead - * only the DAV endpoint. - * This makes sure that the cookies will be valid for the whole scope - * @see https://github.com/owncloud/core/issues/22893 - */ - $this->session->set( - Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() - ); - return true; + try { + if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request)) { + /** + * Add DAV authenticated. This should in an ideal world not be + * necessary but the iOS App reads cookies from anywhere instead + * only the DAV endpoint. + * This makes sure that the cookies will be valid for the whole scope + * @see https://github.com/owncloud/core/issues/22893 + */ + $this->session->set( + Auth::DAV_AUTHENTICATED, $this->getUser()->getUID() + ); + return true; + } + } catch (PasswordLoginForbiddenException $ex) { + // Nothing to do } } return false; @@ -714,4 +717,21 @@ class Session implements IUserSession, Emitter { setcookie('oc_remember_login', '', time() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); } + /** + * Update password of the browser session token if there is one + * + * @param string $password + */ + public function updateSessionTokenPassword($password) { + try { + $sessionId = $this->session->getId(); + $token = $this->tokenProvider->getToken($sessionId); + $this->tokenProvider->setPassword($token, $sessionId, $password); + } catch (SessionNotAvailableException $ex) { + // Nothing to do + } catch (InvalidTokenException $ex) { + // Nothing to do + } + } + } diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index 65d00c16388..78445dab020 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -962,11 +962,12 @@ class OC_Util { public static function checkLoggedIn() { // Check if we are a user if (!OC_User::isLoggedIn()) { - header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php', - [ - 'redirect_url' => \OC::$server->getRequest()->getRequestUri() - ] - ) + header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute( + 'core.login.showLoginForm', + [ + 'redirect_url' => urlencode(\OC::$server->getRequest()->getRequestUri()), + ] + ) ); exit(); } diff --git a/public.php b/public.php index 964ed03c1aa..b7125502ee8 100644 --- a/public.php +++ b/public.php @@ -35,9 +35,9 @@ try { exit; } - OC::checkMaintenanceMode(); - OC::checkSingleUserMode(true); $request = \OC::$server->getRequest(); + OC::checkMaintenanceMode($request); + OC::checkSingleUserMode(true); $pathInfo = $request->getPathInfo(); if (!$pathInfo && $request->getParam('service', '') === '') { diff --git a/settings/ChangePassword/Controller.php b/settings/ChangePassword/Controller.php index 5a6c985f181..1f3ea1b446a 100644 --- a/settings/ChangePassword/Controller.php +++ b/settings/ChangePassword/Controller.php @@ -46,6 +46,7 @@ class Controller { exit(); } if (!is_null($password) && \OC_User::setPassword($username, $password)) { + \OC::$server->getUserSession()->updateSessionTokenPassword($password); \OC_JSON::success(); } else { \OC_JSON::error(); diff --git a/settings/css/settings.css b/settings/css/settings.css index 5fc96343502..e4ddec9152a 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -101,33 +101,37 @@ table.nostyle label { margin-right: 2em; } table.nostyle td { padding: 0.2em 0; } #sessions table, -#devices table { +#apppasswords table { width: 100%; min-height: 150px; padding-top: 25px; } #sessions table th, -#devices table th { +#apppasswords table th { font-weight: 800; } #sessions table th, #sessions table td, -#devices table th, -#devices table td { +#apppasswords table th, +#apppasswords table td { padding: 10px; } #sessions .token-list td, -#devices .token-list td { +#apppasswords .token-list td { border-top: 1px solid #DDD; + text-overflow: ellipsis; + max-width: 200px; + white-space: nowrap; + overflow: hidden; } #sessions .token-list td a.icon-delete, -#devices .token-list td a.icon-delete { +#apppasswords .token-list td a.icon-delete { display: block; opacity: 0.6; } -#device-new-token { +#new-app-password { width: 186px; font-family: monospace; background-color: lightyellow; diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 664dfd28148..da5861689a0 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -27,9 +27,9 @@ var TEMPLATE_TOKEN = '<tr data-id="{{id}}">' - + '<td>{{name}}</td>' - + '<td><span class="last-activity" title="{{lastActivityTime}}">{{lastActivity}}</span></td>' - + '<td><a class="icon-delete" title="' + t('core', 'Disconnect') + '"></a></td>' + + '<td class="has-tooltip" title="{{name}}"><span class="token-name">{{name}}</span></td>' + + '<td><span class="last-activity has-tooltip" title="{{lastActivityTime}}">{{lastActivity}}</span></td>' + + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>' + '<tr>'; var SubView = Backbone.View.extend({ @@ -80,8 +80,7 @@ viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); var html = _this.template(viewData); var $html = $(html); - $html.find('.last-activity').tooltip(); - $html.find('.icon-delete').tooltip(); + $html.find('.has-tooltip').tooltip({container: 'body'}); list.append($html); }); }, @@ -104,13 +103,13 @@ _tokenName: undefined, - _addTokenBtn: undefined, + _addAppPasswordBtn: undefined, _result: undefined, - _newToken: undefined, + _newAppPassword: undefined, - _hideTokenBtn: undefined, + _hideAppPasswordBtn: undefined, _addingToken: false, @@ -120,7 +119,7 @@ var tokenTypes = [0, 1]; var _this = this; _.each(tokenTypes, function(type) { - var el = type === 0 ? '#sessions' : '#devices'; + var el = type === 0 ? '#sessions' : '#apppasswords'; _this._views.push(new SubView({ el: el, type: type, @@ -131,16 +130,16 @@ $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this)); }); - this._form = $('#device-token-form'); - this._tokenName = $('#device-token-name'); - this._addTokenBtn = $('#device-add-token'); - this._addTokenBtn.click(_.bind(this._addDeviceToken, this)); + this._form = $('#app-password-form'); + this._tokenName = $('#app-password-name'); + this._addAppPasswordBtn = $('#add-app-password'); + this._addAppPasswordBtn.click(_.bind(this._addAppPassword, this)); - this._result = $('#device-token-result'); - this._newToken = $('#device-new-token'); - this._newToken.on('focus', _.bind(this._onNewTokenFocus, this)); - this._hideTokenBtn = $('#device-token-hide'); - this._hideTokenBtn.click(_.bind(this._hideToken, this)); + this._result = $('#app-password-result'); + this._newAppPassword = $('#new-app-password'); + this._newAppPassword.on('focus', _.bind(this._onNewTokenFocus, this)); + this._hideAppPasswordBtn = $('#app-password-hide'); + this._hideAppPasswordBtn.click(_.bind(this._hideToken, this)); }, render: function() { @@ -167,7 +166,7 @@ }); }, - _addDeviceToken: function() { + _addAppPassword: function() { var _this = this; this._toggleAddingToken(true); @@ -182,9 +181,9 @@ $.when(creatingToken).done(function(resp) { _this.collection.add(resp.deviceToken); _this.render(); - _this._newToken.val(resp.token); + _this._newAppPassword.val(resp.token); _this._toggleFormResult(false); - _this._newToken.select(); + _this._newAppPassword.select(); _this._tokenName.val(''); }); $.when(creatingToken).fail(function() { @@ -196,7 +195,7 @@ }, _onNewTokenFocus: function() { - this._newToken.select(); + this._newAppPassword.select(); }, _hideToken: function() { @@ -205,7 +204,7 @@ _toggleAddingToken: function(state) { this._addingToken = state; - this._addTokenBtn.toggleClass('icon-loading-small', state); + this._addAppPasswordBtn.toggleClass('icon-loading-small', state); }, _onDeleteToken: function(event) { diff --git a/settings/js/personal.js b/settings/js/personal.js index aea2400e999..73d65034d9a 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -368,6 +368,17 @@ $(document).ready(function () { collection: collection }); view.reload(); + + // 'redirect' to anchor sections + // anchors are lost on redirects (e.g. while solving the 2fa challenge) otherwise + // example: /settings/person?section=devices will result in /settings/person?#devices + if (!window.location.hash) { + var query = OC.parseQueryString(location.search); + if (query && query.section) { + OC.Util.History.replaceState({}); + window.location.hash = query.section; + } + } }); if (!OC.Encryption) { diff --git a/settings/l10n/ca.js b/settings/l10n/ca.js index c569f412b80..c89bafed900 100644 --- a/settings/l10n/ca.js +++ b/settings/l10n/ca.js @@ -10,6 +10,7 @@ OC.L10N.register( "Unable to change password" : "No es pot canviar la contrasenya", "Enabled" : "Activat", "Not enabled" : "Desactivat", + "Federated Cloud Sharing" : "Compartició federada de núvol", "A problem occurred, please check your log files (Error: %s)" : "S'ha produït un problema, si us plau revisi els arxius de registre (Error: %s)", "Migration Completed" : "Migració completada", "Group already exists." : "El grup ja existeix.", diff --git a/settings/l10n/ca.json b/settings/l10n/ca.json index e8729fe8729..6956f339122 100644 --- a/settings/l10n/ca.json +++ b/settings/l10n/ca.json @@ -8,6 +8,7 @@ "Unable to change password" : "No es pot canviar la contrasenya", "Enabled" : "Activat", "Not enabled" : "Desactivat", + "Federated Cloud Sharing" : "Compartició federada de núvol", "A problem occurred, please check your log files (Error: %s)" : "S'ha produït un problema, si us plau revisi els arxius de registre (Error: %s)", "Migration Completed" : "Migració completada", "Group already exists." : "El grup ja existeix.", diff --git a/settings/l10n/lb.js b/settings/l10n/lb.js index 0668fddcf76..42376d82028 100644 --- a/settings/l10n/lb.js +++ b/settings/l10n/lb.js @@ -9,8 +9,10 @@ OC.L10N.register( "Email saved" : "E-mail gespäichert", "APCu" : "APCu", "Redis" : "Redis", + "Sharing" : "Gedeelt", "Cron" : "Cron", "Log" : "Log", + "Updates" : "Updates", "Language changed" : "Sprooch huet geännert", "Invalid request" : "Ongülteg Requête", "Admins can't remove themself from the admin group" : "Admins kennen sech selwer net aus enger Admin Group läschen.", @@ -23,9 +25,11 @@ OC.L10N.register( "undo" : "réckgängeg man", "never" : "ni", "__language_name__" : "__language_name__", + "None" : "Keng", "Login" : "Login", "Open documentation" : "Dokumentatioun opmaachen", "Allow apps to use the Share API" : "Erlab Apps d'Share API ze benotzen", + "days" : "Deeg", "Allow resharing" : "Resharing erlaben", "Authentication required" : "Authentifizéierung néideg", "Server address" : "Server Adress", @@ -41,13 +45,14 @@ OC.L10N.register( "Current password" : "Momentan 't Passwuert", "New password" : "Neit Passwuert", "Change password" : "Passwuert änneren", - "Name" : "Numm", "Language" : "Sprooch", "Help translate" : "Hëllef iwwersetzen", + "Name" : "Numm", "Desktop client" : "Desktop-Programm", "Android app" : "Android-App", "iOS app" : "iOS-App", "Username" : "Benotzernumm", + "E-Mail" : "E-Mail", "Create" : "Erstellen", "Group" : "Grupp", "Default Quota" : "Standard Quota", diff --git a/settings/l10n/lb.json b/settings/l10n/lb.json index 20500fbc909..d59f06cf834 100644 --- a/settings/l10n/lb.json +++ b/settings/l10n/lb.json @@ -7,8 +7,10 @@ "Email saved" : "E-mail gespäichert", "APCu" : "APCu", "Redis" : "Redis", + "Sharing" : "Gedeelt", "Cron" : "Cron", "Log" : "Log", + "Updates" : "Updates", "Language changed" : "Sprooch huet geännert", "Invalid request" : "Ongülteg Requête", "Admins can't remove themself from the admin group" : "Admins kennen sech selwer net aus enger Admin Group läschen.", @@ -21,9 +23,11 @@ "undo" : "réckgängeg man", "never" : "ni", "__language_name__" : "__language_name__", + "None" : "Keng", "Login" : "Login", "Open documentation" : "Dokumentatioun opmaachen", "Allow apps to use the Share API" : "Erlab Apps d'Share API ze benotzen", + "days" : "Deeg", "Allow resharing" : "Resharing erlaben", "Authentication required" : "Authentifizéierung néideg", "Server address" : "Server Adress", @@ -39,13 +43,14 @@ "Current password" : "Momentan 't Passwuert", "New password" : "Neit Passwuert", "Change password" : "Passwuert änneren", - "Name" : "Numm", "Language" : "Sprooch", "Help translate" : "Hëllef iwwersetzen", + "Name" : "Numm", "Desktop client" : "Desktop-Programm", "Android app" : "Android-App", "iOS app" : "iOS-App", "Username" : "Benotzernumm", + "E-Mail" : "E-Mail", "Create" : "Erstellen", "Group" : "Grupp", "Default Quota" : "Standard Quota", diff --git a/settings/l10n/pl.js b/settings/l10n/pl.js index 79adbb02948..7278c7aa73b 100644 --- a/settings/l10n/pl.js +++ b/settings/l10n/pl.js @@ -30,8 +30,10 @@ OC.L10N.register( "Unable to change full name" : "Nie można zmienić pełnej nazwy", "Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji", "Sharing" : "Udostępnianie", + "Server-side encryption" : "Szyfrowanie po stronie serwera", "External Storage" : "Zewnętrzna zasoby dyskowe", "Cron" : "Cron", + "Email server" : "Serwer pocztowy", "Log" : "Logi", "Updates" : "Aktualizacje", "Couldn't remove app." : "Nie można usunąć aplikacji.", @@ -43,6 +45,8 @@ OC.L10N.register( "Couldn't update app." : "Nie można uaktualnić aplikacji.", "Are you really sure you want add \"{domain}\" as trusted domain?" : "Czy jesteś pewien/pewna że chcesz dodać \"{domain}\" jako zaufaną domenę?", "Add trusted domain" : "Dodaj zaufaną domenę", + "Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.", + "Migration started …" : "Migracja rozpoczęta...", "Sending..." : "Wysyłam...", "All" : "Wszystkie", "No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji", @@ -58,8 +62,12 @@ OC.L10N.register( "Uninstalling ...." : "Odinstalowywanie....", "Error while uninstalling app" : "Błąd przy odinstalowywaniu aplikacji", "Uninstall" : "Odinstaluj", + "Error while loading browser sessions and device tokens" : "Błąd podczas ładowania sesji przeglądarek i tokenów urządzeń", + "Error while creating device token" : "Błąd podczas tworzenia tokena urządzenia.", + "Error while deleting the token" : "Błąd podczas usuwania tokena.", "Valid until {date}" : "Ważny do {date}", "Delete" : "Usuń", + "An error occurred: {message}" : "Wystąpił błąd: {message}", "Select a profile picture" : "Wybierz zdjęcie profilu", "Very weak password" : "Bardzo słabe hasło", "Weak password" : "Słabe hasło", @@ -68,6 +76,7 @@ OC.L10N.register( "Strong password" : "Mocne hasło", "Groups" : "Grupy", "Unable to delete {objName}" : "Nie można usunąć {objName}", + "Error creating group: {message}" : "Błąd podczas tworzenia grupy: {message}", "A valid group name must be provided" : "Należy podać prawidłową nazwę grupy", "deleted {groupName}" : "usunięto {groupName}", "undo" : "cofnij", @@ -77,10 +86,15 @@ OC.L10N.register( "add group" : "dodaj grupę", "Changing the password will result in data loss, because data recovery is not available for this user" : "Zmiana hasła spowoduje utratę danych, ponieważ odzyskiwanie danych nie jest włączone dla tego użytkownika", "A valid username must be provided" : "Należy podać prawidłową nazwę użytkownika", + "Error creating user: {message}" : "Błąd podczas tworzenia użytkownika: {message}", "A valid password must be provided" : "Należy podać prawidłowe hasło", "A valid email must be provided" : "Podaj poprawny adres email", "__language_name__" : "polski", "Unlimited" : "Bez limitu", + "Personal info" : "Informacje osobiste", + "Sessions" : "Sesje", + "Devices" : "Urządzenia", + "Sync clients" : "Klienty synchronizacji", "Everything (fatal issues, errors, warnings, info, debug)" : "Wszystko (Informacje, ostrzeżenia, błędy i poważne problemy, debug)", "Info, warnings, errors and fatal issues" : "Informacje, ostrzeżenia, błędy i poważne problemy", "Warnings, errors and fatal issues" : "Ostrzeżenia, błędy i poważne problemy", @@ -117,7 +131,11 @@ OC.L10N.register( "Execute one task with each page loaded" : "Wykonuj jedno zadanie wraz z każdą wczytaną stroną", "cron.php is registered at a webcron service to call cron.php every 15 minutes over http." : "cron.php jest zarejestrowany w serwisie webcron do uruchamiania cron.php raz na 15 minut przez http.", "Use system's cron service to call the cron.php file every 15 minutes." : "Użyj systemowej usługi cron do wywoływania cron.php co 15 minut.", + "Enable server-side encryption" : "Włącz szyfrowanie po stronie serwera", + "Be aware that encryption always increases the file size." : "Należy pamiętać, że szyfrowanie zawsze zwiększa rozmiar pliku.", + "This is the final warning: Do you really want to enable encryption?" : "To ostatnie ostrzeżenie: Czy na pewno chcesz włączyć szyfrowanie?", "Enable encryption" : "Włącz szyfrowanie", + "Select default encryption module:" : "Wybierz domyślny moduł szyfrujący:", "This is used for sending out notifications." : "To jest używane do wysyłania powiadomień", "Send mode" : "Tryb wysyłki", "Encryption" : "Szyfrowanie", diff --git a/settings/l10n/pl.json b/settings/l10n/pl.json index a4608e9cd26..cc7022746ca 100644 --- a/settings/l10n/pl.json +++ b/settings/l10n/pl.json @@ -28,8 +28,10 @@ "Unable to change full name" : "Nie można zmienić pełnej nazwy", "Security & setup warnings" : "Ostrzeżenia bezpieczeństwa i konfiguracji", "Sharing" : "Udostępnianie", + "Server-side encryption" : "Szyfrowanie po stronie serwera", "External Storage" : "Zewnętrzna zasoby dyskowe", "Cron" : "Cron", + "Email server" : "Serwer pocztowy", "Log" : "Logi", "Updates" : "Aktualizacje", "Couldn't remove app." : "Nie można usunąć aplikacji.", @@ -41,6 +43,8 @@ "Couldn't update app." : "Nie można uaktualnić aplikacji.", "Are you really sure you want add \"{domain}\" as trusted domain?" : "Czy jesteś pewien/pewna że chcesz dodać \"{domain}\" jako zaufaną domenę?", "Add trusted domain" : "Dodaj zaufaną domenę", + "Migration in progress. Please wait until the migration is finished" : "Trwa migracja. Proszę poczekać, aż migracja dobiegnie końca.", + "Migration started …" : "Migracja rozpoczęta...", "Sending..." : "Wysyłam...", "All" : "Wszystkie", "No apps found for your version" : "Nie znaleziono aplikacji dla twojej wersji", @@ -56,8 +60,12 @@ "Uninstalling ...." : "Odinstalowywanie....", "Error while uninstalling app" : "Błąd przy odinstalowywaniu aplikacji", "Uninstall" : "Odinstaluj", + "Error while loading browser sessions and device tokens" : "Błąd podczas ładowania sesji przeglądarek i tokenów urządzeń", + "Error while creating device token" : "Błąd podczas tworzenia tokena urządzenia.", + "Error while deleting the token" : "Błąd podczas usuwania tokena.", "Valid until {date}" : "Ważny do {date}", "Delete" : "Usuń", + "An error occurred: {message}" : "Wystąpił błąd: {message}", "Select a profile picture" : "Wybierz zdjęcie profilu", "Very weak password" : "Bardzo słabe hasło", "Weak password" : "Słabe hasło", @@ -66,6 +74,7 @@ "Strong password" : "Mocne hasło", "Groups" : "Grupy", "Unable to delete {objName}" : "Nie można usunąć {objName}", + "Error creating group: {message}" : "Błąd podczas tworzenia grupy: {message}", "A valid group name must be provided" : "Należy podać prawidłową nazwę grupy", "deleted {groupName}" : "usunięto {groupName}", "undo" : "cofnij", @@ -75,10 +84,15 @@ "add group" : "dodaj grupę", "Changing the password will result in data loss, because data recovery is not available for this user" : "Zmiana hasła spowoduje utratę danych, ponieważ odzyskiwanie danych nie jest włączone dla tego użytkownika", "A valid username must be provided" : "Należy podać prawidłową nazwę użytkownika", + "Error creating user: {message}" : "Błąd podczas tworzenia użytkownika: {message}", "A valid password must be provided" : "Należy podać prawidłowe hasło", "A valid email must be provided" : "Podaj poprawny adres email", "__language_name__" : "polski", "Unlimited" : "Bez limitu", + "Personal info" : "Informacje osobiste", + "Sessions" : "Sesje", + "Devices" : "Urządzenia", + "Sync clients" : "Klienty synchronizacji", "Everything (fatal issues, errors, warnings, info, debug)" : "Wszystko (Informacje, ostrzeżenia, błędy i poważne problemy, debug)", "Info, warnings, errors and fatal issues" : "Informacje, ostrzeżenia, błędy i poważne problemy", "Warnings, errors and fatal issues" : "Ostrzeżenia, błędy i poważne problemy", @@ -115,7 +129,11 @@ "Execute one task with each page loaded" : "Wykonuj jedno zadanie wraz z każdą wczytaną stroną", "cron.php is registered at a webcron service to call cron.php every 15 minutes over http." : "cron.php jest zarejestrowany w serwisie webcron do uruchamiania cron.php raz na 15 minut przez http.", "Use system's cron service to call the cron.php file every 15 minutes." : "Użyj systemowej usługi cron do wywoływania cron.php co 15 minut.", + "Enable server-side encryption" : "Włącz szyfrowanie po stronie serwera", + "Be aware that encryption always increases the file size." : "Należy pamiętać, że szyfrowanie zawsze zwiększa rozmiar pliku.", + "This is the final warning: Do you really want to enable encryption?" : "To ostatnie ostrzeżenie: Czy na pewno chcesz włączyć szyfrowanie?", "Enable encryption" : "Włącz szyfrowanie", + "Select default encryption module:" : "Wybierz domyślny moduł szyfrujący:", "This is used for sending out notifications." : "To jest używane do wysyłania powiadomień", "Send mode" : "Tryb wysyłki", "Encryption" : "Szyfrowanie", diff --git a/settings/l10n/sv.js b/settings/l10n/sv.js index aa5e3a8ef48..0218c95e74c 100644 --- a/settings/l10n/sv.js +++ b/settings/l10n/sv.js @@ -223,6 +223,8 @@ OC.L10N.register( "Documentation:" : "Dokumentation:", "User documentation" : "Användardokumentation", "Admin documentation" : "Administratörsdokumentation", + "Visit website" : "Besök webbsida", + "Report a bug" : "Rapportera ett problem", "Show description …" : "Visa beskrivning", "Hide description …" : "Dölj beskrivning", "This app has an update available." : "Denna applikation har en uppdatering tillgänglig.", @@ -267,14 +269,14 @@ OC.L10N.register( "Current password" : "Nuvarande lösenord", "New password" : "Nytt lösenord", "Change password" : "Ändra lösenord", + "Language" : "Språk", + "Help translate" : "Hjälp att översätta", "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dessa webbläsare,pc och mobila klienter är för tillfället inloggade på din ownCloud.", "Browser" : "Webbläsare", "Most recent activity" : "Senaste aktivitet", "You've linked these devices." : "Du har länkat dessa enheter.", "Name" : "Namn", "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ett enhetslösenord är ett lösenord som ger en app eller enhet tillåtelse att komma åt ditt ownCloud-konto.", - "Language" : "Språk", - "Help translate" : "Hjälp att översätta", "Get the apps to sync your files" : "Skaffa apparna för att synkronisera dina filer", "Desktop client" : "Skrivbordsklient", "Android app" : "Android-app", diff --git a/settings/l10n/sv.json b/settings/l10n/sv.json index 0cf69401b31..35785bfbf08 100644 --- a/settings/l10n/sv.json +++ b/settings/l10n/sv.json @@ -221,6 +221,8 @@ "Documentation:" : "Dokumentation:", "User documentation" : "Användardokumentation", "Admin documentation" : "Administratörsdokumentation", + "Visit website" : "Besök webbsida", + "Report a bug" : "Rapportera ett problem", "Show description …" : "Visa beskrivning", "Hide description …" : "Dölj beskrivning", "This app has an update available." : "Denna applikation har en uppdatering tillgänglig.", @@ -265,14 +267,14 @@ "Current password" : "Nuvarande lösenord", "New password" : "Nytt lösenord", "Change password" : "Ändra lösenord", + "Language" : "Språk", + "Help translate" : "Hjälp att översätta", "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "Dessa webbläsare,pc och mobila klienter är för tillfället inloggade på din ownCloud.", "Browser" : "Webbläsare", "Most recent activity" : "Senaste aktivitet", "You've linked these devices." : "Du har länkat dessa enheter.", "Name" : "Namn", "A device password is a passcode that gives an app or device permissions to access your ownCloud account." : "Ett enhetslösenord är ett lösenord som ger en app eller enhet tillåtelse att komma åt ditt ownCloud-konto.", - "Language" : "Språk", - "Help translate" : "Hjälp att översätta", "Get the apps to sync your files" : "Skaffa apparna för att synkronisera dina filer", "Desktop client" : "Skrivbordsklient", "Android app" : "Android-app", diff --git a/settings/personal.php b/settings/personal.php index 0b2781fb21b..e7a928f88bf 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -177,7 +177,7 @@ $tmpl->assign('groups', $groups2); $formsAndMore = []; $formsAndMore[]= ['anchor' => 'avatar', 'section-name' => $l->t('Personal info')]; $formsAndMore[]= ['anchor' => 'sessions', 'section-name' => $l->t('Sessions')]; -$formsAndMore[]= ['anchor' => 'devices', 'section-name' => $l->t('Devices')]; +$formsAndMore[]= ['anchor' => 'apppasswords', 'section-name' => $l->t('App passwords')]; $formsAndMore[]= ['anchor' => 'clientsbox', 'section-name' => $l->t('Sync clients')]; $forms=OC_App::getForms('personal'); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index ced76fc3bf6..b9b8429d943 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -139,6 +139,34 @@ if($_['passwordChangeSupported']) { } ?> +<form id="language" class="section"> + <h2> + <label for="languageinput"><?php p($l->t('Language'));?></label> + </h2> + <select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language'));?>"> + <option value="<?php p($_['activelanguage']['code']);?>"> + <?php p($_['activelanguage']['name']);?> + </option> + <?php foreach($_['commonlanguages'] as $language):?> + <option value="<?php p($language['code']);?>"> + <?php p($language['name']);?> + </option> + <?php endforeach;?> + <optgroup label="––––––––––"></optgroup> + <?php foreach($_['languages'] as $language):?> + <option value="<?php p($language['code']);?>"> + <?php p($language['name']);?> + </option> + <?php endforeach;?> + </select> + <?php if (OC_Util::getEditionString() === ''): ?> + <a href="https://www.transifex.com/projects/p/owncloud/" + target="_blank" rel="noreferrer"> + <em><?php p($l->t('Help translate'));?></em> + </a> + <?php endif; ?> +</form> + <div id="sessions" class="section"> <h2><?php p($l->t('Sessions'));?></h2> <span class="hidden-when-empty"><?php p($l->t('These are the web, desktop and mobile clients currently logged in to your ownCloud.'));?></span> @@ -155,9 +183,9 @@ if($_['passwordChangeSupported']) { </table> </div> -<div id="devices" class="section"> - <h2><?php p($l->t('Devices'));?></h2> - <span class="hidden-when-empty"><?php p($l->t("You've linked these devices."));?></span> +<div id="apppasswords" class="section"> + <h2><?php p($l->t('App passwords'));?></h2> + <span class="hidden-when-empty"><?php p($l->t("You've linked these apps."));?></span> <table> <thead class="hidden-when-empty"> <tr> @@ -169,45 +197,17 @@ if($_['passwordChangeSupported']) { <tbody class="token-list icon-loading"> </tbody> </table> - <p><?php p($l->t('A device password is a passcode that gives an app or device permissions to access your ownCloud account.'));?></p> - <div id="device-token-form"> - <input id="device-token-name" type="text" placeholder="Device name"> - <button id="device-add-token" class="button">Create new device password</button> + <p><?php p($l->t('An app password is a passcode that gives an app or device permissions to access your %s account.', [$theme->getName()]));?></p> + <div id="app-password-form"> + <input id="app-password-name" type="text" placeholder="<?php p($l->t('App name')); ?>"> + <button id="add-app-password" class="button"><?php p($l->t('Create new app password')); ?></button> </div> - <div id="device-token-result" class="hidden"> - <input id="device-new-token" type="text" readonly="readonly"/> - <button id="device-token-hide" class="button">Done</button> + <div id="app-password-result" class="hidden"> + <input id="new-app-password" type="text" readonly="readonly"/> + <button id="app-password-hide" class="button"><?php p($l->t('Done')); ?></button> </div> </div> -<form id="language" class="section"> - <h2> - <label for="languageinput"><?php p($l->t('Language'));?></label> - </h2> - <select id="languageinput" name="lang" data-placeholder="<?php p($l->t('Language'));?>"> - <option value="<?php p($_['activelanguage']['code']);?>"> - <?php p($_['activelanguage']['name']);?> - </option> - <?php foreach($_['commonlanguages'] as $language):?> - <option value="<?php p($language['code']);?>"> - <?php p($language['name']);?> - </option> - <?php endforeach;?> - <optgroup label="––––––––––"></optgroup> - <?php foreach($_['languages'] as $language):?> - <option value="<?php p($language['code']);?>"> - <?php p($language['name']);?> - </option> - <?php endforeach;?> - </select> - <?php if (OC_Util::getEditionString() === ''): ?> - <a href="https://www.transifex.com/projects/p/owncloud/" - target="_blank" rel="noreferrer"> - <em><?php p($l->t('Help translate'));?></em> - </a> - <?php endif; ?> -</form> - <div id="clientsbox" class="section clientsbox"> <h2><?php p($l->t('Get the apps to sync your files'));?></h2> <a href="<?php p($_['clients']['desktop']); ?>" rel="noreferrer" target="_blank"> diff --git a/tests/Core/Controller/OccControllerTest.php b/tests/Core/Controller/OccControllerTest.php new file mode 100644 index 00000000000..682d9170096 --- /dev/null +++ b/tests/Core/Controller/OccControllerTest.php @@ -0,0 +1,143 @@ +<?php +/** + * @author Victor Dubiniuk <dubiniuk@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Tests\Core\Controller; + +use OC\Console\Application; +use OC\Core\Controller\OccController; +use OCP\IConfig; +use Symfony\Component\Console\Output\Output; +use Test\TestCase; + +/** + * Class OccControllerTest + * + * @package OC\Core\Controller + */ +class OccControllerTest extends TestCase { + + const TEMP_SECRET = 'test'; + + /** @var \OC\AppFramework\Http\Request | \PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var \OC\Core\Controller\OccController | \PHPUnit_Framework_MockObject_MockObject */ + private $controller; + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; + /** @var Application | \PHPUnit_Framework_MockObject_MockObject */ + private $console; + + public function testFromInvalidLocation(){ + $this->getControllerMock('example.org'); + + $response = $this->controller->execute('status', ''); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('Web executor is not allowed to run from a different host', $responseData['details']); + } + + public function testNotWhiteListedCommand(){ + $this->getControllerMock('localhost'); + + $response = $this->controller->execute('missing_command', ''); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('Command "missing_command" is not allowed to run via web request', $responseData['details']); + } + + public function testWrongToken(){ + $this->getControllerMock('localhost'); + + $response = $this->controller->execute('status', self::TEMP_SECRET . '-'); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(126, $responseData['exitCode']); + + $this->assertArrayHasKey('details', $responseData); + $this->assertEquals('updater.secret does not match the provided token', $responseData['details']); + } + + public function testSuccess(){ + $this->getControllerMock('localhost'); + $this->console->expects($this->once())->method('run') + ->willReturnCallback( + function ($input, $output) { + /** @var Output $output */ + $output->writeln('{"installed":true,"version":"9.1.0.8","versionstring":"9.1.0 beta 2","edition":""}'); + return 0; + } + ); + + $response = $this->controller->execute('status', self::TEMP_SECRET, ['--output'=>'json']); + $responseData = $response->getData(); + + $this->assertArrayHasKey('exitCode', $responseData); + $this->assertEquals(0, $responseData['exitCode']); + + $this->assertArrayHasKey('response', $responseData); + $decoded = json_decode($responseData['response'], true); + + $this->assertArrayHasKey('installed', $decoded); + $this->assertEquals(true, $decoded['installed']); + } + + private function getControllerMock($host){ + $this->request = $this->getMockBuilder('OC\AppFramework\Http\Request') + ->setConstructorArgs([ + ['server' => []], + \OC::$server->getSecureRandom(), + \OC::$server->getConfig() + ]) + ->setMethods(['getRemoteAddress']) + ->getMock(); + + $this->request->expects($this->any())->method('getRemoteAddress') + ->will($this->returnValue($host)); + + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->config->expects($this->any())->method('getSystemValue') + ->with('updater.secret') + ->willReturn(password_hash(self::TEMP_SECRET, PASSWORD_DEFAULT)); + + $this->console = $this->getMockBuilder('\OC\Console\Application') + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = new OccController( + 'core', + $this->request, + $this->config, + $this->console + ); + } + +} diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php index a398dc2320c..54d2831d25f 100644 --- a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php @@ -164,6 +164,31 @@ class CORSMiddlewareTest extends \Test\TestCase { * @CORS * @expectedException \OC\AppFramework\Middleware\Security\Exceptions\SecurityException */ + public function testCORSShouldFailIfPasswordLoginIsForbidden() { + $request = new Request( + ['server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass' + ]], + $this->getMock('\OCP\Security\ISecureRandom'), + $this->getMock('\OCP\IConfig') + ); + $this->session->expects($this->once()) + ->method('logout'); + $this->session->expects($this->once()) + ->method('logClientIn') + ->with($this->equalTo('user'), $this->equalTo('pass')) + ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException)); + $this->reflector->reflect($this, __FUNCTION__); + $middleware = new CORSMiddleware($request, $this->reflector, $this->session); + + $middleware->beforeController($this, __FUNCTION__, new Response()); + } + + /** + * @CORS + * @expectedException \OC\AppFramework\Middleware\Security\Exceptions\SecurityException + */ public function testCORSShouldNotAllowCookieAuth() { $request = new Request( ['server' => [ diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index d0bb38e6bb1..28a59529dec 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -186,6 +186,39 @@ class DefaultTokenProviderTest extends TestCase { $tokenProvider->getPassword($tk, $token); } + public function testSetPassword() { + $token = new DefaultToken(); + $tokenId = 'token123'; + $password = '123456'; + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with('secret') + ->will($this->returnValue('ocsecret')); + $this->crypto->expects($this->once()) + ->method('encrypt') + ->with($password, $tokenId . 'ocsecret') + ->will($this->returnValue('encryptedpassword')); + $this->mapper->expects($this->once()) + ->method('update') + ->with($token); + + $this->tokenProvider->setPassword($token, $tokenId, $password); + + $this->assertEquals('encryptedpassword', $token->getPassword()); + } + + /** + * @expectedException \OC\Authentication\Exceptions\InvalidTokenException + */ + public function testSetPasswordInvalidToken() { + $token = $this->getMock('\OC\Authentication\Token\IToken'); + $tokenId = 'token123'; + $password = '123456'; + + $this->tokenProvider->setPassword($token, $tokenId, $password); + } + public function testInvalidateToken() { $this->mapper->expects($this->once()) ->method('invalidate') diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 6b72cf81bc9..eef4c7ff5ea 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -314,6 +314,9 @@ class SessionTest extends \Test\TestCase { $userSession->login('foo', 'bar'); } + /** + * @expectedException \OC\Authentication\Exceptions\PasswordLoginForbiddenException + */ public function testLogClientInNoTokenPasswordWith2fa() { $manager = $this->getMockBuilder('\OC\User\Manager') ->disableOriginalConstructor() @@ -337,7 +340,7 @@ class SessionTest extends \Test\TestCase { ->with('token_auth_enforced', false) ->will($this->returnValue(true)); - $this->assertFalse($userSession->logClientIn('john', 'doe', $request)); + $userSession->logClientIn('john', 'doe', $request); } public function testLogClientInWithTokenPassword() { @@ -369,6 +372,9 @@ class SessionTest extends \Test\TestCase { $this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request)); } + /** + * @expectedException \OC\Authentication\Exceptions\PasswordLoginForbiddenException + */ public function testLogClientInNoTokenPasswordNo2fa() { $manager = $this->getMockBuilder('\OC\User\Manager') ->disableOriginalConstructor() @@ -397,7 +403,7 @@ class SessionTest extends \Test\TestCase { ->with('john') ->will($this->returnValue(true)); - $this->assertFalse($userSession->logClientIn('john', 'doe', $request)); + $userSession->logClientIn('john', 'doe', $request); } public function testRememberLoginValidToken() { @@ -813,4 +819,69 @@ class SessionTest extends \Test\TestCase { $this->assertEquals(1000, $token->getLastCheck()); } + public function testUpdateSessionTokenPassword() { + $userManager = $this->getMock('\OCP\IUserManager'); + $session = $this->getMock('\OCP\ISession'); + $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory'); + $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider'); + $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config); + + $password = '123456'; + $sessionId ='session1234'; + $token = new \OC\Authentication\Token\DefaultToken(); + + $session->expects($this->once()) + ->method('getId') + ->will($this->returnValue($sessionId)); + $tokenProvider->expects($this->once()) + ->method('getToken') + ->with($sessionId) + ->will($this->returnValue($token)); + $tokenProvider->expects($this->once()) + ->method('setPassword') + ->with($token, $sessionId, $password); + + $userSession->updateSessionTokenPassword($password); + } + + public function testUpdateSessionTokenPasswordNoSessionAvailable() { + $userManager = $this->getMock('\OCP\IUserManager'); + $session = $this->getMock('\OCP\ISession'); + $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory'); + $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider'); + $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config); + + $session->expects($this->once()) + ->method('getId') + ->will($this->throwException(new \OCP\Session\Exceptions\SessionNotAvailableException())); + + $userSession->updateSessionTokenPassword('1234'); + } + + public function testUpdateSessionTokenPasswordInvalidTokenException() { + $userManager = $this->getMock('\OCP\IUserManager'); + $session = $this->getMock('\OCP\ISession'); + $timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory'); + $tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider'); + $userSession = new \OC\User\Session($userManager, $session, $timeFactory, $tokenProvider, $this->config); + + $password = '123456'; + $sessionId ='session1234'; + $token = new \OC\Authentication\Token\DefaultToken(); + + $session->expects($this->once()) + ->method('getId') + ->will($this->returnValue($sessionId)); + $tokenProvider->expects($this->once()) + ->method('getToken') + ->with($sessionId) + ->will($this->returnValue($token)); + $tokenProvider->expects($this->once()) + ->method('setPassword') + ->with($token, $sessionId, $password) + ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException())); + + $userSession->updateSessionTokenPassword($password); + } + } |