diff --git a/appinfo/app.php b/appinfo/app.php index 19a70055..b629bec4 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -3,9 +3,3 @@ use OCP\Util; $app = \OC::$server->query(\OCA\ScienceMesh\AppInfo\ScienceMeshApp::class); - -$eventDispatcher = \OC::$server->getEventDispatcher(); -$eventDispatcher->addListener('OCA\Files::loadAdditionalScripts', function(){ - Util::addScript('sciencemesh', 'open-with'); - Util::addStyle('sciencemesh', 'open-with'); -}); diff --git a/appinfo/info.xml b/appinfo/info.xml index c3400826..48a0ab0d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -23,7 +23,7 @@ integration https://github.com/pondersource/nc-sciencemesh/issues - + diff --git a/appinfo/routes.php b/appinfo/routes.php index f1187bc0..88bd982e 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -38,31 +38,25 @@ ['name' => 'reva#UnsetArbitraryMetadata', 'url' => '/~{userId}/api/storage/UnsetArbitraryMetadata', 'verb' => 'POST'], ['name' => 'reva#UpdateGrant', 'url' => '/~{userId}/api/storage/UpdateGrant', 'verb' => 'POST'], ['name' => 'reva#Upload', 'url' => '/~{userId}/api/storage/Upload/{path}', 'verb' => 'PUT', 'requirements' => ['path' => '.+']], + ['name' => 'reva#Download', 'url' => '/~{userId}/api/storage/Download/{path}', 'verb' => 'GET', 'requirements' => array('path' => '.+')], // OCM routes - ['name' => 'ocm#addSentShare', 'url' => '/~{userId}/api/ocm/addSentShare', 'verb' => 'POST'], - ['name' => 'ocm#addReceivedShare', 'url' => '/~{userId}/api/ocm/addReceivedShare', 'verb' => 'POST'], - ['name' => 'ocm#GetSentShare', 'url' => '/~{userId}/api/ocm/GetSentShare', 'verb' => 'POST'], - ['name' => 'ocm#GetSentShareByToken', 'url' => '/~nobody/api/ocm/GetSentShareByToken', 'verb' => 'POST'], - ['name' => 'ocm#Unshare', 'url' => '/~{userId}/api/ocm/Unshare', 'verb' => 'POST'], - ['name' => 'ocm#UpdateShare', 'url' => '/~{userId}/api/ocm/UpdateShare', 'verb' => 'POST'], - ['name' => 'ocm#ListSentShares', 'url' => '/~{userId}/api/ocm/ListSentShares', 'verb' => 'POST'], - ['name' => 'ocm#ListSentShares', 'url' => '/~{userId}/api/ocm/ListShares', 'verb' => 'POST'], // alias for ListSentShares - ['name' => 'ocm#ListReceivedShares', 'url' => '/~{userId}/api/ocm/ListReceivedShares', 'verb' => 'POST'], - ['name' => 'ocm#GetReceivedShare', 'url' => '/~{userId}/api/ocm/GetReceivedShare', 'verb' => 'POST'], - ['name' => 'ocm#UpdateSentShare', 'url' => '/~{userId}/api/ocm/UpdateSentShare', 'verb' => 'POST'], - ['name' => 'ocm#UpdateReceivedShare', 'url' => '/~{userId}/api/ocm/UpdateReceivedShare', 'verb' => 'POST'], - ['name' => 'ocm#GetUser', 'url' => '/~{dummy}/api/user/GetUser', 'verb' => 'POST'], - ['name' => 'ocm#GetUserByClaim', 'url' => '/~{dummy}/api/user/GetUserByClaim', 'verb' => 'POST'], - - /* - ['name' => 'storage#createHome', 'url' => '/~{userId}/CreateHome', 'verb' => 'POST'], - ['name' => 'storage#listFolder', 'url' => '/~{userId}/ListFolder', 'verb' => 'POST'], - ['name' => 'storage#initiateUpload', 'url' => '/~{userId}/InitiateUpload', 'verb' => 'POST'], - ['name' => 'storage#upload', 'url' => '/~{userId}/Upload', 'verb' => 'POST'], - ['name' => 'storage#handleUpload', 'url' => '/~{userId}/Upload/{path}', 'verb' => 'PUT'], - ['name' => 'storage#getMD', 'url' => '/~{userId}/GetMD', 'verb' => 'POST'], - */ + ['name' => 'reva#addSentShare', 'url' => '/~{userId}/api/ocm/addSentShare', 'verb' => 'POST'], + ['name' => 'reva#addReceivedShare', 'url' => '/~{userId}/api/ocm/addReceivedShare', 'verb' => 'POST'], + ['name' => 'reva#GetSentShare', 'url' => '/~{userId}/api/ocm/GetSentShare', 'verb' => 'POST'], + ['name' => 'reva#Unshare', 'url' => '/~{userId}/api/ocm/Unshare', 'verb' => 'POST'], + ['name' => 'reva#UpdateShare', 'url' => '/~{userId}/api/ocm/UpdateShare', 'verb' => 'POST'], + ['name' => 'reva#ListSentShares', 'url' => '/~{userId}/api/ocm/ListSentShares', 'verb' => 'POST'], + ['name' => 'reva#ListSentShares', 'url' => '/~{userId}/api/ocm/ListShares', 'verb' => 'POST'], // alias for ListSentShares + ['name' => 'reva#ListReceivedShares', 'url' => '/~{userId}/api/ocm/ListReceivedShares', 'verb' => 'POST'], + ['name' => 'reva#GetReceivedShare', 'url' => '/~{userId}/api/ocm/GetReceivedShare', 'verb' => 'POST'], + ['name' => 'reva#UpdateSentShare', 'url' => '/~{userId}/api/ocm/UpdateSentShare', 'verb' => 'POST'], + ['name' => 'reva#UpdateReceivedShare', 'url' => '/~{userId}/api/ocm/UpdateReceivedShare', 'verb' => 'POST'], + ['name' => 'reva#GetUser', 'url' => '/~{dummy}/api/user/GetUser', 'verb' => 'POST'], + ['name' => 'reva#GetUserByClaim', 'url' => '/~{dummy}/api/user/GetUserByClaim', 'verb' => 'POST'], + // See: https://github.com/cs3org/reva/pull/4115#discussion_r1308371946 + // we need to handle this route for both nobody and userId. + ['name' => 'reva#GetSentShareByToken', 'url' => '/~{userId}/api/ocm/GetSentShareByToken', 'verb' => 'POST'], // Files routes ['name' => 'storage#handleGet', 'url' => '/~{userId}/files/{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+']], @@ -71,7 +65,6 @@ ['name' => 'storage#handleDelete', 'url' => '/~{userId}/files/{path}', 'verb' => 'DELETE', 'requirements' => ['path' => '.+']], ['name' => 'storage#handleHead', 'url' => '/~{userId}/files/{path}', 'verb' => 'HEAD', 'requirements' => ['path' => '.+']], - // Internal app routes ['name' => 'app#contacts', 'url' => '/', 'verb' => 'GET'], ['name' => 'app#generate', 'url' => '/generate', 'verb' => 'GET'], diff --git a/lib/Controller/AppController.php b/lib/Controller/AppController.php index 49bbb173..78899612 100644 --- a/lib/Controller/AppController.php +++ b/lib/Controller/AppController.php @@ -124,8 +124,10 @@ public function accept() { public function invitationsGenerate() { $recipient = $this->request->getParam('email'); $invitationsData = $this->httpClient->generateTokenFromReva($this->userId, $recipient); - $inviteLinkStr = $invitationsData["invite_link"]; - + + // check if invite_link exist before accessing. + $inviteLinkStr = isset($invitationsData["invite_link"]) ? $invitationsData["invite_link"] : false; + if (!$inviteLinkStr) { return new TextPlainResponse("Unexpected response from Reva", Http::STATUS_INTERNAL_SERVER_ERROR); } diff --git a/lib/Controller/OcmController.php b/lib/Controller/OcmController.php deleted file mode 100644 index bd7e974e..00000000 --- a/lib/Controller/OcmController.php +++ /dev/null @@ -1,732 +0,0 @@ -rootFolder = $rootFolder; - $this->request = $request; - $this->session = $session; - $this->userManager = $userManager; - $this->urlGenerator = $urlGenerator; - - $this->config = new \OCA\ScienceMesh\ServerConfig($config); - - $this->trashManager = $trashManager; - $this->shareManager = $shareManager; - $this->groupManager = $groupManager; - $this->cloudFederationProviderManager = $cloudFederationProviderManager; - $this->factory = $factory; - $this->cloudIdManager = $cloudIdManager; - $this->logger = $logger; - $this->appManager = $appManager; - $this->l = $l10n; - $this->shareProvider = $shareProvider; - } - private function init($userId) { - $this->userId = $userId; - $this->checkRevadAuth(); - if ($userId and $this->userManager->userExists($userId)) { - $this->userFolder = $this->rootFolder->getUserFolder($userId); - } - } - - private function revaPathToNextcloudPath($revaPath) { - $ret = NEXTCLOUD_PREFIX . substr($revaPath, strlen(REVA_PREFIX)); - return $ret; - } - - private function nextcloudPathToRevaPath($nextcloudPath) { - // return REVA_PREFIX . substr($nextcloudPath, strlen($this->userFolder->getPath() . NEXTCLOUD_PREFIX)); - return REVA_PREFIX . substr($nextcloudPath, strlen(NEXTCLOUD_PREFIX)); - } - - /** - * @param array $nodeInfo - * - * Returns the data of a CS3 provider.ResourceInfo object https://github.com/cs3org/cs3apis/blob/a86e5cb/cs3/storage/provider/v1beta1/resources.proto#L35-L93 - * @return array - * - * @throws \OCP\Files\InvalidPathException - * @throws \OCP\Files\NotFoundException - */ - private function lock(\OCP\Files\Node $node) { - $node->lock(ILockingProvider::LOCK_SHARED); - $this->lockedNode = $node; - } - - /** - * Make sure that the passed date is valid ISO 8601 - * So YYYY-MM-DD - * If not throw an exception - * - * @param string $expireDate - * - * @throws \Exception - * @return \DateTime - */ - private function parseDate(string $expireDate): \DateTime { - try { - $date = new \DateTime($expireDate); - } catch (\Exception $e) { - throw new \Exception('Invalid date. Format must be YYYY-MM-DD'); - } - - $date->setTime(0, 0, 0); - - return $date; - } - private function checkRevadAuth() { - $authHeader = $this->request->getHeader('X-Reva-Secret'); - if ($authHeader != $this->config->getRevaSharedSecret()) { - throw new \OCP\Files\NotPermittedException('Please set an http request header "X-Reva-Secret: "!'); - } - } - private function getRevaPathFromOpaqueId($opaqueId) { - return substr($opaqueId, strlen("fileid-")); - } - - private function nodeToCS3ResourceInfo(\OCP\Files\Node $node) : array { - $isDirectory = ($node->getType() === \OCP\Files\FileInfo::TYPE_FOLDER); - $nextcloudPath = substr($node->getPath(), strlen($this->userFolder->getPath()) + 1); - $revaPath = $this->nextcloudPathToRevaPath($nextcloudPath); - return [ - "opaque" => [ - "map" => null, - ], - "type" => ($isDirectory ? 2 : 1), - "id" => [ - "opaque_id" => "fileid-" . $revaPath, - ], - "checksum" => [ - "type" => 0, - "sum" => "", - ], - "etag" => "deadbeef", - "mime_type" => ($isDirectory ? "folder" : $node->getMimetype()), - "mtime" => [ - "seconds" => $node->getMTime(), - ], - "path" => $revaPath, - "permission_set" => [ - "add_grant" => false, - "create_container" => false, - "delete" => false, - "get_path" => false, - "get_quota" => false, - "initiate_file_download" => false, - "initiate_file_upload" => false, - ], - "size" => $node->getSize(), - "canonical_metadata" => [ - "target" => null, - ], - "arbitrary_metadata" => [ - "metadata" => [ - ".placeholder" => "ignore", - ], - ], - "owner" => [ - "opaque_id" => $this->userId, - "idp" => $this->config->getIopUrl(), - ], - ]; - } - - # For ListReceivedShares, GetReceivedShare and UpdateReceivedShare we need to include "state:2" - private function shareInfoToCs3Share(IShare $share, $token = ''): array { - $shareeParts = explode("@", $share->getSharedWith()); - if (count($shareeParts) == 1) { - error_log("warning, could not find sharee user@host from '" . $share->getSharedWith() . "'"); - $shareeParts = [ "unknown", "unknown" ]; - } - $ownerParts = explode("@", $share->getShareOwner()); - if (count($ownerParts) == 1) { - error_log("warning, could not find owner user@host from '" . $share->getShareOwner() . "'"); - $ownerParts = [ $ownerParts[0], "unknown" ]; - } - $stime = 0; // $share->getShareTime()->getTimeStamp(); - try { - $opaqueId = "fileid-" . $share->getNode()->getPath(); - } catch (\OCP\Files\NotFoundException $e) { - $opaqueId = "unknown"; - } - - // produces JSON that maps to - // https://github.com/cs3org/reva/blob/v1.18.0/pkg/ocm/share/manager/nextcloud/nextcloud.go#L77 - // and - // https://github.com/cs3org/go-cs3apis/blob/d297419/cs3/sharing/ocm/v1beta1/resources.pb.go#L100 - return [ - "id" => [ - // https://github.com/cs3org/go-cs3apis/blob/d297419/cs3/sharing/ocm/v1beta1/resources.pb.go#L423 - "opaque_id" => $share->getId() - ], - "resource_id" => [ - - "opaque_id" => $opaqueId, - ], - "permissions" => [ - "permissions" => [ - "add_grant" => true, - "create_container" => true, - "delete" => true, - "get_path" => true, - "get_quota" => true, - "initiate_file_download" => true, - "initiate_file_upload" => true, - ] - ], - // https://github.com/cs3org/go-cs3apis/blob/d29741980082ecd0f70fe10bd2e98cf75764e858/cs3/storage/provider/v1beta1/resources.pb.go#L897 - "grantee" => [ - "type" => 1, // https://github.com/cs3org/go-cs3apis/blob/d29741980082ecd0f70fe10bd2e98cf75764e858/cs3/storage/provider/v1beta1/resources.pb.go#L135 - "id" => [ - "opaque_id" => $shareeParts[0], - "idp" => $shareeParts[1] - ], - ], - "owner" => [ - "id" => [ - "opaque_id" => $ownerParts[0], - "idp" => $ownerParts[1] - ], - ], - "creator" => [ - "id" => [ - "opaque_id" => $ownerParts[0], - "idp" => $ownerParts[1] - ], - ], - "ctime" => [ - "seconds" => $stime - ], - "mtime" => [ - "seconds" => $stime - ], - "token" => $token - ]; - } - - # correspondes the permissions we got from Reva to Nextcloud - private function getPermissionsCode(array $permissions) : int { - $permissionsCode = 0; - if (!empty($permissions["get_path"]) || !empty($permissions["get_quota"]) || !empty($permissions["initiate_file_download"]) || !empty($permissions["initiate_file_upload"]) || !empty($permissions["stat"])) { - $permissionsCode += \OCP\Constants::PERMISSION_READ; - } - if (!empty($permissions["create_container"]) || !empty($permissions["move"]) || !empty($permissions["add_grant"]) || !empty($permissions["restore_file_version"]) || !empty($permissions["restore_recycle_item"])) { - $permissionsCode += \OCP\Constants::PERMISSION_CREATE; - } - if (!empty($permissions["move"]) || !empty($permissions["delete"]) || !empty($permissions["remove_grant"])) { - $permissionsCode += \OCP\Constants::PERMISSION_DELETE; - } - if (!empty($permissions["list_grants"]) || !empty($permissions["list_file_versions"]) || !empty($permissions["list_recycle"])) { - $permissionsCode += \OCP\Constants::PERMISSION_SHARE; - } - if (!empty($permissions["update_grant"])) { - $permissionsCode += \OCP\Constants::PERMISSION_UPDATE; - } - return $permissionsCode; - } - /** - * @param int - * - * @return int - * @throws NotFoundException - */ - private function getStorageUrl($userId) { - $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("sciencemesh.storage.handleHead", ["userId" => $userId, "path" => "foo"])); - $storageUrl = preg_replace('/foo$/', '', $storageUrl); - return $storageUrl; - } - - /* Reva handlers */ - - private function formatUser($user) { - return [ - "id" => [ - "idp" => $this->config->getIopUrl(), - "opaque_id" => $user->getUID(), - ], - "display_name" => $user->getDisplayName(), - "username" => $user->getUID(), - "email" => $user->getEmailAddress(), - "type" => 1, - ]; - } - - - /** - * Write a new file. - * - * @param string $path - * @param string $contents - * @param Config $config Config object - * - * @return bool false on failure, true on success - * - * @throws \OCP\Files\InvalidPathException - */ - private function write($path, $contents, Config $config) { - try { - if ($this->userFolder->nodeExists($path)) { - $node = $this->userFolder->get($path); - $node->putContent($contents); - } else { - } - } catch (\Exception $e) { - return false; - } - return true; - } - - /** - * @PublicPage - * @NoCSRFRequired - * @NoSameSiteCookieRequired - * - * Get user list. - */ - public function GetUser($dummy) { - $this->init(false); - - $userToCheck = $this->request->getParam('opaque_id'); - if ($this->userManager->userExists($userToCheck)) { - $user = $this->userManager->get($userToCheck); - $response = $this->formatUser($user); - return new JSONResponse($response, Http::STATUS_OK); - } - return new JSONResponse( - ['message' => 'User does not exist'], - Http::STATUS_NOT_FOUND - ); - } - - /** - * @PublicPage - * @NoCSRFRequired - * @NoSameSiteCookieRequired - * - * Get user list. - */ - public function GetUserByClaim($dummy) { - $this->init(false); - $userToCheck = $this->request->getParam('value'); - if ($this->request->getParam('claim') == 'username') { - error_log("GetUserByClaim, claim = 'username', value = $userToCheck"); - } else { - return new JSONResponse('Please set the claim to username', Http::STATUS_BAD_REQUEST); - } - if ($this->userManager->userExists($userToCheck)) { - $user = $this->userManager->get($userToCheck); - $response = $this->formatUser($user); - return new JSONResponse($response, Http::STATUS_OK); - } - return new JSONResponse( - ['message' => 'User does not exist'], - Http::STATUS_NOT_FOUND - ); - } - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * @throws NotFoundException - * @throws OCSNotFoundException - * Create a new share in fn with the given access control list. - */ - public function addSentShare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $params = $this->request->getParams(); - $owner = $params["owner"]["opaqueId"]; // . "@" . $params["owner"]["idp"]; - $name = $params["name"]; // "fileid-/other/q/f gr" - $resourceOpaqueId = $params["resourceId"]["opaqueId"]; // "fileid-/other/q/f gr" - $revaPath = $this->getRevaPathFromOpaqueId($resourceOpaqueId); // "/other/q/f gr" - $nextcloudPath = $this->revaPathToNextcloudPath($revaPath); - - $revaPermissions = null; - - foreach($params['accessMethods'] as $accessMethod) { - if (isset($accessMethod['webdavOptions'])) { - $revaPermissions = $accessMethod['webdavOptions']['permissions']; - break; - } - } - - if (!isset($revaPermissions)) { - throw new \Exception('reva permissions not found'); - } - - $granteeType = $params["grantee"]["type"]; // "GRANTEE_TYPE_USER" - $granteeHost = $params["grantee"]["userId"]["idp"]; // "revanc2.docker" - $granteeUser = $params["grantee"]["userId"]["opaqueId"]; // "marie" - - if ($revaPermissions === null) { - $revaPermissions = [ - "initiate_file_download" => true - ]; - } - $nextcloudPermissions = $this->getPermissionsCode($revaPermissions); - $shareWith = $granteeUser."@".$granteeHost; - $sharedSecret = $params["token"]; - try { - $node = $this->userFolder->get($nextcloudPath); - } catch (NotFoundException $e) { - return new JSONResponse(["error" => "Share failed. Resource Path not found"], Http::STATUS_BAD_REQUEST); - } - $share = $this->shareManager->newShare(); - $share->setNode($node); - try { - $this->lock($share->getNode()); - } catch (LockedException $e) { - throw new OCSNotFoundException($this->l->t('Could not create share')); - } - $share->setShareType(IShare::TYPE_REMOTE);//IShare::TYPE_SCIENCEMESH); - $share->setSharedBy($userId); - $share->setSharedWith($shareWith); - $share->setShareOwner($owner); - $share->setPermissions($nextcloudPermissions); - $share->setToken($sharedSecret); - $share = $this->shareProvider->createInternal($share); - return new DataResponse($share->getId(), Http::STATUS_CREATED); - } - - /** - * add a received share - * - * @NoCSRFRequired - * @PublicPage - * @return Http\DataResponse|JSONResponse - */ - public function addReceivedShare($userId) { - $params = $this->request->getParams(); - foreach($params['protocols'] as $protocol) { - if (isset($protocol['webdavOptions'])) { - $sharedSecret = $protocol['webdavOptions']['sharedSecret']; - // make sure you have webdav_endpoint = "https://nc1.docker/" under - // [grpc.services.ocmshareprovider] in the sending Reva's config - $uri = $protocol['webdavOptions']['uri']; // e.g. https://nc1.docker/remote.php/dav/ocm/vaKE36Wf1lJWCvpDcRQUScraVP5quhzA - $remote = implode('/', array_slice(explode('/', $uri), 0, 3)); // e.g. https://nc1.docker - break; - } - } - if (!isset($sharedSecret)) { - throw new \Exception('sharedSecret not found'); - } - - $shareData = [ - "remote" => $remote, //https://nc1.docker - "remote_id" => $params["remoteShareId"], // the id of the share in the oc_share table of the remote. - "share_token" => $sharedSecret, // 'tDPRTrLI4hE3C5T' - "password" => "", - "name" => rtrim($params["name"], "/"), // '/grfe' - "owner" => $params["owner"]["opaqueId"], // 'einstein' - "user" => $userId // 'marie' - ]; - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - - $scienceMeshData = [ - "is_external" => true, - ]; - - $id = $this->shareProvider->addScienceMeshShare($scienceMeshData, $shareData); - return new JSONResponse($id, 201); - } - - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * - * Remove Share from share table - */ - public function Unshare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; - $name = $this->getNameByOpaqueId($opaqueId); - if ($this->shareProvider->deleteSentShareByName($userId, $name)) { - return new JSONResponse("Deleted Sent Share",Http::STATUS_OK); - } else { - if ($this->shareProvider->deleteReceivedShareByOpaqueId($userId, $opaqueId)) { - return new JSONResponse("Deleted Received Share",Http::STATUS_OK); - } else { - return new JSONResponse("Could not find share", Http::STATUS_BAD_REQUEST); - } - } - } - - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - */ - public function UpdateSentShare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $opaqueId = $this->request->getParam("ref")["Spec"]["Id"]["opaque_id"]; - $permissions = $this->request->getParam("p")["permissions"]; - $permissionsCode = $this->getPermissionsCode($permissions); - $name = $this->getNameByOpaqueId($opaqueId); - if (!($share = $this->shareProvider->getSentShareByName($userId,$name))) { - return new JSONResponse(["error" => "UpdateSentShare failed"], Http::STATUS_INTERNAL_SERVER_ERROR); - } - $share->setPermissions($permissionsCode); - $shareUpdated = $this->shareProvider->update($share); - $response = $this->shareInfoToCs3Share($shareUpdated); - return new JSONResponse($response, Http::STATUS_OK); - } - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * UpdateReceivedShare updates the received share with share state. - */ - public function UpdateReceivedShare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $response = []; - $resourceId = $this->request->getParam("received_share")["share"]["resource_id"]; - $permissions = $this->request->getParam("received_share")["share"]["permissions"]; - $permissionsCode = $this->getPermissionsCode($permissions); - try { - $share = $this->shareProvider->getReceivedShareByToken($resourceId); - $share->setPermissions($permissionsCode); - $shareUpdate = $this->shareProvider->UpdateReceivedShare($share); - $response = $this->shareInfoToCs3Share($shareUpdate); - $response["state"] = 2; - return new JSONResponse($response, Http::STATUS_OK); - } catch (\Exception $e) { - return new JSONResponse(["error" => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); - } - } - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * ListSentShares returns the shares created by the user. If md is provided is not nil, - * it returns only shares attached to the given resource. - */ - public function ListSentShares($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $responses = []; - $shares = $this->shareProvider->getSentShares($userId); - if ($shares) { - foreach ($shares as $share) { - array_push($responses, $this->shareInfoToCs3Share($share)); - } - } - return new JSONResponse($responses, Http::STATUS_OK); - } - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * ListReceivedShares returns the list of shares the user has access. - */ - public function ListReceivedShares($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $responses = []; - $shares = $this->shareProvider->getReceivedShares($userId); - if ($shares) { - foreach ($shares as $share) { - $response = $this->shareInfoToCs3Share($share); - array_push($responses,[ - "share" => $response, - "state" => 2 - ]); - } - } - return new JSONResponse($responses, Http::STATUS_OK); - } - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * GetReceivedShare returns the information for a received share the user has access. - */ - public function GetReceivedShare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; - $name = $this->getNameByOpaqueId($opaqueId); - try { - $share = $this->shareProvider->getReceivedShareByToken($opaqueId); - $response = $this->shareInfoToCs3Share($share); - $response["state"] = 2; - return new JSONResponse($response, Http::STATUS_OK); - } catch (\Exception $e) { - return new JSONResponse(["error" => $e->getMessage()],Http::STATUS_BAD_REQUEST); - } - } - - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * GetSentShare gets the information for a share by the given ref. - */ - public function GetSentShare($userId) { - if ($this->userManager->userExists($userId)) { - $this->init($userId); - } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); - } - $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; - $name = $this->getNameByOpaqueId($opaqueId); - $share = $this->shareProvider->getSentShareByName($userId,$name); - if ($share) { - $response = $this->shareInfoToCs3Share($share); - return new JSONResponse($response, Http::STATUS_OK); - } - return new JSONResponse(["error" => "GetSentShare failed"], Http::STATUS_BAD_REQUEST); - } - - /** - * @PublicPage - * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse - * - * GetSentShareByToken gets the information for a share by the given token. - */ - public function GetSentShareByToken() { - $token = $this->request->getParam("Spec")["Token"]; - $share = $this->shareProvider->getShareByToken($token); - if ($share) { - $response = $this->shareInfoToCs3Share($share, $token); - return new JSONResponse($response, Http::STATUS_OK); - } - return new JSONResponse(["error" => "GetSentShare failed"], Http::STATUS_BAD_REQUEST); - } -} diff --git a/lib/Controller/RevaController.php b/lib/Controller/RevaController.php index dcb97944..193ddf48 100644 --- a/lib/Controller/RevaController.php +++ b/lib/Controller/RevaController.php @@ -2,13 +2,6 @@ namespace OCA\ScienceMesh\Controller; -use OCA\ScienceMesh\NextcloudAdapter; -use OCA\ScienceMesh\ShareProvider\ScienceMeshShareProvider; -use OCA\ScienceMesh\Share\ScienceMeshSharePermissions; -use OCA\ScienceMesh\User\ScienceMeshUserId; - -use OCA\Files_Trashbin\Trash\ITrashManager; - use OCP\IRequest; use OCP\IUserManager; use OCP\IGroupManager; @@ -16,17 +9,23 @@ use OCP\ISession; use OCP\IConfig; +use OCP\IL10N; +use OCP\App\IAppManager; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +use OC\Files\View; use OCP\Files\IRootFolder; use OCP\Files\NotPermittedException; use \OCP\Files\NotFoundException; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\StreamResponse; use OCP\AppFramework\Controller; use OCP\AppFramework\OCS\OCSNotFoundException; -use OCA\CloudFederationAPI\Config; use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; use OCP\Federation\ICloudIdManager; @@ -35,18 +34,20 @@ use OCP\Share\IShare; use OCP\Share\Exceptions\ShareNotFound; -use Psr\Log\LoggerInterface; +use OCA\CloudFederationAPI\Config; +use OCA\Files_Trashbin\Trash\ITrashManager; -use OCP\App\IAppManager; -use OCP\Lock\ILockingProvider; -use OCP\Lock\LockedException; -use OCP\IL10N; +use OCA\ScienceMesh\AppInfo\ScienceMeshApp; +use OCA\ScienceMesh\ShareProvider\ScienceMeshShareProvider; + +use Psr\Log\LoggerInterface; define('RESTRICT_TO_SCIENCEMESH_FOLDER', false); -define('NEXTCLOUD_PREFIX', (RESTRICT_TO_SCIENCEMESH_FOLDER ? 'sciencemesh/' : '')); +define('EFSS_PREFIX', (RESTRICT_TO_SCIENCEMESH_FOLDER ? 'sciencemesh/' : '')); define('REVA_PREFIX', '/home/'); // See https://github.com/pondersource/sciencemesh-php/issues/96#issuecomment-1298656896 -class RevaController extends Controller { +class RevaController extends Controller +{ /* @var ISession */ private $session; @@ -106,7 +107,7 @@ public function __construct( ScienceMeshShareProvider $shareProvider ) { parent::__construct($AppName, $request); - require_once(__DIR__.'/../../vendor/autoload.php'); + require_once(__DIR__ . '/../../vendor/autoload.php'); $this->rootFolder = $rootFolder; $this->request = $request; @@ -127,26 +128,67 @@ public function __construct( $this->l = $l10n; $this->shareProvider = $shareProvider; } - private function init($userId) { - error_log("RevaController init"); + + private function init($userId) + { + error_log("RevaController init for user '$userId'"); $this->userId = $userId; $this->checkRevadAuth(); if ($userId) { - error_log("Getting user folder for '$userId'"); + error_log("root folder absolute path '" . $this->rootFolder->getPath() . "'"); $this->userFolder = $this->rootFolder->getUserFolder($userId); } } - private function revaPathToNextcloudPath($revaPath) { - $ret = NEXTCLOUD_PREFIX . substr($revaPath, strlen(REVA_PREFIX)); - error_log("Interpreting $revaPath as $ret"); - // return $this->userFolder->getPath() . NEXTCLOUD_PREFIX . substr($revaPath, strlen(REVA_PREFIX)); - return $ret; + private function getDomainFromURL($url) + { + // converts https://revaowncloud1.docker/ to revaowncloud1.docker + // Note: DO not use it on anything whithout http(s) in the start, it would return null. + return str_ireplace("www.", "", parse_url($url, PHP_URL_HOST)); } - private function nextcloudPathToRevaPath($nextcloudPath) { - // return REVA_PREFIX . substr($nextcloudPath, strlen($this->userFolder->getPath() . NEXTCLOUD_PREFIX)); - return REVA_PREFIX . substr($nextcloudPath, strlen(NEXTCLOUD_PREFIX)); + private function removePrefix($string, $prefix) + { + // first check if string is actually prefixed or not. + $len = strlen($prefix); + if (substr($string, 0, $len) === $prefix) { + $ret = substr($string, $len); + } else { + $ret = $string; + } + + return $ret; + } + + private function revaPathFromOpaqueId($opaqueId) + { + return $this->removePrefix($opaqueId, "fileid-"); + } + + private function revaPathToEfssPath($revaPath) + { + if ("$revaPath/" == REVA_PREFIX) { + error_log("revaPathToEfssPath: Interpreting special case $revaPath as ''"); + return ''; + } + $ret = EFSS_PREFIX . $this->removePrefix($revaPath, REVA_PREFIX); + error_log("revaPathToEfssPath: Interpreting $revaPath as $ret"); + return $ret; + } + + private function efssPathToRevaPath($efssPath) + { + $ret = REVA_PREFIX . $this->removePrefix($efssPath, EFSS_PREFIX); + error_log("efssPathToRevaPath: Interpreting $efssPath as $ret"); + return $ret; + } + + private function efssFullPathToRelativePath($efssFullPath) + { + + $ret = $this->removePrefix($efssFullPath, $this->userFolder->getPath()); + error_log("efssFullPathToRelativePath: Interpreting $efssFullPath as $ret"); + return $ret; } /** @@ -158,7 +200,8 @@ private function nextcloudPathToRevaPath($nextcloudPath) { * @throws \OCP\Files\InvalidPathException * @throws \OCP\Files\NotFoundException */ - private function lock(\OCP\Files\Node $node) { + private function lock(\OCP\Files\Node $node) + { $node->lock(ILockingProvider::LOCK_SHARED); $this->lockedNode = $node; } @@ -173,7 +216,8 @@ private function lock(\OCP\Files\Node $node) { * @throws \Exception * @return \DateTime */ - private function parseDate(string $expireDate): \DateTime { + private function parseDate(string $expireDate): \DateTime + { try { $date = new \DateTime($expireDate); } catch (\Exception $e) { @@ -184,77 +228,101 @@ private function parseDate(string $expireDate): \DateTime { return $date; } - private function checkRevadAuth() { + + private function checkRevadAuth() + { error_log("checkRevadAuth"); $authHeader = $this->request->getHeader('X-Reva-Secret'); - if ($authHeader != $this->config->getRevaSharedSecret()) { - throw new \OCP\Files\NotPermittedException('Please set an http request header "X-Reva-Secret: "!'); + + if ($authHeader != $this->config->getRevaSharedSecret()) { + throw new \OCP\Files\NotPermittedException('Please set an http request header "X-Reva-Secret: "!'); } } - private function getRevaPathFromOpaqueId($opaqueId) { - return substr($opaqueId, strlen("fileid-")); + + private function getChecksum(\OCP\Files\Node $node, $checksumType = 4): string + { + $checksumTypes = array( + 1 => "UNSET:", + 2 => "ADLER32:", + 3 => "MD5:", + 4 => "SHA1:", + ); + + // checksum is in db table oc_filecache. + // folders do not have checksum + $checksums = explode(' ', $node->getFileInfo()->getChecksum()); + + foreach ($checksums as $checksum) { + + // Note that the use of !== false is deliberate (neither != false nor === true will return the desired result); + // strpos() returns either the offset at which the needle string begins in the haystack string, or the boolean + // false if the needle isn't found. Since 0 is a valid offset and 0 is "falsey", we can't use simpler constructs + // like !strpos($a, 'are'). + if (strpos($checksum, $checksumTypes[$checksumType]) !== false) { + return substr($checksum, strlen($checksumTypes[$checksumType])); + } + } + + return ''; } - private function nodeToCS3ResourceInfo(\OCP\Files\Node $node) : array { + private function nodeToCS3ResourceInfo(\OCP\Files\Node $node, $token = ''): array + { $isDirectory = ($node->getType() === \OCP\Files\FileInfo::TYPE_FOLDER); - $nextcloudPath = substr($node->getPath(), strlen($this->userFolder->getPath()) + 1); - $revaPath = $this->nextcloudPathToRevaPath($nextcloudPath); - return [ - "opaque" => [ - "map" => null, - ], + $efssPath = substr($node->getPath(), strlen($this->userFolder->getPath()) + 1); + $revaPath = $this->efssPathToRevaPath($efssPath); + + $payload = [ "type" => ($isDirectory ? 2 : 1), "id" => [ "opaque_id" => "fileid-" . $revaPath, ], "checksum" => [ - "type" => 0, - "sum" => "", + // checksum algorithms: + // 1 UNSET + // 2 ADLER32 + // 3 MD5 + // 4 SHA1 + + // note: folders do not have checksum, their type should be unset. + "type" => $isDirectory ? 1 : 4, + "sum" => $this->getChecksum($node, $isDirectory ? 1 : 4), ], - "etag" => "deadbeef", + "etag" => $node->getEtag(), "mime_type" => ($isDirectory ? "folder" : $node->getMimetype()), "mtime" => [ "seconds" => $node->getMTime(), ], "path" => $revaPath, - "permission_set" => [ - "add_grant" => false, - "create_container" => false, - "delete" => false, - "get_path" => false, - "get_quota" => false, - "initiate_file_download" => false, - "initiate_file_upload" => false, - ], + "permissions" => $node->getPermissions(), "size" => $node->getSize(), - "canonical_metadata" => [ - "target" => null, - ], - "arbitrary_metadata" => [ - "metadata" => [ - ".placeholder" => "ignore", - ], - ], "owner" => [ "opaque_id" => $this->userId, - "idp" => $this->config->getIopUrl(), - ], + "idp" => $this->getDomainFromURL($this->config->getIopUrl()), + ] ]; + + error_log("nodeToCS3ResourceInfo " . var_export($payload, true)); + + return $payload; } # For ListReceivedShares, GetReceivedShare and UpdateReceivedShare we need to include "state:2" - private function shareInfoToCs3Share(IShare $share): array { + private function shareInfoToCs3Share(IShare $share, $token = ''): array + { $shareeParts = explode("@", $share->getSharedWith()); if (count($shareeParts) == 1) { - $shareeParts = [ "unknown", "unknown" ]; - } - $ownerParts = explode("@", $share->getShareOwner()); - if (count($ownerParts) == 1) { - $ownerParts = [ "unknown", "unknown" ]; + error_log("warning, could not find sharee user@host from '" . $share->getSharedWith() . "'"); + $shareeParts = ["unknown", "unknown"]; } + + $ownerParts = [$share->getShareOwner(), $this->getDomainFromURL($this->config->getIopUrl())]; + $stime = 0; // $share->getShareTime()->getTimeStamp(); + try { - $opaqueId = "fileid-" . $share->getNode()->getPath(); + $filePath = $share->getNode()->getPath(); + $opaqueId = "fileid-" . $filePath; } catch (\OCP\Files\NotFoundException $e) { $opaqueId = "unknown"; } @@ -263,57 +331,53 @@ private function shareInfoToCs3Share(IShare $share): array { // https://github.com/cs3org/reva/blob/v1.18.0/pkg/ocm/share/manager/nextcloud/nextcloud.go#L77 // and // https://github.com/cs3org/go-cs3apis/blob/d297419/cs3/sharing/ocm/v1beta1/resources.pb.go#L100 - return [ + $payload = [ "id" => [ // https://github.com/cs3org/go-cs3apis/blob/d297419/cs3/sharing/ocm/v1beta1/resources.pb.go#L423 "opaque_id" => $share->getId() ], "resource_id" => [ - "opaque_id" => $opaqueId, - ], - "permissions" => [ - "permissions" => [ - "add_grant" => false, - "create_container" => false, - "delete" => false, - "get_path" => false, - "get_quota" => false, - "initiate_file_download" => false, - "initiate_file_upload" => false, - ] + "opaque_id" => $opaqueId, ], + "permissions" => $share->getNode()->getPermissions(), // https://github.com/cs3org/go-cs3apis/blob/d29741980082ecd0f70fe10bd2e98cf75764e858/cs3/storage/provider/v1beta1/resources.pb.go#L897 "grantee" => [ "type" => 1, // https://github.com/cs3org/go-cs3apis/blob/d29741980082ecd0f70fe10bd2e98cf75764e858/cs3/storage/provider/v1beta1/resources.pb.go#L135 - "id" => [ + "id" => [ "opaque_id" => $shareeParts[0], "idp" => $shareeParts[1] - ], + ], ], "owner" => [ - "id" => [ + "id" => [ "opaque_id" => $ownerParts[0], "idp" => $ownerParts[1] - ], + ], ], "creator" => [ "id" => [ "opaque_id" => $ownerParts[0], "idp" => $ownerParts[1] - ], + ], ], "ctime" => [ "seconds" => $stime ], "mtime" => [ "seconds" => $stime - ] + ], + "token" => $token ]; + + error_log("shareInfoToCs3Share " . var_export($payload, true)); + + return $payload; } # correspondes the permissions we got from Reva to Nextcloud - private function getPermissionsCode(array $permissions) : int { + private function getPermissionsCode(array $permissions): int + { $permissionsCode = 0; if (!empty($permissions["get_path"]) || !empty($permissions["get_quota"]) || !empty($permissions["initiate_file_download"]) || !empty($permissions["initiate_file_upload"]) || !empty($permissions["stat"])) { $permissionsCode += \OCP\Constants::PERMISSION_READ; @@ -332,13 +396,15 @@ private function getPermissionsCode(array $permissions) : int { } return $permissionsCode; } + /** * @param int * * @return int * @throws NotFoundException */ - private function getStorageUrl($userId) { + private function getStorageUrl($userId) + { $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("sciencemesh.storage.handleHead", ["userId" => $userId, "path" => "foo"])); $storageUrl = preg_replace('/foo$/', '', $storageUrl); return $storageUrl; @@ -352,61 +418,87 @@ private function getStorageUrl($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function AddGrant($userId) { + public function AddGrant($userId) + { error_log("AddGrant"); if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); // FIXME: Expected a param with a grant to add here; + return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } - private function formatUser($user) { + private function formatUser($user) + { return [ "id" => [ - "idp" => $this->config->getIopUrl(), + "idp" => $this->getDomainFromURL($this->config->getIopUrl()), "opaque_id" => $user->getUID(), ], "display_name" => $user->getDisplayName(), + "username" => $user->getUID(), "email" => $user->getEmailAddress(), "type" => 1, ]; } + private function formatFederatedUser($username, $remote) + { + return [ + "id" => [ + "idp" => $remote, + "opaque_id" => $username, + ], + "display_name" => $username, // FIXME: this comes in the OCM share payload + "username" => $username, + "email" => "unknown@unknown", // FIXME: this comes in the OCM share payload + "type" => 6, + ]; + } + /** * @PublicPage * @NoAdminRequired * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function Authenticate($userId) { + public function Authenticate($userId) + { error_log("Authenticate"); if ($this->userManager->userExists($userId)) { $this->init($userId); } else { - return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + $share = $this->shareProvider->getSentShareByToken($userId); + if ($share) { + $sharedWith = explode("@", $share->getSharedWith()); + $result = [ + "user" => $this->formatFederatedUser($sharedWith[0], $sharedWith[1]), + "scopes" => [], + ]; + return new JSONResponse($result, Http::STATUS_OK); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } } + $userId = $this->request->getParam("clientID"); $password = $this->request->getParam("clientSecret"); // Try e.g.: // curl -v -H 'Content-Type:application/json' -d'{"clientID":"einstein",clientSecret":"relativity"}' http://einstein:relativity@localhost/index.php/apps/sciencemesh/~einstein/api/auth/Authenticate - // Ref https://github.com/cs3org/reva/issues/2356 + // Ref https://github.com/cs3org/reva/issues/2356 if ($password == $this->config->getRevaLoopbackSecret()) { $user = $this->userManager->get($userId); } else { - $user = $this->userManager->checkPassword($userId, $password); + $user = $this->userManager->checkPassword($userId, $password); } - - if ($user) { - /// the `value` is a base64 encoded value of: - /// {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"} $result = [ "user" => $this->formatUser($user), "scopes" => [ @@ -421,6 +513,7 @@ public function Authenticate($userId) { ]; return new JSONResponse($result, Http::STATUS_OK); } + return new JSONResponse("Username / password not recognized", 401); } @@ -431,14 +524,18 @@ public function Authenticate($userId) { * @return Http\DataResponse|JSONResponse * @throws \OCP\Files\NotPermittedException */ - public function CreateDir($userId) { + public function CreateDir($userId) + { error_log("CreateDir"); if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $urlDecodedPath = urldecode($this->request->getParam("path")); + $path = $this->revaPathToEfssPath($urlDecodedPath); + try { $this->userFolder->newFolder($path); } catch (NotPermittedException $e) { @@ -454,7 +551,8 @@ public function CreateDir($userId) { * @return Http\DataResponse|JSONResponse * @throws \OCP\Files\NotPermittedException */ - public function CreateHome($userId) { + public function CreateHome($userId) + { error_log("CreateHome"); if (RESTRICT_TO_SCIENCEMESH_FOLDER) { if ($this->userManager->userExists($userId)) { @@ -481,7 +579,8 @@ public function CreateHome($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function CreateReference($userId) { + public function CreateReference($userId) + { error_log("CreateReference"); if ($this->userManager->userExists($userId)) { @@ -489,7 +588,7 @@ public function CreateReference($userId) { } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + $path = $this->revaPathToEfssPath($this->request->getParam("path")); return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } @@ -499,7 +598,8 @@ public function CreateReference($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function CreateStorageSpace($userId) { + public function CreateStorageSpace($userId) + { error_log("CreateStorageSpace"); return new JSONResponse([ "status" => [ @@ -551,14 +651,17 @@ public function CreateStorageSpace($userId) { * @return Http\DataResponse|JSONResponse * @throws FileNotFoundException */ - public function Delete($userId) { + public function Delete($userId) + { error_log("Delete"); if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); + try { $node = $this->userFolder->get($path); $node->delete($path); @@ -574,13 +677,16 @@ public function Delete($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function EmptyRecycle($userId) { + public function EmptyRecycle($userId) + { + // DIFFERENT FUNCTION IN NC/OC error_log("EmptyRecycle"); if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } + $user = $this->userManager->get($userId); $trashItems = $this->trashManager->listTrashRoot($user); @@ -600,34 +706,48 @@ public function EmptyRecycle($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function GetMD($userId) { + public function GetMD($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } + $ref = $this->request->getParam("ref"); error_log("GetMD " . var_export($ref, true)); + if (isset($ref["path"])) { - $revaPath = $ref["path"]; // e.g. GetMD {"ref":{"path":"/home/asdf"},"mdKeys":null} - } else if (isset($ref["resource_id"]) && isset($ref["resource_id"]["opaque_id"]) && str_starts_with($ref["resource_id"]["opaque_id"], "fileid-/home/")) { - $revaPath = substr($ref["resource_id"]["opaque_id"], strlen("fileid-")); // e.g. GetMD {"ref":{"resource_id":{"storage_id":"00000000-0000-0000-0000-000000000000","opaque_id":"fileid-/home/asdf"}},"mdKeys":null} + // e.g. GetMD {"ref": {"path": "/home/asdf"}, "mdKeys": null} + $revaPath = $ref["path"]; + } else if (isset($ref["resource_id"]) && isset($ref["resource_id"]["opaque_id"]) && str_starts_with($ref["resource_id"]["opaque_id"], "fileid-")) { + // e.g. GetMD {"ref": {"resource_id": {"storage_id": "00000000-0000-0000-0000-000000000000", "opaque_id": "fileid-/asdf"}}, "mdKeys":null} + $revaPath = $this->revaPathFromOpaqueId($ref["resource_id"]["opaque_id"]); } else { throw new \Exception("ref not understood!"); } - $path = $this->revaPathToNextcloudPath($revaPath); - error_log("Looking for nc path '$path' in user folder; reva path '".$ref["path"]."' "); - $dirContents = $this->userFolder->getDirectoryListing(); - $paths = array_map(function (\OCP\Files\Node $node) { - return $node->getPath(); - }, $dirContents); - error_log("User folder ".$this->userFolder->getPath()." has: " . implode(",", $paths)); - $success = $this->userFolder->nodeExists($path); + + // this path is url coded, we need to decode it + // for example this converts "we%20have%20space" to "we have space" + $revaPathDecoded = urldecode($revaPath); + + $path = $this->revaPathToEfssPath($revaPathDecoded); + error_log("Looking for EFSS path '$path' in user folder; reva path '$revaPathDecoded' "); + + // apparently nodeExists requires relative path to the user folder: + // see https://github.com/owncloud/core/blob/b7bcbdd9edabf7d639b4bb42c4fb87862ddf4a80/lib/private/Files/Node/Folder.php#L45-L55; + // another string manipulation is necessary to extract relative path from full path. + $relativePath = $this->efssFullPathToRelativePath($path); + + $success = $this->userFolder->nodeExists($relativePath); if ($success) { - $node = $this->userFolder->get($path); + error_log("File found"); + $node = $this->userFolder->get($relativePath); $resourceInfo = $this->nodeToCS3ResourceInfo($node); return new JSONResponse($resourceInfo, Http::STATUS_OK); } + + error_log("File not found"); return new JSONResponse(["error" => "File not found"], 404); } @@ -637,7 +757,8 @@ public function GetMD($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function GetPathByID($userId) { + public function GetPathByID($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { @@ -647,6 +768,7 @@ public function GetPathByID($userId) { $path = "subdir/"; $storageId = $this->request->getParam("storage_id"); $opaqueId = $this->request->getParam("opaque_id"); + return new DataResponse($path, Http::STATUS_OK); } @@ -656,9 +778,11 @@ public function GetPathByID($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function InitiateUpload($userId) { + public function InitiateUpload($userId) + { $ref = $this->request->getParam("ref"); - $path = $this->revaPathToNextcloudPath((isset($ref["path"]) ? $ref["path"] : "")); + $path = $this->revaPathToEfssPath((isset($ref["path"]) ? $ref["path"] : "")); + if ($this->userManager->userExists($userId)) { $this->init($userId); } else { @@ -667,6 +791,7 @@ public function InitiateUpload($userId) { $response = [ "simple" => $path ]; + return new JSONResponse($response, Http::STATUS_OK); } @@ -676,18 +801,29 @@ public function InitiateUpload($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function ListFolder($userId) { + public function ListFolder($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } + $ref = $this->request->getParam("ref"); - $path = $this->revaPathToNextcloudPath((isset($ref["path"]) ? $ref["path"] : "")); + + // this path is url coded, we need to decode it + // for example this converts "we%20have%20space" to "we have space" + $pathDecoded = urldecode((isset($ref["path"]) ? $ref["path"] : "")); + $path = $this->revaPathToEfssPath($pathDecoded); $success = $this->userFolder->nodeExists($path); + error_log("ListFolder: $path"); + if (!$success) { + error_log("ListFolder: path not found"); return new JSONResponse(["error" => "Folder not found"], 404); } + error_log("ListFolder: path found"); + $node = $this->userFolder->get($path); $nodes = $node->getDirectoryListing(); $resourceInfos = array_map(function (\OCP\Files\Node $node) { @@ -702,10 +838,17 @@ public function ListFolder($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function ListGrants($userId) { - $this->init($userId); - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); - return new JSONResponse("Not implemented",Http::STATUS_NOT_IMPLEMENTED); + public function ListGrants($userId) + { + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); + + return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } /** @@ -714,18 +857,21 @@ public function ListGrants($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function ListRecycle($userId) { + public function ListRecycle($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } + $user = $this->userManager->get($userId); $trashItems = $this->trashManager->listTrashRoot($user); $result = []; + foreach ($trashItems as $node) { if (preg_match("/^sciencemesh/", $node->getOriginalLocation())) { - $path = $this->nextcloudPathToRevaPath($node->getOriginalLocation()); + $path = $this->efssPathToRevaPath($node->getOriginalLocation()); $result = [ [ "opaque" => [ @@ -742,9 +888,11 @@ public function ListRecycle($userId) { "deletion_time" => [ "seconds" => 1234567890 ] - ]]; + ] + ]; } } + return new JSONResponse($result, Http::STATUS_OK); } @@ -754,14 +902,17 @@ public function ListRecycle($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function ListRevisions($userId) { + public function ListRevisions($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); - return new JSONResponse("Not implemented",Http::STATUS_NOT_IMPLEMENTED); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); + + return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } /** @@ -770,14 +921,17 @@ public function ListRevisions($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function RemoveGrant($userId) { + public function RemoveGrant($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); // FIXME: Expected a grant to remove here; + return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } @@ -787,12 +941,14 @@ public function RemoveGrant($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function RestoreRecycleItem($userId) { + public function RestoreRecycleItem($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } + $key = $this->request->getParam("key"); $user = $this->userManager->get($userId); $trashItems = $this->trashManager->listTrashRoot($user); @@ -803,12 +959,13 @@ public function RestoreRecycleItem($userId) { // unique key string, see: // https://github.com/cs3org/cs3apis/blob/6eab4643f5113a54f4ce4cd8cb462685d0cdd2ef/cs3/storage/provider/v1beta1/resources.proto#L318 - if ($this->revaPathToNextcloudPath($key) == $node->getOriginalLocation()) { + if ($this->revaPathToEfssPath($key) == $node->getOriginalLocation()) { $this->trashManager->restoreItem($node); return new JSONResponse("OK", Http::STATUS_OK); } } } + return new JSONResponse('["error" => "Not found."]', 404); } @@ -818,14 +975,17 @@ public function RestoreRecycleItem($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function RestoreRevision($userId) { + public function RestoreRevision($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); // FIXME: Expected a revision param here; + return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } @@ -835,16 +995,20 @@ public function RestoreRevision($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function SetArbitraryMetadata($userId) { + public function SetArbitraryMetadata($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); $metadata = $this->request->getParam("metadata"); - // FIXME: What do we do with the existing metadata? Just toss it and overwrite with the new value? Or do we merge? - return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); + + // FIXME: this needs to be implemented for real, merging the incoming metadata with the existing ones. + // For now we return OK to let the uploads go through, see https://github.com/sciencemesh/nc-sciencemesh/issues/43 + return new JSONResponse("I'm cheating", Http::STATUS_OK); } /** @@ -853,13 +1017,17 @@ public function SetArbitraryMetadata($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function UnsetArbitraryMetadata($userId) { + public function UnsetArbitraryMetadata($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); + + // FIXME: this needs to be implemented for real return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } @@ -869,13 +1037,16 @@ public function UnsetArbitraryMetadata($userId) { * @NoCSRFRequired * @return Http\DataResponse|JSONResponse */ - public function UpdateGrant($userId) { + public function UpdateGrant($userId) + { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $path = $this->revaPathToNextcloudPath($this->request->getParam("path")); + + $path = $this->revaPathToEfssPath($this->request->getParam("path")); + // FIXME: Expected a paramater with the grant(s) return new JSONResponse("Not implemented", Http::STATUS_NOT_IMPLEMENTED); } @@ -891,7 +1062,8 @@ public function UpdateGrant($userId) { * * @throws \OCP\Files\InvalidPathException */ - private function write($path, $contents, Config $config) { + private function write($path, $contents, Config $config) + { try { if ($this->userFolder->nodeExists($path)) { $node = $this->userFolder->get($path); @@ -908,38 +1080,511 @@ private function write($path, $contents, Config $config) { * @PublicPage * @NoAdminRequired * @NoCSRFRequired - * @return Http\DataResponse|JSONResponse + * @return JSONResponse|StreamResponse + * @throws NotFoundException */ - public function Upload($userId, $path) { + public function Download($userId, $path) + { + error_log("Download"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + error_log("Download path: $path"); + + $efssPath = $this->removePrefix($path, "home/"); + error_log("Download efss path: $efssPath"); + + if ($this->userFolder->nodeExists($efssPath)) { + error_log("Download: file found"); + $node = $this->userFolder->get($efssPath); + $view = new View(); + $nodeLocalFilePath = $view->getLocalFile($node->getPath()); + error_log("Download local file path: $nodeLocalFilePath"); + return new StreamResponse($nodeLocalFilePath); + } + + error_log("Download: file not found"); + return new JSONResponse(["error" => "File not found"], 404); + } + + /** + * @PublicPage + * @NoAdminRequired + * @NoCSRFRequired + * @param $userId + * @param $path + * @return JSONResponse + */ + public function Upload($userId, $path): JSONResponse + { $revaPath = "/$path"; - error_log("RevaController Upload! $userId $revaPath"); + error_log("RevaController Upload! user: $userId , reva path: $revaPath"); + try { if ($this->userManager->userExists($userId)) { $this->init($userId); } else { return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); } - $contents = $entityBody = file_get_contents('php://input'); - // error_log("PUT body = " . var_export($contents, true)); - error_log("Uploading! $revaPath"); - $ncPath = $this->revaPathToNextcloudPath($revaPath); - if ($this->userFolder->nodeExists($ncPath)) { - $node = $this->userFolder->get($ncPath); - $node->putContent($contents); + + $contents = file_get_contents('php://input'); + $efssPath = $this->revaPathToEfssPath($revaPath); + + error_log("Uploading! reva path: $revaPath"); + error_log("Uploading! efss path $efssPath"); + + if ($this->userFolder->nodeExists($efssPath)) { + $node = $this->userFolder->get($efssPath); + $view = new View(); + $view->file_put_contents($node->getPath(), $contents); return new JSONResponse("OK", Http::STATUS_OK); } else { - $filename = basename($ncPath); - $dirname = dirname($ncPath); + $dirname = dirname($efssPath); + $filename = basename($efssPath); + if (!$this->userFolder->nodeExists($dirname)) { $this->userFolder->newFolder($dirname); } + $node = $this->userFolder->get($dirname); - $node->newFile($filename, $contents); - return new JSONResponse("CREATED", Http::STATUS_CREATED); + $node->newFile($filename); + + $node = $this->userFolder->get($efssPath); + $view = new View(); + $view->file_put_contents($node->getPath(), $contents); + + return new JSONResponse("CREATED", Http::STATUS_CREATED); } } catch (\Exception $e) { return new JSONResponse(["error" => "Upload failed"], Http::STATUS_INTERNAL_SERVER_ERROR); } } - + + /** + * @PublicPage + * @NoCSRFRequired + * @NoSameSiteCookieRequired + * + * Get user list. + */ + public function GetUser($dummy) + { + $this->init(false); + + $userToCheck = $this->request->getParam('opaque_id'); + + if ($this->userManager->userExists($userToCheck)) { + $user = $this->userManager->get($userToCheck); + $response = $this->formatUser($user); + return new JSONResponse($response, Http::STATUS_OK); + } + + return new JSONResponse( + ['message' => 'User does not exist'], + Http::STATUS_NOT_FOUND + ); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @NoSameSiteCookieRequired + * + * Get user by claim. + */ + public function GetUserByClaim($dummy) + { + $this->init(false); + + $userToCheck = $this->request->getParam('value'); + + if ($this->request->getParam('claim') == 'username') { + error_log("GetUserByClaim, claim = 'username', value = $userToCheck"); + } else { + return new JSONResponse('Please set the claim to username', Http::STATUS_BAD_REQUEST); + } + + if ($this->userManager->userExists($userToCheck)) { + $user = $this->userManager->get($userToCheck); + $response = $this->formatUser($user); + return new JSONResponse($response, Http::STATUS_OK); + } + + return new JSONResponse( + ['message' => 'User does not exist'], + Http::STATUS_NOT_FOUND + ); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * @throws NotFoundException + * @throws OCSNotFoundException + * Create a new share in fn with the given access control list. + */ + + public function addSentShare($userId) + { + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $params = $this->request->getParams(); + error_log("addSentShare " . var_export($params, true)); + + $owner = $params["owner"]["opaqueId"]; // . "@" . $params["owner"]["idp"]; + $name = $params["name"]; // "fileid-/other/q/f gr" + $resourceOpaqueId = $params["resourceId"]["opaqueId"]; // "fileid-/other/q/f gr" + $revaPath = $this->revaPathFromOpaqueId($resourceOpaqueId); // "/other/q/f gr" + $efssPath = $this->revaPathToEfssPath($revaPath); + + $revaPermissions = null; + + foreach ($params['accessMethods'] as $accessMethod) { + if (isset($accessMethod['webdavOptions'])) { + $revaPermissions = $accessMethod['webdavOptions']['permissions']; + break; + } + } + + if (!isset($revaPermissions)) { + throw new \Exception('reva permissions not found'); + } + + $granteeType = $params["grantee"]["type"]; // "GRANTEE_TYPE_USER" + $granteeHost = $params["grantee"]["userId"]["idp"]; // "revanc2.docker" + $granteeUser = $params["grantee"]["userId"]["opaqueId"]; // "marie" + + if ($revaPermissions === null) { + $revaPermissions = [ + "initiate_file_download" => true + ]; + } + $efssPermissions = $this->getPermissionsCode($revaPermissions); + $shareWith = $granteeUser . "@" . $granteeHost; + $sharedSecret = $params["token"]; + + try { + $node = $this->userFolder->get($efssPath); + } catch (NotFoundException $e) { + return new JSONResponse(["error" => "Share failed. Resource Path not found"], Http::STATUS_BAD_REQUEST); + } + + error_log("calling newShare"); + $share = $this->shareManager->newShare(); + $share->setNode($node); + + try { + $this->lock($share->getNode()); + } catch (LockedException $e) { + throw new OCSNotFoundException($this->l->t('Could not create share')); + } + + $share->setShareType(IShare::TYPE_SCIENCEMESH); + $share->setSharedBy($userId); + $share->setSharedWith($shareWith); + $share->setShareOwner($owner); + $share->setPermissions($efssPermissions); + $share->setToken($sharedSecret); + $share = $this->shareProvider->createInternal($share); + + return new DataResponse($share->getId(), Http::STATUS_CREATED); + } + + /** + * add a received share + * + * @NoCSRFRequired + * @PublicPage + * @return Http\DataResponse|JSONResponse + */ + public function addReceivedShare($userId) + { + $params = $this->request->getParams(); + error_log("addReceivedShare " . var_export($params, true)); + foreach ($params['protocols'] as $protocol) { + if (isset($protocol['webdavOptions'])) { + $sharedSecret = $protocol['webdavOptions']['sharedSecret']; + // make sure you have webdav_endpoint = "https://nc1.docker/" under + // [grpc.services.ocmshareprovider] in the sending Reva's config + $uri = $protocol['webdavOptions']['uri']; // e.g. https://nc1.docker/remote.php/dav/ocm/vaKE36Wf1lJWCvpDcRQUScraVP5quhzA + $remote = implode('/', array_slice(explode('/', $uri), 0, 3)); // e.g. https://nc1.docker + break; + } + } + if (!isset($sharedSecret)) { + throw new \Exception('sharedSecret not found'); + } + + $shareData = [ + "remote" => $remote, //https://nc1.docker + "remote_id" => $params["remoteShareId"], // the id of the share in the oc_share table of the remote. + "share_token" => $sharedSecret, // 'tDPRTrLI4hE3C5T' + "password" => "", + "name" => rtrim($params["name"], "/"), // '/grfe' + "owner" => $params["owner"]["opaqueId"], // 'einstein' + "user" => $userId // 'marie' + ]; + + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $scienceMeshData = [ + "is_external" => true, + ]; + + $id = $this->shareProvider->addScienceMeshShare($scienceMeshData, $shareData); + return new JSONResponse($id, 201); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * + * Remove Share from share table + */ + public function Unshare($userId) + { + error_log("Unshare"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; + $name = $this->getNameByOpaqueId($opaqueId); + + if ($this->shareProvider->deleteSentShareByName($userId, $name)) { + return new JSONResponse("Deleted Sent Share", Http::STATUS_OK); + } else { + if ($this->shareProvider->deleteReceivedShareByOpaqueId($userId, $opaqueId)) { + return new JSONResponse("Deleted Received Share", Http::STATUS_OK); + } else { + return new JSONResponse("Could not find share", Http::STATUS_BAD_REQUEST); + } + } + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + */ + public function UpdateSentShare($userId) + { + error_log("UpdateSentShare"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + $opaqueId = $this->request->getParam("ref")["Spec"]["Id"]["opaque_id"]; + $permissions = $this->request->getParam("p")["permissions"]; + $permissionsCode = $this->getPermissionsCode($permissions); + $name = $this->getNameByOpaqueId($opaqueId); + if (!($share = $this->shareProvider->getSentShareByName($userId, $name))) { + return new JSONResponse(["error" => "UpdateSentShare failed"], Http::STATUS_INTERNAL_SERVER_ERROR); + } + $share->setPermissions($permissionsCode); + $shareUpdated = $this->shareProvider->update($share); + $response = $this->shareInfoToCs3Share($shareUpdated); + return new JSONResponse($response, Http::STATUS_OK); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * UpdateReceivedShare updates the received share with share state. + */ + public function UpdateReceivedShare($userId) + { + error_log("UpdateReceivedShare"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $response = []; + $resourceId = $this->request->getParam("received_share")["share"]["resource_id"]; + $permissions = $this->request->getParam("received_share")["share"]["permissions"]; + $permissionsCode = $this->getPermissionsCode($permissions); + + try { + $share = $this->shareProvider->getReceivedShareByToken($resourceId); + $share->setPermissions($permissionsCode); + $shareUpdate = $this->shareProvider->UpdateReceivedShare($share); + $response = $this->shareInfoToCs3Share($shareUpdate, $resourceId); + $response["state"] = 2; + return new JSONResponse($response, Http::STATUS_OK); + } catch (\Exception $e) { + return new JSONResponse(["error" => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * ListSentShares returns the shares created by the user. If md is provided is not nil, + * it returns only shares attached to the given resource. + */ + public function ListSentShares($userId) + { + error_log("ListSentShares"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $responses = []; + $shares = $this->shareProvider->getSentShares($userId); + + if ($shares) { + foreach ($shares as $share) { + array_push($responses, $this->shareInfoToCs3Share($share)); + } + } + return new JSONResponse($responses, Http::STATUS_OK); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * ListReceivedShares returns the list of shares the user has access. + */ + public function ListReceivedShares($userId) + { + error_log("ListReceivedShares"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $responses = []; + $shares = $this->shareProvider->getReceivedShares($userId); + + if ($shares) { + foreach ($shares as $share) { + $response = $this->shareInfoToCs3Share($share); + array_push($responses, [ + "share" => $response, + "state" => 2 + ]); + } + } + + return new JSONResponse($responses, Http::STATUS_OK); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * GetReceivedShare returns the information for a received share the user has access. + */ + public function GetReceivedShare($userId) + { + error_log("GetReceivedShare"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; + $name = $this->getNameByOpaqueId($opaqueId); + + try { + $share = $this->shareProvider->getReceivedShareByToken($opaqueId); + $response = $this->shareInfoToCs3Share($share, $opaqueId); + $response["state"] = 2; + return new JSONResponse($response, Http::STATUS_OK); + } catch (\Exception $e) { + return new JSONResponse(["error" => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * GetSentShare gets the information for a share by the given ref. + */ + public function GetSentShare($userId) + { + error_log("GetSentShare"); + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + + $opaqueId = $this->request->getParam("Spec")["Id"]["opaque_id"]; + $name = $this->getNameByOpaqueId($opaqueId); + $share = $this->shareProvider->getSentShareByName($userId, $name); + + if ($share) { + $response = $this->shareInfoToCs3Share($share); + return new JSONResponse($response, Http::STATUS_OK); + } + + return new JSONResponse(["error" => "GetSentShare failed"], Http::STATUS_NOT_FOUND); + } + + /** + * @PublicPage + * @NoCSRFRequired + * @return Http\DataResponse|JSONResponse + * + * GetSentShareByToken gets the information for a share by the given token. + */ + public function GetSentShareByToken($userId) + { + error_log("GetSentShareByToken: user is -> $userId"); + + // See: https://github.com/cs3org/reva/pull/4115#discussion_r1308371946 + if ($userId !== "nobody") { + if ($this->userManager->userExists($userId)) { + $this->init($userId); + } else { + return new JSONResponse("User not found", Http::STATUS_FORBIDDEN); + } + } + + $token = $this->request->getParam("Spec")["Token"]; + error_log("GetSentShareByToken: " . var_export($this->request->getParam("Spec"), true)); + + $share = $this->shareProvider->getSentShareByToken($token); + + if ($share) { + $response = $this->shareInfoToCs3Share($share, $token); + return new JSONResponse($response, Http::STATUS_OK); + } + + return new JSONResponse(["error" => "GetSentShare failed"], Http::STATUS_BAD_REQUEST); + } } diff --git a/lib/ShareProvider/ScienceMeshShareProvider.php b/lib/ShareProvider/ScienceMeshShareProvider.php index 333318df..50eeafa5 100644 --- a/lib/ShareProvider/ScienceMeshShareProvider.php +++ b/lib/ShareProvider/ScienceMeshShareProvider.php @@ -112,6 +112,45 @@ public function isShareTypeSupported($shareType) { return in_array($shareType, $this->supportedShareType); } + /** + * Share a path + * + * @param IShare $share + * @return IShare The share object + * @throws ShareNotFound + * @throws \Exception + */ + public function createInternal(IShare $share) { + error_log("SSP create"); + $shareWith = $share->getSharedWith(); + + /* + * Check if file is not already shared with the remote user + */ + $alreadyShared = $this->getSharedWith($shareWith, $this::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0); + if (!empty($alreadyShared)) { + $message = 'Sharing %1$s failed, because this item is already shared with %2$s'; + $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with user %2$s', [$share->getNode()->getName(), $shareWith]); + $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'ScienceMesh']); + throw new \Exception($message_t); + } + + // FIXME: don't allow ScienceMesh shares if source and target server are the same + // ScienceMesh shares always have read permissions + if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) { + $message = 'ScienceMesh shares require read permissions'; + $message_t = $this->l->t('ScienceMesh shares require read permissions'); + $this->logger->debug($message, ['app' => 'ScienceMesh']); + throw new \Exception($message_t); + } + + $share->setSharedWith($shareWith); + $shareId = $this->createScienceMeshShare($share); + $data = $this->getRawShare($shareId); + + return $this->createShareObject($data); + } + /** * Share a path * @@ -134,6 +173,14 @@ public function create(IShare $share) { $sourcePath = $prefix . "home/" . implode("/", array_slice($pathParts, $sourceOffset)) . $suffix; $targetPath = $prefix . implode("/", array_slice($pathParts, $targetOffset)) . $suffix; + // TODO: make a function for below operation. it is used in a lot placed, but incorrectly. + // it should split username@host into an array of 2 element + // representing array[0] = username, array[1] = host + // requirement: + // handle usernames with multiple @ in them. + // example: MahdiBaghbani@pondersource@sciencemesh.org + // uername: MahdiBaghbani@pondersource + // host: sciencemesh.org $split_point = '@'; $parts = explode($split_point, $shareWith); $last = array_pop($parts); @@ -158,45 +205,6 @@ public function create(IShare $share) { return $share; } - /** - * Share a path - * - * @param IShare $share - * @return IShare The share object - * @throws ShareNotFound - * @throws \Exception - */ - public function createInternal(IShare $share) { - error_log("SSP create"); - $shareWith = $share->getSharedWith(); - - /* - * Check if file is not already shared with the remote user - */ - $alreadyShared = $this->getSharedWith($shareWith, $this::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0); - if (!empty($alreadyShared)) { - $message = 'Sharing %1$s failed, because this item is already shared with %2$s'; - $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with user %2$s', [$share->getNode()->getName(), $shareWith]); - $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'ScienceMesh']); - throw new \Exception($message_t); - } - - // FIXME: don't allow ScienceMesh shares if source and target server are the same - // ScienceMesh shares always have read permissions - if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) { - $message = 'ScienceMesh shares require read permissions'; - $message_t = $this->l->t('ScienceMesh shares require read permissions'); - $this->logger->debug($message, ['app' => 'ScienceMesh']); - throw new \Exception($message_t); - } - - $share->setSharedWith($shareWith); - $shareId = $this->createScienceMeshShare($share); - $data = $this->getRawShare($shareId); - - return $this->createShareObject($data); - } - /** * create sciencemesh share and inform the recipient * @@ -340,6 +348,10 @@ public function delete(IShare $share) { $this->removeShareFromTable($share); } + // DIFFERENT FUNCTION IN NC/OC + // function revokeShare exists in OC but in NC. + // TODO: why? + /** * Get a share by token * @@ -367,6 +379,35 @@ public function getReceivedShareByToken($token) { return $share; } + /** + * Get a share by token + * + * @param string $token + * @return IShare + * @throws ShareNotFound + */ + public function getSentShareByToken($token) { + error_log("share provider getSentShareByToken '$token'"); + $qb = $this->dbConnection->getQueryBuilder(); + $cursor = $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) + ->execute(); + $data = $cursor->fetch(); + if ($data === false) { + error_log("sent share not found by token '$token'"); + throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); + } + try { + $share = $this->createShareObject($data); + } catch (InvalidShare $e) { + error_log("sent share found invalid by token '$token'"); + throw new ShareNotFound('Share not found', $this->l->t('Could not find share')); + } + error_log("found sent share ". $data["id"] . " by token '$token'"); + return $share; + } + /** * Create a share object from a database row from external shares * @@ -419,19 +460,14 @@ public function getSentShares($userId): iterable { } public function getReceivedShares($userId): iterable { - error_log("listing received shares for user id '$userId'!"); $qb = $this->dbConnection->getQueryBuilder(); $qb->select('*') ->from('share_external') ->where( - // $qb->expr()->eq('share_type', $qb->createNamedParameter($this::SHARE_TYPE_REMOTE)) - // ) - // ->andWhere( $qb->expr()->eq('user', $qb->createNamedParameter($userId)) ); $cursor = $qb->execute(); while ($data = $cursor->fetch()) { - error_log("received share!"); try { $share = $this->createExternalShareObject($data); } catch (InvalidShare $e) { @@ -442,7 +478,6 @@ public function getReceivedShares($userId): iterable { yield $share; } - error_log("done listing received shares!"); $cursor->closeCursor(); } @@ -570,7 +605,7 @@ public function getShareByOpaqueId($opaqueId) { return false; } // FIXME: side effect? - $res = $external?$this->createScienceMeshExternalShare($data):$this->createScienceMeshShare($data); + $res = $external ? $this->createScienceMeshExternalShare($data) : $this->createScienceMeshShare($data); return $res; }