Skip to content

Commit

Permalink
Initial draft at sharing file uploads through Nextcloud
Browse files Browse the repository at this point in the history
  • Loading branch information
Leon Klingele committed Apr 13, 2017
1 parent 91dcf62 commit 615f2ab
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 12 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@
['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' => 'filetransfer#uploadAndShare', 'url' => '/api/v1/filetransfer', 'verb' => 'POST'],
],
];
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
120 changes: 120 additions & 0 deletions controller/filetransfercontroller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?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\Helper;
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 FileTransferController 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;
}

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

// TODO(leon): Validate token!

if (!Helper::areFileTransferUploadsAllowed() || !Helper::doesServiceUserExist()) {
$_response['error'] = ErrorCodes::FILETRANSFER_DISABLED;
} else {
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');
}
// Validate file extension
// Keep in sync with SUPPORTED_DOCUMENT_TYPES
$allowedFileExtensions = array(
'.pdf',
'.odf',
);
$isAllowedFileExtension = false;
foreach ($allowedFileExtensions as $extension) {
if (stripos(strrev($fileName), strrev($extension)) === 0) {
// Found allowed extension
$isAllowedFileExtension = true;
break;
}
}
if (!$isAllowedFileExtension) {
throw new \Exception('Unsupported file extension');
}

$serviceUserFolder = $this->rootFolder->getUserFolder(Settings::SPREEDME_SERVICEUSER_USERNAME);
$uploadFolder = $serviceUserFolder
->newFolder(Settings::SPREEDME_SERVICEUSER_UPLOADFOLDER)
->newFolder($target);
$newFile = $uploadFolder->newFile($fileName);
$newFile->putContent(file_get_contents($file['tmp_name']));

$shareToken = Helper::runAsServiceUser(function () use ($newFile) {
return \OCP\Share::shareItem(
'file',
$newFile->getId(),
\OCP\Share::SHARE_TYPE_LINK,
null, /* shareWith */
\OCP\Constants::PERMISSION_READ,
null, /* itemSourceName */
null/* expirationDate, TODO(leon): Add support for this */
);
});

if (!is_string($shareToken)) {
throw new \Exception('Failed to share uploaded file');
}

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

return new DataResponse($_response);
}

}
2 changes: 2 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
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
84 changes: 82 additions & 2 deletions extra/static/owncloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,23 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {
return defer.promise;
};

var uploadAndShareFile = function(file, filename) {
var defer = $q.defer();
postMessageAPI.requestResponse(filename /* id */, {
type: "uploadAndShareBlob",
// :( blob attributes are lost when sent via postMessage
uploadAndShareBlob: {
blob: file,
name: filename
}
}, function(event) {
var data = event.data.data;
defer.resolve(data);
});

return defer.promise;
};

var openModalWithIframe = function(name, url, title, message, success_cb, error_cb) {
// TODO(leon): This is extremely ugly.
alertify.dialog.notify(
Expand Down Expand Up @@ -705,6 +722,7 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {
getConfig: getConfig,
setConfig: setConfig,
uploadFile: uploadFile,
uploadAndShareFile: uploadAndShareFile,
downloadFile: downloadFile,
FileSelector: FileSelector,
deferreds: deferreds,
Expand All @@ -714,7 +732,7 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {

}]);

app.directive('presentation', ['$compile', '$timeout', 'ownCloud', 'fileData', 'toastr', function($compile, $timeout, ownCloud, fileData, toastr) {
app.directive('presentation', ['$compile', '$timeout', 'ownCloud', 'fileData', 'toastr', 'alertify', function($compile, $timeout, ownCloud, fileData, toastr, alertify) {

var importFromOwnCloud = function() {
var $scope = this;
Expand Down Expand Up @@ -758,7 +776,7 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {
if (!f.info.hasOwnProperty("id")) {
f.info.id = f.id;
}
scope.advertiseFile(f);
scope.advertiseFile(f, true);
});
});
});
Expand All @@ -776,6 +794,61 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {
});
};

var advertiseFileWrapper = function(scope) {
// We have support for remote downloads — Yay! :)

var makeShareDownloadURL = function(url) {
return url + "/download";
};
var origAdvertiseFile = scope.advertiseFile;
return function(file, alreadyUploaded) {
var err = function(msg) {
log("Failed to upload / share file:", msg);
alertify.dialog.error(
"Failed to share the document(s)",
"Failed to share the document(s). Please try it again later.",
null,
null
);
};
if (!alreadyUploaded) {
// The file has not yet been uploaded
// -> Upload & share
return ownCloud.uploadAndShareFile(file.file, file.info.name)
.then(function(data) {
// TODO(leon): Add support for multiple callbacks in then
if (!data.success) {
// Error
err(arguments);
return;
}
file.info.url = makeShareDownloadURL(data.url);
origAdvertiseFile(file);
});
}

// File was already uploaded
// -> Directly share via link
// TODO(leon): Implement this

/*
return ownCloud.uploadFile(file.file, file.info.name)
.then(function(url) {
if (!url) {
err("url is empty");
return;
}
file.file.path = url;
scope.advertiseFile(file, false);
}, function() {
// Error
// TODO(leon): Pass in correct arguments
err(arguments);
});
*/
};
};

var downloadPresentation = function(presentationToDownload) {
var $scope = this;
var $presentationToDownload = angular.element(presentationToDownload);
Expand Down Expand Up @@ -836,6 +909,13 @@ define(modules, function(angular, moment, PostMessageAPI, OwnCloudConfig) {
compile: function(element) {
// Compile
return function(scope, element) {
if (typeof scope.allowRemoteDownloads !== "undefined") {
// Allow remote downloads
scope.allowRemoteDownloads = true;
// Overwrite advertiseFile function to provide support for remote downloads
scope.advertiseFile = advertiseFileWrapper(scope);
}

// Link
var $button = $('<button>');
$button
Expand Down
Loading

0 comments on commit 615f2ab

Please sign in to comment.