aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/comments/l10n/lb.js3
-rw-r--r--apps/comments/l10n/lb.json3
-rw-r--r--apps/dav/appinfo/v1/carddav.php1
-rw-r--r--apps/dav/lib/AppInfo/Application.php3
-rw-r--r--apps/dav/lib/CardDAV/AddressBookImpl.php44
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php8
-rw-r--r--apps/dav/lib/CardDAV/ContactsManager.php14
-rw-r--r--apps/dav/lib/CardDAV/ImageExportPlugin.php146
-rw-r--r--apps/dav/lib/Server.php2
-rw-r--r--apps/dav/tests/unit/CardDAV/AddressBookImplTest.php37
-rw-r--r--apps/dav/tests/unit/CardDAV/CardDavBackendTest.php14
-rw-r--r--apps/dav/tests/unit/CardDAV/ContactsManagerTest.php3
-rw-r--r--apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php151
-rw-r--r--apps/encryption/l10n/sv.js6
-rw-r--r--apps/encryption/l10n/sv.json6
-rw-r--r--apps/federatedfilesharing/lib/DiscoveryManager.php5
-rw-r--r--apps/federatedfilesharing/lib/Notifications.php4
-rw-r--r--apps/federatedfilesharing/tests/DiscoveryManagerTest.php25
-rw-r--r--apps/files/l10n/lb.js2
-rw-r--r--apps/files/l10n/lb.json2
-rw-r--r--apps/files/l10n/sv.js11
-rw-r--r--apps/files/l10n/sv.json11
-rw-r--r--apps/files_external/l10n/lb.js1
-rw-r--r--apps/files_external/l10n/lb.json1
-rw-r--r--apps/files_external/l10n/sv.js1
-rw-r--r--apps/files_external/l10n/sv.json1
-rwxr-xr-xapps/files_external/tests/env/start-swift-ceph.sh5
-rw-r--r--apps/files_sharing/ajax/external.php5
-rw-r--r--apps/files_sharing/l10n/lb.js1
-rw-r--r--apps/files_sharing/l10n/lb.json1
-rw-r--r--apps/files_sharing/lib/External/Storage.php11
-rw-r--r--apps/updatenotification/l10n/lb.js15
-rw-r--r--apps/updatenotification/l10n/lb.json13
-rw-r--r--apps/user_ldap/l10n/lb.js39
-rw-r--r--apps/user_ldap/l10n/lb.json39
-rw-r--r--apps/user_ldap/lib/Access.php9
-rwxr-xr-xautotest.sh8
-rw-r--r--core/Application.php13
-rw-r--r--core/Controller/OccController.php147
-rw-r--r--core/l10n/lb.js1
-rw-r--r--core/l10n/lb.json1
-rw-r--r--core/l10n/sv.js1
-rw-r--r--core/l10n/sv.json1
-rw-r--r--core/routes.php1
-rw-r--r--db_structure.xml9
-rw-r--r--lib/base.php19
-rw-r--r--lib/private/Authentication/Token/DefaultToken.php23
-rw-r--r--lib/private/Authentication/Token/DefaultTokenMapper.php4
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php55
-rw-r--r--lib/private/Authentication/Token/IProvider.php26
-rw-r--r--lib/private/Authentication/Token/IToken.php14
-rw-r--r--lib/private/Console/Application.php3
-rw-r--r--lib/private/Files/View.php23
-rw-r--r--lib/private/User/Session.php192
-rw-r--r--lib/private/legacy/util.php11
-rw-r--r--public.php4
-rw-r--r--settings/ChangePassword/Controller.php1
-rw-r--r--settings/css/settings.css14
-rw-r--r--settings/js/authtoken_view.js36
-rw-r--r--settings/js/personal.js11
-rw-r--r--settings/l10n/lb.js7
-rw-r--r--settings/l10n/lb.json7
-rw-r--r--settings/l10n/sv.js6
-rw-r--r--settings/l10n/sv.json6
-rw-r--r--settings/personal.php2
-rw-r--r--settings/templates/personal.php20
-rw-r--r--tests/Core/Controller/OccControllerTest.php143
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenMapperTest.php4
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenProviderTest.php72
-rw-r--r--tests/lib/User/SessionTest.php226
-rw-r--r--version.php2
71 files changed, 1438 insertions, 318 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/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/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/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/lib/DiscoveryManager.php b/apps/federatedfilesharing/lib/DiscoveryManager.php
index 2a4bb4b7f77..25af0a40fd5 100644
--- a/apps/federatedfilesharing/lib/DiscoveryManager.php
+++ b/apps/federatedfilesharing/lib/DiscoveryManager.php
@@ -84,7 +84,10 @@ class DiscoveryManager {
// Read the data from the response body
try {
- $response = $this->client->get($remote . '/ocs-provider/');
+ $response = $this->client->get($remote . '/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ]);
if($response->getStatusCode() === 200) {
$decodedService = json_decode($response->getBody(), true);
if(is_array($decodedService)) {
diff --git a/apps/federatedfilesharing/lib/Notifications.php b/apps/federatedfilesharing/lib/Notifications.php
index 18212b82c3e..fefa959ba37 100644
--- a/apps/federatedfilesharing/lib/Notifications.php
+++ b/apps/federatedfilesharing/lib/Notifications.php
@@ -287,7 +287,9 @@ class Notifications {
$endpoint = $this->discoveryManager->getShareEndpoint($protocol . $remoteDomain);
try {
$response = $client->post($protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, [
- 'body' => $fields
+ 'body' => $fields,
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
]);
$result['result'] = $response->getBody();
$result['success'] = true;
diff --git a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php b/apps/federatedfilesharing/tests/DiscoveryManagerTest.php
index 73f79b2c169..a9c324f0244 100644
--- a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php
+++ b/apps/federatedfilesharing/tests/DiscoveryManagerTest.php
@@ -77,7 +77,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
$this->client
->expects($this->once())
->method('get')
- ->with('https://myhost.com/ocs-provider/', [])
+ ->with('https://myhost.com/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])
->willReturn($response);
$this->cache
->expects($this->at(0))
@@ -111,7 +114,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
$this->client
->expects($this->once())
->method('get')
- ->with('https://myhost.com/ocs-provider/', [])
+ ->with('https://myhost.com/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])
->willReturn($response);
$expectedResult = '/public.php/MyCustomEndpoint/';
@@ -131,7 +137,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
$this->client
->expects($this->once())
->method('get')
- ->with('https://myhost.com/ocs-provider/', [])
+ ->with('https://myhost.com/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])
->willReturn($response);
$expectedResult = '/public.php/webdav';
@@ -151,7 +160,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
$this->client
->expects($this->once())
->method('get')
- ->with('https://myhost.com/ocs-provider/', [])
+ ->with('https://myhost.com/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])
->willReturn($response);
$expectedResult = '/ocs/v2.php/cloud/MyCustomShareEndpoint';
@@ -171,7 +183,10 @@ class DiscoveryManagerTest extends \Test\TestCase {
$this->client
->expects($this->once())
->method('get')
- ->with('https://myhost.com/ocs-provider/', [])
+ ->with('https://myhost.com/ocs-provider/', [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])
->willReturn($response);
$this->cache
->expects($this->at(0))
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/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/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/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/ajax/external.php b/apps/files_sharing/ajax/external.php
index 5cf86087f94..4a7a6096c91 100644
--- a/apps/files_sharing/ajax/external.php
+++ b/apps/files_sharing/ajax/external.php
@@ -77,7 +77,10 @@ $externalManager = new \OCA\Files_Sharing\External\Manager(
// check for ssl cert
if (substr($remote, 0, 5) === 'https') {
try {
- \OC::$server->getHTTPClientService()->newClient()->get($remote)->getBody();
+ \OC::$server->getHTTPClientService()->newClient()->get($remote, [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])->getBody();
} catch (\Exception $e) {
\OCP\JSON::error(array('data' => array('message' => $l->t('Invalid or untrusted SSL certificate'))));
exit;
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/External/Storage.php b/apps/files_sharing/lib/External/Storage.php
index ca99393a1e0..29b9c7b563c 100644
--- a/apps/files_sharing/lib/External/Storage.php
+++ b/apps/files_sharing/lib/External/Storage.php
@@ -254,7 +254,10 @@ class Storage extends DAV implements ISharedStorage {
$client = $this->httpClient->newClient();
try {
- $result = $client->get($url)->getBody();
+ $result = $client->get($url, [
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ])->getBody();
$data = json_decode($result);
$returnValue = (is_object($data) && !empty($data->version));
} catch (ConnectException $e) {
@@ -301,7 +304,11 @@ class Storage extends DAV implements ISharedStorage {
// TODO: DI
$client = \OC::$server->getHTTPClientService()->newClient();
try {
- $response = $client->post($url, ['body' => ['password' => $password]]);
+ $response = $client->post($url, [
+ 'body' => ['password' => $password],
+ 'timeout' => 10,
+ 'connect_timeout' => 10,
+ ]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
if ($e->getCode() === 401 || $e->getCode() === 403) {
throw new ForbiddenException();
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/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/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/db_structure.xml b/db_structure.xml
index b7dacc05d92..6b91c3c4c5d 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -1120,6 +1120,15 @@
<length>4</length>
</field>
+ <field>
+ <name>last_check</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+
<index>
<name>authtoken_token_index</name>
<unique>true</unique>
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/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php
index 299291e34af..79b03eed27f 100644
--- a/lib/private/Authentication/Token/DefaultToken.php
+++ b/lib/private/Authentication/Token/DefaultToken.php
@@ -74,6 +74,11 @@ class DefaultToken extends Entity implements IToken {
*/
protected $lastActivity;
+ /**
+ * @var int
+ */
+ protected $lastCheck;
+
public function getId() {
return $this->id;
}
@@ -109,4 +114,22 @@ class DefaultToken extends Entity implements IToken {
];
}
+ /**
+ * Get the timestamp of the last password check
+ *
+ * @return int
+ */
+ public function getLastCheck() {
+ return parent::getLastCheck();
+ }
+
+ /**
+ * Get the timestamp of the last password check
+ *
+ * @param int $time
+ */
+ public function setLastCheck($time) {
+ return parent::setLastCheck($time);
+ }
+
}
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
index 9450ed6b9f3..2e105dd4a5d 100644
--- a/lib/private/Authentication/Token/DefaultTokenMapper.php
+++ b/lib/private/Authentication/Token/DefaultTokenMapper.php
@@ -70,7 +70,7 @@ class DefaultTokenMapper extends Mapper {
public function getToken($token) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
- $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity')
+ $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
->from('authtoken')
->where($qb->expr()->eq('token', $qb->createParameter('token')))
->setParameter('token', $token)
@@ -96,7 +96,7 @@ class DefaultTokenMapper extends Mapper {
public function getTokenByUser(IUser $user) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
- $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity')
+ $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check')
->from('authtoken')
->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID())))
->setMaxResults(1000);
diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
index 84effc5f875..b9d06829572 100644
--- a/lib/private/Authentication/Token/DefaultTokenProvider.php
+++ b/lib/private/Authentication/Token/DefaultTokenProvider.php
@@ -92,19 +92,34 @@ class DefaultTokenProvider implements IProvider {
}
/**
+ * Save the updated token
+ *
+ * @param IToken $token
+ */
+ public function updateToken(IToken $token) {
+ if (!($token instanceof DefaultToken)) {
+ throw new InvalidTokenException();
+ }
+ $this->mapper->update($token);
+ }
+
+ /**
* Update token activity timestamp
*
* @throws InvalidTokenException
* @param IToken $token
*/
- public function updateToken(IToken $token) {
+ public function updateTokenActivity(IToken $token) {
if (!($token instanceof DefaultToken)) {
throw new InvalidTokenException();
}
/** @var DefaultToken $token */
- $token->setLastActivity($this->time->getTime());
-
- $this->mapper->update($token);
+ $now = $this->time->getTime();
+ if ($token->getLastActivity() < ($now - 60)) {
+ // Update token only once per minute
+ $token->setLastActivity($now);
+ $this->mapper->update($token);
+ }
}
/**
@@ -151,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
@@ -180,21 +212,6 @@ class DefaultTokenProvider implements IProvider {
/**
* @param string $token
- * @throws InvalidTokenException
- * @return DefaultToken user UID
- */
- public function validateToken($token) {
- try {
- $dbToken = $this->mapper->getToken($this->hashToken($token));
- $this->logger->debug('valid default token for ' . $dbToken->getUID());
- return $dbToken;
- } catch (DoesNotExistException $ex) {
- throw new InvalidTokenException();
- }
- }
-
- /**
- * @param string $token
* @return string
*/
private function hashToken($token) {
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index fece7dcb567..d4bbe158e0a 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -50,13 +50,6 @@ interface IProvider {
public function getToken($tokenId) ;
/**
- * @param string $token
- * @throws InvalidTokenException
- * @return IToken
- */
- public function validateToken($token);
-
- /**
* Invalidate (delete) the given session token
*
* @param string $token
@@ -72,13 +65,20 @@ interface IProvider {
public function invalidateTokenById(IUser $user, $id);
/**
- * Update token activity timestamp
+ * Save the updated token
*
* @param IToken $token
*/
public function updateToken(IToken $token);
/**
+ * Update token activity timestamp
+ *
+ * @param IToken $token
+ */
+ public function updateTokenActivity(IToken $token);
+
+ /**
* Get all token of a user
*
* The provider may limit the number of result rows in case of an abuse
@@ -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/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php
index a34bdc2c43d..096550fd091 100644
--- a/lib/private/Authentication/Token/IToken.php
+++ b/lib/private/Authentication/Token/IToken.php
@@ -55,4 +55,18 @@ interface IToken extends JsonSerializable {
* @return string
*/
public function getPassword();
+
+ /**
+ * Get the timestamp of the last password check
+ *
+ * @return int
+ */
+ public function getLastCheck();
+
+ /**
+ * Get the timestamp of the last password check
+ *
+ * @param int $time
+ */
+ public function setLastCheck($time);
}
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/User/Session.php b/lib/private/User/Session.php
index 4e9c827448d..2b65f31af28 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -193,53 +193,35 @@ class Session implements IUserSession, Emitter {
if (is_null($this->activeUser)) {
return null;
}
- $this->validateSession($this->activeUser);
+ $this->validateSession();
}
return $this->activeUser;
}
- protected function validateSession(IUser $user) {
- try {
- $sessionId = $this->session->getId();
- } catch (SessionNotAvailableException $ex) {
- return;
- }
- try {
- $token = $this->tokenProvider->getToken($sessionId);
- } catch (InvalidTokenException $ex) {
- // Session was invalidated
- $this->logout();
- return;
- }
+ /**
+ * Validate whether the current session is valid
+ *
+ * - For token-authenticated clients, the token validity is checked
+ * - For browsers, the session token validity is checked
+ */
+ protected function validateSession() {
+ $token = null;
+ $appPassword = $this->session->get('app_password');
- // Check whether login credentials are still valid and the user was not disabled
- // This check is performed each 5 minutes
- $lastCheck = $this->session->get('last_login_check') ? : 0;
- $now = $this->timeFacory->getTime();
- if ($lastCheck < ($now - 60 * 5)) {
+ if (is_null($appPassword)) {
try {
- $pwd = $this->tokenProvider->getPassword($token, $sessionId);
- } catch (InvalidTokenException $ex) {
- // An invalid token password was used -> log user out
- $this->logout();
- return;
- } catch (PasswordlessTokenException $ex) {
- // Token has no password, nothing to check
- $this->session->set('last_login_check', $now);
- return;
- }
-
- if ($this->manager->checkPassword($token->getLoginName(), $pwd) === false
- || !$user->isEnabled()) {
- // Password has changed or user was disabled -> log user out
- $this->logout();
+ $token = $this->session->getId();
+ } catch (SessionNotAvailableException $ex) {
return;
}
- $this->session->set('last_login_check', $now);
+ } else {
+ $token = $appPassword;
}
- // Session is valid, so the token can be refreshed
- $this->updateToken($token);
+ if (!$this->validateToken($token)) {
+ // Session was invalidated
+ $this->logout();
+ }
}
/**
@@ -299,20 +281,21 @@ class Session implements IUserSession, Emitter {
public function login($uid, $password) {
$this->session->regenerateId();
if ($this->validateToken($password)) {
- $user = $this->getUser();
-
// When logging in with token, the password must be decrypted first before passing to login hook
try {
$token = $this->tokenProvider->getToken($password);
try {
- $password = $this->tokenProvider->getPassword($token, $password);
- $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
+ $loginPassword = $this->tokenProvider->getPassword($token, $password);
+ $this->manager->emit('\OC\User', 'preLogin', array($uid, $loginPassword));
} catch (PasswordlessTokenException $ex) {
$this->manager->emit('\OC\User', 'preLogin', array($uid, ''));
}
} catch (InvalidTokenException $ex) {
// Invalid token, nothing to do
}
+
+ $this->loginWithToken($password);
+ $user = $this->getUser();
} else {
$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
$user = $this->manager->checkPassword($uid, $password);
@@ -370,7 +353,10 @@ class Session implements IUserSession, Emitter {
return false;
}
- if ($this->supportsCookies($request)) {
+ if ($isTokenPassword) {
+ $this->session->set('app_password', $password);
+ } else if($this->supportsCookies($request)) {
+ // Password login, but cookies supported -> create (browser) session token
$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
}
@@ -463,8 +449,22 @@ class Session implements IUserSession, Emitter {
return false;
}
- private function loginWithToken($uid) {
- // TODO: $this->manager->emit('\OC\User', 'preTokenLogin', array($uid));
+ private function loginWithToken($token) {
+ try {
+ $dbToken = $this->tokenProvider->getToken($token);
+ } catch (InvalidTokenException $ex) {
+ return false;
+ }
+ $uid = $dbToken->getUID();
+
+ $password = '';
+ try {
+ $password = $this->tokenProvider->getPassword($dbToken, $token);
+ } catch (PasswordlessTokenException $ex) {
+ // Ignore and use empty string instead
+ }
+ $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
+
$user = $this->manager->get($uid);
if (is_null($user)) {
// user does not exist
@@ -477,7 +477,8 @@ class Session implements IUserSession, Emitter {
//login
$this->setUser($user);
- // TODO: $this->manager->emit('\OC\User', 'postTokenLogin', array($user));
+
+ $this->manager->emit('\OC\User', 'postLogin', array($user, $password));
return true;
}
@@ -534,37 +535,71 @@ class Session implements IUserSession, Emitter {
}
/**
+ * @param IToken $dbToken
* @param string $token
* @return boolean
*/
- private function validateToken($token) {
+ private function checkTokenCredentials(IToken $dbToken, $token) {
+ // Check whether login credentials are still valid and the user was not disabled
+ // This check is performed each 5 minutes
+ $lastCheck = $dbToken->getLastCheck() ? : 0;
+ $now = $this->timeFacory->getTime();
+ if ($lastCheck > ($now - 60 * 5)) {
+ // Checked performed recently, nothing to do now
+ return true;
+ }
+
try {
- $token = $this->tokenProvider->validateToken($token);
- if (!is_null($token)) {
- $result = $this->loginWithToken($token->getUID());
- if ($result) {
- // Login success
- $this->updateToken($token);
- return true;
- }
- }
+ $pwd = $this->tokenProvider->getPassword($dbToken, $token);
} catch (InvalidTokenException $ex) {
+ // An invalid token password was used -> log user out
+ return false;
+ } catch (PasswordlessTokenException $ex) {
+ // Token has no password
+
+ if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
+ $this->tokenProvider->invalidateToken($token);
+ return false;
+ }
+ $dbToken->setLastCheck($now);
+ $this->tokenProvider->updateToken($dbToken);
+ return true;
}
- return false;
+
+ if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
+ || (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
+ $this->tokenProvider->invalidateToken($token);
+ // Password has changed or user was disabled -> log user out
+ return false;
+ }
+ $dbToken->setLastCheck($now);
+ $this->tokenProvider->updateToken($dbToken);
+ return true;
}
/**
- * @param IToken $token
+ * Check if the given token exists and performs password/user-enabled checks
+ *
+ * Invalidates the token if checks fail
+ *
+ * @param string $token
+ * @return boolean
*/
- private function updateToken(IToken $token) {
- // To save unnecessary DB queries, this is only done once a minute
- $lastTokenUpdate = $this->session->get('last_token_update') ? : 0;
- $now = $this->timeFacory->getTime();
- if ($lastTokenUpdate < ($now - 60)) {
- $this->tokenProvider->updateToken($token);
- $this->session->set('last_token_update', $now);
+ private function validateToken($token) {
+ try {
+ $dbToken = $this->tokenProvider->getToken($token);
+ } catch (InvalidTokenException $ex) {
+ return false;
+ }
+
+ if (!$this->checkTokenCredentials($dbToken, $token)) {
+ return false;
}
+
+ $this->tokenProvider->updateTokenActivity($dbToken);
+
+ return true;
}
/**
@@ -578,15 +613,21 @@ class Session implements IUserSession, Emitter {
if (strpos($authHeader, 'token ') === false) {
// No auth header, let's try session id
try {
- $sessionId = $this->session->getId();
- return $this->validateToken($sessionId);
+ $token = $this->session->getId();
} catch (SessionNotAvailableException $ex) {
return false;
}
} else {
$token = substr($authHeader, 6);
- return $this->validateToken($token);
}
+
+ if (!$this->loginWithToken($token)) {
+ return false;
+ }
+ if(!$this->validateToken($token)) {
+ return false;
+ }
+ return true;
}
/**
@@ -676,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 04538558cae..e4ddec9152a 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -101,24 +101,24 @@ 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;
@@ -126,12 +126,12 @@ table.nostyle td { padding: 0.2em 0; }
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 b1906f0b338..da5861689a0 100644
--- a/settings/js/authtoken_view.js
+++ b/settings/js/authtoken_view.js
@@ -103,13 +103,13 @@
_tokenName: undefined,
- _addTokenBtn: undefined,
+ _addAppPasswordBtn: undefined,
_result: undefined,
- _newToken: undefined,
+ _newAppPassword: undefined,
- _hideTokenBtn: undefined,
+ _hideAppPasswordBtn: undefined,
_addingToken: false,
@@ -119,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,
@@ -130,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() {
@@ -166,7 +166,7 @@
});
},
- _addDeviceToken: function() {
+ _addAppPassword: function() {
var _this = this;
this._toggleAddingToken(true);
@@ -181,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() {
@@ -195,7 +195,7 @@
},
_onNewTokenFocus: function() {
- this._newToken.select();
+ this._newAppPassword.select();
},
_hideToken: function() {
@@ -204,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/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/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 942944ffab7..b9b8429d943 100644
--- a/settings/templates/personal.php
+++ b/settings/templates/personal.php
@@ -183,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>
@@ -197,14 +197,14 @@ 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>
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/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
index 5d49f75aaa4..6b73cab5ed0 100644
--- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
+++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
@@ -63,6 +63,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
])->execute();
$qb->insert('authtoken')->values([
'uid' => $qb->createNamedParameter('user2'),
@@ -72,6 +73,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 60 * 60 * 24 * 3, IQueryBuilder::PARAM_INT), // Three days ago
+ 'last_check' => $this->time - 10, // 10secs ago
])->execute();
$qb->insert('authtoken')->values([
'uid' => $qb->createNamedParameter('user1'),
@@ -81,6 +83,7 @@ class DefaultTokenMapperTest extends TestCase {
'token' => $qb->createNamedParameter('47af8697ba590fb82579b5f1b3b6e8066773a62100abbe0db09a289a62f5d980dc300fa3d98b01d7228468d1ab05c1aa14c8d14bd5b6eee9cdf1ac14864680c3'),
'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+ 'last_check' => $this->time - 60 * 10, // 10mins ago
])->execute();
}
@@ -127,6 +130,7 @@ class DefaultTokenMapperTest extends TestCase {
$token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b');
$token->setType(IToken::TEMPORARY_TOKEN);
$token->setLastActivity($this->time - 60 * 60 * 24 * 3);
+ $token->setLastCheck($this->time - 10);
$dbToken = $this->mapper->getToken($token->getToken());
diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
index 98cee208065..28a59529dec 100644
--- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
+++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
@@ -97,14 +97,25 @@ class DefaultTokenProviderTest extends TestCase {
public function testUpdateToken() {
$tk = new DefaultToken();
+ $tk->setLastActivity($this->time - 200);
$this->mapper->expects($this->once())
->method('update')
->with($tk);
- $this->tokenProvider->updateToken($tk);
+ $this->tokenProvider->updateTokenActivity($tk);
$this->assertEquals($this->time, $tk->getLastActivity());
}
+
+ public function testUpdateTokenDebounce() {
+ $tk = new DefaultToken();
+ $tk->setLastActivity($this->time - 30);
+ $this->mapper->expects($this->never())
+ ->method('update')
+ ->with($tk);
+
+ $this->tokenProvider->updateTokenActivity($tk);
+ }
public function testGetTokenByUser() {
$user = $this->getMock('\OCP\IUser');
@@ -175,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')
@@ -207,30 +251,4 @@ class DefaultTokenProviderTest extends TestCase {
$this->tokenProvider->invalidateOldTokens();
}
- public function testValidateToken() {
- $token = 'sometoken';
- $dbToken = new DefaultToken();
- $this->mapper->expects($this->once())
- ->method('getToken')
- ->with(hash('sha512', $token))
- ->will($this->returnValue($dbToken));
-
- $actual = $this->tokenProvider->validateToken($token);
-
- $this->assertEquals($dbToken, $actual);
- }
-
- /**
- * @expectedException \OC\Authentication\Exceptions\InvalidTokenException
- */
- public function testValidateInvalidToken() {
- $token = 'sometoken';
- $this->mapper->expects($this->once())
- ->method('getToken')
- ->with(hash('sha512', $token))
- ->will($this->throwException(new DoesNotExistException('')));
-
- $this->tokenProvider->validateToken($token);
- }
-
}
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
index 7a34d42a2bc..eef4c7ff5ea 100644
--- a/tests/lib/User/SessionTest.php
+++ b/tests/lib/User/SessionTest.php
@@ -41,6 +41,7 @@ class SessionTest extends \Test\TestCase {
public function testGetUser() {
$token = new \OC\Authentication\Token\DefaultToken();
$token->setLoginName('User123');
+ $token->setLastCheck(200);
$expectedUser = $this->getMock('\OCP\IUser');
$expectedUser->expects($this->any())
@@ -56,41 +57,32 @@ class SessionTest extends \Test\TestCase {
$manager = $this->getMockBuilder('\OC\User\Manager')
->disableOriginalConstructor()
->getMock();
+ $session->expects($this->at(1))
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue(null)); // No password set -> browser session
$session->expects($this->once())
->method('getId')
->will($this->returnValue($sessionId));
$this->tokenProvider->expects($this->once())
->method('getToken')
+ ->with($sessionId)
->will($this->returnValue($token));
- $session->expects($this->at(2))
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(null)); // No check has been run yet
$this->tokenProvider->expects($this->once())
->method('getPassword')
->with($token, $sessionId)
- ->will($this->returnValue('password123'));
+ ->will($this->returnValue('passme'));
$manager->expects($this->once())
->method('checkPassword')
- ->with('User123', 'password123')
+ ->with('User123', 'passme')
->will($this->returnValue(true));
$expectedUser->expects($this->once())
->method('isEnabled')
->will($this->returnValue(true));
- $session->expects($this->at(3))
- ->method('set')
- ->with('last_login_check', 10000);
- $session->expects($this->at(4))
- ->method('get')
- ->with('last_token_update')
- ->will($this->returnValue(null)); // No check run so far
$this->tokenProvider->expects($this->once())
- ->method('updateToken')
+ ->method('updateTokenActivity')
->with($token);
- $session->expects($this->at(5))
- ->method('set')
- ->with('last_token_update', $this->equalTo(10000));
$manager->expects($this->any())
->method('get')
@@ -100,6 +92,7 @@ class SessionTest extends \Test\TestCase {
$userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$user = $userSession->getUser();
$this->assertSame($expectedUser, $user);
+ $this->assertSame(10000, $token->getLastCheck());
}
public function isLoggedInData() {
@@ -155,6 +148,10 @@ class SessionTest extends \Test\TestCase {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
$session->expects($this->once())
->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
$session->expects($this->exactly(2))
->method('set')
->with($this->callback(function ($key) {
@@ -219,6 +216,10 @@ class SessionTest extends \Test\TestCase {
->method('set');
$session->expects($this->once())
->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
@@ -252,11 +253,6 @@ class SessionTest extends \Test\TestCase {
public function testLoginInvalidPassword() {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
- $session->expects($this->never())
- ->method('set');
- $session->expects($this->once())
- ->method('regenerateId');
-
$managerMethods = get_class_methods('\OC\User\Manager');
//keep following methods intact in order to ensure hooks are
//working
@@ -268,10 +264,20 @@ class SessionTest extends \Test\TestCase {
}
}
$manager = $this->getMock('\OC\User\Manager', $managerMethods, array());
-
$backend = $this->getMock('\Test\Util\User\Dummy');
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$user = $this->getMock('\OC\User\User', array(), array('foo', $backend));
+
+ $session->expects($this->never())
+ ->method('set');
+ $session->expects($this->once())
+ ->method('regenerateId');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
+
$user->expects($this->never())
->method('isEnabled');
$user->expects($this->never())
@@ -282,27 +288,29 @@ class SessionTest extends \Test\TestCase {
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$userSession->login('foo', 'bar');
}
public function testLoginNonExisting() {
$session = $this->getMock('\OC\Session\Memory', array(), array(''));
+ $manager = $this->getMock('\OC\User\Manager');
+ $backend = $this->getMock('\Test\Util\User\Dummy');
+ $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
+
$session->expects($this->never())
->method('set');
$session->expects($this->once())
->method('regenerateId');
-
- $manager = $this->getMock('\OC\User\Manager');
-
- $backend = $this->getMock('\Test\Util\User\Dummy');
+ $this->tokenProvider->expects($this->once())
+ ->method('getToken')
+ ->with('bar')
+ ->will($this->throwException(new \OC\Authentication\Exceptions\InvalidTokenException()));
$manager->expects($this->once())
->method('checkPassword')
->with('foo', 'bar')
->will($this->returnValue(false));
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
$userSession->login('foo', 'bar');
}
@@ -354,24 +362,14 @@ class SessionTest extends \Test\TestCase {
->will($this->returnValue(true));
$userSession->expects($this->once())
->method('login')
- ->with('john', 'doe')
+ ->with('john', 'I-AM-AN-APP-PASSWORD')
->will($this->returnValue(true));
- $userSession->expects($this->once())
- ->method('supportsCookies')
- ->with($request)
- ->will($this->returnValue(true));
- $userSession->expects($this->once())
- ->method('getUser')
- ->will($this->returnValue($user));
- $user->expects($this->once())
- ->method('getUID')
- ->will($this->returnValue('user123'));
- $userSession->expects($this->once())
- ->method('createSessionToken')
- ->with($request, 'user123', 'john', 'doe');
-
- $this->assertTrue($userSession->logClientIn('john', 'doe', $request));
+ $session->expects($this->once())
+ ->method('set')
+ ->with('app_password', 'I-AM-AN-APP-PASSWORD');
+
+ $this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request));
}
/**
@@ -706,9 +704,15 @@ class SessionTest extends \Test\TestCase {
->disableOriginalConstructor()
->getMock();
$session = new Memory('');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLoginName('fritz');
+ $token->setUid('fritz0');
+ $token->setLastCheck(100); // Needs check
$user = $this->getMock('\OCP\IUser');
- $userSession = new \OC\User\Session($manager, $session, $this->timeFactory, $this->tokenProvider, $this->config);
+ $userSession = $this->getMockBuilder('\OC\User\Session')
+ ->setMethods(['logout'])
+ ->setConstructorArgs([$manager, $session, $this->timeFactory, $this->tokenProvider, $this->config])
+ ->getMock();
$request = $this->getMock('\OCP\IRequest');
$request->expects($this->once())
@@ -716,15 +720,12 @@ class SessionTest extends \Test\TestCase {
->with('Authorization')
->will($this->returnValue('token xxxxx'));
$this->tokenProvider->expects($this->once())
- ->method('validateToken')
+ ->method('getToken')
->with('xxxxx')
->will($this->returnValue($token));
- $token->expects($this->once())
- ->method('getUID')
- ->will($this->returnValue('user123'));
$manager->expects($this->once())
->method('get')
- ->with('user123')
+ ->with('fritz0')
->will($this->returnValue($user));
$user->expects($this->once())
->method('isEnabled')
@@ -744,40 +745,40 @@ class SessionTest extends \Test\TestCase {
->getMock();
$user = $this->getMock('\OCP\IUser');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLoginName('susan');
+ $token->setLastCheck(20);
$session->expects($this->once())
- ->method('getId')
- ->will($this->returnValue('sessionid'));
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue('APP-PASSWORD'));
$tokenProvider->expects($this->once())
->method('getToken')
- ->with('sessionid')
+ ->with('APP-PASSWORD')
->will($this->returnValue($token));
- $session->expects($this->once())
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(1000));
$timeFactory->expects($this->once())
->method('getTime')
- ->will($this->returnValue(5000));
+ ->will($this->returnValue(1000)); // more than 5min since last check
$tokenProvider->expects($this->once())
->method('getPassword')
- ->with($token, 'sessionid')
+ ->with($token, 'APP-PASSWORD')
->will($this->returnValue('123456'));
- $token->expects($this->once())
- ->method('getLoginName')
- ->will($this->returnValue('User5'));
$userManager->expects($this->once())
->method('checkPassword')
- ->with('User5', '123456')
+ ->with('susan', '123456')
->will($this->returnValue(true));
$user->expects($this->once())
->method('isEnabled')
->will($this->returnValue(false));
+ $tokenProvider->expects($this->once())
+ ->method('invalidateToken')
+ ->with('APP-PASSWORD');
$userSession->expects($this->once())
->method('logout');
- $this->invokePrivate($userSession, 'validateSession', [$user]);
+ $userSession->setUser($user);
+ $this->invokePrivate($userSession, 'validateSession');
}
public function testValidateSessionNoPassword() {
@@ -791,31 +792,96 @@ class SessionTest extends \Test\TestCase {
->getMock();
$user = $this->getMock('\OCP\IUser');
- $token = $this->getMock('\OC\Authentication\Token\IToken');
+ $token = new \OC\Authentication\Token\DefaultToken();
+ $token->setLastCheck(20);
$session->expects($this->once())
- ->method('getId')
- ->will($this->returnValue('sessionid'));
+ ->method('get')
+ ->with('app_password')
+ ->will($this->returnValue('APP-PASSWORD'));
$tokenProvider->expects($this->once())
->method('getToken')
- ->with('sessionid')
+ ->with('APP-PASSWORD')
->will($this->returnValue($token));
- $session->expects($this->once())
- ->method('get')
- ->with('last_login_check')
- ->will($this->returnValue(1000));
$timeFactory->expects($this->once())
->method('getTime')
- ->will($this->returnValue(5000));
+ ->will($this->returnValue(1000)); // more than 5min since last check
$tokenProvider->expects($this->once())
->method('getPassword')
- ->with($token, 'sessionid')
+ ->with($token, 'APP-PASSWORD')
->will($this->throwException(new \OC\Authentication\Exceptions\PasswordlessTokenException()));
- $session->expects($this->once())
- ->method('set')
- ->with('last_login_check', 5000);
+ $tokenProvider->expects($this->once())
+ ->method('updateToken')
+ ->with($token);
$this->invokePrivate($userSession, 'validateSession', [$user]);
+
+ $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);
}
}
diff --git a/version.php b/version.php
index 698636a2196..3015d976e53 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = array(9, 1, 0, 8);
+$OC_Version = array(9, 1, 0, 9);
// The human readable string
$OC_VersionString = '9.1.0 beta 2';