diff options
125 files changed, 3536 insertions, 667 deletions
diff --git a/apps/comments/appinfo/app.php b/apps/comments/appinfo/app.php index cd1ccb2d7d3..b060a5db1ca 100644 --- a/apps/comments/appinfo/app.php +++ b/apps/comments/appinfo/app.php @@ -51,3 +51,10 @@ $managerListener = function(\OCP\Comments\CommentsEvent $event) use ($activityMa }; $eventDispatcher->addListener(\OCP\Comments\CommentsEvent::EVENT_ADD, $managerListener); + +$eventDispatcher->addListener(\OCP\Comments\CommentsEntityEvent::EVENT_ENTITY, function(\OCP\Comments\CommentsEntityEvent $event) { + $event->addEntityCollection('files', function($name) { + $nodes = \OC::$server->getUserFolder()->getById(intval($name)); + return !empty($nodes); + }); +}); diff --git a/apps/dav/lib/Comments/EntityCollection.php b/apps/dav/lib/Comments/EntityCollection.php index a55a18c00c0..8fa13da6162 100644 --- a/apps/dav/lib/Comments/EntityCollection.php +++ b/apps/dav/lib/Comments/EntityCollection.php @@ -22,11 +22,12 @@ namespace OCA\DAV\Comments; use OCP\Comments\ICommentsManager; -use OCP\Files\Folder; +use OCP\Comments\NotFoundException; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\IProperties; use Sabre\DAV\PropPatch; /** @@ -37,12 +38,9 @@ use Sabre\DAV\PropPatch; * * @package OCA\DAV\Comments */ -class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties { +class EntityCollection extends RootCollection implements IProperties { const PROPERTY_NAME_READ_MARKER = '{http://owncloud.org/ns}readMarker'; - /** @var Folder */ - protected $fileRoot; - /** @var string */ protected $id; @@ -53,7 +51,6 @@ class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties * @param string $id * @param string $name * @param ICommentsManager $commentsManager - * @param Folder $fileRoot * @param IUserManager $userManager * @param IUserSession $userSession * @param ILogger $logger @@ -62,7 +59,6 @@ class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties $id, $name, ICommentsManager $commentsManager, - Folder $fileRoot, IUserManager $userManager, IUserSession $userSession, ILogger $logger @@ -76,7 +72,6 @@ class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties $this->id = $id; $this->name = $name; $this->commentsManager = $commentsManager; - $this->fileRoot = $fileRoot; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; @@ -111,7 +106,7 @@ class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties $this->userSession, $this->logger ); - } catch (\OCP\Comments\NotFoundException $e) { + } catch (NotFoundException $e) { throw new NotFound(); } } @@ -159,7 +154,7 @@ class EntityCollection extends RootCollection implements \Sabre\DAV\IProperties try { $this->commentsManager->get($name); return true; - } catch (\OCP\Comments\NotFoundException $e) { + } catch (NotFoundException $e) { return false; } } diff --git a/apps/dav/lib/Comments/EntityTypeCollection.php b/apps/dav/lib/Comments/EntityTypeCollection.php index 6bc42484207..66fdb7f8de6 100644 --- a/apps/dav/lib/Comments/EntityTypeCollection.php +++ b/apps/dav/lib/Comments/EntityTypeCollection.php @@ -22,7 +22,6 @@ namespace OCA\DAV\Comments; use OCP\Comments\ICommentsManager; -use OCP\Files\Folder; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; @@ -41,27 +40,31 @@ use Sabre\DAV\Exception\NotFound; * @package OCA\DAV\Comments */ class EntityTypeCollection extends RootCollection { - /** @var Folder */ - protected $fileRoot; /** @var ILogger */ protected $logger; + /** @var IUserManager */ + protected $userManager; + + /** @var \Closure */ + protected $childExistsFunction; + /** * @param string $name * @param ICommentsManager $commentsManager - * @param Folder $fileRoot * @param IUserManager $userManager * @param IUserSession $userSession * @param ILogger $logger + * @param \Closure $childExistsFunction */ public function __construct( $name, ICommentsManager $commentsManager, - Folder $fileRoot, IUserManager $userManager, IUserSession $userSession, - ILogger $logger + ILogger $logger, + \Closure $childExistsFunction ) { $name = trim($name); if(empty($name) || !is_string($name)) { @@ -69,10 +72,10 @@ class EntityTypeCollection extends RootCollection { } $this->name = $name; $this->commentsManager = $commentsManager; - $this->fileRoot = $fileRoot; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; + $this->childExistsFunction = $childExistsFunction; } /** @@ -93,7 +96,6 @@ class EntityTypeCollection extends RootCollection { $name, $this->name, $this->commentsManager, - $this->fileRoot, $this->userManager, $this->userSession, $this->logger @@ -117,9 +119,7 @@ class EntityTypeCollection extends RootCollection { * @return bool */ function childExists($name) { - $nodes = $this->fileRoot->getById(intval($name)); - return !empty($nodes); + return call_user_func($this->childExistsFunction, $name); } - } diff --git a/apps/dav/lib/Comments/RootCollection.php b/apps/dav/lib/Comments/RootCollection.php index cda666f7162..b02532b0674 100644 --- a/apps/dav/lib/Comments/RootCollection.php +++ b/apps/dav/lib/Comments/RootCollection.php @@ -21,8 +21,8 @@ namespace OCA\DAV\Comments; +use OCP\Comments\CommentsEntityEvent; use OCP\Comments\ICommentsManager; -use OCP\Files\IRootFolder; use OCP\ILogger; use OCP\IUserManager; use OCP\IUserSession; @@ -30,11 +30,12 @@ use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class RootCollection implements ICollection { - /** @var EntityTypeCollection[] */ - private $entityTypeCollections = []; + /** @var EntityTypeCollection[]|null */ + private $entityTypeCollections; /** @var ICommentsManager */ protected $commentsManager; @@ -47,34 +48,32 @@ class RootCollection implements ICollection { /** @var IUserManager */ protected $userManager; - /** - * @var IUserSession - */ + + /** @var IUserSession */ protected $userSession; - /** - * @var IRootFolder - */ - protected $rootFolder; + + /** @var EventDispatcherInterface */ + protected $dispatcher; /** * @param ICommentsManager $commentsManager * @param IUserManager $userManager * @param IUserSession $userSession - * @param IRootFolder $rootFolder + * @param EventDispatcherInterface $dispatcher * @param ILogger $logger */ public function __construct( ICommentsManager $commentsManager, IUserManager $userManager, IUserSession $userSession, - IRootFolder $rootFolder, + EventDispatcherInterface $dispatcher, ILogger $logger) { $this->commentsManager = $commentsManager; $this->logger = $logger; $this->userManager = $userManager; $this->userSession = $userSession; - $this->rootFolder = $rootFolder; + $this->dispatcher = $dispatcher; } /** @@ -85,22 +84,28 @@ class RootCollection implements ICollection { * @throws NotAuthenticated */ protected function initCollections() { - if(!empty($this->entityTypeCollections)) { + if($this->entityTypeCollections !== null) { return; } $user = $this->userSession->getUser(); if(is_null($user)) { throw new NotAuthenticated(); } - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $this->entityTypeCollections['files'] = new EntityTypeCollection( - 'files', - $this->commentsManager, - $userFolder, - $this->userManager, - $this->userSession, - $this->logger - ); + + $event = new CommentsEntityEvent(CommentsEntityEvent::EVENT_ENTITY); + $this->dispatcher->dispatch(CommentsEntityEvent::EVENT_ENTITY, $event); + + $this->entityTypeCollections = []; + foreach ($event->getEntityCollections() as $entity => $entityExistsFunction) { + $this->entityTypeCollections[$entity] = new EntityTypeCollection( + $entity, + $this->commentsManager, + $this->userManager, + $this->userSession, + $this->logger, + $entityExistsFunction + ); + } } /** diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index b6e1747e990..f18bfaf496d 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -77,7 +77,7 @@ class RootCollection extends SimpleCollection { \OC::$server->getCommentsManager(), \OC::$server->getUserManager(), \OC::$server->getUserSession(), - \OC::$server->getRootFolder(), + \OC::$server->getEventDispatcher(), \OC::$server->getLogger() ); diff --git a/apps/dav/tests/unit/comments/entitycollection.php b/apps/dav/tests/unit/comments/entitycollection.php index 5bf155f12ba..bc009e92549 100644 --- a/apps/dav/tests/unit/comments/entitycollection.php +++ b/apps/dav/tests/unit/comments/entitycollection.php @@ -23,18 +23,21 @@ namespace OCA\DAV\Tests\Unit\Comments; class EntityCollection extends \Test\TestCase { + /** @var \OCP\Comments\ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */ protected $commentsManager; - protected $folder; + /** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */ protected $userManager; + /** @var \OCP\ILogger|\PHPUnit_Framework_MockObject_MockObject */ protected $logger; + /** @var \OCA\DAV\Comments\EntityCollection */ protected $collection; + /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ protected $userSession; public function setUp() { parent::setUp(); $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); - $this->folder = $this->getMock('\OCP\Files\Folder'); $this->userManager = $this->getMock('\OCP\IUserManager'); $this->userSession = $this->getMock('\OCP\IUserSession'); $this->logger = $this->getMock('\OCP\ILogger'); @@ -43,7 +46,6 @@ class EntityCollection extends \Test\TestCase { '19', 'files', $this->commentsManager, - $this->folder, $this->userManager, $this->userSession, $this->logger diff --git a/apps/dav/tests/unit/comments/entitytypecollection.php b/apps/dav/tests/unit/comments/entitytypecollection.php index f3aa2dbd71f..96b1cad8373 100644 --- a/apps/dav/tests/unit/comments/entitytypecollection.php +++ b/apps/dav/tests/unit/comments/entitytypecollection.php @@ -25,52 +25,52 @@ use OCA\DAV\Comments\EntityCollection as EntityCollectionImplemantation; class EntityTypeCollection extends \Test\TestCase { + /** @var \OCP\Comments\ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */ protected $commentsManager; - protected $folder; + /** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */ protected $userManager; + /** @var \OCP\ILogger|\PHPUnit_Framework_MockObject_MockObject */ protected $logger; + /** @var \OCA\DAV\Comments\EntityTypeCollection */ protected $collection; + /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ protected $userSession; + protected $childMap = []; + public function setUp() { parent::setUp(); $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); - $this->folder = $this->getMock('\OCP\Files\Folder'); $this->userManager = $this->getMock('\OCP\IUserManager'); $this->userSession = $this->getMock('\OCP\IUserSession'); $this->logger = $this->getMock('\OCP\ILogger'); + $instance = $this; + $this->collection = new \OCA\DAV\Comments\EntityTypeCollection( 'files', $this->commentsManager, - $this->folder, $this->userManager, $this->userSession, - $this->logger + $this->logger, + function ($child) use ($instance) { + return !empty($instance->childMap[$child]); + } ); } public function testChildExistsYes() { - $this->folder->expects($this->once()) - ->method('getById') - ->with('17') - ->will($this->returnValue([$this->getMock('\OCP\Files\Node')])); + $this->childMap[17] = true; $this->assertTrue($this->collection->childExists('17')); } public function testChildExistsNo() { - $this->folder->expects($this->once()) - ->method('getById') - ->will($this->returnValue([])); $this->assertFalse($this->collection->childExists('17')); } public function testGetChild() { - $this->folder->expects($this->once()) - ->method('getById') - ->with('17') - ->will($this->returnValue([$this->getMock('\OCP\Files\Node')])); + $this->childMap[17] = true; $ec = $this->collection->getChild('17'); $this->assertTrue($ec instanceof EntityCollectionImplemantation); @@ -80,11 +80,6 @@ class EntityTypeCollection extends \Test\TestCase { * @expectedException \Sabre\DAV\Exception\NotFound */ public function testGetChildException() { - $this->folder->expects($this->once()) - ->method('getById') - ->with('17') - ->will($this->returnValue([])); - $this->collection->getChild('17'); } diff --git a/apps/dav/tests/unit/comments/rootcollection.php b/apps/dav/tests/unit/comments/rootcollection.php index 369006e7159..a59482fba73 100644 --- a/apps/dav/tests/unit/comments/rootcollection.php +++ b/apps/dav/tests/unit/comments/rootcollection.php @@ -22,15 +22,24 @@ namespace OCA\DAV\Tests\Unit\Comments; use OCA\DAV\Comments\EntityTypeCollection as EntityTypeCollectionImplementation; +use OCP\Comments\CommentsEntityEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; class RootCollection extends \Test\TestCase { + /** @var \OCP\Comments\ICommentsManager|\PHPUnit_Framework_MockObject_MockObject */ protected $commentsManager; + /** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */ protected $userManager; + /** @var \OCP\ILogger|\PHPUnit_Framework_MockObject_MockObject */ protected $logger; + /** @var \OCA\DAV\Comments\RootCollection */ protected $collection; + /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ protected $userSession; - protected $rootFolder; + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ + protected $dispatcher; + /** @var \OCP\IUser|\PHPUnit_Framework_MockObject_MockObject */ protected $user; public function setUp() { @@ -41,14 +50,14 @@ class RootCollection extends \Test\TestCase { $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); $this->userManager = $this->getMock('\OCP\IUserManager'); $this->userSession = $this->getMock('\OCP\IUserSession'); - $this->rootFolder = $this->getMock('\OCP\Files\IRootFolder'); + $this->dispatcher = new EventDispatcher(); $this->logger = $this->getMock('\OCP\ILogger'); $this->collection = new \OCA\DAV\Comments\RootCollection( $this->commentsManager, $this->userManager, $this->userSession, - $this->rootFolder, + $this->dispatcher, $this->logger ); } @@ -62,10 +71,11 @@ class RootCollection extends \Test\TestCase { ->method('getUser') ->will($this->returnValue($this->user)); - $this->rootFolder->expects($this->once()) - ->method('getUserFolder') - ->with('alice') - ->will($this->returnValue($this->getMock('\OCP\Files\Folder'))); + $this->dispatcher->addListener(CommentsEntityEvent::EVENT_ENTITY, function(CommentsEntityEvent $event) { + $event->addEntityCollection('files', function() { + return true; + }); + }); } /** diff --git a/apps/federatedfilesharing/appinfo/app.php b/apps/federatedfilesharing/appinfo/app.php index 4666d343f7e..c33a887c6d6 100644 --- a/apps/federatedfilesharing/appinfo/app.php +++ b/apps/federatedfilesharing/appinfo/app.php @@ -20,4 +20,21 @@ */ $app = new \OCA\FederatedFileSharing\AppInfo\Application('federatedfilesharing'); + +use OCA\FederatedFileSharing\Notifier; + +$l = \OC::$server->getL10N('files_sharing'); + $app->registerSettings(); + +$manager = \OC::$server->getNotificationManager(); +$manager->registerNotifier(function() { + return new Notifier( + \OC::$server->getL10NFactory() + ); +}, function() use ($l) { + return [ + 'id' => 'files_sharing', + 'name' => $l->t('Federated sharing'), + ]; +}); diff --git a/apps/federatedfilesharing/appinfo/database.xml b/apps/federatedfilesharing/appinfo/database.xml new file mode 100644 index 00000000000..1dbe8ee2ec9 --- /dev/null +++ b/apps/federatedfilesharing/appinfo/database.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> + +<!-- +Keep a mapping of the share ID stored in the local oc_share table +and the share ID stored in the remote servers oc_share table. +This is needed in order to send updates in both directions between +the servers (e.g. permissions change, unshare,...) +--> + +<database> + <name>*dbname*</name> + <create>true</create> + <overwrite>false</overwrite> + <charset>utf8</charset> + <table> + <name>*dbprefix*federated_reshares</name> + <declaration> + <field> + <name>share_id</name> + <type>integer</type> + <notnull>true</notnull> + <length>4</length> + </field> + <field> + <name>remote_id</name> + <type>integer</type> + <notnull>true</notnull> + <length>4</length> + <comments>share ID at the remote server</comments> + </field> + <index> + <name>share_id_index</name> + <unique>true</unique> + <field> + <name>share_id</name> + <sorting>ascending</sorting> + </field> + </index> + </declaration> + </table> +</database> diff --git a/apps/federatedfilesharing/appinfo/info.xml b/apps/federatedfilesharing/appinfo/info.xml index 643281bd145..5cf4039f196 100644 --- a/apps/federatedfilesharing/appinfo/info.xml +++ b/apps/federatedfilesharing/appinfo/info.xml @@ -5,7 +5,7 @@ <description>Provide federated file sharing across ownCloud servers</description> <licence>AGPL</licence> <author>Bjoern Schiessle, Roeland Jago Douma</author> - <version>0.2.0</version> + <version>0.3.0</version> <namespace>FederatedFileSharing</namespace> <category>other</category> <dependencies> diff --git a/apps/federatedfilesharing/lib/AppInfo/Application.php b/apps/federatedfilesharing/lib/AppInfo/Application.php index 5a213aec8e2..d1b0646ba5b 100644 --- a/apps/federatedfilesharing/lib/AppInfo/Application.php +++ b/apps/federatedfilesharing/lib/AppInfo/Application.php @@ -81,7 +81,8 @@ class Application extends App { \OC::$server->getL10N('federatedfilesharing'), \OC::$server->getLogger(), \OC::$server->getLazyRootFolder(), - \OC::$server->getConfig() + \OC::$server->getConfig(), + \OC::$server->getUserManager() ); } diff --git a/apps/federatedfilesharing/lib/BackgroundJob/UnShare.php b/apps/federatedfilesharing/lib/BackgroundJob/RetryJob.php index b056db4eac7..109a607bff0 100644 --- a/apps/federatedfilesharing/lib/BackgroundJob/UnShare.php +++ b/apps/federatedfilesharing/lib/BackgroundJob/RetryJob.php @@ -32,26 +32,26 @@ use OCP\BackgroundJob\IJobList; use OCP\ILogger; /** - * Class UnShare + * Class RetryJob * - * Background job to re-send the un-share notification to the remote server in + * Background job to re-send update of federated re-shares to the remote server in * case the server was not available on the first try * * @package OCA\FederatedFileSharing\BackgroundJob */ -class UnShare extends Job { +class RetryJob extends Job { /** @var bool */ private $retainJob = true; - + /** @var Notifications */ private $notifications; - /** @var int max number of attempts to send the un-share request */ - private $maxTry = 10; + /** @var int max number of attempts to send the request */ + private $maxTry = 20; - /** @var int how much time should be between two tries (12 hours) */ - private $interval = 43200; + /** @var int how much time should be between two tries (10 minutes) */ + private $interval = 600; /** * UnShare constructor. @@ -77,7 +77,7 @@ class UnShare extends Job { \OC::$server->getJobList() ); } - + } /** @@ -99,12 +99,14 @@ class UnShare extends Job { protected function run($argument) { $remote = $argument['remote']; - $id = (int)$argument['id']; + $remoteId = $argument['remoteId']; $token = $argument['token']; + $action = $argument['action']; + $data = json_decode($argument['data'], true); $try = (int)$argument['try'] + 1; - $result = $this->notifications->sendRemoteUnShare($remote, $id, $token, $try); - + $result = $this->notifications->sendUpdateToRemote($remote, $remoteId, $token, $action, $data, $try); + if ($result === true || $try > $this->maxTry) { $this->retainJob = false; } @@ -117,11 +119,13 @@ class UnShare extends Job { * @param array $argument */ protected function reAddJob(IJobList $jobList, array $argument) { - $jobList->add('OCA\FederatedFileSharing\BackgroundJob\UnShare', + $jobList->add('OCA\FederatedFileSharing\BackgroundJob\RetryJob', [ 'remote' => $argument['remote'], - 'id' => $argument['id'], + 'remoteId' => $argument['remoteId'], 'token' => $argument['token'], + 'data' => $argument['data'], + 'action' => $argument['action'], 'try' => (int)$argument['try'] + 1, 'lastRun' => time() ] diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index d014a6219a3..76fed01c308 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -25,10 +25,10 @@ namespace OCA\FederatedFileSharing; use OC\Share20\Share; use OCP\Files\IRootFolder; -use OCP\IAppConfig; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; +use OCP\IUserManager; use OCP\Share\IShare; use OCP\Share\IShareProvider; use OC\Share20\Exception\InvalidShare; @@ -70,6 +70,12 @@ class FederatedShareProvider implements IShareProvider { /** @var IConfig */ private $config; + /** @var string */ + private $externalShareTable = 'share_external'; + + /** @var IUserManager */ + private $userManager; + /** * DefaultShareProvider constructor. * @@ -81,6 +87,7 @@ class FederatedShareProvider implements IShareProvider { * @param ILogger $logger * @param IRootFolder $rootFolder * @param IConfig $config + * @param IUserManager $userManager */ public function __construct( IDBConnection $connection, @@ -90,7 +97,8 @@ class FederatedShareProvider implements IShareProvider { IL10N $l10n, ILogger $logger, IRootFolder $rootFolder, - IConfig $config + IConfig $config, + IUserManager $userManager ) { $this->dbConnection = $connection; $this->addressHandler = $addressHandler; @@ -100,6 +108,7 @@ class FederatedShareProvider implements IShareProvider { $this->logger = $logger; $this->rootFolder = $rootFolder; $this->config = $config; + $this->userManager = $userManager; } /** @@ -124,10 +133,9 @@ class FederatedShareProvider implements IShareProvider { $shareWith = $share->getSharedWith(); $itemSource = $share->getNodeId(); $itemType = $share->getNodeType(); - $uidOwner = $share->getShareOwner(); $permissions = $share->getPermissions(); $sharedBy = $share->getSharedBy(); - + /* * Check if file is not already shared with the remote user */ @@ -151,31 +159,136 @@ class FederatedShareProvider implements IShareProvider { throw new \Exception($message_t); } - $token = $this->tokenHandler->generateToken(); + $share->setSharedWith($user . '@' . $remote); + + try { + $remoteShare = $this->getShareFromExternalShareTable($share); + } catch (ShareNotFound $e) { + $remoteShare = null; + } - $shareWith = $user . '@' . $remote; + if ($remoteShare) { + try { + $uidOwner = $remoteShare['owner'] . '@' . $remoteShare['remote']; + $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, 'tmp_token_' . time()); + $share->setId($shareId); + list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId); + // remote share was create successfully if we get a valid token as return + $send = is_string($token) && $token !== ''; + } catch (\Exception $e) { + // fall back to old re-share behavior if the remote server + // doesn't support flat re-shares (was introduced with ownCloud 9.1) + $this->removeShareFromTable($share); + $shareId = $this->createFederatedShare($share); + } + if ($send) { + $this->updateSuccessfulReshare($shareId, $token); + $this->storeRemoteId($shareId, $remoteId); + } else { + $this->removeShareFromTable($share); + $message_t = $this->l->t('File is already shared with %s', [$shareWith]); + throw new \Exception($message_t); + } - $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token); + } else { + $shareId = $this->createFederatedShare($share); + } + $data = $this->getRawShare($shareId); + return $this->createShareObject($data); + } + + /** + * create federated share and inform the recipient + * + * @param IShare $share + * @return int + * @throws ShareNotFound + * @throws \Exception + */ + protected function createFederatedShare(IShare $share) { + $token = $this->tokenHandler->generateToken(); + $shareId = $this->addShareToDB( + $share->getNodeId(), + $share->getNodeType(), + $share->getSharedWith(), + $share->getSharedBy(), + $share->getShareOwner(), + $share->getPermissions(), + $token + ); + $sharedByFederatedId = $share->getSharedBy(); + if ($this->userManager->userExists($sharedByFederatedId)) { + $sharedByFederatedId = $sharedByFederatedId . '@' . $this->addressHandler->generateRemoteURL(); + } $send = $this->notifications->sendRemoteShare( $token, - $shareWith, + $share->getSharedWith(), $share->getNode()->getName(), $shareId, - $share->getSharedBy() + $share->getShareOwner(), + $share->getShareOwner() . '@' . $this->addressHandler->generateRemoteURL(), + $share->getSharedBy(), + $sharedByFederatedId ); - $data = $this->getRawShare($shareId); - $share = $this->createShare($data); - if ($send === false) { - $this->delete($share); + $data = $this->getRawShare($shareId); + $share = $this->createShareObject($data); + $this->removeShareFromTable($share); $message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', - [$share->getNode()->getName(), $shareWith]); + [$share->getNode()->getName(), $share->getSharedWith()]); throw new \Exception($message_t); } - return $share; + return $shareId; + } + + /** + * @param string $shareWith + * @param IShare $share + * @param string $shareId internal share Id + * @return array + * @throws \Exception + */ + protected function askOwnerToReShare($shareWith, IShare $share, $shareId) { + + $remoteShare = $this->getShareFromExternalShareTable($share); + $token = $remoteShare['share_token']; + $remoteId = $remoteShare['remote_id']; + $remote = $remoteShare['remote']; + + list($token, $remoteId) = $this->notifications->requestReShare( + $token, + $remoteId, + $shareId, + $remote, + $shareWith, + $share->getPermissions() + ); + + return [$token, $remoteId]; + } + + /** + * get federated share from the share_external table but exclude mounted link shares + * + * @param IShare $share + * @return array + * @throws ShareNotFound + */ + protected function getShareFromExternalShareTable(IShare $share) { + $query = $this->dbConnection->getQueryBuilder(); + $query->select('*')->from($this->externalShareTable) + ->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner()))) + ->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget()))); + $result = $query->execute()->fetchAll(); + + if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) { + return $result[0]; + } + + throw new ShareNotFound('share not found in share_external table'); } /** @@ -234,10 +347,86 @@ class FederatedShareProvider implements IShareProvider { ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) ->execute(); + // send the updated permission to the owner/initiator, if they are not the same + if ($share->getShareOwner() !== $share->getSharedBy()) { + $this->sendPermissionUpdate($share); + } + return $share; } /** + * send the updated permission to the owner/initiator, if they are not the same + * + * @param IShare $share + * @throws ShareNotFound + * @throws \OC\HintException + */ + protected function sendPermissionUpdate(IShare $share) { + $remoteId = $this->getRemoteId($share); + // if the local user is the owner we send the permission change to the initiator + if ($this->userManager->userExists($share->getShareOwner())) { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy()); + } else { // ... if not we send the permission change to the owner + list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner()); + } + $this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions()); + } + + + /** + * update successful reShare with the correct token + * + * @param int $shareId + * @param string $token + */ + protected function updateSuccessfulReShare($shareId, $token) { + $query = $this->dbConnection->getQueryBuilder(); + $query->update('share') + ->where($query->expr()->eq('id', $query->createNamedParameter($shareId))) + ->set('token', $query->createNamedParameter($token)) + ->execute(); + } + + /** + * store remote ID in federated reShare table + * + * @param $shareId + * @param $remoteId + */ + public function storeRemoteId($shareId, $remoteId) { + $query = $this->dbConnection->getQueryBuilder(); + $query->insert('federated_reshares') + ->values( + [ + 'share_id' => $query->createNamedParameter($shareId), + 'remote_id' => $query->createNamedParameter($remoteId), + ] + ); + $query->execute(); + } + + /** + * get share ID on remote server for federated re-shares + * + * @param IShare $share + * @return int + * @throws ShareNotFound + */ + public function getRemoteId(IShare $share) { + $query = $this->dbConnection->getQueryBuilder(); + $query->select('remote_id')->from('federated_reshares') + ->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId()))); + $data = $query->execute()->fetch(); + + if (!is_array($data) || !isset($data['remote_id'])) { + throw new ShareNotFound(); + } + + return (int)$data['remote_id']; + } + + /** * @inheritdoc */ public function move(IShare $share, $recipient) { @@ -266,7 +455,7 @@ class FederatedShareProvider implements IShareProvider { $cursor = $qb->execute(); while($data = $cursor->fetch()) { - $children[] = $this->createShare($data); + $children[] = $this->createShareObject($data); } $cursor->closeCursor(); @@ -274,18 +463,77 @@ class FederatedShareProvider implements IShareProvider { } /** - * Delete a share + * Delete a share (owner unShares the file) * * @param IShare $share */ public function delete(IShare $share) { + + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith()); + + $isOwner = false; + + // if the local user is the owner we can send the unShare request directly... + if ($this->userManager->userExists($share->getShareOwner())) { + $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken()); + $this->revokeShare($share, true); + $isOwner = true; + } else { // ... if not we need to correct ID for the unShare request + $remoteId = $this->getRemoteId($share); + $this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken()); + $this->revokeShare($share, false); + } + + // send revoke notification to the other user, if initiator and owner are not the same user + if ($share->getShareOwner() !== $share->getSharedBy()) { + $remoteId = $this->getRemoteId($share); + if ($isOwner) { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy()); + } else { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner()); + } + $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken()); + } + + $this->removeShareFromTable($share); + } + + /** + * in case of a re-share we need to send the other use (initiator or owner) + * a message that the file was unshared + * + * @param IShare $share + * @param bool $isOwner the user can either be the owner or the user who re-sahred it + * @throws ShareNotFound + * @throws \OC\HintException + */ + protected function revokeShare($share, $isOwner) { + // also send a unShare request to the initiator, if this is a different user than the owner + if ($share->getShareOwner() !== $share->getSharedBy()) { + if ($isOwner) { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy()); + } else { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner()); + } + $remoteId = $this->getRemoteId($share); + $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken()); + } + } + + /** + * remove share from table + * + * @param IShare $share + */ + public function removeShareFromTable(IShare $share) { $qb = $this->dbConnection->getQueryBuilder(); $qb->delete('share') ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))); $qb->execute(); - list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith()); - $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken()); + $qb->delete('federated_reshares') + ->where($qb->expr()->eq('share_id', $qb->createNamedParameter($share->getId()))); + $qb->execute(); } /** @@ -348,7 +596,7 @@ class FederatedShareProvider implements IShareProvider { $cursor = $qb->execute(); $shares = []; while($data = $cursor->fetch()) { - $shares[] = $this->createShare($data); + $shares[] = $this->createShareObject($data); } $cursor->closeCursor(); @@ -375,7 +623,7 @@ class FederatedShareProvider implements IShareProvider { } try { - $share = $this->createShare($data); + $share = $this->createShareObject($data); } catch (InvalidShare $e) { throw new ShareNotFound(); } @@ -400,7 +648,7 @@ class FederatedShareProvider implements IShareProvider { $shares = []; while($data = $cursor->fetch()) { - $shares[] = $this->createShare($data); + $shares[] = $this->createShareObject($data); } $cursor->closeCursor(); @@ -439,7 +687,7 @@ class FederatedShareProvider implements IShareProvider { $cursor = $qb->execute(); while($data = $cursor->fetch()) { - $shares[] = $this->createShare($data); + $shares[] = $this->createShareObject($data); } $cursor->closeCursor(); @@ -470,7 +718,7 @@ class FederatedShareProvider implements IShareProvider { } try { - $share = $this->createShare($data); + $share = $this->createShareObject($data); } catch (InvalidShare $e) { throw new ShareNotFound(); } @@ -512,9 +760,9 @@ class FederatedShareProvider implements IShareProvider { * @throws InvalidShare * @throws ShareNotFound */ - private function createShare($data) { + private function createShareObject($data) { - $share = new Share($this->rootFolder); + $share = new Share($this->rootFolder, $this->userManager); $share->setId((int)$data['id']) ->setShareType((int)$data['share_type']) ->setPermissions((int)$data['permissions']) diff --git a/apps/federatedfilesharing/lib/Notifications.php b/apps/federatedfilesharing/lib/Notifications.php index 9cdc7760361..bf9e0fc9634 100644 --- a/apps/federatedfilesharing/lib/Notifications.php +++ b/apps/federatedfilesharing/lib/Notifications.php @@ -23,6 +23,7 @@ namespace OCA\FederatedFileSharing; +use OCP\AppFramework\Http; use OCP\BackgroundJob\IJobList; use OCP\Http\Client\IClientService; @@ -67,9 +68,14 @@ class Notifications { * @param string $name * @param int $remote_id * @param string $owner + * @param string $ownerFederatedId + * @param string $sharedBy + * @param string $sharedByFederatedId * @return bool + * @throws \OC\HintException + * @throws \OC\ServerNotAvailableException */ - public function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) { + public function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner, $ownerFederatedId, $sharedBy, $sharedByFederatedId) { list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith); @@ -83,6 +89,9 @@ class Notifications { 'name' => $name, 'remoteId' => $remote_id, 'owner' => $owner, + 'ownerFederatedId' => $ownerFederatedId, + 'sharedBy' => $sharedBy, + 'sharedByFederatedId' => $sharedByFederatedId, 'remote' => $local, ); @@ -101,34 +110,142 @@ class Notifications { } /** + * ask owner to re-share the file with the given user + * + * @param string $token + * @param int $id remote Id + * @param int $shareId internal share Id + * @param string $remote remote address of the owner + * @param string $shareWith + * @param int $permission + * @return bool + * @throws \OC\HintException + * @throws \OC\ServerNotAvailableException + */ + public function requestReShare($token, $id, $shareId, $remote, $shareWith, $permission) { + + $fields = array( + 'shareWith' => $shareWith, + 'token' => $token, + 'permission' => $permission, + 'remoteId' => $shareId + ); + + $url = $this->addressHandler->removeProtocolFromUrl($remote); + $result = $this->tryHttpPostToShareEndpoint(rtrim($url, '/'), '/' . $id . '/reshare', $fields); + $status = json_decode($result['result'], true); + + $httpRequestSuccessful = $result['success']; + $ocsCallSuccessful = $status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200; + $validToken = isset($status['ocs']['data']['token']) && is_string($status['ocs']['data']['token']); + $validRemoteId = isset($status['ocs']['data']['remoteId']); + + if ($httpRequestSuccessful && $ocsCallSuccessful && $validToken && $validRemoteId) { + return [ + $status['ocs']['data']['token'], + (int)$status['ocs']['data']['remoteId'] + ]; + } + + return false; + } + + /** * send server-to-server unshare to remote server * * @param string $remote url * @param int $id share id * @param string $token - * @param int $try how often did we already tried to send the un-share request * @return bool */ - public function sendRemoteUnShare($remote, $id, $token, $try = 0) { - $url = rtrim($remote, '/'); - $fields = array('token' => $token, 'format' => 'json'); - $url = $this->addressHandler->removeProtocolFromUrl($url); - $result = $this->tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields); + public function sendRemoteUnShare($remote, $id, $token) { + $this->sendUpdateToRemote($remote, $id, $token, 'unshare'); + } + + /** + * send server-to-server unshare to remote server + * + * @param string $remote url + * @param int $id share id + * @param string $token + * @return bool + */ + public function sendRevokeShare($remote, $id, $token) { + $this->sendUpdateToRemote($remote, $id, $token, 'revoke'); + } + + /** + * send notification to remote server if the permissions was changed + * + * @param string $remote + * @param int $remoteId + * @param string $token + * @param int $permissions + * @return bool + */ + public function sendPermissionChange($remote, $remoteId, $token, $permissions) { + $this->sendUpdateToRemote($remote, $remoteId, $token, 'permissions', ['permissions' => $permissions]); + } + + /** + * forward accept reShare to remote server + * + * @param string $remote + * @param int $remoteId + * @param string $token + */ + public function sendAcceptShare($remote, $remoteId, $token) { + $this->sendUpdateToRemote($remote, $remoteId, $token, 'accept'); + } + + /** + * forward decline reShare to remote server + * + * @param string $remote + * @param int $remoteId + * @param string $token + */ + public function sendDeclineShare($remote, $remoteId, $token) { + $this->sendUpdateToRemote($remote, $remoteId, $token, 'decline'); + } + + /** + * inform remote server whether server-to-server share was accepted/declined + * + * @param string $remote + * @param string $token + * @param int $remoteId Share id on the remote host + * @param string $action possible actions: accept, decline, unshare, revoke, permissions + * @param array $data + * @param int $try + * @return boolean + */ + public function sendUpdateToRemote($remote, $remoteId, $token, $action, $data = [], $try = 0) { + + $fields = array('token' => $token); + foreach ($data as $key => $value) { + $fields[$key] = $value; + } + + $url = $this->addressHandler->removeProtocolFromUrl($remote); + $result = $this->tryHttpPostToShareEndpoint(rtrim($url, '/'), '/' . $remoteId . '/' . $action, $fields); $status = json_decode($result['result'], true); - if ($result['success'] && - ($status['ocs']['meta']['statuscode'] === 100 || + if ($result['success'] && + ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200 ) ) { return true; } elseif ($try === 0) { // only add new job on first try - $this->jobList->add('OCA\FederatedFileSharing\BackgroundJob\UnShare', + $this->jobList->add('OCA\FederatedFileSharing\BackgroundJob\RetryJob', [ 'remote' => $remote, - 'id' => $id, + 'remoteId' => $remoteId, 'token' => $token, + 'action' => $action, + 'data' => json_encode($data), 'try' => $try, 'lastRun' => $this->getTimestamp() ] @@ -138,6 +255,7 @@ class Notifications { return false; } + /** * return current timestamp * @@ -154,6 +272,7 @@ class Notifications { * @param string $urlSuffix * @param array $fields post parameters * @return array + * @throws \Exception */ protected function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) { $client = $this->httpClientService->newClient(); @@ -174,6 +293,12 @@ class Notifications { $result['success'] = true; break; } catch (\Exception $e) { + // if flat re-sharing is not supported by the remote server + // we re-throw the exception and fall back to the old behaviour. + // (flat re-shares has been introduced in ownCloud 9.1) + if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) { + throw $e; + } $try++; $protocol = 'http://'; } diff --git a/apps/federatedfilesharing/lib/RequestHandler.php b/apps/federatedfilesharing/lib/RequestHandler.php new file mode 100644 index 00000000000..01ab96822d8 --- /dev/null +++ b/apps/federatedfilesharing/lib/RequestHandler.php @@ -0,0 +1,580 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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\FederatedFileSharing; + +use OCA\Files_Sharing\Activity; +use OCP\AppFramework\Http; +use OCP\Constants; +use OCP\Files\NotFoundException; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\Share; + +/** + * Class RequestHandler + * + * handles OCS Request to the federated share API + * + * @package OCA\FederatedFileSharing\API + */ +class RequestHandler { + + /** @var FederatedShareProvider */ + private $federatedShareProvider; + + /** @var IDBConnection */ + private $connection; + + /** @var Share\IManager */ + private $shareManager; + + /** @var IRequest */ + private $request; + + /** @var Notifications */ + private $notifications; + + /** @var AddressHandler */ + private $addressHandler; + + /** @var IUserManager */ + private $userManager; + + /** @var string */ + private $shareTable = 'share'; + + /** + * Server2Server constructor. + * + * @param FederatedShareProvider $federatedShareProvider + * @param IDBConnection $connection + * @param Share\IManager $shareManager + * @param IRequest $request + * @param Notifications $notifications + * @param AddressHandler $addressHandler + * @param IUserManager $userManager + */ + public function __construct(FederatedShareProvider $federatedShareProvider, + IDBConnection $connection, + Share\IManager $shareManager, + IRequest $request, + Notifications $notifications, + AddressHandler $addressHandler, + IUserManager $userManager + ) { + $this->federatedShareProvider = $federatedShareProvider; + $this->connection = $connection; + $this->shareManager = $shareManager; + $this->request = $request; + $this->notifications = $notifications; + $this->addressHandler = $addressHandler; + $this->userManager = $userManager; + } + + /** + * create a new share + * + * @param array $params + * @return \OC_OCS_Result + */ + public function createShare($params) { + + if (!$this->isS2SEnabled(true)) { + return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); + } + + $remote = isset($_POST['remote']) ? $_POST['remote'] : null; + $token = isset($_POST['token']) ? $_POST['token'] : null; + $name = isset($_POST['name']) ? $_POST['name'] : null; + $owner = isset($_POST['owner']) ? $_POST['owner'] : null; + $sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null; + $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; + $remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null; + $sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null; + $ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null; + + if ($remote && $token && $name && $owner && $remoteId && $shareWith) { + + if(!\OCP\Util::isValidFileName($name)) { + return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.'); + } + + // FIXME this should be a method in the user management instead + \OCP\Util::writeLog('files_sharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG); + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + array('uid' => &$shareWith) + ); + \OCP\Util::writeLog('files_sharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG); + + if (!\OCP\User::userExists($shareWith)) { + return new \OC_OCS_Result(null, 400, 'User does not exists'); + } + + \OC_Util::setupFS($shareWith); + + $discoveryManager = new DiscoveryManager( + \OC::$server->getMemCacheFactory(), + \OC::$server->getHTTPClientService() + ); + $externalManager = new \OCA\Files_Sharing\External\Manager( + \OC::$server->getDatabaseConnection(), + \OC\Files\Filesystem::getMountManager(), + \OC\Files\Filesystem::getLoader(), + \OC::$server->getHTTPHelper(), + \OC::$server->getNotificationManager(), + $discoveryManager, + $shareWith + ); + + try { + $externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId); + $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external'); + + if ($ownerFederatedId === null) { + $ownerFederatedId = $owner . '@' . $this->cleanupRemote($remote); + } + // if the owner of the share and the initiator are the same user + // we also complete the federated share ID for the initiator + if ($sharedByFederatedId === null && $owner === $sharedBy) { + $sharedByFederatedId = $ownerFederatedId; + } + + \OC::$server->getActivityManager()->publishActivity( + Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_RECEIVED, array($ownerFederatedId, trim($name, '/')), '', array(), + '', '', $shareWith, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_LOW); + + $urlGenerator = \OC::$server->getURLGenerator(); + + $notificationManager = \OC::$server->getNotificationManager(); + $notification = $notificationManager->createNotification(); + $notification->setApp('files_sharing') + ->setUser($shareWith) + ->setDateTime(new \DateTime()) + ->setObject('remote_share', $shareId) + ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]); + + $declineAction = $notification->createAction(); + $declineAction->setLabel('decline') + ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE'); + $notification->addAction($declineAction); + + $acceptAction = $notification->createAction(); + $acceptAction->setLabel('accept') + ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST'); + $notification->addAction($acceptAction); + + $notificationManager->notify($notification); + + return new \OC_OCS_Result(); + } catch (\Exception $e) { + \OCP\Util::writeLog('files_sharing', 'server can not add remote share, ' . $e->getMessage(), \OCP\Util::ERROR); + return new \OC_OCS_Result(null, 500, 'internal server error, was not able to add share from ' . $remote); + } + } + + return new \OC_OCS_Result(null, 400, 'server can not add remote share, missing parameter'); + } + + /** + * create re-share on behalf of another user + * + * @param $params + * @return \OC_OCS_Result + */ + public function reShare($params) { + + $id = isset($params['id']) ? (int)$params['id'] : null; + $token = $this->request->getParam('token', null); + $shareWith = $this->request->getParam('shareWith', null); + $permission = (int)$this->request->getParam('permission', null); + $remoteId = (int)$this->request->getParam('remoteId', null); + + if ($id === null || + $token === null || + $shareWith === null || + $permission === null || + $remoteId === null + ) { + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + } + + try { + $share = $this->federatedShareProvider->getShareById($id); + } catch (Share\Exceptions\ShareNotFound $e) { + return new \OC_OCS_Result(null, Http::STATUS_NOT_FOUND); + } + + // don't allow to share a file back to the owner + list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith); + $owner = $share->getShareOwner(); + $currentServer = $this->addressHandler->generateRemoteURL(); + if ($this->addressHandler->compareAddresses($user, $remote,$owner , $currentServer)) { + return new \OC_OCS_Result(null, Http::STATUS_FORBIDDEN); + } + + if ($this->verifyShare($share, $token)) { + + // check if re-sharing is allowed + if ($share->getPermissions() | ~Constants::PERMISSION_SHARE) { + $share->setPermissions($share->getPermissions() & $permission); + // the recipient of the initial share is now the initiator for the re-share + $share->setSharedBy($share->getSharedWith()); + $share->setSharedWith($shareWith); + try { + $result = $this->federatedShareProvider->create($share); + $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId); + return new \OC_OCS_Result(['token' => $result->getToken(), 'remoteId' => $result->getId()]); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + } + } else { + return new \OC_OCS_Result(null, Http::STATUS_FORBIDDEN); + } + } + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + + } + + /** + * accept server-to-server share + * + * @param array $params + * @return \OC_OCS_Result + */ + public function acceptShare($params) { + + if (!$this->isS2SEnabled()) { + return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); + } + + $id = $params['id']; + $token = isset($_POST['token']) ? $_POST['token'] : null; + + try { + $share = $this->federatedShareProvider->getShareById($id); + } catch (Share\Exceptions\ShareNotFound $e) { + return new \OC_OCS_Result(); + } + + if ($this->verifyShare($share, $token)) { + $this->executeAcceptShare($share); + if ($share->getShareOwner() !== $share->getSharedBy()) { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy()); + $remoteId = $this->federatedShareProvider->getRemoteId($share); + $this->notifications->sendAcceptShare($remote, $remoteId, $share->getToken()); + } + } + + return new \OC_OCS_Result(); + } + + protected function executeAcceptShare(Share\IShare $share) { + list($file, $link) = $this->getFile($this->getCorrectUid($share), $share->getNode()->getId()); + + $event = \OC::$server->getActivityManager()->generateEvent(); + $event->setApp(Activity::FILES_SHARING_APP) + ->setType(Activity::TYPE_REMOTE_SHARE) + ->setAffectedUser($this->getCorrectUid($share)) + ->setSubject(Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), basename($file)]) + ->setObject('files', $share->getNode()->getId(), $file) + ->setLink($link); + \OC::$server->getActivityManager()->publish($event); + } + + /** + * decline server-to-server share + * + * @param array $params + * @return \OC_OCS_Result + */ + public function declineShare($params) { + + if (!$this->isS2SEnabled()) { + return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); + } + + $id = (int)$params['id']; + $token = isset($_POST['token']) ? $_POST['token'] : null; + + try { + $share = $this->federatedShareProvider->getShareById($id); + } catch (Share\Exceptions\ShareNotFound $e) { + return new \OC_OCS_Result(); + } + + if($this->verifyShare($share, $token)) { + if ($share->getShareOwner() !== $share->getSharedBy()) { + list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy()); + $remoteId = $this->federatedShareProvider->getRemoteId($share); + $this->notifications->sendDeclineShare($remote, $remoteId, $share->getToken()); + } + $this->executeDeclineShare($share); + } + + return new \OC_OCS_Result(); + } + + /** + * delete declined share and create a activity + * + * @param Share\IShare $share + */ + protected function executeDeclineShare(Share\IShare $share) { + $this->federatedShareProvider->removeShareFromTable($share); + list($file, $link) = $this->getFile($this->getCorrectUid($share), $share->getNode()->getId()); + + $event = \OC::$server->getActivityManager()->generateEvent(); + $event->setApp(Activity::FILES_SHARING_APP) + ->setType(Activity::TYPE_REMOTE_SHARE) + ->setAffectedUser($this->getCorrectUid($share)) + ->setSubject(Activity::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), basename($file)]) + ->setObject('files', $share->getNode()->getId(), $file) + ->setLink($link); + \OC::$server->getActivityManager()->publish($event); + + } + + /** + * check if we are the initiator or the owner of a re-share and return the correct UID + * + * @param Share\IShare $share + * @return string + */ + protected function getCorrectUid(Share\IShare $share) { + if($this->userManager->userExists($share->getShareOwner())) { + return $share->getShareOwner(); + } + + return $share->getSharedBy(); + } + + /** + * remove server-to-server share if it was unshared by the owner + * + * @param array $params + * @return \OC_OCS_Result + */ + public function unshare($params) { + + if (!$this->isS2SEnabled()) { + return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); + } + + $id = $params['id']; + $token = isset($_POST['token']) ? $_POST['token'] : null; + + $query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); + $query->execute(array($id, $token)); + $share = $query->fetchRow(); + + if ($token && $id && !empty($share)) { + + $remote = $this->cleanupRemote($share['remote']); + + $owner = $share['owner'] . '@' . $remote; + $mountpoint = $share['mountpoint']; + $user = $share['user']; + + $query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); + $query->execute(array($id, $token)); + + if ($share['accepted']) { + $path = trim($mountpoint, '/'); + } else { + $path = trim($share['name'], '/'); + } + + $notificationManager = \OC::$server->getNotificationManager(); + $notification = $notificationManager->createNotification(); + $notification->setApp('files_sharing') + ->setUser($share['user']) + ->setObject('remote_share', (int) $share['id']); + $notificationManager->markProcessed($notification); + + \OC::$server->getActivityManager()->publishActivity( + Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_UNSHARED, array($owner, $path), '', array(), + '', '', $user, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_MEDIUM); + } + + return new \OC_OCS_Result(); + } + + private function cleanupRemote($remote) { + $remote = substr($remote, strpos($remote, '://') + 3); + + return rtrim($remote, '/'); + } + + + /** + * federated share was revoked, either by the owner or the re-sharer + * + * @param $params + * @return \OC_OCS_Result + */ + public function revoke($params) { + $id = (int)$params['id']; + $token = $this->request->getParam('token'); + + $share = $this->federatedShareProvider->getShareById($id); + + if ($this->verifyShare($share, $token)) { + $this->federatedShareProvider->removeShareFromTable($share); + return new \OC_OCS_Result(); + } + + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + + } + + /** + * get share + * + * @param int $id + * @param string $token + * @return array|bool + */ + protected function getShare($id, $token) { + $query = $this->connection->getQueryBuilder(); + $query->select('*')->from($this->shareTable) + ->where($query->expr()->eq('token', $query->createNamedParameter($token))) + ->andWhere($query->expr()->eq('share_type', $query->createNamedParameter(FederatedShareProvider::SHARE_TYPE_REMOTE))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id))); + + $result = $query->execute()->fetchAll(); + + if (!empty($result) && isset($result[0])) { + return $result[0]; + } + + return false; + } + + /** + * get file + * + * @param string $user + * @param int $fileSource + * @return array with internal path of the file and a absolute link to it + */ + private function getFile($user, $fileSource) { + \OC_Util::setupFS($user); + + try { + $file = \OC\Files\Filesystem::getPath($fileSource); + } catch (NotFoundException $e) { + $file = null; + } + $args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file); + $link = \OCP\Util::linkToAbsolute('files', 'index.php', $args); + + return array($file, $link); + + } + + /** + * check if server-to-server sharing is enabled + * + * @param bool $incoming + * @return bool + */ + private function isS2SEnabled($incoming = false) { + + $result = \OCP\App::isEnabled('files_sharing'); + + if ($incoming) { + $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled(); + } else { + $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled(); + } + + return $result; + } + + /** + * check if we got the right share + * + * @param Share\IShare $share + * @param string $token + * @return bool + */ + protected function verifyShare(Share\IShare $share, $token) { + if ( + $share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE && + $share->getToken() === $token + ) { + return true; + } + + return false; + } + + /** + * update share information to keep federated re-shares in sync + * + * @param array $params + * @return \OC_OCS_Result + */ + public function updatePermissions($params) { + $id = (int)$params['id']; + $token = $this->request->getParam('token', null); + $permissions = $this->request->getParam('permissions', null); + + try { + $share = $this->federatedShareProvider->getShareById($id); + } catch (Share\Exceptions\ShareNotFound $e) { + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + } + + $validPermission = ctype_digit($permissions); + $validToken = $this->verifyShare($share, $token); + if ($validPermission && $validToken) { + $this->updatePermissionsInDatabase($share, (int)$permissions); + } else { + return new \OC_OCS_Result(null, Http::STATUS_BAD_REQUEST); + } + + return new \OC_OCS_Result(); + } + + /** + * update permissions in database + * + * @param IShare $share + * @param int $permissions + */ + protected function updatePermissionsInDatabase(IShare $share, $permissions) { + $query = $this->connection->getQueryBuilder(); + $query->update('share') + ->where($query->expr()->eq('id', $query->createNamedParameter($share->getId()))) + ->set('permissions', $query->createNamedParameter($permissions)) + ->execute(); + } + +} diff --git a/apps/files_sharing/lib/notifier.php b/apps/federatedfilesharing/lib/notifier.php index 27e4e2565f2..1c8d92c7eca 100644 --- a/apps/files_sharing/lib/notifier.php +++ b/apps/federatedfilesharing/lib/notifier.php @@ -19,7 +19,7 @@ * */ -namespace OCA\Files_Sharing; +namespace OCA\FederatedFileSharing; use OCP\Notification\INotification; @@ -54,9 +54,15 @@ class Notifier implements INotifier { // Deal with known subjects case 'remote_share': $params = $notification->getSubjectParameters(); - $notification->setParsedSubject( - (string) $l->t('You received "/%2$s" as a remote share from %1$s', $params) - ); + if ($params[0] !== $params[1] && $params[1] !== null) { + $notification->setParsedSubject( + (string) $l->t('You received "/%3$s" as a remote share from %1$s (on behalf of %2$s)', $params) + ); + } else { + $notification->setParsedSubject( + (string)$l->t('You received "/%3$s" as a remote share from %1$s', $params) + ); + } // Deal with the actions for a known subject foreach ($notification->getActions() as $action) { diff --git a/apps/federatedfilesharing/tests/AddressHandlerTest.php b/apps/federatedfilesharing/tests/AddressHandlerTest.php index 9f7d8c49b4d..bb1c2c5a25a 100644 --- a/apps/federatedfilesharing/tests/AddressHandlerTest.php +++ b/apps/federatedfilesharing/tests/AddressHandlerTest.php @@ -27,9 +27,8 @@ namespace OCA\FederatedFileSharing\Tests; use OCA\FederatedFileSharing\AddressHandler; use OCP\IL10N; use OCP\IURLGenerator; -use Test\TestCase; -class AddressHandlerTest extends TestCase { +class AddressHandlerTest extends \Test\TestCase { /** @var AddressHandler */ private $addressHandler; diff --git a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php b/apps/federatedfilesharing/tests/DiscoveryManagerTest.php index 9ae62b1ae4d..5af6b394dd2 100644 --- a/apps/federatedfilesharing/tests/DiscoveryManagerTest.php +++ b/apps/federatedfilesharing/tests/DiscoveryManagerTest.php @@ -26,9 +26,8 @@ use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\ICache; use OCP\ICacheFactory; -use Test\TestCase; -class DiscoveryManagerTest extends TestCase { +class DiscoveryManagerTest extends \Test\TestCase { /** @var ICache */ private $cache; /** @var IClient */ diff --git a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php index 1fbae90a46f..9b3edf0398d 100644 --- a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php +++ b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php @@ -30,8 +30,8 @@ use OCP\IConfig; use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; +use OCP\IUserManager; use OCP\Share\IManager; -use Test\TestCase; /** * Class FederatedShareProviderTest @@ -39,7 +39,7 @@ use Test\TestCase; * @package OCA\FederatedFileSharing\Tests * @group DB */ -class FederatedShareProviderTest extends TestCase { +class FederatedShareProviderTest extends \Test\TestCase { /** @var IDBConnection */ protected $connection; @@ -57,6 +57,8 @@ class FederatedShareProviderTest extends TestCase { protected $rootFolder; /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ protected $config; + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; /** @var IManager */ protected $shareManager; @@ -82,7 +84,11 @@ class FederatedShareProviderTest extends TestCase { $this->logger = $this->getMock('OCP\ILogger'); $this->rootFolder = $this->getMock('OCP\Files\IRootFolder'); $this->config = $this->getMock('OCP\IConfig'); - $this->addressHandler = new AddressHandler(\OC::$server->getURLGenerator(), $this->l); + $this->userManager = $this->getMock('OCP\IUserManager'); + //$this->addressHandler = new AddressHandler(\OC::$server->getURLGenerator(), $this->l); + $this->addressHandler = $this->getMockBuilder('OCA\FederatedFileSharing\AddressHandler')->disableOriginalConstructor()->getMock(); + + $this->userManager->expects($this->any())->method('userExists')->willReturn(true); $this->provider = new FederatedShareProvider( $this->connection, @@ -92,7 +98,8 @@ class FederatedShareProviderTest extends TestCase { $this->l, $this->logger, $this->rootFolder, - $this->config + $this->config, + $this->userManager ); $this->shareManager = \OC::$server->getShareManager(); @@ -119,6 +126,11 @@ class FederatedShareProviderTest extends TestCase { $this->tokenHandler->method('generateToken')->willReturn('token'); + $this->addressHandler->expects($this->any())->method('generateRemoteURL') + ->willReturn('http://localhost/'); + $this->addressHandler->expects($this->any())->method('splitUserRemote') + ->willReturn(['user', 'server.com']); + $this->notifications->expects($this->once()) ->method('sendRemoteShare') ->with( @@ -126,7 +138,10 @@ class FederatedShareProviderTest extends TestCase { $this->equalTo('user@server.com'), $this->equalTo('myFile'), $this->anything(), - 'sharedBy' + 'shareOwner', + 'shareOwner@http://localhost/', + 'sharedBy', + 'sharedBy@http://localhost/' )->willReturn(true); $this->rootFolder->expects($this->never())->method($this->anything()); @@ -182,6 +197,11 @@ class FederatedShareProviderTest extends TestCase { $this->tokenHandler->method('generateToken')->willReturn('token'); + $this->addressHandler->expects($this->any())->method('generateRemoteURL') + ->willReturn('http://localhost/'); + $this->addressHandler->expects($this->any())->method('splitUserRemote') + ->willReturn(['user', 'server.com']); + $this->notifications->expects($this->once()) ->method('sendRemoteShare') ->with( @@ -189,7 +209,10 @@ class FederatedShareProviderTest extends TestCase { $this->equalTo('user@server.com'), $this->equalTo('myFile'), $this->anything(), - 'sharedBy' + 'shareOwner', + 'shareOwner@http://localhost/', + 'sharedBy', + 'sharedBy@http://localhost/' )->willReturn(false); $this->rootFolder->expects($this->once()) @@ -226,7 +249,10 @@ class FederatedShareProviderTest extends TestCase { $node->method('getId')->willReturn(42); $node->method('getName')->willReturn('myFile'); - $shareWith = 'sharedBy@' . $this->addressHandler->generateRemoteURL(); + $this->addressHandler->expects($this->any())->method('compareAddresses') + ->willReturn(true); + + $shareWith = 'sharedBy@localhost'; $share->setSharedWith($shareWith) ->setSharedBy('sharedBy') @@ -262,6 +288,10 @@ class FederatedShareProviderTest extends TestCase { $node->method('getId')->willReturn(42); $node->method('getName')->willReturn('myFile'); + + $this->addressHandler->expects($this->any())->method('splitUserRemote') + ->willReturn(['user', 'server.com']); + $share->setSharedWith('user@server.com') ->setSharedBy('sharedBy') ->setShareOwner('shareOwner') @@ -270,6 +300,9 @@ class FederatedShareProviderTest extends TestCase { $this->tokenHandler->method('generateToken')->willReturn('token'); + $this->addressHandler->expects($this->any())->method('generateRemoteURL') + ->willReturn('http://localhost/'); + $this->notifications->expects($this->once()) ->method('sendRemoteShare') ->with( @@ -277,7 +310,10 @@ class FederatedShareProviderTest extends TestCase { $this->equalTo('user@server.com'), $this->equalTo('myFile'), $this->anything(), - 'sharedBy' + 'shareOwner', + 'shareOwner@http://localhost/', + 'sharedBy', + 'sharedBy@http://localhost/' )->willReturn(true); $this->rootFolder->expects($this->never())->method($this->anything()); @@ -291,20 +327,46 @@ class FederatedShareProviderTest extends TestCase { } } - public function testUpdate() { + /** + * @dataProvider datatTestUpdate + * + */ + public function testUpdate($owner, $sharedBy) { + + $this->provider = $this->getMockBuilder('OCA\FederatedFileSharing\FederatedShareProvider') + ->setConstructorArgs( + [ + $this->connection, + $this->addressHandler, + $this->notifications, + $this->tokenHandler, + $this->l, + $this->logger, + $this->rootFolder, + $this->config, + $this->userManager + ] + )->setMethods(['sendPermissionUpdate'])->getMock(); + $share = $this->shareManager->newShare(); $node = $this->getMock('\OCP\Files\File'); $node->method('getId')->willReturn(42); $node->method('getName')->willReturn('myFile'); + + $this->addressHandler->expects($this->any())->method('splitUserRemote') + ->willReturn(['user', 'server.com']); + $share->setSharedWith('user@server.com') - ->setSharedBy('sharedBy') - ->setShareOwner('shareOwner') + ->setSharedBy($sharedBy) + ->setShareOwner($owner) ->setPermissions(19) ->setNode($node); $this->tokenHandler->method('generateToken')->willReturn('token'); + $this->addressHandler->expects($this->any())->method('generateRemoteURL') + ->willReturn('http://localhost/'); $this->notifications->expects($this->once()) ->method('sendRemoteShare') @@ -313,9 +375,18 @@ class FederatedShareProviderTest extends TestCase { $this->equalTo('user@server.com'), $this->equalTo('myFile'), $this->anything(), - 'sharedBy' + $owner, + $owner . '@http://localhost/', + $sharedBy, + $sharedBy . '@http://localhost/' )->willReturn(true); + if($owner === $sharedBy) { + $this->provider->expects($this->never())->method('sendPermissionUpdate'); + } else { + $this->provider->expects($this->once())->method('sendPermissionUpdate'); + } + $this->rootFolder->expects($this->never())->method($this->anything()); $share = $this->provider->create($share); @@ -328,11 +399,24 @@ class FederatedShareProviderTest extends TestCase { $this->assertEquals(1, $share->getPermissions()); } + public function datatTestUpdate() { + return [ + ['sharedBy', 'shareOwner'], + ['shareOwner', 'shareOwner'] + ]; + } + public function testGetSharedBy() { $node = $this->getMock('\OCP\Files\File'); $node->method('getId')->willReturn(42); $node->method('getName')->willReturn('myFile'); + $this->addressHandler->expects($this->at(0))->method('splitUserRemote') + ->willReturn(['user', 'server.com']); + + $this->addressHandler->expects($this->at(1))->method('splitUserRemote') + ->willReturn(['user2', 'server.com']); + $this->tokenHandler->method('generateToken')->willReturn('token'); $this->notifications ->method('sendRemoteShare') @@ -439,6 +523,14 @@ class FederatedShareProviderTest extends TestCase { $node->method('getId')->willReturn(42); $node->method('getName')->willReturn('myFile'); + $this->addressHandler->expects($this->any())->method('splitUserRemote') + ->willReturnCallback(function ($uid) { + if ($uid === 'user@server.com') { + return ['user', 'server.com']; + } + return ['user2', 'server.com']; + }); + $this->tokenHandler->method('generateToken')->willReturn('token'); $this->notifications ->method('sendRemoteShare') diff --git a/apps/federatedfilesharing/tests/NotificationsTest.php b/apps/federatedfilesharing/tests/NotificationsTest.php index bde69a82bad..50a62e9829e 100644 --- a/apps/federatedfilesharing/tests/NotificationsTest.php +++ b/apps/federatedfilesharing/tests/NotificationsTest.php @@ -28,9 +28,8 @@ use OCA\FederatedFileSharing\DiscoveryManager; use OCA\FederatedFileSharing\Notifications; use OCP\BackgroundJob\IJobList; use OCP\Http\Client\IClientService; -use Test\TestCase; -class NotificationsTest extends TestCase { +class NotificationsTest extends \Test\TestCase { /** @var AddressHandler | \PHPUnit_Framework_MockObject_MockObject */ private $addressHandler; @@ -85,14 +84,15 @@ class NotificationsTest extends TestCase { return $instance; } + /** - * @dataProvider dataTestSendRemoteUnShare + * @dataProvider dataTestSendUpdateToRemote * * @param int $try * @param array $httpRequestResult * @param bool $expected */ - public function testSendRemoteUnShare($try, $httpRequestResult, $expected) { + public function testSendUpdateToRemote($try, $httpRequestResult, $expected) { $remote = 'remote'; $id = 42; $timestamp = 63576; @@ -102,20 +102,22 @@ class NotificationsTest extends TestCase { $instance->expects($this->any())->method('getTimestamp')->willReturn($timestamp); $instance->expects($this->once())->method('tryHttpPostToShareEndpoint') - ->with($remote, '/'.$id.'/unshare', ['token' => $token, 'format' => 'json']) + ->with($remote, '/'.$id.'/unshare', ['token' => $token, 'data1Key' => 'data1Value']) ->willReturn($httpRequestResult); $this->addressHandler->expects($this->once())->method('removeProtocolFromUrl') ->with($remote)->willReturn($remote); - + // only add background job on first try if ($try === 0 && $expected === false) { $this->jobList->expects($this->once())->method('add') ->with( - 'OCA\FederatedFileSharing\BackgroundJob\UnShare', + 'OCA\FederatedFileSharing\BackgroundJob\RetryJob', [ 'remote' => $remote, - 'id' => $id, + 'remoteId' => $id, + 'action' => 'unshare', + 'data' => json_encode(['data1Key' => 'data1Value']), 'token' => $token, 'try' => $try, 'lastRun' => $timestamp @@ -124,14 +126,15 @@ class NotificationsTest extends TestCase { } else { $this->jobList->expects($this->never())->method('add'); } - + $this->assertSame($expected, - $instance->sendRemoteUnShare($remote, $id, $token, $try) + $instance->sendUpdateToRemote($remote, $id, $token, 'unshare', ['data1Key' => 'data1Value'], $try) ); - + } - - public function dataTestSendRemoteUnshare() { + + + public function dataTestSendUpdateToRemote() { return [ // test if background job is added correctly [0, ['success' => true, 'result' => json_encode(['ocs' => ['meta' => ['statuscode' => 200]]])], true], diff --git a/apps/files_sharing/tests/server2server.php b/apps/federatedfilesharing/tests/RequestHandlerTest.php index 1c8b5ed7a17..14da756a0ef 100644 --- a/apps/files_sharing/tests/server2server.php +++ b/apps/federatedfilesharing/tests/RequestHandlerTest.php @@ -23,14 +23,22 @@ * */ -use OCA\Files_Sharing\Tests\TestCase; +namespace OCA\FederatedFileSharing\Tests; + +use OC\Files\Filesystem; +use OCA\FederatedFileSharing\DiscoveryManager; +use OCA\FederatedFileSharing\FederatedShareProvider; +use OCA\FederatedFileSharing\RequestHandler; +use OCP\IUserManager; +use OCP\Share\IShare; /** - * Class Test_Files_Sharing_Api + * Class RequestHandlerTest * + * @package OCA\FederatedFileSharing\Tests * @group DB */ -class Test_Files_Sharing_S2S_OCS_API extends TestCase { +class RequestHandlerTest extends TestCase { const TEST_FOLDER_NAME = '/folder_share_api_test'; @@ -40,13 +48,25 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { private $connection; /** - * @var \OCA\Files_Sharing\API\Server2Server + * @var RequestHandler */ private $s2s; /** @var \OCA\FederatedFileSharing\FederatedShareProvider | PHPUnit_Framework_MockObject_MockObject */ private $federatedShareProvider; + /** @var \OCA\FederatedFileSharing\Notifications | PHPUnit_Framework_MockObject_MockObject */ + private $notifications; + + /** @var \OCA\FederatedFileSharing\AddressHandler | PHPUnit_Framework_MockObject_MockObject */ + private $addressHandler; + + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + + /** @var IShare | \PHPUnit_Framework_MockObject_MockObject */ + private $share; + protected function setUp() { parent::setUp(); @@ -60,16 +80,33 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { ->setConstructorArgs([$config, $clientService]) ->getMock(); $httpHelperMock->expects($this->any())->method('post')->with($this->anything())->will($this->returnValue(true)); + $this->share = $this->getMock('\OCP\Share\IShare'); $this->federatedShareProvider = $this->getMockBuilder('OCA\FederatedFileSharing\FederatedShareProvider') ->disableOriginalConstructor()->getMock(); $this->federatedShareProvider->expects($this->any()) ->method('isOutgoingServer2serverShareEnabled')->willReturn(true); $this->federatedShareProvider->expects($this->any()) ->method('isIncomingServer2serverShareEnabled')->willReturn(true); + $this->federatedShareProvider->expects($this->any())->method('getShareById') + ->willReturn($this->share); + $this->notifications = $this->getMockBuilder('OCA\FederatedFileSharing\Notifications') + ->disableOriginalConstructor()->getMock(); + $this->addressHandler = $this->getMockBuilder('OCA\FederatedFileSharing\AddressHandler') + ->disableOriginalConstructor()->getMock(); + $this->userManager = $this->getMock('OCP\IUserManager'); + $this->registerHttpHelper($httpHelperMock); - $this->s2s = new \OCA\Files_Sharing\API\Server2Server($this->federatedShareProvider); + $this->s2s = new RequestHandler( + $this->federatedShareProvider, + \OC::$server->getDatabaseConnection(), + \OC::$server->getShareManager(), + \OC::$server->getRequest(), + $this->notifications, + $this->addressHandler, + $this->userManager + ); $this->connection = \OC::$server->getDatabaseConnection(); } @@ -78,6 +115,9 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { $query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external`'); $query->execute(); + $query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share`'); + $query->execute(); + $this->restoreHttpHelper(); parent::tearDown(); @@ -135,28 +175,34 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { function testDeclineShare() { - $dummy = \OCP\DB::prepare(' - INSERT INTO `*PREFIX*share` - (`share_type`, `uid_owner`, `item_type`, `item_source`, `item_target`, `file_source`, `file_target`, `permissions`, `stime`, `token`, `share_with`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - '); - $dummy->execute(array(\OCP\Share::SHARE_TYPE_REMOTE, self::TEST_FILES_SHARING_API_USER1, 'test', '1', '/1', '1', '/test.txt', '1', time(), 'token', 'foo@bar')); - $verify = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); - $result = $verify->execute(); - $data = $result->fetchAll(); - $this->assertSame(1, count($data)); + $this->s2s = $this->getMockBuilder('\OCA\FederatedFileSharing\RequestHandler') + ->setConstructorArgs( + [ + $this->federatedShareProvider, + \OC::$server->getDatabaseConnection(), + \OC::$server->getShareManager(), + \OC::$server->getRequest(), + $this->notifications, + $this->addressHandler, + $this->userManager + ] + )->setMethods(['executeDeclineShare', 'verifyShare'])->getMock(); + + $this->s2s->expects($this->once())->method('executeDeclineShare'); + + $this->s2s->expects($this->any())->method('verifyShare')->willReturn(true); $_POST['token'] = 'token'; - $this->s2s->declineShare(array('id' => $data[0]['id'])); - $verify = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share`'); - $result = $verify->execute(); - $data = $result->fetchAll(); - $this->assertEmpty($data); + $this->s2s->declineShare(array('id' => 42)); + } - function testDeclineShareMultiple() { + function XtestDeclineShareMultiple() { + + $this->share->expects($this->any())->method('verifyShare')->willReturn(true); + $dummy = \OCP\DB::prepare(' INSERT INTO `*PREFIX*share` (`share_type`, `uid_owner`, `item_type`, `item_source`, `item_target`, `file_source`, `file_target`, `permissions`, `stime`, `token`, `share_with`) @@ -194,14 +240,14 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { function testDeleteUser($toDelete, $expected, $remainingUsers) { $this->createDummyS2SShares(); - $discoveryManager = new \OCA\FederatedFileSharing\DiscoveryManager( + $discoveryManager = new DiscoveryManager( \OC::$server->getMemCacheFactory(), \OC::$server->getHTTPClientService() ); - $manager = new OCA\Files_Sharing\External\Manager( + $manager = new \OCA\Files_Sharing\External\Manager( \OC::$server->getDatabaseConnection(), - \OC\Files\Filesystem::getMountManager(), - \OC\Files\Filesystem::getLoader(), + Filesystem::getMountManager(), + Filesystem::getLoader(), \OC::$server->getHTTPHelper(), \OC::$server->getNotificationManager(), $discoveryManager, @@ -260,4 +306,76 @@ class Test_Files_Sharing_S2S_OCS_API extends TestCase { $this->assertSame(10, count($dummyEntries)); } + /** + * @dataProvider dataTestGetShare + * + * @param bool $found + * @param bool $correctId + * @param bool $correctToken + */ + public function testGetShare($found, $correctId, $correctToken) { + + $connection = \OC::$server->getDatabaseConnection(); + $query = $connection->getQueryBuilder(); + $stime = time(); + $query->insert('share') + ->values( + [ + 'share_type' => $query->createNamedParameter(FederatedShareProvider::SHARE_TYPE_REMOTE), + 'uid_owner' => $query->createNamedParameter(self::TEST_FILES_SHARING_API_USER1), + 'uid_initiator' => $query->createNamedParameter(self::TEST_FILES_SHARING_API_USER2), + 'item_type' => $query->createNamedParameter('test'), + 'item_source' => $query->createNamedParameter('1'), + 'item_target' => $query->createNamedParameter('/1'), + 'file_source' => $query->createNamedParameter('1'), + 'file_target' => $query->createNamedParameter('/test.txt'), + 'permissions' => $query->createNamedParameter('1'), + 'stime' => $query->createNamedParameter($stime), + 'token' => $query->createNamedParameter('token'), + 'share_with' => $query->createNamedParameter('foo@bar'), + ] + )->execute(); + $id = $query->getLastInsertId(); + + $expected = [ + 'share_type' => (string)FederatedShareProvider::SHARE_TYPE_REMOTE, + 'uid_owner' => self::TEST_FILES_SHARING_API_USER1, + 'item_type' => 'test', + 'item_source' => '1', + 'item_target' => '/1', + 'file_source' => '1', + 'file_target' => '/test.txt', + 'permissions' => '1', + 'stime' => (string)$stime, + 'token' => 'token', + 'share_with' => 'foo@bar', + 'id' => (string)$id, + 'uid_initiator' => self::TEST_FILES_SHARING_API_USER2, + 'parent' => null, + 'accepted' => '0', + 'expiration' => null, + 'mail_send' => '0' + ]; + + $searchToken = $correctToken ? 'token' : 'wrongToken'; + $searchId = $correctId ? $id : -1; + + $result = $this->invokePrivate($this->s2s, 'getShare', [$searchId, $searchToken]); + + if ($found) { + $this->assertEquals($expected, $result); + } else { + $this->assertSame(false, $result); + } + } + + public function dataTestGetShare() { + return [ + [true, true, true], + [false, false, true], + [false, true, false], + [false, false, false], + ]; + } + } diff --git a/apps/federatedfilesharing/tests/TestCase.php b/apps/federatedfilesharing/tests/TestCase.php new file mode 100644 index 00000000000..64c6d045598 --- /dev/null +++ b/apps/federatedfilesharing/tests/TestCase.php @@ -0,0 +1,132 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\FederatedFileSharing\Tests; + +use OC\Files\Filesystem; +use OCA\Files\Share; + +/** + * Class Test_Files_Sharing_Base + * + * @group DB + * + * Base class for sharing tests. + */ +abstract class TestCase extends \Test\TestCase { + + const TEST_FILES_SHARING_API_USER1 = "test-share-user1"; + const TEST_FILES_SHARING_API_USER2 = "test-share-user2"; + + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + + // reset backend + \OC_User::clearBackends(); + \OC_Group::clearBackends(); + + // create users + $backend = new \Test\Util\User\Dummy(); + \OC_User::useBackend($backend); + $backend->createUser(self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER1); + $backend->createUser(self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER2); + } + + protected function setUp() { + parent::setUp(); + + //login as user1 + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + } + + public static function tearDownAfterClass() { + // cleanup users + $user = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER1); + if ($user !== null) { + $user->delete(); + } + $user = \OC::$server->getUserManager()->get(self::TEST_FILES_SHARING_API_USER2); + if ($user !== null) { + $user->delete(); + } + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + Filesystem::tearDown(); + + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + \OC_Group::clearBackends(); + \OC_Group::useBackend(new \OC_Group_Database()); + + parent::tearDownAfterClass(); + } + + /** + * @param string $user + * @param bool $create + * @param bool $password + */ + protected static function loginHelper($user, $create = false, $password = false) { + + if ($password === false) { + $password = $user; + } + + if ($create) { + \OC::$server->getUserManager()->createUser($user, $password); + \OC_Group::createGroup('group'); + \OC_Group::addToGroup($user, 'group'); + } + + self::resetStorage(); + + \OC_Util::tearDownFS(); + \OC::$server->getUserSession()->setUser(null); + \OC\Files\Filesystem::tearDown(); + \OC::$server->getUserSession()->login($user, $password); + \OC::$server->getUserFolder($user); + + \OC_Util::setupFS($user); + } + + /** + * reset init status for the share storage + */ + protected static function resetStorage() { + $storage = new \ReflectionClass('\OC\Files\Storage\Shared'); + $isInitialized = $storage->getProperty('initialized'); + $isInitialized->setAccessible(true); + $isInitialized->setValue($storage, false); + $isInitialized->setAccessible(false); + } + +} diff --git a/apps/federatedfilesharing/tests/TokenHandlerTest.php b/apps/federatedfilesharing/tests/TokenHandlerTest.php index 490c0d95d7b..004a3a61933 100644 --- a/apps/federatedfilesharing/tests/TokenHandlerTest.php +++ b/apps/federatedfilesharing/tests/TokenHandlerTest.php @@ -26,9 +26,8 @@ namespace OCA\FederatedFileSharing\Tests; use OCA\FederatedFileSharing\TokenHandler; use OCP\Security\ISecureRandom; -use Test\TestCase; -class TokenHandlerTest extends TestCase { +class TokenHandlerTest extends \Test\TestCase { /** @var TokenHandler */ private $tokenHandler; diff --git a/apps/federation/api/ocsauthapi.php b/apps/federation/lib/API/OCSAuthAPI.php index 1c4e73cc8de..1c4e73cc8de 100644 --- a/apps/federation/api/ocsauthapi.php +++ b/apps/federation/lib/API/OCSAuthAPI.php diff --git a/apps/federation/appinfo/application.php b/apps/federation/lib/AppInfo/Application.php index 74185345329..74185345329 100644 --- a/apps/federation/appinfo/application.php +++ b/apps/federation/lib/AppInfo/Application.php diff --git a/apps/federation/backgroundjob/getsharedsecret.php b/apps/federation/lib/BackgroundJob/GetSharedSecret.php index 66ab082c1a2..66ab082c1a2 100644 --- a/apps/federation/backgroundjob/getsharedsecret.php +++ b/apps/federation/lib/BackgroundJob/GetSharedSecret.php diff --git a/apps/federation/backgroundjob/requestsharedsecret.php b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php index 040e8e1d8e2..040e8e1d8e2 100644 --- a/apps/federation/backgroundjob/requestsharedsecret.php +++ b/apps/federation/lib/BackgroundJob/RequestSharedSecret.php diff --git a/apps/federation/command/syncfederationaddressbooks.php b/apps/federation/lib/Command/SyncFederationAddressBooks.php index 879d38f8c22..879d38f8c22 100644 --- a/apps/federation/command/syncfederationaddressbooks.php +++ b/apps/federation/lib/Command/SyncFederationAddressBooks.php diff --git a/apps/federation/controller/settingscontroller.php b/apps/federation/lib/Controller/SettingsController.php index 3adb6fced66..3adb6fced66 100644 --- a/apps/federation/controller/settingscontroller.php +++ b/apps/federation/lib/Controller/SettingsController.php diff --git a/apps/federation/dav/fedauth.php b/apps/federation/lib/DAV/FedAuth.php index bb1041adcdf..bb1041adcdf 100644 --- a/apps/federation/dav/fedauth.php +++ b/apps/federation/lib/DAV/FedAuth.php diff --git a/apps/federation/lib/dbhandler.php b/apps/federation/lib/DbHandler.php index 8720560efc6..8720560efc6 100644 --- a/apps/federation/lib/dbhandler.php +++ b/apps/federation/lib/DbHandler.php diff --git a/apps/federation/lib/hooks.php b/apps/federation/lib/Hooks.php index b7f63d27f55..b7f63d27f55 100644 --- a/apps/federation/lib/hooks.php +++ b/apps/federation/lib/Hooks.php diff --git a/apps/federation/middleware/addservermiddleware.php b/apps/federation/lib/Middleware/AddServerMiddleware.php index 15781251349..4b752f51ee4 100644 --- a/apps/federation/middleware/addservermiddleware.php +++ b/apps/federation/lib/Middleware/AddServerMiddleware.php @@ -20,7 +20,7 @@ * */ -namespace OCA\Federation\Middleware ; +namespace OCA\Federation\Middleware; use OC\HintException; use OCP\AppFramework\Http; diff --git a/apps/federation/lib/syncfederationaddressbooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index 209094266ca..209094266ca 100644 --- a/apps/federation/lib/syncfederationaddressbooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php diff --git a/apps/federation/lib/syncjob.php b/apps/federation/lib/SyncJob.php index 2b904813b92..2b904813b92 100644 --- a/apps/federation/lib/syncjob.php +++ b/apps/federation/lib/SyncJob.php diff --git a/apps/federation/lib/trustedservers.php b/apps/federation/lib/TrustedServers.php index 3b356ea2a49..3b356ea2a49 100644 --- a/apps/federation/lib/trustedservers.php +++ b/apps/federation/lib/TrustedServers.php diff --git a/apps/federation/tests/api/ocsauthapitest.php b/apps/federation/tests/API/OCSAuthAPITest.php index d3e61c0641a..d3e61c0641a 100644 --- a/apps/federation/tests/api/ocsauthapitest.php +++ b/apps/federation/tests/API/OCSAuthAPITest.php diff --git a/apps/federation/tests/backgroundjob/getsharedsecrettest.php b/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php index 25f7502741d..25f7502741d 100644 --- a/apps/federation/tests/backgroundjob/getsharedsecrettest.php +++ b/apps/federation/tests/BackgroundJob/GetSharedSecretTest.php diff --git a/apps/federation/tests/backgroundjob/requestsharedsecrettest.php b/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php index 5b4a1f87a5f..2fc4fe881a6 100644 --- a/apps/federation/tests/backgroundjob/requestsharedsecrettest.php +++ b/apps/federation/tests/BackgroundJob/RequestSharedSecretTest.php @@ -24,7 +24,13 @@ namespace OCA\Federation\Tests\BackgroundJob; use OCA\Federation\BackgroundJob\RequestSharedSecret; +use OCA\Federation\DbHandler; +use OCA\Federation\TrustedServers; use OCP\AppFramework\Http; +use OCP\BackgroundJob\IJobList; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IResponse; +use OCP\IURLGenerator; use Test\TestCase; class RequestSharedSecretTest extends TestCase { diff --git a/apps/federation/tests/controller/settingscontrollertest.php b/apps/federation/tests/Controller/SettingsControllerTest.php index 65f7d5f91d3..65f7d5f91d3 100644 --- a/apps/federation/tests/controller/settingscontrollertest.php +++ b/apps/federation/tests/Controller/SettingsControllerTest.php diff --git a/apps/federation/tests/dav/fedauthtest.php b/apps/federation/tests/DAV/FedAuthTest.php index b716084a45d..b716084a45d 100644 --- a/apps/federation/tests/dav/fedauthtest.php +++ b/apps/federation/tests/DAV/FedAuthTest.php diff --git a/apps/federation/tests/lib/dbhandlertest.php b/apps/federation/tests/DbHandlerTest.php index 3ded486d36a..6e5b8f2f06c 100644 --- a/apps/federation/tests/lib/dbhandlertest.php +++ b/apps/federation/tests/DbHandlerTest.php @@ -21,7 +21,7 @@ */ -namespace OCA\Federation\Tests\lib; +namespace OCA\Federation\Tests; use OCA\Federation\DbHandler; diff --git a/apps/federation/tests/lib/hookstest.php b/apps/federation/tests/HooksTest.php index 71569226dd2..014829d9cf6 100644 --- a/apps/federation/tests/lib/hookstest.php +++ b/apps/federation/tests/HooksTest.php @@ -20,7 +20,7 @@ */ -namespace OCA\Federation\Tests\lib; +namespace OCA\Federation\Tests; use OCA\Federation\Hooks; diff --git a/apps/federation/tests/middleware/addservermiddlewaretest.php b/apps/federation/tests/Middleware/AddServerMiddlewareTest.php index be1d97c4035..be1d97c4035 100644 --- a/apps/federation/tests/middleware/addservermiddlewaretest.php +++ b/apps/federation/tests/Middleware/AddServerMiddlewareTest.php diff --git a/apps/federation/tests/lib/syncfederationaddressbookstest.php b/apps/federation/tests/SyncFederationAddressbooksTest.php index aa2bd9ac2cb..4c266fae3a0 100644 --- a/apps/federation/tests/lib/syncfederationaddressbookstest.php +++ b/apps/federation/tests/SyncFederationAddressbooksTest.php @@ -20,7 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ -namespace OCA\Federation\Tests\lib; +namespace OCA\Federation\Tests; use OCA\Federation\DbHandler; use OCA\Federation\SyncFederationAddressBooks; @@ -52,6 +52,7 @@ class SyncFederationAddressbooksTest extends \Test\TestCase { $syncService->expects($this->once())->method('syncRemoteAddressBook') ->willReturn(1); + /** @var \OCA\DAV\CardDAV\SyncService $syncService */ $s = new SyncFederationAddressBooks($dbHandler, $syncService); $s->syncThemAll(function($url, $ex) { $this->callBacks[] = [$url, $ex]; @@ -79,6 +80,7 @@ class SyncFederationAddressbooksTest extends \Test\TestCase { $syncService->expects($this->once())->method('syncRemoteAddressBook') ->willThrowException(new \Exception('something did not work out')); + /** @var \OCA\DAV\CardDAV\SyncService $syncService */ $s = new SyncFederationAddressBooks($dbHandler, $syncService); $s->syncThemAll(function($url, $ex) { $this->callBacks[] = [$url, $ex]; diff --git a/apps/federation/tests/lib/trustedserverstest.php b/apps/federation/tests/TrustedServersTest.php index a8c7c7afb1f..e49db2556be 100644 --- a/apps/federation/tests/lib/trustedserverstest.php +++ b/apps/federation/tests/TrustedServersTest.php @@ -21,7 +21,7 @@ */ -namespace OCA\Federation\Tests\lib; +namespace OCA\Federation\Tests; use OCA\Federation\DbHandler; @@ -38,7 +38,7 @@ use Test\TestCase; class TrustedServersTest extends TestCase { - /** @var TrustedServers */ + /** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers */ private $trustedServers; /** @var \PHPUnit_Framework_MockObject_MockObject | DbHandler */ @@ -101,7 +101,7 @@ class TrustedServersTest extends TestCase { * @param bool $success */ public function testAddServer($success) { - /** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers $trustedServer */ + /** @var \PHPUnit_Framework_MockObject_MockObject|TrustedServers $trustedServers */ $trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers') ->setConstructorArgs( [ @@ -172,7 +172,7 @@ class TrustedServersTest extends TestCase { ->with('federation', 'autoAddServers', '1')->willReturn($status); $this->assertSame($expected, - $this->trustedServers->getAutoAddServers($status) + $this->trustedServers->getAutoAddServers() ); } @@ -208,6 +208,7 @@ class TrustedServersTest extends TestCase { function($eventId, $event) { $this->assertSame($eventId, 'OCP\Federation\TrustedServerEvent::remove'); $this->assertInstanceOf('Symfony\Component\EventDispatcher\GenericEvent', $event); + /** @var \Symfony\Component\EventDispatcher\GenericEvent $event */ $this->assertSame('url_hash', $event->getSubject()); } ); @@ -215,9 +216,10 @@ class TrustedServersTest extends TestCase { } public function testGetServers() { - $this->dbHandler->expects($this->once())->method('getAllServer')->willReturn(true); + $this->dbHandler->expects($this->once())->method('getAllServer')->willReturn(['servers']); - $this->assertTrue( + $this->assertEquals( + ['servers'], $this->trustedServers->getServers() ); } @@ -257,7 +259,7 @@ class TrustedServersTest extends TestCase { $server = 'server1'; - /** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers $trustedServer */ + /** @var \PHPUnit_Framework_MockObject_MockObject | TrustedServers $trustedServers */ $trustedServers = $this->getMockBuilder('OCA\Federation\TrustedServers') ->setConstructorArgs( [ diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 55c12cc0ac9..8d2cb52d67c 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -33,8 +33,12 @@ var MOUNT_OPTIONS_DROPDOWN_TEMPLATE = ' <option value="1" selected="selected">{{t "files_external" "Once every direct access"}}</option>' + ' </select>' + ' </div>' + + ' <div class="optionRow">' + + ' <input id="mountOptionsEncoding" name="encoding_compatibility" type="checkbox" value="true"/>' + + ' <label for="mountOptionsEncoding">{{mountOptionsEncodingLabel}}</label>' + + ' </div>' + '</div>'; - + /** * Returns the selection of applicable users in the given configuration row * @@ -476,9 +480,9 @@ MountOptionsDropdown.prototype = { * * @param {Object} $container container * @param {Object} mountOptions mount options - * @param {Array} enabledOptions enabled mount options + * @param {Array} visibleOptions enabled mount options */ - show: function($container, mountOptions, enabledOptions) { + show: function($container, mountOptions, visibleOptions) { if (MountOptionsDropdown._last) { MountOptionsDropdown._last.hide(); } @@ -489,10 +493,12 @@ MountOptionsDropdown.prototype = { MountOptionsDropdown._template = template; } - var $el = $(template()); + var $el = $(template({ + mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)') + })); this.$el = $el; - this.setOptions(mountOptions, enabledOptions); + this.setOptions(mountOptions, visibleOptions); this.$el.appendTo($container); MountOptionsDropdown._last = this; @@ -538,9 +544,9 @@ MountOptionsDropdown.prototype = { * Sets the mount options to the dropdown controls * * @param {Object} options mount options - * @param {Array} enabledOptions enabled mount options + * @param {Array} visibleOptions enabled mount options */ - setOptions: function(options, enabledOptions) { + setOptions: function(options, visibleOptions) { var $el = this.$el; _.each(options, function(value, key) { var $optionEl = $el.find('input, select').filterAttr('name', key); @@ -556,7 +562,7 @@ MountOptionsDropdown.prototype = { $el.find('.optionRow').each(function(i, row){ var $row = $(row); var optionId = $row.find('input, select').attr('name'); - if (enabledOptions.indexOf(optionId) === -1) { + if (visibleOptions.indexOf(optionId) === -1) { $row.hide(); } else { $row.show(); @@ -883,7 +889,8 @@ MountConfigListView.prototype = _.extend({ 'encrypt': true, 'previews': true, 'enable_sharing': false, - 'filesystem_check_changes': 1 + 'filesystem_check_changes': 1, + 'encoding_compatibility': false })); } @@ -1253,11 +1260,16 @@ MountConfigListView.prototype = _.extend({ var storage = this.getStorageConfig($tr); var $toggle = $tr.find('.mountOptionsToggle'); var dropDown = new MountOptionsDropdown(); - var enabledOptions = ['previews', 'filesystem_check_changes', 'enable_sharing']; + var visibleOptions = [ + 'previews', + 'filesystem_check_changes', + 'enable_sharing', + 'encoding_compatibility' + ]; if (this._encryptionEnabled) { - enabledOptions.push('encrypt'); + visibleOptions.push('encrypt'); } - dropDown.show($toggle, storage.mountOptions || [], enabledOptions); + dropDown.show($toggle, storage.mountOptions || [], visibleOptions); $('body').on('mouseup.mountOptionsDropdown', function(event) { var $target = $(event.target); if ($toggle.has($target).length) { diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 462407e9540..7aa49b2c82a 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -370,7 +370,8 @@ describe('OCA.External.Settings tests', function() { encrypt: true, previews: true, enable_sharing: false, - filesystem_check_changes: 0 + filesystem_check_changes: 0, + encoding_compatibility: false }); }); }); diff --git a/apps/files_sharing/api/server2server.php b/apps/files_sharing/api/server2server.php deleted file mode 100644 index 034ec5105e4..00000000000 --- a/apps/files_sharing/api/server2server.php +++ /dev/null @@ -1,325 +0,0 @@ -<?php -/** - * @author Arthur Schiwon <blizzz@owncloud.com> - * @author Björn Schießle <schiessle@owncloud.com> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\Files_Sharing\API; - -use OCA\FederatedFileSharing\DiscoveryManager; -use OCA\FederatedFileSharing\FederatedShareProvider; -use OCA\Files_Sharing\Activity; -use OCP\Files\NotFoundException; - -class Server2Server { - - /** @var FederatedShareProvider */ - private $federatedShareProvider; - - - /** - * Server2Server constructor. - * - * @param FederatedShareProvider $federatedShareProvider - */ - public function __construct(FederatedShareProvider $federatedShareProvider) { - $this->federatedShareProvider = $federatedShareProvider; - } - - /** - * create a new share - * - * @param array $params - * @return \OC_OCS_Result - */ - public function createShare($params) { - - if (!$this->isS2SEnabled(true)) { - return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); - } - - $remote = isset($_POST['remote']) ? $_POST['remote'] : null; - $token = isset($_POST['token']) ? $_POST['token'] : null; - $name = isset($_POST['name']) ? $_POST['name'] : null; - $owner = isset($_POST['owner']) ? $_POST['owner'] : null; - $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; - $remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null; - - if ($remote && $token && $name && $owner && $remoteId && $shareWith) { - - if(!\OCP\Util::isValidFileName($name)) { - return new \OC_OCS_Result(null, 400, 'The mountpoint name contains invalid characters.'); - } - - // FIXME this should be a method in the user management instead - \OCP\Util::writeLog('files_sharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG); - \OCP\Util::emitHook( - '\OCA\Files_Sharing\API\Server2Server', - 'preLoginNameUsedAsUserName', - array('uid' => &$shareWith) - ); - \OCP\Util::writeLog('files_sharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG); - - if (!\OCP\User::userExists($shareWith)) { - return new \OC_OCS_Result(null, 400, 'User does not exists'); - } - - \OC_Util::setupFS($shareWith); - - $discoveryManager = new DiscoveryManager( - \OC::$server->getMemCacheFactory(), - \OC::$server->getHTTPClientService() - ); - $externalManager = new \OCA\Files_Sharing\External\Manager( - \OC::$server->getDatabaseConnection(), - \OC\Files\Filesystem::getMountManager(), - \OC\Files\Filesystem::getLoader(), - \OC::$server->getHTTPHelper(), - \OC::$server->getNotificationManager(), - $discoveryManager, - $shareWith - ); - - try { - $externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId); - $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external'); - - $user = $owner . '@' . $this->cleanupRemote($remote); - - \OC::$server->getActivityManager()->publishActivity( - Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_RECEIVED, array($user, trim($name, '/')), '', array(), - '', '', $shareWith, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_LOW); - - $urlGenerator = \OC::$server->getURLGenerator(); - - $notificationManager = \OC::$server->getNotificationManager(); - $notification = $notificationManager->createNotification(); - $notification->setApp('files_sharing') - ->setUser($shareWith) - ->setDateTime(new \DateTime()) - ->setObject('remote_share', $shareId) - ->setSubject('remote_share', [$user, trim($name, '/')]); - - $declineAction = $notification->createAction(); - $declineAction->setLabel('decline') - ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE'); - $notification->addAction($declineAction); - - $acceptAction = $notification->createAction(); - $acceptAction->setLabel('accept') - ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST'); - $notification->addAction($acceptAction); - - $notificationManager->notify($notification); - - return new \OC_OCS_Result(); - } catch (\Exception $e) { - \OCP\Util::writeLog('files_sharing', 'server can not add remote share, ' . $e->getMessage(), \OCP\Util::ERROR); - return new \OC_OCS_Result(null, 500, 'internal server error, was not able to add share from ' . $remote); - } - } - - return new \OC_OCS_Result(null, 400, 'server can not add remote share, missing parameter'); - } - - /** - * accept server-to-server share - * - * @param array $params - * @return \OC_OCS_Result - */ - public function acceptShare($params) { - - if (!$this->isS2SEnabled()) { - return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); - } - - $id = $params['id']; - $token = isset($_POST['token']) ? $_POST['token'] : null; - $share = self::getShare($id, $token); - - if ($share) { - list($file, $link) = self::getFile($share['uid_owner'], $share['file_source']); - - $event = \OC::$server->getActivityManager()->generateEvent(); - $event->setApp(Activity::FILES_SHARING_APP) - ->setType(Activity::TYPE_REMOTE_SHARE) - ->setAffectedUser($share['uid_owner']) - ->setSubject(Activity::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share['share_with'], basename($file)]) - ->setObject('files', $share['file_source'], $file) - ->setLink($link); - \OC::$server->getActivityManager()->publish($event); - } - - return new \OC_OCS_Result(); - } - - /** - * decline server-to-server share - * - * @param array $params - * @return \OC_OCS_Result - */ - public function declineShare($params) { - - if (!$this->isS2SEnabled()) { - return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); - } - - $id = $params['id']; - $token = isset($_POST['token']) ? $_POST['token'] : null; - - $share = $this->getShare($id, $token); - - if ($share) { - // userId must be set to the user who unshares - \OCP\Share::unshare($share['item_type'], $share['item_source'], $share['share_type'], $share['share_with'], $share['uid_owner']); - - list($file, $link) = $this->getFile($share['uid_owner'], $share['file_source']); - - $event = \OC::$server->getActivityManager()->generateEvent(); - $event->setApp(Activity::FILES_SHARING_APP) - ->setType(Activity::TYPE_REMOTE_SHARE) - ->setAffectedUser($share['uid_owner']) - ->setSubject(Activity::SUBJECT_REMOTE_SHARE_DECLINED, [$share['share_with'], basename($file)]) - ->setObject('files', $share['file_source'], $file) - ->setLink($link); - \OC::$server->getActivityManager()->publish($event); - } - - return new \OC_OCS_Result(); - } - - /** - * remove server-to-server share if it was unshared by the owner - * - * @param array $params - * @return \OC_OCS_Result - */ - public function unshare($params) { - - if (!$this->isS2SEnabled()) { - return new \OC_OCS_Result(null, 503, 'Server does not support federated cloud sharing'); - } - - $id = $params['id']; - $token = isset($_POST['token']) ? $_POST['token'] : null; - - $query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); - $query->execute(array($id, $token)); - $share = $query->fetchRow(); - - if ($token && $id && !empty($share)) { - - $remote = $this->cleanupRemote($share['remote']); - - $owner = $share['owner'] . '@' . $remote; - $mountpoint = $share['mountpoint']; - $user = $share['user']; - - $query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?'); - $query->execute(array($id, $token)); - - if ($share['accepted']) { - $path = trim($mountpoint, '/'); - } else { - $path = trim($share['name'], '/'); - } - - $notificationManager = \OC::$server->getNotificationManager(); - $notification = $notificationManager->createNotification(); - $notification->setApp('files_sharing') - ->setUser($share['user']) - ->setObject('remote_share', (int) $share['id']); - $notificationManager->markProcessed($notification); - - \OC::$server->getActivityManager()->publishActivity( - Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_UNSHARED, array($owner, $path), '', array(), - '', '', $user, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_MEDIUM); - } - - return new \OC_OCS_Result(); - } - - private function cleanupRemote($remote) { - $remote = substr($remote, strpos($remote, '://') + 3); - - return rtrim($remote, '/'); - } - - /** - * get share - * - * @param int $id - * @param string $token - * @return array - */ - private function getShare($id, $token) { - $query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ? AND `token` = ? AND `share_type` = ?'); - $query->execute(array($id, $token, \OCP\Share::SHARE_TYPE_REMOTE)); - $share = $query->fetchRow(); - - return $share; - } - - /** - * get file - * - * @param string $user - * @param int $fileSource - * @return array with internal path of the file and a absolute link to it - */ - private function getFile($user, $fileSource) { - \OC_Util::setupFS($user); - - try { - $file = \OC\Files\Filesystem::getPath($fileSource); - } catch (NotFoundException $e) { - $file = null; - } - $args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file); - $link = \OCP\Util::linkToAbsolute('files', 'index.php', $args); - - return array($file, $link); - - } - - /** - * check if server-to-server sharing is enabled - * - * @param bool $incoming - * @return bool - */ - private function isS2SEnabled($incoming = false) { - - $result = \OCP\App::isEnabled('files_sharing'); - - if ($incoming) { - $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled(); - } else { - $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled(); - } - - return $result; - } - -} diff --git a/apps/files_sharing/api/share20ocs.php b/apps/files_sharing/api/share20ocs.php index af762845326..28166b943b8 100644 --- a/apps/files_sharing/api/share20ocs.php +++ b/apps/files_sharing/api/share20ocs.php @@ -99,7 +99,15 @@ class Share20OCS { */ protected function formatShare(\OCP\Share\IShare $share) { $sharedBy = $this->userManager->get($share->getSharedBy()); - $shareOwner = $this->userManager->get($share->getShareOwner()); + // for federated shares the owner can be a remote user, in this + // case we use the initiator + if ($this->userManager->userExists($share->getShareOwner())) { + $shareOwner = $this->userManager->get($share->getShareOwner()); + $localUser = $share->getShareOwner(); + } else { + $shareOwner = $this->userManager->get($share->getSharedBy()); + $localUser = $share->getSharedBy(); + } $result = [ 'id' => $share->getId(), 'share_type' => $share->getShareType(), @@ -115,7 +123,7 @@ class Share20OCS { ]; $node = $share->getNode(); - $result['path'] = $this->rootFolder->getUserFolder($share->getShareOwner())->getRelativePath($node->getPath()); + $result['path'] = $this->rootFolder->getUserFolder($localUser)->getRelativePath($node->getPath()); if ($node instanceOf \OCP\Files\Folder) { $result['item_type'] = 'folder'; } else { diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index da573d11ec5..32eee9b6c9c 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -103,15 +103,3 @@ if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') { } } } - -$manager = \OC::$server->getNotificationManager(); -$manager->registerNotifier(function() { - return new \OCA\Files_Sharing\Notifier( - \OC::$server->getL10NFactory() - ); -}, function() use ($l) { - return [ - 'id' => 'files_sharing', - 'name' => $l->t('Federated sharing'), - ]; -}); diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index 7dc9f66f114..5b7a13f1eb1 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -325,6 +325,10 @@ class Manager { } public function removeShare($mountPoint) { + + $mountPointObj = $this->mountManager->find($mountPoint); + $id = $mountPointObj->getStorage()->getCache()->getId(); + $mountPoint = $this->stripPath($mountPoint); $hash = md5($mountPoint); @@ -338,13 +342,43 @@ class Manager { $share = $getShare->fetch(); $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); } + $getShare->closeCursor(); $query = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `mountpoint_hash` = ? AND `user` = ? '); - return (bool)$query->execute(array($hash, $this->uid)); + $result = (bool)$query->execute(array($hash, $this->uid)); + + if($result) { + $this->removeReShares($id); + } + + return $result; + } + + /** + * remove re-shares from share table and mapping in the federated_reshares table + * + * @param $mountPointId + */ + protected function removeReShares($mountPointId) { + $selectQuery = $this->connection->getQueryBuilder(); + $query = $this->connection->getQueryBuilder(); + $selectQuery->select('id')->from('share') + ->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId))); + $select = $selectQuery->getSQL(); + + + $query->delete('federated_reshares') + ->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')'))); + $query->execute(); + + $deleteReShares = $this->connection->getQueryBuilder(); + $deleteReShares->delete('share') + ->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId))); + $deleteReShares->execute(); } /** diff --git a/apps/files_sharing/tests/api/share20ocstest.php b/apps/files_sharing/tests/api/share20ocstest.php index ffb74da2af7..96ce34f963c 100644 --- a/apps/files_sharing/tests/api/share20ocstest.php +++ b/apps/files_sharing/tests/api/share20ocstest.php @@ -82,6 +82,8 @@ class Share20OCSTest extends \Test\TestCase { $this->currentUser = $this->getMock('OCP\IUser'); $this->currentUser->method('getUID')->willReturn('currentUser'); + $this->userManager->expects($this->any())->method('userExists')->willReturn(true); + $this->l = $this->getMock('\OCP\IL10N'); $this->l->method('t') ->will($this->returnCallback(function($text, $parameters = []) { diff --git a/apps/files_trashbin/lib/Storage.php b/apps/files_trashbin/lib/Storage.php index c4c523810ac..b621c511db7 100644 --- a/apps/files_trashbin/lib/Storage.php +++ b/apps/files_trashbin/lib/Storage.php @@ -150,7 +150,7 @@ class Storage extends Wrapper { return false; } - $normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path); + $normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path, true, false, true); $result = true; $view = Filesystem::getView(); if (!isset($this->deletedFiles[$normalized]) && $view instanceof View) { diff --git a/core/Command/Db/ConvertType.php b/core/Command/Db/ConvertType.php index 864499dcce0..8c8b6350fac 100644 --- a/core/Command/Db/ConvertType.php +++ b/core/Command/Db/ConvertType.php @@ -105,6 +105,13 @@ class ConvertType extends Command { InputOption::VALUE_NONE, 'whether to create schema for all apps instead of only installed apps' ) + ->addOption( + 'chunk-size', + null, + InputOption::VALUE_REQUIRED, + 'the maximum number of database rows to handle in a single query, bigger tables will be handled in chunks of this size. Lower this if the process runs out of memory during conversion.', + 1000 + ) ; } @@ -246,25 +253,60 @@ class ConvertType extends Command { } protected function copyTable(Connection $fromDB, Connection $toDB, $table, InputInterface $input, OutputInterface $output) { + $chunkSize = $input->getOption('chunk-size'); + /** @var $progress \Symfony\Component\Console\Helper\ProgressHelper */ $progress = $this->getHelperSet()->get('progress'); - $query = 'SELECT COUNT(*) FROM '.$table; - $count = $fromDB->fetchColumn($query); - $query = 'SELECT * FROM '.$table; - $statement = $fromDB->executeQuery($query); + + $query = $fromDB->getQueryBuilder(); + $query->automaticTablePrefix(false); + $query->selectAlias($query->createFunction('COUNT(*)'), 'num_entries') + ->from($table); + $result = $query->execute(); + $count = $result->fetchColumn(); + $result->closeCursor(); + + $numChunks = ceil($count/$chunkSize); + if ($numChunks > 1) { + $output->writeln('chunked query, ' . $numChunks . ' chunks'); + } + $progress->start($output, $count); - $progress->setRedrawFrequency($count > 100 ? 5 : 1); - while($row = $statement->fetch()) { - $progress->advance(); - if ($input->getArgument('type') === 'oci') { - $data = $row; - } else { - $data = array(); - foreach ($row as $columnName => $value) { - $data[$toDB->quoteIdentifier($columnName)] = $value; + $redraw = $count > $chunkSize ? 100 : ($count > 100 ? 5 : 1); + $progress->setRedrawFrequency($redraw); + + + $query = $fromDB->getQueryBuilder(); + $query->automaticTablePrefix(false); + $query->select('*') + ->from($table) + ->setMaxResults($chunkSize); + + $insertQuery = $toDB->getQueryBuilder(); + $insertQuery->automaticTablePrefix(false); + $insertQuery->insert($table); + $parametersCreated = false; + + for ($chunk = 0; $chunk < $numChunks; $chunk++) { + $query->setFirstResult($chunk * $chunkSize); + + $result = $query->execute(); + + while ($row = $result->fetch()) { + $progress->advance(); + if (!$parametersCreated) { + foreach ($row as $key => $value) { + $insertQuery->setValue($key, $insertQuery->createParameter($key)); + } + $parametersCreated = true; + } + + foreach ($row as $key => $value) { + $insertQuery->setParameter($key, $value); } + $insertQuery->execute(); } - $toDB->insert($table, $data); + $result->closeCursor(); } $progress->finish(); } diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 08451a46151..4a64eacb247 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -28,7 +28,6 @@ use OCP\AppFramework\Db\Entity; * @method void setId(int $id) * @method void setUid(string $uid); * @method void setPassword(string $password) - * @method string getPassword() * @method void setName(string $name) * @method string getName() * @method void setToken(string $token) @@ -87,4 +86,13 @@ class DefaultToken extends Entity implements IToken { return parent::getPassword(); } + public function jsonSerialize() { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'lastActivity' => $this->lastActivity, + 'type' => $this->type, + ]; + } + } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 9f173571270..970c2242dbe 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -111,4 +111,17 @@ class DefaultTokenMapper extends Mapper { return $entities; } + /** + * @param IUser $user + * @param int $id + */ + public function deleteById(IUser $user, $id) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))); + $qb->execute(); + } + } diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 6c69d852d7b..0f7c54dab57 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -134,6 +134,7 @@ class DefaultTokenProvider implements IProvider { /** * @param IToken $savedToken * @param string $tokenId session token + * @throws InvalidTokenException * @return string */ public function getPassword(IToken $savedToken, $tokenId) { @@ -150,6 +151,16 @@ class DefaultTokenProvider implements IProvider { } /** + * Invalidate (delete) the given token + * + * @param IUser $user + * @param int $id + */ + public function invalidateTokenById(IUser $user, $id) { + $this->mapper->deleteById($user, $id); + } + + /** * Invalidate (delete) old session tokens */ public function invalidateOldTokens() { @@ -203,6 +214,7 @@ class DefaultTokenProvider implements IProvider { * * @param string $password * @param string $token + * @throws InvalidTokenException * @return string the decrypted key */ private function decryptPassword($password, $token) { diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index a5c5faa5639..e4e4581e738 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -35,7 +35,7 @@ interface IProvider { * @param string $password * @param string $name * @param int $type token type - * @return DefaultToken + * @return IToken */ public function generateToken($token, $uid, $password, $name, $type = IToken::TEMPORARY_TOKEN); @@ -47,7 +47,7 @@ interface IProvider { * @return IToken */ public function getToken($tokenId) ; - + /** * @param string $token * @throws InvalidTokenException @@ -63,6 +63,14 @@ interface IProvider { public function invalidateToken($token); /** + * Invalidate (delete) the given token + * + * @param IUser $user + * @param int $id + */ + public function invalidateTokenById(IUser $user, $id); + + /** * Update token activity timestamp * * @param IToken $token @@ -85,6 +93,7 @@ interface IProvider { * * @param IToken $token * @param string $tokenId + * @throws InvalidTokenException * @return string */ public function getPassword(IToken $token, $tokenId); diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 2a01ea75ea9..b741cd4ac22 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -22,7 +22,9 @@ namespace OC\Authentication\Token; -interface IToken { +use JsonSerializable; + +interface IToken extends JsonSerializable { const TEMPORARY_TOKEN = 0; const PERMANENT_TOKEN = 1; @@ -30,7 +32,7 @@ interface IToken { /** * Get the token ID * - * @return string + * @return int */ public function getId(); diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index 03aca1924b0..fd309a4ac45 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -341,7 +341,7 @@ class Scanner extends BasicEmitter implements IScanner { if (is_resource($dh)) { while (($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { - $children[] = $file; + $children[] = trim(\OC\Files\Filesystem::normalizePath($file), '/'); } } } @@ -355,29 +355,45 @@ class Scanner extends BasicEmitter implements IScanner { * @param string $path * @param bool $recursive * @param int $reuse - * @param array $folderData existing cache data for the folder to be scanned + * @param int $folderId id for the folder to be scanned * @param bool $lock set to false to disable getting an additional read lock during scanning * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true) { if ($reuse === -1) { $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; } $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId)); $size = 0; - $childQueue = array(); - if (is_array($folderData) and isset($folderData['fileid'])) { - $folderId = $folderData['fileid']; - } else { + if (!is_null($folderId)) { $folderId = $this->cache->getId($path); } + $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size); + + foreach ($childQueue as $child => $childId) { + $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse, $childId, $lock); + if ($childSize === -1) { + $size = -1; + } else if ($size !== -1) { + $size += $childSize; + } + } + $this->updateCache($path, array('size' => $size), $folderId); + $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId)); + return $size; + } + + private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) { + // we put this in it's own function so it cleans up the memory before we start recursing $existingChildren = $this->getExistingChildren($folderId); $newChildren = $this->getNewChildren($path); if ($this->useTransactions) { \OC::$server->getDatabaseConnection()->beginTransaction(); } + $exceptionOccurred = false; + $childQueue = []; foreach ($newChildren as $file) { $child = ($path) ? $path . '/' . $file : $file; try { @@ -385,7 +401,7 @@ class Scanner extends BasicEmitter implements IScanner { $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock); if ($data) { if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) { - $childQueue[$child] = $data; + $childQueue[$child] = $data['fileid']; } else if ($data['size'] === -1) { $size = -1; } else if ($size !== -1) { @@ -420,20 +436,7 @@ class Scanner extends BasicEmitter implements IScanner { // we reload them here \OC::$server->getMimeTypeLoader()->reset(); } - - foreach ($childQueue as $child => $childData) { - $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse, $childData, $lock); - if ($childSize === -1) { - $size = -1; - } else if ($size !== -1) { - $size += $childSize; - } - } - if (!is_array($folderData) or !isset($folderData['size']) or $folderData['size'] !== $size) { - $this->updateCache($path, array('size' => $size), $folderId); - } - $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId)); - return $size; + return $childQueue; } /** @@ -466,7 +469,7 @@ class Scanner extends BasicEmitter implements IScanner { } else { $lastPath = null; while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) { - $this->runBackgroundScanJob(function() use ($path) { + $this->runBackgroundScanJob(function () use ($path) { $this->scan($path, self::SCAN_RECURSIVE, self::REUSE_ETAG); }, $path); // FIXME: this won't proceed with the next item, needs revamping of getIncomplete() diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php new file mode 100644 index 00000000000..0171dfe19b7 --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -0,0 +1,533 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OCP\ICache; +use OC\Cache\CappedMemoryCache; + +/** + * Encoding wrapper that deals with file names that use unsupported encodings like NFD. + * + * When applied and a UTF-8 path name was given, the wrapper will first attempt to access + * the actual given name and then try its NFD form. + */ +class Encoding extends Wrapper { + + /** + * @var ICache + */ + private $namesCache; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + $this->namesCache = new CappedMemoryCache(); + } + + /** + * Returns whether the given string is only made of ASCII characters + * + * @param string $str string + * + * @return bool true if the string is all ASCII, false otherwise + */ + private function isAscii($str) { + return (bool) !preg_match('/[\\x80-\\xff]+/', $str); + } + + /** + * Checks whether the given path exists in NFC or NFD form after checking + * each form for each path section and returns the correct form. + * If no existing path found, returns the path as it was given. + * + * @param string $fullPath path to check + * + * @return string original or converted path + */ + private function findPathToUse($fullPath) { + $cachedPath = $this->namesCache[$fullPath]; + if ($cachedPath !== null) { + return $cachedPath; + } + + $sections = explode('/', $fullPath); + $path = ''; + foreach ($sections as $section) { + $convertedPath = $this->findPathToUseLastSection($path, $section); + if ($convertedPath === null) { + // no point in continuing if the section was not found, use original path + return $fullPath; + } + $path = $convertedPath . '/'; + } + $path = rtrim($path, '/'); + return $path; + } + + /** + * Checks whether the last path section of the given path exists in NFC or NFD form + * and returns the correct form. If no existing path found, returns null. + * + * @param string $basePath base path to check + * @param string $lastSection last section of the path to check for NFD/NFC variations + * + * @return string|null original or converted path, or null if none of the forms was found + */ + private function findPathToUseLastSection($basePath, $lastSection) { + $fullPath = $basePath . $lastSection; + if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) { + $this->namesCache[$fullPath] = $fullPath; + return $fullPath; + } + + // swap encoding + if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) { + $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D); + } else { + $otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C); + } + $otherFullPath = $basePath . $otherFormPath; + if ($this->storage->file_exists($otherFullPath)) { + $this->namesCache[$fullPath] = $otherFullPath; + return $otherFullPath; + } + + // return original path, file did not exist at all + $this->namesCache[$fullPath] = $fullPath; + return null; + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + // note: no conversion here, method should not be called with non-NFC names! + $result = $this->storage->mkdir($path); + if ($result) { + $this->namesCache[$path] = $path; + } + return $result; + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + $result = $this->storage->rmdir($this->findPathToUse($path)); + if ($result) { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($this->findPathToUse($path)); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($this->findPathToUse($path)); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($this->findPathToUse($path)); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($this->findPathToUse($path)); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($this->findPathToUse($path)); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($this->findPathToUse($path)); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->storage->file_put_contents($this->findPathToUse($path), $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + $result = $this->storage->unlink($this->findPathToUse($path)); + if ($result) { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + // second name always NFC + return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2)); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2)); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $result = $this->storage->fopen($this->findPathToUse($path), $mode); + if ($result && $mode !== 'r' && $mode !== 'rb') { + unset($this->namesCache[$path]); + } + return $result; + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($this->findPathToUse($path)); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $this->findPathToUse($path), $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($this->findPathToUse($path)); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($this->findPathToUse($path), $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($this->findPathToUse($path)); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($this->findPathToUse($path), $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getCache($this->findPathToUse($path), $storage); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getScanner($this->findPathToUse($path), $storage); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($this->findPathToUse($path)); + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath)); + } + + $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$sourceInternalPath]); + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + $result = $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->findPathToUse($targetInternalPath)); + if ($result) { + unset($this->namesCache[$sourceInternalPath]); + unset($this->namesCache[$targetInternalPath]); + } + return $result; + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->storage->getMetaData($this->findPathToUse($path)); + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index 0b7b8f9e403..ea0c436d84b 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -223,6 +223,7 @@ class Server extends ServerContainer implements IServerContainer { $timeFactory = new TimeFactory(); return new \OC\Authentication\Token\DefaultTokenProvider($mapper, $crypto, $config, $logger, $timeFactory); }); + $this->registerAlias('OC\Authentication\Token\IProvider', 'OC\Authentication\Token\DefaultTokenProvider'); $this->registerService('UserSession', function (Server $c) { $manager = $c->getUserManager(); $session = new \OC\Session\Memory(''); @@ -230,7 +231,7 @@ class Server extends ServerContainer implements IServerContainer { // Token providers might require a working database. This code // might however be called when ownCloud is not yet setup. if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $defaultTokenProvider = $c->query('OC\Authentication\Token\DefaultTokenProvider'); + $defaultTokenProvider = $c->query('OC\Authentication\Token\IProvider'); } else { $defaultTokenProvider = null; } diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index f0de39fdad3..8a39c18a495 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -733,7 +733,7 @@ class DefaultShareProvider implements IShareProvider { * @throws InvalidShare */ private function createShare($data) { - $share = new Share($this->rootFolder); + $share = new Share($this->rootFolder, $this->userManager); $share->setId((int)$data['id']) ->setShareType((int)$data['share_type']) ->setPermissions((int)$data['permissions']) diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index dee9e0cdd21..3568995472a 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -201,7 +201,12 @@ class Manager implements IManager { } // And you can't share your rootfolder - if ($this->rootFolder->getUserFolder($share->getSharedBy())->getPath() === $share->getNode()->getPath()) { + if ($this->userManager->userExists($share->getSharedBy())) { + $sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath(); + } else { + $sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath(); + } + if ($sharedPath === $share->getNode()->getPath()) { throw new \InvalidArgumentException('You can\'t share your root folder'); } @@ -713,7 +718,11 @@ class Manager implements IManager { } if ($share->getPermissions() !== $originalShare->getPermissions()) { - $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + if ($this->userManager->userExists($share->getShareOwner())) { + $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner()); + } else { + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); + } \OC_Hook::emit('OCP\Share', 'post_update_permissions', array( 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', 'itemSource' => $share->getNode()->getId(), @@ -1107,7 +1116,7 @@ class Manager implements IManager { * @return \OCP\Share\IShare; */ public function newShare() { - return new \OC\Share20\Share($this->rootFolder); + return new \OC\Share20\Share($this->rootFolder, $this->userManager); } /** diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php index 0bedfb84fc7..b436a7bc5f3 100644 --- a/lib/private/Share20/ProviderFactory.php +++ b/lib/private/Share20/ProviderFactory.php @@ -115,7 +115,8 @@ class ProviderFactory implements IProviderFactory { $l, $this->serverContainer->getLogger(), $this->serverContainer->getLazyRootFolder(), - $this->serverContainer->getConfig() + $this->serverContainer->getConfig(), + $this->serverContainer->getUserManager() ); } diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index c361f01216f..f56fd94b409 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -24,8 +24,7 @@ use OCP\Files\File; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; -use OCP\IUser; -use OCP\IGroup; +use OCP\IUserManager; use OCP\Share\Exceptions\IllegalIDChangeException; class Share implements \OCP\Share\IShare { @@ -68,8 +67,12 @@ class Share implements \OCP\Share\IShare { /** @var IRootFolder */ private $rootFolder; - public function __construct(IRootFolder $rootFolder) { + /** @var IUserManager */ + private $userManager; + + public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { $this->rootFolder = $rootFolder; + $this->userManager = $userManager; } /** @@ -145,7 +148,13 @@ class Share implements \OCP\Share\IShare { throw new NotFoundException(); } - $userFolder = $this->rootFolder->getUserFolder($this->shareOwner); + // for federated shares the owner can be a remote user, in this + // case we use the initiator + if($this->userManager->userExists($this->shareOwner)) { + $userFolder = $this->rootFolder->getUserFolder($this->shareOwner); + } else { + $userFolder = $this->rootFolder->getUserFolder($this->sharedBy); + } $nodes = $userFolder->getById($this->fileId); if (empty($nodes)) { diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index 327c0c32dfe..9c4b0ff0864 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -159,28 +159,31 @@ class URLGenerator implements IURLGenerator { } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.svg") && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$app/img/$basename.png")) { $path = \OC::$WEBROOT . "/themes/$theme/apps/$app/img/$basename.png"; - } elseif ($appPath && file_exists($appPath . "/img/$image")) { - $path = \OC_App::getAppWebPath($app) . "/img/$image"; - } elseif ($appPath && !file_exists($appPath . "/img/$basename.svg") - && file_exists($appPath . "/img/$basename.png")) { - $path = \OC_App::getAppWebPath($app) . "/img/$basename.png"; } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$image")) { $path = \OC::$WEBROOT . "/themes/$theme/$app/img/$image"; } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.svg") && file_exists(\OC::$SERVERROOT . "/themes/$theme/$app/img/$basename.png"))) { $path = \OC::$WEBROOT . "/themes/$theme/$app/img/$basename.png"; - } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/$app/img/$image")) { - $path = \OC::$WEBROOT . "/$app/img/$image"; - } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/$app/img/$basename.svg") - && file_exists(\OC::$SERVERROOT . "/$app/img/$basename.png"))) { - $path = \OC::$WEBROOT . "/$app/img/$basename.png"; } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$image")) { $path = \OC::$WEBROOT . "/themes/$theme/core/img/$image"; } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg") && file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) { $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; + } elseif ($appPath && file_exists($appPath . "/img/$image")) { + $path = \OC_App::getAppWebPath($app) . "/img/$image"; + } elseif ($appPath && !file_exists($appPath . "/img/$basename.svg") + && file_exists($appPath . "/img/$basename.png")) { + $path = \OC_App::getAppWebPath($app) . "/img/$basename.png"; + } elseif (!empty($app) and file_exists(\OC::$SERVERROOT . "/$app/img/$image")) { + $path = \OC::$WEBROOT . "/$app/img/$image"; + } elseif (!empty($app) and (!file_exists(\OC::$SERVERROOT . "/$app/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/$app/img/$basename.png"))) { + $path = \OC::$WEBROOT . "/$app/img/$basename.png"; } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$image")) { $path = \OC::$WEBROOT . "/core/img/$image"; + } elseif (!file_exists(\OC::$SERVERROOT . "/core/img/$basename.svg") + && file_exists(\OC::$SERVERROOT . "/core/img/$basename.png")) { + $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png"; } if($path !== '') { diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index 4f7a8668dfc..de97a762246 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -172,6 +172,13 @@ class OC_Util { return $storage; }); + \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { + if ($mount->getOption('encoding_compatibility', true) && !$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); + } + return $storage; + }); + \OC\Files\Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) { // set up quota for home storages, even for other users // which can happen when using sharing diff --git a/lib/public/Comments/CommentsEntityEvent.php b/lib/public/Comments/CommentsEntityEvent.php new file mode 100644 index 00000000000..5f012a79885 --- /dev/null +++ b/lib/public/Comments/CommentsEntityEvent.php @@ -0,0 +1,76 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@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 OCP\Comments; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Class CommentsEntityEvent + * + * @package OCP\Comments + * @since 9.1.0 + */ +class CommentsEntityEvent extends Event { + + const EVENT_ENTITY = 'OCP\Comments\ICommentsManager::registerEntity'; + + /** @var string */ + protected $event; + /** @var \Closure[] */ + protected $collections; + + /** + * DispatcherEvent constructor. + * + * @param string $event + * @since 9.1.0 + */ + public function __construct($event) { + $this->event = $event; + $this->collections = []; + } + + /** + * @param string $name + * @param \Closure $entityExistsFunction The closure should take one + * argument, which is the id of the entity, that comments + * should be handled for. The return should then be bool, + * depending on whether comments are allowed (true) or not. + * @throws \OutOfBoundsException when the entity name is already taken + * @since 9.1.0 + */ + public function addEntityCollection($name, \Closure $entityExistsFunction) { + if (isset($this->collections[$name])) { + throw new \OutOfBoundsException('Duplicate entity name "' . $name . '"'); + } + + $this->collections[$name] = $entityExistsFunction; + } + + /** + * @return \Closure[] + * @since 9.1.0 + */ + public function getEntityCollections() { + return $this->collections; + } +} diff --git a/ocs/routes.php b/ocs/routes.php index af9c3e74137..7f4f78dd35d 100644 --- a/ocs/routes.php +++ b/ocs/routes.php @@ -100,7 +100,25 @@ API::register( // Server-to-Server Sharing if (\OC::$server->getAppManager()->isEnabledForUser('files_sharing')) { $federatedSharingApp = new \OCA\FederatedFileSharing\AppInfo\Application('federatedfilesharing'); - $s2s = new \OCA\Files_Sharing\API\Server2Server($federatedSharingApp->getFederatedShareProvider()); + $addressHandler = new \OCA\FederatedFileSharing\AddressHandler( + \OC::$server->getURLGenerator(), + \OC::$server->getL10N('federatedfilesharing') + ); + $notification = new \OCA\FederatedFileSharing\Notifications( + $addressHandler, + \OC::$server->getHTTPClientService(), + new \OCA\FederatedFileSharing\DiscoveryManager(\OC::$server->getMemCacheFactory(), \OC::$server->getHTTPClientService()), + \OC::$server->getJobList() + ); + $s2s = new OCA\FederatedFileSharing\RequestHandler( + $federatedSharingApp->getFederatedShareProvider(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getShareManager(), + \OC::$server->getRequest(), + $notification, + $addressHandler, + \OC::$server->getUserManager() + ); API::register('post', '/cloud/shares', array($s2s, 'createShare'), @@ -109,6 +127,21 @@ if (\OC::$server->getAppManager()->isEnabledForUser('files_sharing')) { ); API::register('post', + '/cloud/shares/{id}/reshare', + array($s2s, 'reShare'), + 'files_sharing', + API::GUEST_AUTH + ); + + API::register('post', + '/cloud/shares/{id}/permissions', + array($s2s, 'updatePermissions'), + 'files_sharing', + API::GUEST_AUTH + ); + + + API::register('post', '/cloud/shares/{id}/accept', array($s2s, 'acceptShare'), 'files_sharing', @@ -128,4 +161,11 @@ if (\OC::$server->getAppManager()->isEnabledForUser('files_sharing')) { 'files_sharing', API::GUEST_AUTH ); + + API::register('post', + '/cloud/shares/{id}/revoke', + array($s2s, 'revoke'), + 'files_sharing', + API::GUEST_AUTH + ); } diff --git a/settings/Application.php b/settings/Application.php index 5b84d028abf..728c2bf9de4 100644 --- a/settings/Application.php +++ b/settings/Application.php @@ -29,7 +29,9 @@ namespace OC\Settings; use OC\Files\View; +use OC\Server; use OC\Settings\Controller\AppSettingsController; +use OC\Settings\Controller\AuthSettingsController; use OC\Settings\Controller\CertificateController; use OC\Settings\Controller\CheckSetupController; use OC\Settings\Controller\EncryptionController; @@ -39,10 +41,9 @@ use OC\Settings\Controller\MailSettingsController; use OC\Settings\Controller\SecuritySettingsController; use OC\Settings\Controller\UsersController; use OC\Settings\Middleware\SubadminMiddleware; -use \OCP\AppFramework\App; +use OCP\AppFramework\App; use OCP\IContainer; -use \OCP\Util; -use OC\Server; +use OCP\Util; /** * @package OC\Settings @@ -97,6 +98,17 @@ class Application extends App { $c->query('OcsClient') ); }); + $container->registerService('AuthSettingsController', function(IContainer $c) { + return new AuthSettingsController( + $c->query('AppName'), + $c->query('Request'), + $c->query('ServerContainer')->query('OC\Authentication\Token\IProvider'), + $c->query('UserManager'), + $c->query('ServerContainer')->getSession(), + $c->query('ServerContainer')->getSecureRandom(), + $c->query('UserId') + ); + }); $container->registerService('SecuritySettingsController', function(IContainer $c) { return new SecuritySettingsController( $c->query('AppName'), diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php new file mode 100644 index 00000000000..75311920d2a --- /dev/null +++ b/settings/Controller/AuthSettingsController.php @@ -0,0 +1,151 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Settings\Controller; + +use OC\AppFramework\Http; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IProvider; +use OC\Authentication\Token\IToken; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; +use OCP\ISession; +use OCP\IUserManager; +use OCP\Security\ISecureRandom; +use OCP\Session\Exceptions\SessionNotAvailableException; + +class AuthSettingsController extends Controller { + + /** @var IProvider */ + private $tokenProvider; + + /** @var IUserManager */ + private $userManager; + + /** @var ISession */ + private $session; + + /** @var string */ + private $uid; + + /** @var ISecureRandom */ + private $random; + + /** + * @param string $appName + * @param IRequest $request + * @param IProvider $tokenProvider + * @param IUserManager $userManager + * @param ISession $session + * @param ISecureRandom $random + * @param string $uid + */ + public function __construct($appName, IRequest $request, IProvider $tokenProvider, IUserManager $userManager, + ISession $session, ISecureRandom $random, $uid) { + parent::__construct($appName, $request); + $this->tokenProvider = $tokenProvider; + $this->userManager = $userManager; + $this->uid = $uid; + $this->session = $session; + $this->random = $random; + } + + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @return JSONResponse + */ + public function index() { + $user = $this->userManager->get($this->uid); + if (is_null($user)) { + return []; + } + return $this->tokenProvider->getTokenByUser($user); + } + + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @return JSONResponse + */ + public function create($name) { + try { + $sessionId = $this->session->getId(); + } catch (SessionNotAvailableException $ex) { + $resp = new JSONResponse(); + $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); + return $resp; + } + + try { + $sessionToken = $this->tokenProvider->getToken($sessionId); + $password = $this->tokenProvider->getPassword($sessionToken, $sessionId); + } catch (InvalidTokenException $ex) { + $resp = new JSONResponse(); + $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); + return $resp; + } + + $token = $this->generateRandomDeviceToken(); + $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $password, $name, IToken::PERMANENT_TOKEN); + + return [ + 'token' => $token, + 'deviceToken' => $deviceToken + ]; + } + + /** + * Return a 20 digit device password + * + * Example: ABCDE-FGHIJ-KLMNO-PQRST + * + * @return string + */ + private function generateRandomDeviceToken() { + $groups = []; + for ($i = 0; $i < 4; $i++) { + $groups[] = $this->random->generate(5, implode('', range('A', 'Z'))); + } + return implode('-', $groups); + } + + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @return JSONResponse + */ + public function destroy($id) { + $user = $this->userManager->get($this->uid); + if (is_null($user)) { + return []; + } + + $this->tokenProvider->invalidateTokenById($user, $id); + return []; + } + +} diff --git a/settings/css/settings.css b/settings/css/settings.css index edc4939d2d8..5fc96343502 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -100,6 +100,39 @@ input#identity { table.nostyle label { margin-right: 2em; } table.nostyle td { padding: 0.2em 0; } +#sessions table, +#devices table { + width: 100%; + min-height: 150px; + padding-top: 25px; +} +#sessions table th, +#devices table th { + font-weight: 800; +} +#sessions table th, +#sessions table td, +#devices table th, +#devices table td { + padding: 10px; +} + +#sessions .token-list td, +#devices .token-list td { + border-top: 1px solid #DDD; +} +#sessions .token-list td a.icon-delete, +#devices .token-list td a.icon-delete { + display: block; + opacity: 0.6; +} + +#device-new-token { + width: 186px; + font-family: monospace; + background-color: lightyellow; +} + /* USERS */ #newgroup-init a span { margin-left: 20px; } #newgroup-init a span:before { diff --git a/settings/js/authtoken.js b/settings/js/authtoken.js new file mode 100644 index 00000000000..215192d7163 --- /dev/null +++ b/settings/js/authtoken.js @@ -0,0 +1,33 @@ +/* global Backbone */ + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +(function(OC, Backbone) { + 'use strict'; + + OC.Settings = OC.Settings || {}; + + var AuthToken = Backbone.Model.extend({ + }); + + OC.Settings.AuthToken = AuthToken; + +})(OC, Backbone); diff --git a/settings/js/authtoken_collection.js b/settings/js/authtoken_collection.js new file mode 100644 index 00000000000..a78e053995f --- /dev/null +++ b/settings/js/authtoken_collection.js @@ -0,0 +1,52 @@ +/* global Backbone */ + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +(function(OC, Backbone) { + 'use strict'; + + OC.Settings = OC.Settings || {}; + + var AuthTokenCollection = Backbone.Collection.extend({ + + model: OC.Settings.AuthToken, + + /** + * Show recently used sessions/devices first + * + * @param {OC.Settigns.AuthToken} t1 + * @param {OC.Settigns.AuthToken} t2 + * @returns {Boolean} + */ + comparator: function (t1, t2) { + var ts1 = parseInt(t1.get('lastActivity'), 10); + var ts2 = parseInt(t2.get('lastActivity'), 10); + return ts1 < ts2; + }, + + tokenType: null, + + url: OC.generateUrl('/settings/personal/authtokens') + }); + + OC.Settings.AuthTokenCollection = AuthTokenCollection; + +})(OC, Backbone); diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js new file mode 100644 index 00000000000..a165a465247 --- /dev/null +++ b/settings/js/authtoken_view.js @@ -0,0 +1,242 @@ +/* global Backbone, Handlebars, moment */ + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +(function(OC, _, Backbone, $, Handlebars, moment) { + 'use strict'; + + OC.Settings = OC.Settings || {}; + + var TEMPLATE_TOKEN = + '<tr data-id="{{id}}">' + + '<td>{{name}}</td>' + + '<td><span class="last-activity" title="{{lastActivityTime}}">{{lastActivity}}</span></td>' + + '<td><a class="icon-delete" title="' + t('core', 'Disconnect') + '"></a></td>' + + '<tr>'; + + var SubView = Backbone.View.extend({ + collection: null, + + /** + * token type + * - 0: browser + * - 1: device + * + * @see OC\Authentication\Token\IToken + */ + type: 0, + + _template: undefined, + + template: function(data) { + if (_.isUndefined(this._template)) { + this._template = Handlebars.compile(TEMPLATE_TOKEN); + } + + return this._template(data); + }, + + initialize: function(options) { + this.type = options.type; + this.collection = options.collection; + + this.on(this.collection, 'change', this.render); + }, + + render: function() { + var _this = this; + + var list = this.$('.token-list'); + var tokens = this.collection.filter(function(token) { + return parseInt(token.get('type'), 10) === _this.type; + }); + list.html(''); + + // Show header only if there are tokens to show + console.log(tokens.length > 0); + this._toggleHeader(tokens.length > 0); + + tokens.forEach(function(token) { + var viewData = token.toJSON(); + var ts = viewData.lastActivity * 1000; + viewData.lastActivity = OC.Util.relativeModifiedDate(ts); + viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); + var html = _this.template(viewData); + var $html = $(html); + $html.find('.last-activity').tooltip(); + $html.find('.icon-delete').tooltip(); + list.append($html); + }); + }, + + toggleLoading: function(state) { + this.$('.token-list').toggleClass('icon-loading', state); + }, + + _toggleHeader: function(show) { + this.$('.hidden-when-empty').toggleClass('hidden', !show); + } + }); + + var AuthTokenView = Backbone.View.extend({ + collection: null, + + _views: [], + + _form: undefined, + + _tokenName: undefined, + + _addTokenBtn: undefined, + + _result: undefined, + + _newToken: undefined, + + _hideTokenBtn: undefined, + + _addingToken: false, + + initialize: function(options) { + this.collection = options.collection; + + var tokenTypes = [0, 1]; + var _this = this; + _.each(tokenTypes, function(type) { + var el = type === 0 ? '#sessions' : '#devices'; + _this._views.push(new SubView({ + el: el, + type: type, + collection: _this.collection + })); + + var $el = $(el); + $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._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)); + }, + + render: function() { + _.each(this._views, function(view) { + view.render(); + view.toggleLoading(false); + }); + }, + + reload: function() { + var _this = this; + + _.each(this._views, function(view) { + view.toggleLoading(true); + }); + + var loadingTokens = this.collection.fetch(); + + $.when(loadingTokens).done(function() { + _this.render(); + }); + $.when(loadingTokens).fail(function() { + OC.Notification.showTemporary(t('core', 'Error while loading browser sessions and device tokens')); + }); + }, + + _addDeviceToken: function() { + var _this = this; + this._toggleAddingToken(true); + + var deviceName = this._tokenName.val(); + var creatingToken = $.ajax(OC.generateUrl('/settings/personal/authtokens'), { + method: 'POST', + data: { + name: deviceName + } + }); + + $.when(creatingToken).done(function(resp) { + _this.collection.add(resp.deviceToken); + _this.render(); + _this._newToken.val(resp.token); + _this._toggleFormResult(false); + _this._newToken.select(); + _this._tokenName.val(''); + }); + $.when(creatingToken).fail(function() { + OC.Notification.showTemporary(t('core', 'Error while creating device token')); + }); + $.when(creatingToken).always(function() { + _this._toggleAddingToken(false); + }); + }, + + _onNewTokenFocus: function() { + this._newToken.select(); + }, + + _hideToken: function() { + this._toggleFormResult(true); + }, + + _toggleAddingToken: function(state) { + this._addingToken = state; + this._addTokenBtn.toggleClass('icon-loading-small', state); + }, + + _onDeleteToken: function(event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + var id = $row.data('id'); + + var token = this.collection.get(id); + if (_.isUndefined(token)) { + // Ignore event + return; + } + + var destroyingToken = token.destroy(); + + var _this = this; + $.when(destroyingToken).fail(function() { + OC.Notification.showTemporary(t('core', 'Error while deleting the token')); + }); + $.when(destroyingToken).always(function() { + _this.render(); + }); + }, + + _toggleFormResult: function(showForm) { + this._form.toggleClass('hidden', !showForm); + this._result.toggleClass('hidden', showForm); + } + }); + + OC.Settings.AuthTokenView = AuthTokenView; + +})(OC, _, Backbone, $, Handlebars, moment); diff --git a/settings/js/personal.js b/settings/js/personal.js index 09f63f3f6af..aea2400e999 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -361,6 +361,13 @@ $(document).ready(function () { if (oc_config.enable_avatars) { $('#avatar .avatardiv').avatar(OC.currentUser, 145); } + + // Show token views + var collection = new OC.Settings.AuthTokenCollection(); + var view = new OC.Settings.AuthTokenView({ + collection: collection + }); + view.reload(); }); if (!OC.Encryption) { diff --git a/settings/personal.php b/settings/personal.php index 6c2fccbec9b..3b283fb2d38 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -42,6 +42,9 @@ $config = \OC::$server->getConfig(); $urlGenerator = \OC::$server->getURLGenerator(); // Highlight navigation entry +OC_Util::addScript('settings', 'authtoken'); +OC_Util::addScript('settings', 'authtoken_collection'); +OC_Util::addScript('settings', 'authtoken_view'); OC_Util::addScript( 'settings', 'personal' ); OC_Util::addScript('settings', 'certificates'); OC_Util::addStyle( 'settings', 'settings' ); @@ -171,6 +174,8 @@ $tmpl->assign('groups', $groups2); // add hardcoded forms from the template $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' => 'clientsbox', 'section-name' => $l->t('Sync clients')]; $forms=OC_App::getForms('personal'); diff --git a/settings/routes.php b/settings/routes.php index 90e1d1e442b..5c356e01734 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -36,7 +36,8 @@ $application = new Application(); $application->registerRoutes($this, [ 'resources' => [ 'groups' => ['url' => '/settings/users/groups'], - 'users' => ['url' => '/settings/users/users'] + 'users' => ['url' => '/settings/users/users'], + 'AuthSettings' => ['url' => '/settings/personal/authtokens'], ], 'routes' => [ ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'], diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 29bf240e7e3..dcc83b3e99e 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -139,6 +139,47 @@ if($_['passwordChangeSupported']) { } ?> +<div id="sessions" class="section"> + <h2><?php p($l->t('Sessions'));?></h2> + <span class="hidden-when-empty"><?php p($l->t('These are the web browsers currently logged in to your ownCloud.'));?></span> + <table> + <thead class="token-list-header"> + <tr> + <th><?php p($l->t('Browser'));?></th> + <th><?php p($l->t('Most recent activity'));?></th> + <th></th> + </tr> + </thead> + <tbody class="token-list icon-loading"> + </tbody> + </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> + <table> + <thead class="hidden-when-empty"> + <tr> + <th><?php p($l->t('Name'));?></th> + <th><?php p($l->t('Most recent activity'));?></th> + <th></th> + </tr> + </thead> + <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> + </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> +</div> + <form id="language" class="section"> <h2> <label for="languageinput"><?php p($l->t('Language'));?></label> diff --git a/tests/core/command/config/app/deleteconfigtest.php b/tests/Core/Command/Config/App/DeleteConfigTest.php index 7056e1b1ff9..7056e1b1ff9 100644 --- a/tests/core/command/config/app/deleteconfigtest.php +++ b/tests/Core/Command/Config/App/DeleteConfigTest.php diff --git a/tests/core/command/config/app/getconfigtest.php b/tests/Core/Command/Config/App/GetConfigTest.php index 1ceeb16ccf4..1ceeb16ccf4 100644 --- a/tests/core/command/config/app/getconfigtest.php +++ b/tests/Core/Command/Config/App/GetConfigTest.php diff --git a/tests/core/command/config/app/setconfigtest.php b/tests/Core/Command/Config/App/SetConfigTest.php index 14d7b0cb7b5..14d7b0cb7b5 100644 --- a/tests/core/command/config/app/setconfigtest.php +++ b/tests/Core/Command/Config/App/SetConfigTest.php diff --git a/tests/core/command/config/importtest.php b/tests/Core/Command/Config/ImportTest.php index f14880f8bf4..f14880f8bf4 100644 --- a/tests/core/command/config/importtest.php +++ b/tests/Core/Command/Config/ImportTest.php diff --git a/tests/core/command/config/listconfigstest.php b/tests/Core/Command/Config/ListConfigsTest.php index bde6a1b0db3..bde6a1b0db3 100644 --- a/tests/core/command/config/listconfigstest.php +++ b/tests/Core/Command/Config/ListConfigsTest.php diff --git a/tests/core/command/config/system/deleteconfigtest.php b/tests/Core/Command/Config/System/DeleteConfigTest.php index 11bfb6ae7ad..11bfb6ae7ad 100644 --- a/tests/core/command/config/system/deleteconfigtest.php +++ b/tests/Core/Command/Config/System/DeleteConfigTest.php diff --git a/tests/core/command/config/system/getconfigtest.php b/tests/Core/Command/Config/System/GetConfigTest.php index ebbea634cde..ebbea634cde 100644 --- a/tests/core/command/config/system/getconfigtest.php +++ b/tests/Core/Command/Config/System/GetConfigTest.php diff --git a/tests/core/command/config/system/setconfigtest.php b/tests/Core/Command/Config/System/SetConfigTest.php index c0b664d7522..c0b664d7522 100644 --- a/tests/core/command/config/system/setconfigtest.php +++ b/tests/Core/Command/Config/System/SetConfigTest.php diff --git a/tests/core/command/encryption/changekeystorageroottest.php b/tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php index 2a1f48983f1..2a1f48983f1 100644 --- a/tests/core/command/encryption/changekeystorageroottest.php +++ b/tests/Core/Command/Encryption/ChangeKeyStorageRootTest.php diff --git a/tests/core/command/encryption/decryptalltest.php b/tests/Core/Command/Encryption/DecryptAllTest.php index 972ea03150c..972ea03150c 100644 --- a/tests/core/command/encryption/decryptalltest.php +++ b/tests/Core/Command/Encryption/DecryptAllTest.php diff --git a/tests/core/command/encryption/disabletest.php b/tests/Core/Command/Encryption/DisableTest.php index dfd06e2e26e..dfd06e2e26e 100644 --- a/tests/core/command/encryption/disabletest.php +++ b/tests/Core/Command/Encryption/DisableTest.php diff --git a/tests/core/command/encryption/enabletest.php b/tests/Core/Command/Encryption/EnableTest.php index e2357464aa1..e2357464aa1 100644 --- a/tests/core/command/encryption/enabletest.php +++ b/tests/Core/Command/Encryption/EnableTest.php diff --git a/tests/core/command/encryption/encryptalltest.php b/tests/Core/Command/Encryption/EncryptAllTest.php index 128b4caa148..128b4caa148 100644 --- a/tests/core/command/encryption/encryptalltest.php +++ b/tests/Core/Command/Encryption/EncryptAllTest.php diff --git a/tests/core/command/encryption/setdefaultmoduletest.php b/tests/Core/Command/Encryption/SetDefaultModuleTest.php index 3230a57db07..3230a57db07 100644 --- a/tests/core/command/encryption/setdefaultmoduletest.php +++ b/tests/Core/Command/Encryption/SetDefaultModuleTest.php diff --git a/tests/core/command/log/managetest.php b/tests/Core/Command/Log/ManageTest.php index 6fb83347f23..6fb83347f23 100644 --- a/tests/core/command/log/managetest.php +++ b/tests/Core/Command/Log/ManageTest.php diff --git a/tests/core/command/log/owncloudtest.php b/tests/Core/Command/Log/OwnCloudTest.php index 3cb05221c37..3cb05221c37 100644 --- a/tests/core/command/log/owncloudtest.php +++ b/tests/Core/Command/Log/OwnCloudTest.php diff --git a/tests/core/command/maintenance/datafingerprinttest.php b/tests/Core/Command/Maintenance/DataFingerprintTest.php index 4d661b5c027..4d661b5c027 100644 --- a/tests/core/command/maintenance/datafingerprinttest.php +++ b/tests/Core/Command/Maintenance/DataFingerprintTest.php diff --git a/tests/core/command/maintenance/mimetype/updatedbtest.php b/tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php index 217301102c5..217301102c5 100644 --- a/tests/core/command/maintenance/mimetype/updatedbtest.php +++ b/tests/Core/Command/Maintenance/Mimetype/UpdateDBTest.php diff --git a/tests/core/command/maintenance/singleusertest.php b/tests/Core/Command/Maintenance/SingleUserTest.php index 6629f39564f..6629f39564f 100644 --- a/tests/core/command/maintenance/singleusertest.php +++ b/tests/Core/Command/Maintenance/SingleUserTest.php diff --git a/tests/core/command/user/deletetest.php b/tests/Core/Command/User/DeleteTest.php index bb813626d7a..bb813626d7a 100644 --- a/tests/core/command/user/deletetest.php +++ b/tests/Core/Command/User/DeleteTest.php diff --git a/tests/core/command/user/lastseentest.php b/tests/Core/Command/User/LastSeenTest.php index 84805f5c072..84805f5c072 100644 --- a/tests/core/command/user/lastseentest.php +++ b/tests/Core/Command/User/LastSeenTest.php diff --git a/tests/core/controller/avatarcontrollertest.php b/tests/Core/Controller/AvatarControllerTest.php index c99b69228b1..937d8aaf17b 100644 --- a/tests/core/controller/avatarcontrollertest.php +++ b/tests/Core/Controller/AvatarControllerTest.php @@ -18,13 +18,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ -namespace OC\Core\Controller; -use OC; +namespace Tests\Core\Controller; + use OC\Core\Application; use OCP\AppFramework\IAppContainer; use OCP\AppFramework\Http; -use OCP\Files\Folder; use OCP\Files\File; use OCP\Files\NotFoundException; use OCP\IUser; @@ -52,7 +51,7 @@ class AvatarControllerTest extends \Test\TestCase { /** @var IAppContainer */ private $container; - /** @var AvatarController */ + /** @var \OC\Core\Controller\AvatarController */ private $avatarController; /** @var IAvatar */ private $avatarMock; @@ -228,7 +227,7 @@ class AvatarControllerTest extends \Test\TestCase { * Fetch tmp avatar */ public function testTmpAvatarValid() { - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $response = $this->avatarController->getTmpAvatar(); $this->assertEquals(Http::STATUS_OK, $response->getStatus()); @@ -250,14 +249,14 @@ class AvatarControllerTest extends \Test\TestCase { public function testPostAvatarFile() { //Create temp file $fileName = tempnam(null, "avatarTest"); - $copyRes = copy(OC::$SERVERROOT.'/tests/data/testimage.jpg', $fileName); + $copyRes = copy(\OC::$SERVERROOT.'/tests/data/testimage.jpg', $fileName); $this->assertTrue($copyRes); //Create file in cache - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); //Create request return - $reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => [filesize(OC::$SERVERROOT.'/tests/data/testimage.jpg')]]; + $reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => [filesize(\OC::$SERVERROOT.'/tests/data/testimage.jpg')]]; $this->container['Request']->method('getUploadedFile')->willReturn($reqRet); $response = $this->avatarController->postAvatar(null); @@ -288,14 +287,14 @@ class AvatarControllerTest extends \Test\TestCase { public function testPostAvatarFileGif() { //Create temp file $fileName = tempnam(null, "avatarTest"); - $copyRes = copy(OC::$SERVERROOT.'/tests/data/testimage.gif', $fileName); + $copyRes = copy(\OC::$SERVERROOT.'/tests/data/testimage.gif', $fileName); $this->assertTrue($copyRes); //Create file in cache - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.gif')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.gif')); //Create request return - $reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => filesize(OC::$SERVERROOT.'/tests/data/testimage.gif')]; + $reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => filesize(\OC::$SERVERROOT.'/tests/data/testimage.gif')]; $this->container['Request']->method('getUploadedFile')->willReturn($reqRet); $response = $this->avatarController->postAvatar(null); @@ -313,7 +312,7 @@ class AvatarControllerTest extends \Test\TestCase { //Mock node API call $file = $this->getMockBuilder('OCP\Files\File') ->disableOriginalConstructor()->getMock(); - $file->method('getContent')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $file->method('getContent')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $this->container['UserFolder']->method('get')->willReturn($file); //Create request return @@ -349,7 +348,7 @@ class AvatarControllerTest extends \Test\TestCase { ->will($this->throwException(new \Exception("foo"))); $file = $this->getMockBuilder('OCP\Files\File') ->disableOriginalConstructor()->getMock(); - $file->method('getContent')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $file->method('getContent')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $this->container['UserFolder']->method('get')->willReturn($file); $this->container['Logger']->expects($this->once()) @@ -382,7 +381,7 @@ class AvatarControllerTest extends \Test\TestCase { * Test with non square crop */ public function testPostCroppedAvatarNoSquareCrop() { - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $this->avatarMock->method('set')->will($this->throwException(new \OC\NotSquareException)); $this->container['AvatarManager']->method('getAvatar')->willReturn($this->avatarMock); @@ -395,7 +394,7 @@ class AvatarControllerTest extends \Test\TestCase { * Check for proper reply on proper crop argument */ public function testPostCroppedAvatarValidCrop() { - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $this->container['AvatarManager']->method('getAvatar')->willReturn($this->avatarMock); $response = $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 10]); @@ -407,7 +406,7 @@ class AvatarControllerTest extends \Test\TestCase { * Test what happens if the cropping of the avatar fails */ public function testPostCroppedAvatarException() { - $this->container['Cache']->method('get')->willReturn(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg')); + $this->container['Cache']->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT.'/tests/data/testimage.jpg')); $this->avatarMock->method('set')->will($this->throwException(new \Exception('foo'))); $this->container['AvatarManager']->method('getAvatar')->willReturn($this->avatarMock); @@ -424,7 +423,7 @@ class AvatarControllerTest extends \Test\TestCase { * Check for proper reply on proper crop argument */ public function testFileTooBig() { - $fileName = OC::$SERVERROOT.'/tests/data/testimage.jpg'; + $fileName = \OC::$SERVERROOT.'/tests/data/testimage.jpg'; //Create request return $reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => [21*1024*1024]]; $this->container['Request']->method('getUploadedFile')->willReturn($reqRet); diff --git a/tests/core/controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php index 139d48ad7da..32902a01530 100644 --- a/tests/core/controller/LoginControllerTest.php +++ b/tests/Core/Controller/LoginControllerTest.php @@ -19,8 +19,9 @@ * */ -namespace OC\Core\Controller; +namespace Tests\Core\Controller; +use OC\Core\Controller\LoginController; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\IConfig; diff --git a/tests/core/controller/lostcontrollertest.php b/tests/Core/Controller/LostControllerTest.php index 44bc539247f..ca63c3404eb 100644 --- a/tests/core/controller/lostcontrollertest.php +++ b/tests/Core/Controller/LostControllerTest.php @@ -19,8 +19,9 @@ * */ -namespace OC\Core\Controller; +namespace Tests\Core\Controller; +use OC\Core\Controller\LostController; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; diff --git a/tests/core/controller/TokenControllerTest.php b/tests/Core/Controller/TokenControllerTest.php index 4635f96f48f..b600bfa5451 100644 --- a/tests/core/controller/TokenControllerTest.php +++ b/tests/Core/Controller/TokenControllerTest.php @@ -20,9 +20,10 @@ * */ -namespace OC\Core\Controller; +namespace Tests\Core\Controller; use OC\AppFramework\Http; +use OC\Core\Controller\TokenController; use OCP\AppFramework\Http\Response; use Test\TestCase; diff --git a/tests/core/templates/templates.php b/tests/Core/Templates/TemplatesTest.php index 338d80b276c..03565411a13 100644 --- a/tests/core/templates/templates.php +++ b/tests/Core/Templates/TemplatesTest.php @@ -2,7 +2,7 @@ namespace Tests\Core\Templates; -class Templates extends \Test\TestCase { +class TemplatesTest extends \Test\TestCase { public function test403() { $template = \OC::$SERVERROOT . '/core/templates/403.php'; diff --git a/tests/settings/controller/AppSettingsControllerTest.php b/tests/Settings/Controller/AppSettingsControllerTest.php index dba5728ca4b..9dcc55e135b 100644 --- a/tests/settings/controller/AppSettingsControllerTest.php +++ b/tests/Settings/Controller/AppSettingsControllerTest.php @@ -19,8 +19,9 @@ * */ -namespace OC\Settings\Controller; +namespace Tests\Settings\Controller; +use OC\Settings\Controller\AppSettingsController; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\TemplateResponse; @@ -36,7 +37,7 @@ use OC\OCSClient; /** * Class AppSettingsControllerTest * - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class AppSettingsControllerTest extends TestCase { /** @var AppSettingsController */ diff --git a/tests/settings/controller/CertificateControllerTest.php b/tests/Settings/Controller/CertificateControllerTest.php index 2fdbbb8b0ac..c9ea2a4024f 100644 --- a/tests/settings/controller/CertificateControllerTest.php +++ b/tests/Settings/Controller/CertificateControllerTest.php @@ -19,8 +19,9 @@ * */ -namespace OC\Settings\Controller; +namespace Tests\Settings\Controller; +use OC\Settings\Controller\CertificateController; use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -31,7 +32,7 @@ use OCP\ICertificateManager; /** * Class CertificateControllerTest * - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class CertificateControllerTest extends \Test\TestCase { /** @var CertificateController */ diff --git a/tests/settings/controller/CheckSetupControllerTest.php b/tests/Settings/Controller/CheckSetupControllerTest.php index 3ce7c64b4a3..f48e9c04f3d 100644 --- a/tests/settings/controller/CheckSetupControllerTest.php +++ b/tests/Settings/Controller/CheckSetupControllerTest.php @@ -19,8 +19,9 @@ * */ -namespace OC\Settings\Controller; +namespace Tests\Settings\Controller; +use OC\Settings\Controller\CheckSetupController; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\DataResponse; @@ -47,7 +48,7 @@ function version_compare($version1, $version2) { /** * Class CheckSetupControllerTest * - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class CheckSetupControllerTest extends TestCase { /** @var int */ diff --git a/tests/settings/controller/EncryptionControllerTest.php b/tests/Settings/Controller/EncryptionControllerTest.php index 565aaf29c9a..adbbe2cf6a4 100644 --- a/tests/settings/controller/EncryptionControllerTest.php +++ b/tests/Settings/Controller/EncryptionControllerTest.php @@ -19,10 +19,11 @@ * */ -namespace OC\Settings\Controller; +namespace Tests\Settings\Controller; use OC\DB\Connection; use OC\Files\View; +use OC\Settings\Controller\EncryptionController; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -33,7 +34,7 @@ use Test\TestCase; /** * Class EncryptionControllerTest * - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class EncryptionControllerTest extends TestCase { /** @var IRequest */ diff --git a/tests/settings/controller/groupscontrollertest.php b/tests/Settings/Controller/GroupsControllerTest.php index 82b4c7d3c05..70cb8282b26 100644 --- a/tests/settings/controller/groupscontrollertest.php +++ b/tests/Settings/Controller/GroupsControllerTest.php @@ -7,16 +7,18 @@ * later. * See the COPYING-README file. */ -namespace OC\Settings\Controller; + +namespace Tests\Settings\Controller; use OC\Group\Group; use OC\Group\MetaData; use \OC\Settings\Application; +use OC\Settings\Controller\GroupsController; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; /** - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class GroupsControllerTest extends \Test\TestCase { diff --git a/tests/settings/controller/logsettingscontrollertest.php b/tests/Settings/Controller/LogSettingsControllerTest.php index 60680ba4647..092c04aecc7 100644 --- a/tests/settings/controller/logsettingscontrollertest.php +++ b/tests/Settings/Controller/LogSettingsControllerTest.php @@ -7,13 +7,14 @@ * later. * See the COPYING-README file. */ -namespace Test\Settings\Controller; + +namespace Tests\Settings\Controller; use \OC\Settings\Application; use OC\Settings\Controller\LogSettingsController; /** - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class LogSettingsControllerTest extends \Test\TestCase { diff --git a/tests/settings/controller/mailsettingscontrollertest.php b/tests/Settings/Controller/MailSettingsControllerTest.php index cc25fda52f7..1ac6bae69ea 100644 --- a/tests/settings/controller/mailsettingscontrollertest.php +++ b/tests/Settings/Controller/MailSettingsControllerTest.php @@ -7,12 +7,13 @@ * later. * See the COPYING-README file. */ -namespace OC\Settings\Controller; -use \OC\Settings\Application; +namespace Tests\Settings\Controller; + +use OC\Settings\Application; /** - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class MailSettingsControllerTest extends \Test\TestCase { diff --git a/tests/settings/controller/securitysettingscontrollertest.php b/tests/Settings/Controller/SecuritySettingsControllerTest.php index 56848d8df30..11b0edcae23 100644 --- a/tests/settings/controller/securitysettingscontrollertest.php +++ b/tests/Settings/Controller/SecuritySettingsControllerTest.php @@ -7,12 +7,13 @@ * later. * See the COPYING-README file. */ -namespace OC\Settings\Controller; +namespace Tests\Settings\Controller; use \OC\Settings\Application; +use OC\Settings\Controller\SecuritySettingsController; /** - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class SecuritySettingsControllerTest extends \PHPUnit_Framework_TestCase { diff --git a/tests/settings/controller/userscontrollertest.php b/tests/Settings/Controller/UsersControllerTest.php index 2a2b53d8ff8..244d1f744d3 100644 --- a/tests/settings/controller/userscontrollertest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -7,7 +7,8 @@ * later. * See the COPYING-README file. */ -namespace OC\Settings\Controller; + +namespace Tests\Settings\Controller; use \OC\Settings\Application; use OCP\AppFramework\Http; @@ -16,7 +17,7 @@ use OCP\AppFramework\Http\DataResponse; /** * @group DB * - * @package OC\Settings\Controller + * @package Tests\Settings\Controller */ class UsersControllerTest extends \Test\TestCase { diff --git a/tests/settings/middleware/subadminmiddlewaretest.php b/tests/Settings/Middleware/SubadminMiddlewareTest.php index c16c21c9c10..652f8b2d151 100644 --- a/tests/settings/middleware/subadminmiddlewaretest.php +++ b/tests/Settings/Middleware/SubadminMiddlewareTest.php @@ -8,10 +8,11 @@ * See the COPYING-README file. */ -namespace OC\Settings\Middleware; +namespace Tests\Settings\Middleware; use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Settings\Middleware\SubadminMiddleware; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; @@ -19,7 +20,7 @@ use OCP\AppFramework\Http\TemplateResponse; * Verifies whether an user has at least subadmin rights. * To bypass use the `@NoSubadminRequired` annotation * - * @package OC\Settings\Middleware + * @package Tests\Settings\Middleware */ class SubadminMiddlewareTest extends \Test\TestCase { /** @var SubadminMiddleware */ diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index e17149a5c1b..9179e23bfb2 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -159,4 +159,31 @@ class DefaultTokenMapperTest extends TestCase { $this->assertCount(0, $this->mapper->getTokenByUser($user)); } + public function testDeleteById() { + $user = $this->getMock('\OCP\IUser'); + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('id') + ->from('authtoken') + ->where($qb->expr()->eq('token', $qb->createNamedParameter('9c5a2e661482b65597408a6bb6c4a3d1af36337381872ac56e445a06cdb7fea2b1039db707545c11027a4966919918b19d875a8b774840b18c6cbb7ae56fe206'))); + $result = $qb->execute(); + $id = $result->fetch()['id']; + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user1')); + + $this->mapper->deleteById($user, $id); + $this->assertEquals(2, $this->getNumberOfTokens()); + } + + public function testDeleteByIdWrongUser() { + $user = $this->getMock('\OCP\IUser'); + $id = 33; + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user10000')); + + $this->mapper->deleteById($user, $id); + $this->assertEquals(3, $this->getNumberOfTokens()); + } + } diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index eeb249cfa8a..8af5e1e933a 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -170,6 +170,17 @@ class DefaultTokenProviderTest extends TestCase { $this->tokenProvider->invalidateToken('token7'); } + public function testInvaildateTokenById() { + $id = 123; + $user = $this->getMock('\OCP\IUser'); + + $this->mapper->expects($this->once()) + ->method('deleteById') + ->with($user, $id); + + $this->tokenProvider->invalidateTokenById($user, $id); + } + public function testInvalidateOldTokens() { $defaultSessionLifetime = 60 * 60 * 24; $this->config->expects($this->once()) diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index 44a48535b9b..6ef00721a70 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -57,6 +57,8 @@ class DefaultShareProviderTest extends \Test\TestCase { $this->groupManager = $this->getMock('OCP\IGroupManager'); $this->rootFolder = $this->getMock('OCP\Files\IRootFolder'); + $this->userManager->expects($this->any())->method('userExists')->willReturn(true); + //Empty share table $this->dbConn->getQueryBuilder()->delete('share')->execute(); @@ -587,7 +589,7 @@ class DefaultShareProviderTest extends \Test\TestCase { } public function testCreateUserShare() { - $share = new \OC\Share20\Share($this->rootFolder); + $share = new \OC\Share20\Share($this->rootFolder, $this->userManager); $shareOwner = $this->getMock('OCP\IUser'); $shareOwner->method('getUID')->WillReturn('shareOwner'); @@ -635,7 +637,7 @@ class DefaultShareProviderTest extends \Test\TestCase { } public function testCreateGroupShare() { - $share = new \OC\Share20\Share($this->rootFolder); + $share = new \OC\Share20\Share($this->rootFolder, $this->userManager); $shareOwner = $this->getMock('\OCP\IUser'); $shareOwner->method('getUID')->willReturn('shareOwner'); @@ -683,7 +685,7 @@ class DefaultShareProviderTest extends \Test\TestCase { } public function testCreateLinkShare() { - $share = new \OC\Share20\Share($this->rootFolder); + $share = new \OC\Share20\Share($this->rootFolder, $this->userManager); $shareOwner = $this->getMock('\OCP\IUser'); $shareOwner->method('getUID')->willReturn('shareOwner'); diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index 7d79150449c..a50ea6c892a 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2283,6 +2283,9 @@ class ManagerTest extends \Test\TestCase { } public function testUpdateShareUser() { + + $this->userManager->expects($this->any())->method('userExists')->willReturn(true); + $manager = $this->createManagerMock() ->setMethods([ 'canShare', @@ -2567,4 +2570,4 @@ class DummyFactory implements IProviderFactory { public function getProviderForType($shareType) { return $this->provider; } -}
\ No newline at end of file +} diff --git a/tests/lib/Share20/ShareTest.php b/tests/lib/Share20/ShareTest.php index fdfc69f6577..91bd2fe84b6 100644 --- a/tests/lib/Share20/ShareTest.php +++ b/tests/lib/Share20/ShareTest.php @@ -36,7 +36,8 @@ class ShareTest extends \Test\TestCase { public function setUp() { $this->rootFolder = $this->getMock('\OCP\Files\IRootFolder'); - $this->share = new \OC\Share20\Share($this->rootFolder); + $this->userManager = $this->getMock('OCP\IUserManager'); + $this->share = new \OC\Share20\Share($this->rootFolder, $this->userManager); } /** diff --git a/tests/lib/files/storage/wrapper/Encoding.php b/tests/lib/files/storage/wrapper/Encoding.php new file mode 100644 index 00000000000..79b7a3bfb6e --- /dev/null +++ b/tests/lib/files/storage/wrapper/Encoding.php @@ -0,0 +1,203 @@ +<?php +/** + * Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage\Wrapper; + +class Encoding extends \Test\Files\Storage\Storage { + + const NFD_NAME = 'ümlaut'; + const NFC_NAME = 'ümlaut'; + + /** + * @var \OC\Files\Storage\Temporary + */ + private $sourceStorage; + + public function setUp() { + parent::setUp(); + $this->sourceStorage = new \OC\Files\Storage\Temporary([]); + $this->instance = new \OC\Files\Storage\Wrapper\Encoding([ + 'storage' => $this->sourceStorage + ]); + } + + public function tearDown() { + $this->sourceStorage->cleanUp(); + parent::tearDown(); + } + + public function directoryProvider() { + $a = parent::directoryProvider(); + $a[] = [self::NFD_NAME]; + return $a; + } + + public function fileNameProvider() { + $a = parent::fileNameProvider(); + $a[] = [self::NFD_NAME . '.txt']; + return $a; + } + + public function copyAndMoveProvider() { + $a = parent::copyAndMoveProvider(); + $a[] = [self::NFD_NAME . '.txt', self::NFC_NAME . '-renamed.txt']; + return $a; + } + + public function accessNameProvider() { + return [ + [self::NFD_NAME], + [self::NFC_NAME], + ]; + } + + /** + * @dataProvider accessNameProvider + */ + public function testFputEncoding($accessName) { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar'); + $this->assertEquals('bar', $this->instance->file_get_contents($accessName)); + } + + /** + * @dataProvider accessNameProvider + */ + public function testFopenReadEncoding($accessName) { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar'); + $fh = $this->instance->fopen($accessName, 'r'); + $data = fgets($fh); + fclose($fh); + $this->assertEquals('bar', $data); + } + + /** + * @dataProvider accessNameProvider + */ + public function testFopenOverwriteEncoding($accessName) { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar'); + $fh = $this->instance->fopen($accessName, 'w'); + $data = fputs($fh, 'test'); + fclose($fh); + $data = $this->sourceStorage->file_get_contents(self::NFD_NAME); + $this->assertEquals('test', $data); + $this->assertFalse($this->sourceStorage->file_exists(self::NFC_NAME)); + } + + /** + * @dataProvider accessNameProvider + */ + public function testFileExistsEncoding($accessName) { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar'); + $this->assertTrue($this->instance->file_exists($accessName)); + } + + /** + * @dataProvider accessNameProvider + */ + public function testUnlinkEncoding($accessName) { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar'); + $this->assertTrue($this->instance->unlink($accessName)); + $this->assertFalse($this->sourceStorage->file_exists(self::NFC_NAME)); + $this->assertFalse($this->sourceStorage->file_exists(self::NFD_NAME)); + } + + public function testNfcHigherPriority() { + $this->sourceStorage->file_put_contents(self::NFC_NAME, 'nfc'); + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'nfd'); + $this->assertEquals('nfc', $this->instance->file_get_contents(self::NFC_NAME)); + } + + public function encodedDirectoriesProvider() { + return [ + [self::NFD_NAME, self::NFC_NAME], + [self::NFD_NAME . '/' . self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME], + [self::NFD_NAME . '/' . self::NFC_NAME . '/' .self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME . '/' . self::NFC_NAME], + ]; + } + + /** + * @dataProvider encodedDirectoriesProvider + */ + public function testOperationInsideDirectory($sourceDir, $accessDir) { + $this->sourceStorage->mkdir($sourceDir); + $this->instance->file_put_contents($accessDir . '/test.txt', 'bar'); + $this->assertTrue($this->instance->file_exists($accessDir . '/test.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents($accessDir . '/test.txt')); + + $this->sourceStorage->file_put_contents($sourceDir . '/' . self::NFD_NAME, 'foo'); + $this->assertTrue($this->instance->file_exists($accessDir . '/' . self::NFC_NAME)); + $this->assertEquals('foo', $this->instance->file_get_contents($accessDir . '/' . self::NFC_NAME)); + + // try again to make it use cached path + $this->assertEquals('bar', $this->instance->file_get_contents($accessDir . '/test.txt')); + $this->assertTrue($this->instance->file_exists($accessDir . '/test.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents($accessDir . '/' . self::NFC_NAME)); + $this->assertTrue($this->instance->file_exists($accessDir . '/' . self::NFC_NAME)); + } + + public function testCacheExtraSlash() { + $this->sourceStorage->file_put_contents(self::NFD_NAME, 'foo'); + $this->assertEquals(3, $this->instance->file_put_contents(self::NFC_NAME, 'bar')); + $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME)); + clearstatcache(); + $this->assertEquals(5, $this->instance->file_put_contents('/' . self::NFC_NAME, 'baric')); + $this->assertEquals('baric', $this->instance->file_get_contents(self::NFC_NAME)); + clearstatcache(); + $this->assertEquals(8, $this->instance->file_put_contents('/' . self::NFC_NAME, 'barbaric')); + $this->assertEquals('barbaric', $this->instance->file_get_contents('//' . self::NFC_NAME)); + } + + public function sourceAndTargetDirectoryProvider() { + return [ + [self::NFC_NAME . '1', self::NFC_NAME . '2'], + [self::NFD_NAME . '1', self::NFC_NAME . '2'], + [self::NFC_NAME . '1', self::NFD_NAME . '2'], + [self::NFD_NAME . '1', self::NFD_NAME . '2'], + ]; + } + + /** + * @dataProvider sourceAndTargetDirectoryProvider + */ + public function testCopyAndMoveEncodedFolder($sourceDir, $targetDir) { + $this->sourceStorage->mkdir($sourceDir); + $this->sourceStorage->mkdir($targetDir); + $this->sourceStorage->file_put_contents($sourceDir . '/test.txt', 'bar'); + $this->assertTrue($this->instance->copy(self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test.txt')); + + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '1/test.txt')); + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test.txt')); + + $this->assertTrue($this->instance->rename(self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test2.txt')); + $this->assertFalse($this->instance->file_exists(self::NFC_NAME . '1/test.txt')); + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test2.txt')); + + $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test2.txt')); + } + + /** + * @dataProvider sourceAndTargetDirectoryProvider + */ + public function testCopyAndMoveFromStorageEncodedFolder($sourceDir, $targetDir) { + $this->sourceStorage->mkdir($sourceDir); + $this->sourceStorage->mkdir($targetDir); + $this->sourceStorage->file_put_contents($sourceDir . '/test.txt', 'bar'); + $this->assertTrue($this->instance->copyFromStorage($this->instance, self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test.txt')); + + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '1/test.txt')); + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test.txt')); + + $this->assertTrue($this->instance->moveFromStorage($this->instance, self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test2.txt')); + $this->assertFalse($this->instance->file_exists(self::NFC_NAME . '1/test.txt')); + $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test2.txt')); + + $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test2.txt')); + } +} diff --git a/tests/settings/controller/AuthSettingsControllerTest.php b/tests/settings/controller/AuthSettingsControllerTest.php new file mode 100644 index 00000000000..49491c8ff52 --- /dev/null +++ b/tests/settings/controller/AuthSettingsControllerTest.php @@ -0,0 +1,156 @@ +<?php + +/** + * @author Christoph Wurst <christoph@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace Test\Settings\Controller; + +use OC\AppFramework\Http; +use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Token\IToken; +use OC\Settings\Controller\AuthSettingsController; +use OCP\AppFramework\Http\JSONResponse; +use OCP\Session\Exceptions\SessionNotAvailableException; +use Test\TestCase; + +class AuthSettingsControllerTest extends TestCase { + + /** @var AuthSettingsController */ + private $controller; + private $request; + private $tokenProvider; + private $userManager; + private $session; + private $secureRandom; + private $uid; + + protected function setUp() { + parent::setUp(); + + $this->request = $this->getMock('\OCP\IRequest'); + $this->tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider'); + $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->session = $this->getMock('\OCP\ISession'); + $this->secureRandom = $this->getMock('\OCP\Security\ISecureRandom'); + $this->uid = 'jane'; + $this->user = $this->getMock('\OCP\IUser'); + + $this->controller = new AuthSettingsController('core', $this->request, $this->tokenProvider, $this->userManager, $this->session, $this->secureRandom, $this->uid); + } + + public function testIndex() { + $result = [ + 'token1', + 'token2', + ]; + $this->userManager->expects($this->once()) + ->method('get') + ->with($this->uid) + ->will($this->returnValue($this->user)); + $this->tokenProvider->expects($this->once()) + ->method('getTokenByUser') + ->with($this->user) + ->will($this->returnValue($result)); + + $this->assertEquals($result, $this->controller->index()); + } + + public function testCreate() { + $name = 'Nexus 4'; + $sessionToken = $this->getMock('\OC\Authentication\Token\IToken'); + $deviceToken = $this->getMock('\OC\Authentication\Token\IToken'); + $password = '123456'; + + $this->session->expects($this->once()) + ->method('getId') + ->will($this->returnValue('sessionid')); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('sessionid') + ->will($this->returnValue($sessionToken)); + $this->tokenProvider->expects($this->once()) + ->method('getPassword') + ->with($sessionToken, 'sessionid') + ->will($this->returnValue($password)); + + $this->secureRandom->expects($this->exactly(4)) + ->method('generate') + ->with(5, implode('', range('A', 'Z'))) + ->will($this->returnValue('XXXXX')); + $newToken = 'XXXXX-XXXXX-XXXXX-XXXXX'; + + $this->tokenProvider->expects($this->once()) + ->method('generateToken') + ->with($newToken, $this->uid, $password, $name, IToken::PERMANENT_TOKEN) + ->will($this->returnValue($deviceToken)); + + $expected = [ + 'token' => $newToken, + 'deviceToken' => $deviceToken, + ]; + $this->assertEquals($expected, $this->controller->create($name)); + } + + public function testCreateSessionNotAvailable() { + $name = 'personal phone'; + + $this->session->expects($this->once()) + ->method('getId') + ->will($this->throwException(new SessionNotAvailableException())); + + $expected = new JSONResponse(); + $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); + + $this->assertEquals($expected, $this->controller->create($name)); + } + + public function testCreateInvalidToken() { + $name = 'Company IPhone'; + + $this->session->expects($this->once()) + ->method('getId') + ->will($this->returnValue('sessionid')); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('sessionid') + ->will($this->throwException(new InvalidTokenException())); + + $expected = new JSONResponse(); + $expected->setStatus(Http::STATUS_SERVICE_UNAVAILABLE); + + $this->assertEquals($expected, $this->controller->create($name)); + } + + public function testDestroy() { + $id = 123; + $user = $this->getMock('\OCP\IUser'); + + $this->userManager->expects($this->once()) + ->method('get') + ->with($this->uid) + ->will($this->returnValue($user)); + $this->tokenProvider->expects($this->once()) + ->method('invalidateTokenById') + ->with($user, $id); + + $this->assertEquals([], $this->controller->destroy($id)); + } + +} |