Skip to content

Commit

Permalink
Merge branch 'release-0.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
Leon Klingele committed Jun 29, 2017
2 parents 9b6ca44 + f498e0a commit 1e21215
Show file tree
Hide file tree
Showing 17 changed files with 768 additions and 19 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
nextcloud-spreedme (0.3.10)
* Support file sharing through Nextcloud. This is not of any interest for you unless you are using an WebRTC MCU / SFU.

nextcloud-spreedme (0.3.9)
* Add support for Nextcloud 12

Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<user>https://github.com/strukturag/nextcloud-spreedme/blob/master/README.md</user>
<admin>https://github.com/strukturag/nextcloud-spreedme/blob/master/README.md</admin>
</documentation>
<version>0.3.9</version>
<version>0.3.10</version>
<namespace>SpreedME</namespace>
<category>tools</category>
<bugs>https://github.com/strukturag/nextcloud-spreedme/issues</bugs>
Expand Down
3 changes: 3 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@
['name' => 'api#regenerate_temporary_password_signing_key', 'url' => '/api/v1/admin/config/regenerate/tp-key', 'verb' => 'POST'],
['name' => 'api#generate_spreed_webrtc_config', 'url' => '/api/v1/admin/config/generate/spreed-webrtc-config', 'verb' => 'POST'],
['name' => 'api#download_file', 'url' => '/api/v1/file/download', 'verb' => 'GET'],
// File Transfer
['name' => 'filesharing#uploadAndShare', 'url' => '/api/v1/filetransfers', 'verb' => 'POST'],
['name' => 'filesharing#listShares', 'url' => '/api/v1/filetransfers', 'verb' => 'GET'],
],
];
8 changes: 8 additions & 0 deletions config/config.php.in
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class Config {
// Set to true if at least one another Nextcloud instance uses the same Spreed WebRTC server
const SPREED_WEBRTC_IS_SHARED_INSTANCE = false;

// If set to false (default), all file transfers (e.g. when sharing a presentation or sending a file to another peer) are directly sent to the appropriate user in a peer-to-peer fashion.
// If set to true, all files are first uploaded to Nextcloud, then this file is shared and can be downloaded by other peers. This is required e.g. when using an MCU.
const SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS = false;

// Whether anonymous users (i.e. users which are not logged in) should be able to upload/share files and presentations
// This value is only taken into account when 'SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS' is set to true
const SPREED_WEBRTC_ALLOW_ANONYMOUS_FILE_TRANSFERS = false;

// Set to true if you want to allow access to this app + spreed-webrtc for non-registered users who received a temporary password by an Nextcloud admin.
// You can generate such a temporary password at: /index.php/apps/spreedme/admin/tp (Nextcloud admin user account required)
const OWNCLOUD_TEMPORARY_PASSWORD_LOGIN_ENABLED = false;
Expand Down
7 changes: 7 additions & 0 deletions controller/apicontroller.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public function saveConfig($config) {
'SPREED_WEBRTC_BASEPATH',
'OWNCLOUD_TEMPORARY_PASSWORD_LOGIN_ENABLED',
'SPREED_WEBRTC_IS_SHARED_INSTANCE',
'SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS',
'SPREED_WEBRTC_ALLOW_ANONYMOUS_FILE_TRANSFERS',
);

$_response = array('success' => false);
Expand All @@ -148,6 +150,11 @@ public function saveConfig($config) {
Helper::setDatabaseConfigValueIfEnabled($key, $value);
// Extra configuration for some of the keys
switch ($key) {
case 'SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS':
if ($value === 'true') {
Helper::createServiceUserUnlessExists();
}
break;
case 'OWNCLOUD_TEMPORARY_PASSWORD_LOGIN_ENABLED':
if ($value === 'true' && Helper::getDatabaseConfigValue('OWNCLOUD_TEMPORARY_PASSWORD_SIGNING_KEY') === '') {
// Also generate a 'Temporary Password signing key'
Expand Down
229 changes: 229 additions & 0 deletions controller/filesharingcontroller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php
/**
* Nextcloud - spreedme
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Leon <[email protected]>
* @copyright struktur AG 2016
*/

namespace OCA\SpreedME\Controller;

use OCA\SpreedME\Errors\ErrorCodes;
use OCA\SpreedME\Helper\FileCounter;
use OCA\SpreedME\Helper\Helper;
use OCA\SpreedME\Security\Security;
use OCA\SpreedME\Settings\Settings;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\Files\IRootFolder;
use OCP\ILogger;
use OCP\IRequest;

class FileSharingController extends Controller {

private $logger;
private $rootFolder;

public function __construct($appName, IRequest $request, $userId, ILogger $logger, IRootFolder $rootFolder) {
parent::__construct($appName, $request);

$this->logger = $logger;
$this->rootFolder = $rootFolder;
}

private function validateRequest($tp) {
if (!Helper::isUserLoggedIn() && !Security::validateTemporaryPassword(base64_decode($tp, true))) {
return ErrorCodes::TEMPORARY_PASSWORD_INVALID;
}

if (!Helper::areFileTransferUploadsAllowed() || !Helper::doesServiceUserExist()) {
return ErrorCodes::FILETRANSFER_DISABLED;
}

return null;
}

/**
* @NoAdminRequired
* @PublicPage
*/
public function uploadAndShare($target, $tp) {
$_response = array('success' => false);
$target = stripslashes($target); // TODO(leon): Is this really required? Found it somewhere

$err = $this->validateRequest($tp);
if ($err !== null) {
$_response['error'] = $err;
return new DataResponse($_response);
}

try {
$file = $this->request->getUploadedFile('file');
if (empty($file)) {
throw new \Exception('No file uploaded');
}
$fileName = $file['name'];
if (is_array($fileName)) {
// TODO(leon): We should support multiple file_s_
throw new \Exception('Only a single file may be uploaded');
}
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new \Exception('Upload error: ' . $file['error']);
}
// TODO(leon): We don't need this check?
if (!is_uploaded_file($file['tmp_name'])) {
throw new \Exception('Uploaded file is not an uploaded file?');
}
if ($file['size'] > Helper::getServiceUserMaxUploadSize()) {
throw new \Exception('Uploaded file is too big');
}
if (!Helper::hasAllowedFileExtension($fileName)) {
throw new \Exception('Unsupported file extension');
}

$shareToken = $this->shareAndGetToken($file['name'], $file['tmp_name'], $target);

$_response['token'] = $shareToken;
$_response['success'] = true;
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => Settings::APP_ID]);
$code = $e->getCode();
if ($code > 0) {
$_response['error'] = $code;
} else {
$_response['error'] = ErrorCodes::FILETRANSFER_FAILED;
}
}

return new DataResponse($_response);
}

private function doesFileExist($file, $cwd, $fsView) {
$fsView->lockFile($file, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
$exists = true;
try {
$cwd->get($file);
} catch (\OCP\Files\NotFoundException $e) {
$exists = false;
} finally {
$fsView->unlockFile($file, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
}
return $exists;
}

private function shareFile($file) {
if (!method_exists(\OC::$server, 'getShareManager')) {
return \OCP\Share::shareItem(
'file',
$file->getId(),
\OCP\Share::SHARE_TYPE_LINK,
null, /* shareWith */
\OCP\Constants::PERMISSION_READ,
null, /* itemSourceName */
null/* expirationDate */
);
}
// Nextcloud 9
$manager = \OC::$server->getShareManager();
$username = Settings::SPREEDME_SERVICEUSER_USERNAME;
$shareType = \OCP\Share::SHARE_TYPE_LINK;
$permissions = \OCP\Constants::PERMISSION_READ;
$existingShares = $manager->getSharesBy($username, $shareType, $file, false, 1);
// TODO(leon): Race condition here
if (count($existingShares) > 0) {
// We already have our share
$share = $existingShares[0];
} else {
// Not shared yet, share it now
$share = $manager->newShare();
$share
->setNode($file)
->setShareType($shareType)
->setPermissions($permissions)
->setSharedBy($username);
$manager->createShare($share);
}
return $share->getToken();
}

private function shareAndGetToken($fileName, $filePath, $target) {
$serviceUserFolder = $this->rootFolder->getUserFolder(Settings::SPREEDME_SERVICEUSER_USERNAME);
$uploadFolder = $serviceUserFolder
->newFolder(Settings::SPREEDME_SERVICEUSER_UPLOADFOLDER)
->newFolder($target);

// Make sure the file system is initialized even for unauthenticated users
\OC\Files\Filesystem::init(Settings::SPREEDME_SERVICEUSER_USERNAME, $serviceUserFolder->getPath());

$fileExists = function ($fileName) use ($uploadFolder) {
return $this->doesFileExist($fileName, $uploadFolder, \OC\Files\Filesystem::getView());
};
// Use file counter to determine unused file name
// TODO(leon): Detect duplicates by hash
$fileCounter = new FileCounter($fileName);
do {
$fileName = $fileCounter->next();
} while ($fileExists($fileName));

// TODO(leon): Data race here
$newFile = $uploadFolder->newFile($fileName);
$newFile->putContent(file_get_contents($filePath));

return Helper::runAsServiceUser(function () use ($newFile) {
return $this->shareFile($newFile);
});
}

/**
* @NoAdminRequired
* @PublicPage
*/
public function listShares($target, $tp) {
$_response = array('success' => false);
$target = stripslashes($target); // TODO(leon): Is this really required? Found it somewhere

$err = $this->validateRequest($tp);
if ($err !== null) {
$_response['error'] = $err;
return new DataResponse($_response);
}

try {
$serviceUserFolder = $this->rootFolder->getUserFolder(Settings::SPREEDME_SERVICEUSER_USERNAME);
$uploadFolder = $serviceUserFolder
->newFolder(Settings::SPREEDME_SERVICEUSER_UPLOADFOLDER)
->newFolder($target);

$shares = array();
foreach ($uploadFolder->getDirectoryListing() as $node) {
if ($node->getType() !== 'file' || !Helper::hasAllowedFileExtension($node->getName())) {
continue;
}
$shareToken = Helper::runAsServiceUser(function () use ($node) {
return $this->shareFile($node);
});
$newShare = array(
'name' => $node->getName(),
'size' => $node->getSize(),
);
// Only expose token to logged-in users
//if (Helper::isUserLoggedIn()) { // TODO(leon): Enable once we support lazy loading
$newShare['token'] = $shareToken;
//}
$shares[] = $newShare;
}

$_response['shares'] = $shares;
$_response['success'] = true;
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => Settings::APP_ID]);
$_response['error'] = ErrorCodes::FILETRANSFER_FAILED;
}

return new DataResponse($_response);
}

}
32 changes: 32 additions & 0 deletions doc/API.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ Available endpoints with request methods and content-type:
SPREED_WEBRTC_BASEPATH: Spreed WebRTC basepath (string, optional)
OWNCLOUD_TEMPORARY_PASSWORD_LOGIN_ENABLED: Whether the 'Temporary Password' feature should be enabled (string, optional)
SPREED_WEBRTC_IS_SHARED_INSTANCE: Whether the Spreed WebRTC server is used by multiple Nextcloud instances (string, optional)
SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS: Whether to upload file transfers or share them peer-to-peer (string, optional)
SPREED_WEBRTC_ALLOW_ANONYMOUS_FILE_TRANSFERS: Whether anonymous users (i.e. users which are not logged in) should be able to upload/share files and presentations (string, optional). This value is only taken into account when 'SPREED_WEBRTC_UPLOAD_FILE_TRANSFERS' is set to true
Response 200:
{
"success": true
Expand Down Expand Up @@ -154,6 +156,36 @@ Available endpoints with request methods and content-type:
}


/api/v1/filetransfers

The filetransfers endpoint allows admins list existing and create new shares.
This endpoint requires a valid Nextcloud session or TP + a valid Nextcloud CSRF requesttoken.

POST application/x-www-form-urlencoded
target: The room to upload the file into
file: The file to share form-data
Response 200:
{
"success": true,
"token": "dLDaFKSy2L6Nigv"
}
GET
Response 200:
{
"success": true,
"shares": [{
"name": "MyPresentation.pdf",
"size": 12345,
"token": "dLDaFKSy2L6Nigv"
}, {
"name": "Another Presentation.pdf",
"size": 56789,
"token": "DldAfksY2l6nIGV"
}]
}



End of API.

For questions, contact mailto:[email protected].
Expand Down
2 changes: 2 additions & 0 deletions errors/errorcodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class ErrorCodes {
const DB_CONFIG_ERROR_CONFIG_PHP_EXISTS = 50201;
const REMOTE_CONFIG_EMPTY = 50301;
const REMOTE_CONFIG_INVALID_JSON = 50302;
const FILETRANSFER_DISABLED = 50401;
const FILETRANSFER_FAILED = 50402;

private function __construct() {

Expand Down
Loading

0 comments on commit 1e21215

Please sign in to comment.