MDL-58943 repository: Add repository_nextcloud
Based on repository_owncloud from https://github.com/learnweb/moodle-repository_owncloud at 5b5fdbb. Differences to the original: * Renamed to repository_nextcloud * Updated version.php for core * Removed compatibility polyfills (webdav, privacy) * Removed boilerplate files (e.g. README, CI config) * Implement null_provider for privacy API as personal data is neither stored nor transmitted to the external system
This commit is contained in:
committed by
Damyon Wiese
parent
8b019fb709
commit
b4e24a82db
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Manages the creation and usage of access controlled links.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace repository_nextcloud;
|
||||
|
||||
use context;
|
||||
use \core\oauth2\api;
|
||||
use \core\notification;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once($CFG->libdir . '/webdavlib.php');
|
||||
|
||||
/**
|
||||
* Manages the creation and usage of access controlled links.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class access_controlled_link_manager{
|
||||
/**
|
||||
* OCS client that uses the Open Collaboration Services REST API.
|
||||
* @var ocs_client
|
||||
*/
|
||||
protected $ocsclient;
|
||||
/**
|
||||
* ocsclient of the systemaccount.
|
||||
* @var ocs_client
|
||||
*/
|
||||
protected $systemocsclient;
|
||||
/**
|
||||
* Client to manage oauth2 features from the systemaccount.
|
||||
* @var \core\oauth2\client
|
||||
*/
|
||||
protected $systemoauthclient;
|
||||
/**
|
||||
* Client to manage webdav request from the systemaccount..
|
||||
* @var \webdav_client
|
||||
*/
|
||||
protected $systemwebdavclient;
|
||||
/**
|
||||
* Issuer from the oauthclient.
|
||||
* @var \core\oauth2\issuer
|
||||
*/
|
||||
protected $issuer;
|
||||
/**
|
||||
* Name of the related repository.
|
||||
* @var string
|
||||
*/
|
||||
protected $repositoryname;
|
||||
|
||||
/**
|
||||
* Access_controlled_link_manager constructor.
|
||||
* @param ocs_client $ocsclient
|
||||
* @param \core\oauth2\client $systemoauthclient
|
||||
* @param ocs_client $systemocsclient
|
||||
* @param \core\oauth2\issuer $issuer
|
||||
* @param string $repositoryname
|
||||
* @throws configuration_exception
|
||||
*/
|
||||
public function __construct($ocsclient, $systemoauthclient, $systemocsclient, $issuer, $repositoryname) {
|
||||
$this->ocsclient = $ocsclient;
|
||||
$this->systemoauthclient = $systemoauthclient;
|
||||
$this->systemocsclient = $systemocsclient;
|
||||
|
||||
$this->repositoryname = $repositoryname;
|
||||
$this->issuer = $issuer;
|
||||
$this->systemwebdavclient = $this->create_system_dav();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the share of the systemaccount and a user. In case the share could not be deleted a notification is
|
||||
* displayed.
|
||||
* @param int $shareid Remote ID of the share to be deleted.
|
||||
*/
|
||||
public function delete_share_dataowner_sysaccount($shareid) {
|
||||
$shareid = (int) $shareid;
|
||||
$deleteshareparams = [
|
||||
'share_id' => $shareid
|
||||
];
|
||||
$deleteshareresponse = $this->ocsclient->call('delete_share', $deleteshareparams);
|
||||
$xml = simplexml_load_string($deleteshareresponse);
|
||||
|
||||
if (empty($xml->meta->statuscode) || $xml->meta->statuscode != 100 ) {
|
||||
notification::warning('You just shared a file with a access controlled link.
|
||||
However, the share between you and the systemaccount could not be deleted and is still present in your instance.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a share between a user and the system account. If $username is set the sharing direction is system account -> user,
|
||||
* otherwise user -> system account.
|
||||
* @param string $path Remote path of the file that will be shared
|
||||
* @param string $username optional when set the file is shared with the corresponding user otherwise with
|
||||
* the systemaccount.
|
||||
* @param bool $maywrite if false, only(!) read access is granted.
|
||||
* @return array statuscode, shareid, and filetarget
|
||||
* @throws request_exception
|
||||
*/
|
||||
public function create_share_user_sysaccount($path, $username = null, $maywrite = false) {
|
||||
$result = array();
|
||||
|
||||
if ($username != null) {
|
||||
$shareusername = $username;
|
||||
} else {
|
||||
$systemaccount = \core\oauth2\api::get_system_account($this->issuer);
|
||||
$shareusername = $systemaccount->get('username');
|
||||
}
|
||||
$permissions = ocs_client::SHARE_PERMISSION_READ;
|
||||
if ($maywrite) {
|
||||
// Add more privileges (write, reshare) if allowed for the given user.
|
||||
$permissions |= ocs_client::SHARE_PERMISSION_ALL;
|
||||
}
|
||||
$createshareparams = [
|
||||
'path' => $path,
|
||||
'shareType' => ocs_client::SHARE_TYPE_USER,
|
||||
'publicUpload' => false,
|
||||
'shareWith' => $shareusername,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
|
||||
// File is now shared with the system account.
|
||||
if ($username === null) {
|
||||
$createshareresponse = $this->ocsclient->call('create_share', $createshareparams);
|
||||
} else {
|
||||
$createshareresponse = $this->systemocsclient->call('create_share', $createshareparams);
|
||||
}
|
||||
$xml = simplexml_load_string($createshareresponse);
|
||||
|
||||
$statuscode = (int)$xml->meta->statuscode;
|
||||
if ($statuscode != 100 && $statuscode != 403) {
|
||||
$details = get_string('filenotaccessed', 'repository_nextcloud');
|
||||
throw new request_exception(get_string('request_exception',
|
||||
'repository_nextcloud', array('instance' => $this->repositoryname, 'errormessage' => $details)));
|
||||
}
|
||||
$result['shareid'] = (int)$xml->data->id;
|
||||
$result['statuscode'] = $statuscode;
|
||||
$result['filetarget'] = (string)$xml->data[0]->file_target;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** Copy or moves a file to a new path.
|
||||
* @param string $srcpath source path
|
||||
* @param string $dstpath
|
||||
* @param string $operation move or copy
|
||||
* @param \webdav_client $webdavclient needed when moving files.
|
||||
* @return String Http-status of the request
|
||||
* @throws configuration_exception
|
||||
* @throws \coding_exception
|
||||
* @throws \moodle_exception
|
||||
* @throws \repository_nextcloud\request_exception
|
||||
*/
|
||||
public function transfer_file_to_path($srcpath, $dstpath, $operation, $webdavclient = null) {
|
||||
$this->systemwebdavclient->open();
|
||||
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
|
||||
$srcpath = ltrim($srcpath, '/');
|
||||
$sourcepath = $webdavendpoint['path'] . $srcpath;
|
||||
$dstpath = ltrim($dstpath, '/');
|
||||
$destinationpath = $webdavendpoint['path'] . $dstpath . '/' . $srcpath;
|
||||
|
||||
if ($operation === 'copy') {
|
||||
$result = $this->systemwebdavclient->copy_file($sourcepath, $destinationpath, true);
|
||||
} else if ($operation === 'move') {
|
||||
$result = $webdavclient->move($sourcepath, $destinationpath, false);
|
||||
if ($result == 412) {
|
||||
// A file with that name already exists at that target. Find a unique location!
|
||||
$increment = 0; // Will be appended to/inserted into the filename.
|
||||
// Define the pattern that is used to insert the increment to the filename.
|
||||
if (substr_count($srcpath, '.') === 0) {
|
||||
// No file extension; append increment to the (sprintf-escaped) name.
|
||||
$namepattern = str_replace('%', '%%', $destinationpath) . ' (%s)';
|
||||
} else {
|
||||
// Append the increment to the second-to-last component, which is presumably the one before the extension.
|
||||
// Again, the original path is sprintf-escaped.
|
||||
$components = explode('.', str_replace('%', '%%', $destinationpath));
|
||||
$components[count($components) - 2] .= ' (%s)';
|
||||
$namepattern = implode('.', $components);
|
||||
}
|
||||
}
|
||||
while ($result == 412) {
|
||||
$increment++;
|
||||
$destinationpath = sprintf($namepattern, $increment);
|
||||
$result = $webdavclient->move($sourcepath, $destinationpath, false);
|
||||
}
|
||||
}
|
||||
$this->systemwebdavclient->close();
|
||||
if (!($result == 201 || $result == 412)) {
|
||||
$details = get_string('contactadminwith', 'repository_nextcloud',
|
||||
'A webdav request to ' . $operation . ' a file failed.');
|
||||
throw new request_exception(array('instance' => $this->repositoryname, 'errormessage' => $details));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique folder path for the access controlled link.
|
||||
* @param context $context
|
||||
* @param string $component
|
||||
* @param string $filearea
|
||||
* @param string $itemid
|
||||
* @return string $result full generated path.
|
||||
* @throws request_exception If the folder path cannot be created.
|
||||
*/
|
||||
public function create_folder_path_access_controlled_links($context, $component, $filearea, $itemid) {
|
||||
global $CFG, $SITE;
|
||||
// The fullpath to store the file is generated from the context.
|
||||
$contextlist = array_reverse($context->get_parent_contexts(true));
|
||||
$fullpath = '';
|
||||
$allfolders = [];
|
||||
foreach ($contextlist as $ctx) {
|
||||
// Prepare human readable context folders names, making sure they are still unique within the site.
|
||||
$prevlang = force_current_language($CFG->lang);
|
||||
$foldername = $ctx->get_context_name();
|
||||
force_current_language($prevlang);
|
||||
|
||||
if ($ctx->contextlevel === CONTEXT_SYSTEM) {
|
||||
// Append the site short name to the root folder.
|
||||
$foldername .= ' ('.$SITE->shortname.')';
|
||||
// Append the relevant object id.
|
||||
} else if ($ctx->instanceid) {
|
||||
$foldername .= ' (id '.$ctx->instanceid.')';
|
||||
} else {
|
||||
// This does not really happen but just in case.
|
||||
$foldername .= ' (ctx '.$ctx->id.')';
|
||||
}
|
||||
|
||||
$foldername = clean_param($foldername, PARAM_FILE);
|
||||
$allfolders[] = $foldername;
|
||||
}
|
||||
|
||||
$allfolders[] = clean_param($component, PARAM_FILE);
|
||||
$allfolders[] = clean_param($filearea, PARAM_FILE);
|
||||
$allfolders[] = clean_param($itemid, PARAM_FILE);
|
||||
|
||||
// Extracts the end of the webdavendpoint.
|
||||
$parsedwebdavurl = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$this->systemwebdavclient->open();
|
||||
// Checks whether folder exist and creates non-existent folders.
|
||||
foreach ($allfolders as $foldername) {
|
||||
$fullpath .= '/' . $foldername;
|
||||
$isdir = $this->systemwebdavclient->is_dir($webdavprefix . $fullpath);
|
||||
// Folder already exist, continue.
|
||||
if ($isdir === true) {
|
||||
continue;
|
||||
}
|
||||
$response = $this->systemwebdavclient->mkcol($webdavprefix . $fullpath);
|
||||
|
||||
if ($response != 201) {
|
||||
$this->systemwebdavclient->close();
|
||||
$details = get_string('contactadminwith', 'repository_nextcloud',
|
||||
"Folder path $fullpath could not be created in the system account.");
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => $details));
|
||||
}
|
||||
}
|
||||
$this->systemwebdavclient->close();
|
||||
return $fullpath;
|
||||
}
|
||||
|
||||
/** Creates a new webdav_client for the system account.
|
||||
* @return \webdav_client
|
||||
* @throws configuration_exception
|
||||
*/
|
||||
public function create_system_dav() {
|
||||
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
|
||||
// Selects the necessary information (port, type, server) from the path to build the webdavclient.
|
||||
$server = $webdavendpoint['host'];
|
||||
if ($webdavendpoint['scheme'] === 'https') {
|
||||
$webdavtype = 'ssl://';
|
||||
$webdavport = 443;
|
||||
} else if ($webdavendpoint['scheme'] === 'http') {
|
||||
$webdavtype = '';
|
||||
$webdavport = 80;
|
||||
}
|
||||
|
||||
// Override default port, if a specific one is set.
|
||||
if (isset($webdavendpoint['port'])) {
|
||||
$webdavport = $webdavendpoint['port'];
|
||||
}
|
||||
|
||||
// Authentication method is `bearer` for OAuth 2. Pass oauth client from which WebDAV obtains the token when needed.
|
||||
$dav = new \webdav_client($server, '', '', 'bearer', $webdavtype,
|
||||
$this->systemoauthclient->get_accesstoken()->token, $webdavendpoint['path']);
|
||||
|
||||
$dav->port = $webdavport;
|
||||
$dav->debug = false;
|
||||
return $dav;
|
||||
}
|
||||
|
||||
/** Creates a folder to store access controlled links.
|
||||
* @param string $controlledlinkfoldername
|
||||
* @param \webdav_client $webdavclient
|
||||
* @throws \coding_exception
|
||||
* @throws configuration_exception
|
||||
* @throws request_exception
|
||||
*/
|
||||
public function create_storage_folder($controlledlinkfoldername, $webdavclient) {
|
||||
$parsedwebdavurl = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
// Checks whether folder exist and creates non-existent folders.
|
||||
$webdavclient->open();
|
||||
$isdir = $webdavclient->is_dir($webdavprefix . $controlledlinkfoldername);
|
||||
// Folder already exist, continue.
|
||||
if (!$isdir) {
|
||||
$responsecreateshare = $webdavclient->mkcol($webdavprefix . $controlledlinkfoldername);
|
||||
|
||||
if ($responsecreateshare != 201) {
|
||||
$webdavclient->close();
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('contactadminwith', 'repository_nextcloud',
|
||||
'The folder to store files in the user account could not be created.')));
|
||||
}
|
||||
}
|
||||
$webdavclient->close();
|
||||
}
|
||||
|
||||
/** Gets all shares from a path (the path is file specific) and extracts the share of a specific user. In case
|
||||
* multiple shares exist the first one is taken. Multiple shares can only appear when shares are created outside
|
||||
* of this plugin, therefore this case is not handled.
|
||||
* @param string $path
|
||||
* @param string $username
|
||||
* @return \SimpleXMLElement
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public function get_shares_from_path($path, $username) {
|
||||
$ocsparams = [
|
||||
'path' => $path,
|
||||
'reshares' => true
|
||||
];
|
||||
|
||||
$getsharesresponse = $this->systemocsclient->call('get_shares', $ocsparams);
|
||||
$xml = simplexml_load_string($getsharesresponse);
|
||||
$validelement = array();
|
||||
foreach ($fileid = $xml->data->element as $element) {
|
||||
if ($element->share_with == $username) {
|
||||
$validelement = $element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($validelement)) {
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('filenotaccessed', 'repository_nextcloud')));
|
||||
|
||||
}
|
||||
return $validelement->id;
|
||||
}
|
||||
|
||||
/** This method can only be used if the response is from a newly created share. In this case there is more information
|
||||
* in the response. For a reference refer to
|
||||
* https://docs.nextcloud.com/server/13/developer_manual/core/ocs-share-api.html#get-information-about-a-known-share.
|
||||
* @param int $shareid
|
||||
* @param string $username
|
||||
* @return mixed the id of the share
|
||||
* @throws \coding_exception
|
||||
* @throws \repository_nextcloud\request_exception
|
||||
*/
|
||||
public function get_share_information_from_shareid($shareid, $username) {
|
||||
$ocsparams = [
|
||||
'share_id' => (int) $shareid
|
||||
];
|
||||
|
||||
$shareinformation = $this->ocsclient->call('get_information_of_share', $ocsparams);
|
||||
$xml = simplexml_load_string($shareinformation);
|
||||
foreach ($fileid = $xml->data->element as $element) {
|
||||
if ($element->share_with == $username) {
|
||||
$validelement = $element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($validelement)) {
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('filenotaccessed', 'repository_nextcloud')));
|
||||
|
||||
}
|
||||
return (string) $validelement->file_target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a file that has previously been shared with the system account.
|
||||
* @param string $path Path to file in user context.
|
||||
* @return array shareid: ID of share, filetarget: path to file in sys account.
|
||||
* @throws request_exception If the share cannot be resolved.
|
||||
*/
|
||||
public function find_share_in_sysaccount($path) {
|
||||
$systemaccount = \core\oauth2\api::get_system_account($this->issuer);
|
||||
$systemaccountuser = $systemaccount->get('username');
|
||||
|
||||
// Find out share ID from user files.
|
||||
$ocsparams = [
|
||||
'path' => $path,
|
||||
'reshares' => true
|
||||
];
|
||||
|
||||
$getsharesresponse = $this->ocsclient->call('get_shares', $ocsparams);
|
||||
$xml = simplexml_load_string($getsharesresponse);
|
||||
$validelement = array();
|
||||
foreach ($fileid = $xml->data->element as $element) {
|
||||
if ($element->share_with == $systemaccountuser) {
|
||||
$validelement = $element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($validelement)) {
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('filenotaccessed', 'repository_nextcloud')));
|
||||
}
|
||||
$shareid = (int) $validelement->id;
|
||||
|
||||
// Use share id to find file name in system account's context.
|
||||
$ocsparams = [
|
||||
'share_id' => $shareid
|
||||
];
|
||||
|
||||
$shareinformation = $this->systemocsclient->call('get_information_of_share', $ocsparams);
|
||||
$xml = simplexml_load_string($shareinformation);
|
||||
foreach ($fileid = $xml->data->element as $element) {
|
||||
if ($element->share_with == $systemaccountuser) {
|
||||
$validfile = $element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($validfile)) {
|
||||
throw new request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('filenotaccessed', 'repository_nextcloud')));
|
||||
|
||||
}
|
||||
return [
|
||||
'shareid' => $shareid,
|
||||
'filetarget' => (string) $validfile->file_target
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Exception for when client configuration data is missing.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_nextcloud;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Exception for when client configuration data is missing.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class configuration_exception extends \moodle_exception {
|
||||
|
||||
/**
|
||||
* This exception is used when the configuration of the plugin can not be processed or database entries are
|
||||
* missing.
|
||||
* @param string $hint optional param for additional information of the problem
|
||||
* @param string $debuginfo detailed information how to fix problem
|
||||
*/
|
||||
public function __construct($hint = '', $debuginfo = null) {
|
||||
parent::__construct('configuration_exception', 'repository_nextcloud', '', $hint, $debuginfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
/**
|
||||
* Provide static functions for creating and validating issuers.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace repository_nextcloud;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Provide static functions for creating and validating issuers.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class issuer_management {
|
||||
|
||||
/**
|
||||
* Check if an issuer provides all endpoints that are required by repository_nextcloud.
|
||||
* @param \core\oauth2\issuer $issuer An issuer.
|
||||
* @return bool True, if all endpoints exist; false otherwise.
|
||||
*/
|
||||
public static function is_valid_issuer(\core\oauth2\issuer $issuer) {
|
||||
$endpointwebdav = false;
|
||||
$endpointocs = false;
|
||||
$endpointtoken = false;
|
||||
$endpointauth = false;
|
||||
$endpointuserinfo = false;
|
||||
$endpoints = \core\oauth2\api::get_endpoints($issuer);
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$name = $endpoint->get('name');
|
||||
switch ($name) {
|
||||
case 'webdav_endpoint':
|
||||
$endpointwebdav = true;
|
||||
break;
|
||||
case 'ocs_endpoint':
|
||||
$endpointocs = true;
|
||||
break;
|
||||
case 'token_endpoint':
|
||||
$endpointtoken = true;
|
||||
break;
|
||||
case 'authorization_endpoint':
|
||||
$endpointauth = true;
|
||||
break;
|
||||
case 'userinfo_endpoint':
|
||||
$endpointuserinfo = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $endpointwebdav && $endpointocs && $endpointtoken && $endpointauth && $endpointuserinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed url parts of an endpoint of an issuer.
|
||||
* @param string $endpointname
|
||||
* @param \core\oauth2\issuer $issuer
|
||||
* @return array parseurl [scheme => https/http, host=>'hostname', port=>443, path=>'path']
|
||||
* @throws configuration_exception if an endpoint is undefined
|
||||
*/
|
||||
public static function parse_endpoint_url(string $endpointname, \core\oauth2\issuer $issuer): array {
|
||||
$url = $issuer->get_endpoint_url($endpointname);
|
||||
if (empty($url)) {
|
||||
throw new configuration_exception(sprintf('Endpoint %s not defined.', $endpointname));
|
||||
}
|
||||
return parse_url($url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* REST interface to Nextcloud's implementation of Open Collaboration Services.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace repository_nextcloud;
|
||||
|
||||
use core\oauth2\client;
|
||||
use core\oauth2\rest;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* REST interface to Nextcloud's implementation of Open Collaboration Services.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class ocs_client extends rest {
|
||||
|
||||
/**
|
||||
* shareType=0 creates a private user share.
|
||||
*/
|
||||
const SHARE_TYPE_USER = 0;
|
||||
|
||||
/**
|
||||
* shareType=3 creates a public share.
|
||||
*/
|
||||
const SHARE_TYPE_PUBLIC = 3;
|
||||
|
||||
/**
|
||||
* permissions=1 gives read permission for a share.
|
||||
*/
|
||||
const SHARE_PERMISSION_READ = 1;
|
||||
|
||||
/**
|
||||
* permissions=1 gives read permission for a share.
|
||||
*/
|
||||
const SHARE_PERMISSION_ALL = 31;
|
||||
|
||||
/**
|
||||
* OCS endpoint as configured for the used issuer.
|
||||
* @var \moodle_url
|
||||
*/
|
||||
private $ocsendpoint;
|
||||
|
||||
|
||||
/**
|
||||
* Get endpoint URLs from the used issuer to use them in get_api_functions().
|
||||
* @param client $oauthclient OAuth-authenticated Nextcloud client
|
||||
* @throws configuration_exception Exception if critical endpoints are missing.
|
||||
* @throws \moodle_exception when trying to construct a moodleurl
|
||||
*/
|
||||
public function __construct(client $oauthclient) {
|
||||
parent::__construct($oauthclient);
|
||||
|
||||
$issuer = $oauthclient->get_issuer();
|
||||
$ocsendpoint = $issuer->get_endpoint_url('ocs');
|
||||
if ($ocsendpoint === false) {
|
||||
throw new configuration_exception('Endpoint ocs_endpoint not defined.');
|
||||
}
|
||||
$this->ocsendpoint = new \moodle_url($ocsendpoint);
|
||||
if (empty($this->ocsendpoint->get_param('format'))) {
|
||||
$this->ocsendpoint->params(array('format' => 'xml'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define relevant functions of the OCS API.
|
||||
*
|
||||
* Previously, the instruction to create a oauthclient recommended the user to enter the return format (format=xml).
|
||||
* However, in this case the shareid is appended at the wrong place. Therefore, a new url is build which inserts the
|
||||
* shareid at the suitable place for delete_share and get_information_of_share.
|
||||
* create_share docs: https://docs.nextcloud.com/server/13/developer_manual/core/ocs-share-api.html#create-a-new-share
|
||||
*
|
||||
*/
|
||||
public function get_api_functions() {
|
||||
return [
|
||||
'create_share' => [
|
||||
'endpoint' => $this->ocsendpoint->out(false),
|
||||
'method' => 'post',
|
||||
'args' => [
|
||||
'path' => PARAM_TEXT, // Could be PARAM_PATH, we really don't want to enforce a Moodle understanding of paths.
|
||||
'shareType' => PARAM_INT,
|
||||
'shareWith' => PARAM_TEXT, // Name of receiving user/group. Required if SHARE_TYPE_USER.
|
||||
'publicUpload' => PARAM_RAW, // Actually Boolean, but neither String-Boolean ('false') nor PARAM_BOOL (0/1).
|
||||
'permissions' => PARAM_INT,
|
||||
'shareWith' => PARAM_TEXT,
|
||||
'expireDate' => PARAM_TEXT
|
||||
],
|
||||
'response' => 'text/xml'
|
||||
],
|
||||
'delete_share' => [
|
||||
'endpoint' => $this->build_share_url(),
|
||||
'method' => 'delete',
|
||||
'args' => [
|
||||
'share_id' => PARAM_INT
|
||||
],
|
||||
'response' => 'text/xml'
|
||||
],
|
||||
'get_shares' => [
|
||||
'endpoint' => $this->ocsendpoint->out(false),
|
||||
'method' => 'get',
|
||||
'args' => [
|
||||
'path' => PARAM_TEXT,
|
||||
'reshares' => PARAM_RAW, // Returns not only the shares from the current user but all of the given file.
|
||||
'subfiles' => PARAM_RAW, // Returns all shares within a folder, given that path defines a folder.
|
||||
],
|
||||
'response' => 'text/xml'
|
||||
],
|
||||
'get_information_of_share' => [
|
||||
'endpoint' => $this->build_share_url(),
|
||||
'method' => 'get',
|
||||
'args' => [
|
||||
'share_id' => PARAM_INT
|
||||
],
|
||||
'response' => 'text/xml'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Function to return a url with the shareid in the path.
|
||||
* @return string
|
||||
*/
|
||||
private function build_share_url() {
|
||||
// Out_omit_querystring() in combination with ocsendpoint->get_path() is not used since both function include
|
||||
// /ocs/v1.php.
|
||||
$shareurl = $this->ocsendpoint->get_scheme() . '://' . $this->ocsendpoint->get_host() . ':' .
|
||||
$this->ocsendpoint->get_port() . $this->ocsendpoint->get_path() . '/{share_id}?' .
|
||||
$this->ocsendpoint->get_query_string(false);
|
||||
return $shareurl;
|
||||
}
|
||||
|
||||
/**
|
||||
* In POST requests, Moodle's REST API assumes that params are
|
||||
* - transmitted as part of the URL or
|
||||
* - expressed in JSON.
|
||||
* Neither is true; we are passing an array to $functionargs which is then put into CURLOPT_POSTFIELDS.
|
||||
* Curl assumes the content type to be `multipart/form-data` then, but the Moodle REST API tries to put
|
||||
* a JSON content type. As a result, clients would fail.
|
||||
* To make this less tedious to use, we assume that the params-as-array-in-$functionargs is the default for us.
|
||||
*
|
||||
* @param string $functionname Name of a function from get_api_functions()
|
||||
* @param array $functionargs Request parameters
|
||||
* @param bool|string $rawpost Optional param to include in the body of a post
|
||||
* @param bool|string $contenttype Content type of the request body. Default: multipart/form-data if !$rawpost, JSON otherwise
|
||||
* @return object|string
|
||||
* @throws \coding_exception
|
||||
* @throws \core\oauth2\rest_exception
|
||||
*/
|
||||
public function call($functionname, $functionargs, $rawpost = false, $contenttype = false) {
|
||||
if ($rawpost === false && $contenttype === false) {
|
||||
return parent::call($functionname, $functionargs, false, 'multipart/form-data');
|
||||
} else {
|
||||
return parent::call($functionname, $functionargs, $rawpost, $contenttype);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Privacy provider.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace repository_nextcloud\privacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Privacy provider implementing null_provider.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\null_provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Exception for when an OCS request fails
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace repository_nextcloud;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Exception for when an OCS request fails
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_exception extends \moodle_exception {
|
||||
|
||||
/**
|
||||
* An OCS request has failed.
|
||||
*
|
||||
* @param string $hint optional param for additional information of the problem
|
||||
* @param string $debuginfo detailed information how to fix problem
|
||||
*/
|
||||
public function __construct($hint = '', $debuginfo = null) {
|
||||
parent::__construct('request_exception', 'repository_nextcloud', '', $hint, $debuginfo);
|
||||
}
|
||||
}
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Capability definitions for Nextcloud repository.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = array(
|
||||
'repository/nextcloud:view' => array(
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'user' => CAP_ALLOW
|
||||
)
|
||||
)
|
||||
);
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Language strings' definition for Nextcloud repository.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
// General.
|
||||
$string['pluginname'] = 'Nextcloud';
|
||||
$string['configplugin'] = 'Nextcloud repository configuration';
|
||||
$string['nextcloud'] = 'Nextcloud';
|
||||
$string['nextcloud:view'] = 'View Nextcloud';
|
||||
$string['configplugin'] = 'Nextcloud configuration';
|
||||
$string['pluginname_help'] = 'Nextcloud repository';
|
||||
|
||||
// Settings.
|
||||
$string['issuervalidation_without'] = 'You have not selected an Nextcloud server as the OAuth 2 issuer yet.';
|
||||
$string['issuervalidation_valid'] = 'Currently the {$a} issuer is active.';
|
||||
$string['issuervalidation_invalid'] = 'Currently the {$a} issuer is active, however it does not implement all necessary endpoints. The repository will not work.';
|
||||
$string['right_issuers'] = 'The following issuers implement the required endpoints: <br> {$a}';
|
||||
$string['no_right_issuers'] = 'None of the existing issuers implement all required endpoints. Please register an appropriate issuer.';
|
||||
$string['chooseissuer'] = 'Issuer';
|
||||
$string['chooseissuer_help'] = 'To add a new issuer visit the admin OAuth 2 services page. <br>
|
||||
For additional help with the OAuth 2 API please refer to the Moodle documentation.';
|
||||
$string['foldername'] = 'Name of folder created in Nextcloud users\' private space that holds all access controlled links';
|
||||
$string['foldername_help'] = 'To assure users find files shared with them, shares are saved into a specific folder. <br>
|
||||
This setting determines the name of the folder. It is recommended to chose a name associated with your Moodle instance.';
|
||||
$string['oauth2serviceslink'] = '<a href="{$a}" title="Link to OAuth 2 services configuration">OAuth 2 services configuration</a>';
|
||||
$string['privacy:metadata'] = 'The Nextcloud repository plugin neither stores any personal data nor transmits user data to the remote system.';
|
||||
$string['internal'] = 'Internal (files stored in Moodle)';
|
||||
$string['external'] = 'External (only links stored in Moodle)';
|
||||
$string['both'] = 'Internal and external';
|
||||
$string['supportedreturntypes'] = 'Supported files';
|
||||
$string['defaultreturntype'] = 'Default return type';
|
||||
$string['fileoptions'] = 'The types and defaults for returned files is configurable here. Note that all files linked externally will be updated so that the owner is the Moodle system account.';
|
||||
|
||||
// Exceptions.
|
||||
$string['configuration_exception'] = 'An error in the configuration of the OAuth 2 client occurred: {$a}';
|
||||
$string['request_exception'] = 'A request to {$a->instance} has failed. {$a->errormessage}';
|
||||
$string['requestnotexecuted'] = 'The request could not be executed. If this happens frequently please contact the course or site administrator.';
|
||||
$string['notauthorized'] = 'You are not authorized to execute the demanded request. Please ensure you are authenticated with the right account.';
|
||||
$string['contactadminwith'] = 'The requested action could not be executed. In case this happens frequently please contact the side administrator with the following additional information:<br>"<i>{$a}</i>"';
|
||||
$string['cannotconnect'] = 'The user could not be authenticated, please log in and then upload the file.';
|
||||
$string['filenotaccessed'] = 'The requested file could not be accessed. Please check whether you have chosen a valid file and you are authenticated with the right account.';
|
||||
$string['couldnotmove'] = 'The requested file could not be moved in the {$a} folder.';
|
||||
Executable
+882
@@ -0,0 +1,882 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Nextcloud repository plugin library.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or
|
||||
*/
|
||||
|
||||
use repository_nextcloud\issuer_management;
|
||||
use repository_nextcloud\ocs_client;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/repository/lib.php');
|
||||
require_once($CFG->libdir . '/webdavlib.php');
|
||||
|
||||
/**
|
||||
* Nextcloud repository class.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_nextcloud extends repository {
|
||||
/**
|
||||
* OAuth 2 client
|
||||
* @var \core\oauth2\client
|
||||
*/
|
||||
private $client = null;
|
||||
|
||||
/**
|
||||
* OAuth 2 Issuer
|
||||
* @var \core\oauth2\issuer
|
||||
*/
|
||||
private $issuer = null;
|
||||
|
||||
/**
|
||||
* Additional scopes needed for the repository. Currently, nextcloud does not actually support/use scopes, so
|
||||
* this is intended as a hint at required functionality and will help declare future scopes.
|
||||
*/
|
||||
const SCOPES = 'files ocs';
|
||||
|
||||
/**
|
||||
* Webdav client which is used for webdav operations.
|
||||
*
|
||||
* @var \webdav_client
|
||||
*/
|
||||
private $dav = null;
|
||||
|
||||
/**
|
||||
* Basepath for WebDAV operations
|
||||
* @var string
|
||||
*/
|
||||
private $davbasepath;
|
||||
|
||||
/**
|
||||
* OCS client that uses the Open Collaboration Services REST API.
|
||||
* @var ocs_client
|
||||
*/
|
||||
private $ocsclient;
|
||||
|
||||
/**
|
||||
* @var oauth2_client System account client.
|
||||
*/
|
||||
private $systemoauthclient = false;
|
||||
|
||||
/**
|
||||
* OCS systemocsclient that uses the Open Collaboration Services REST API.
|
||||
* @var ocs_client
|
||||
*/
|
||||
private $systemocsclient = null;
|
||||
/**
|
||||
* Name of the folder for controlled links.
|
||||
* @var string
|
||||
*/
|
||||
private $controlledlinkfoldername;
|
||||
|
||||
/**
|
||||
* repository_nextcloud constructor.
|
||||
* @param int $repositoryid
|
||||
* @param bool|int|stdClass $context
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
|
||||
parent::__construct($repositoryid, $context, $options);
|
||||
try {
|
||||
// Issuer from repository instance config.
|
||||
$issuerid = $this->get_option('issuerid');
|
||||
$this->issuer = \core\oauth2\api::get_issuer($issuerid);
|
||||
} catch (dml_missing_record_exception $e) {
|
||||
// A repository is marked as disabled when no issuer is present.
|
||||
$this->disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the webdav endpoint and parse the basepath.
|
||||
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
// Get basepath without trailing slash, because future uses will come with a leading slash.
|
||||
$basepath = $webdavendpoint['path'];
|
||||
if (strlen($basepath) > 0 && substr($basepath, -1) === '/') {
|
||||
$basepath = substr($basepath, 0, -1);
|
||||
}
|
||||
$this->davbasepath = $basepath;
|
||||
} catch (\repository_nextcloud\configuration_exception $e) {
|
||||
// A repository is marked as disabled when no webdav_endpoint is present
|
||||
// or it fails to parse, because all operations concerning files
|
||||
// rely on the webdav endpoint.
|
||||
$this->disabled = true;
|
||||
return;
|
||||
}
|
||||
$this->controlledlinkfoldername = $this->get_option('controlledlinkfoldername');
|
||||
|
||||
if (!$this->issuer) {
|
||||
$this->disabled = true;
|
||||
return;
|
||||
} else if (!$this->issuer->get('enabled')) {
|
||||
// In case the Issuer is not enabled, the repository is disabled.
|
||||
$this->disabled = true;
|
||||
return;
|
||||
} else if (!issuer_management::is_valid_issuer($this->issuer)) {
|
||||
// Check if necessary endpoints are present.
|
||||
$this->disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ocsclient = new ocs_client($this->get_user_oauth_client());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or initialise an oauth client for the system account.
|
||||
* @return false|oauth2_client False if initialisation was unsuccessful, otherwise an initialised client.
|
||||
*/
|
||||
private function get_system_oauth_client() {
|
||||
if ($this->systemoauthclient === false) {
|
||||
try {
|
||||
$this->systemoauthclient = \core\oauth2\api::get_system_oauth_client($this->issuer);
|
||||
} catch (\moodle_exception $e) {
|
||||
$this->systemoauthclient = false;
|
||||
}
|
||||
}
|
||||
return $this->systemoauthclient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or initialise an ocs client for the system account.
|
||||
* @return null|ocs_client Null if initialisation was unsuccessful, otherwise an initialised client.
|
||||
*/
|
||||
private function get_system_ocs_client() {
|
||||
if ($this->systemocsclient === null) {
|
||||
try {
|
||||
$systemoauth = $this->get_system_oauth_client();
|
||||
if (!$systemoauth) {
|
||||
return null;
|
||||
}
|
||||
$this->systemocsclient = new ocs_client($systemoauth);
|
||||
} catch (\moodle_exception $e) {
|
||||
$this->systemocsclient = null;
|
||||
}
|
||||
}
|
||||
return $this->systemocsclient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the webdav client.
|
||||
* @throws \repository_nextcloud\configuration_exception If configuration is missing (endpoints).
|
||||
*/
|
||||
private function initiate_webdavclient() {
|
||||
if ($this->dav !== null) {
|
||||
return $this->dav;
|
||||
}
|
||||
|
||||
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
|
||||
// Selects the necessary information (port, type, server) from the path to build the webdavclient.
|
||||
$server = $webdavendpoint['host'];
|
||||
if ($webdavendpoint['scheme'] === 'https') {
|
||||
$webdavtype = 'ssl://';
|
||||
$webdavport = 443;
|
||||
} else if ($webdavendpoint['scheme'] === 'http') {
|
||||
$webdavtype = '';
|
||||
$webdavport = 80;
|
||||
}
|
||||
|
||||
// Override default port, if a specific one is set.
|
||||
if (isset($webdavendpoint['port'])) {
|
||||
$webdavport = $webdavendpoint['port'];
|
||||
}
|
||||
|
||||
// Authentication method is `bearer` for OAuth 2. Pass token of authenticated client, too.
|
||||
$this->dav = new \webdav_client($server, '', '', 'bearer', $webdavtype,
|
||||
$this->get_user_oauth_client()->get_accesstoken()->token);
|
||||
|
||||
$this->dav->port = $webdavport;
|
||||
$this->dav->debug = false;
|
||||
return $this->dav;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does exactly the same as in the WebDAV repository. The only difference is, that
|
||||
* the nextcloud OAuth2 client uses OAuth2 instead of Basic Authentication.
|
||||
*
|
||||
* @param string $reference relative path to the file.
|
||||
* @param string $title title of the file.
|
||||
* @return array|bool returns either the moodle path to the file or false.
|
||||
*/
|
||||
public function get_file($reference, $title = '') {
|
||||
// Normal file.
|
||||
$reference = urldecode($reference);
|
||||
|
||||
// Prepare a file with an arbitrary name - cannot be $title because of special chars (cf. MDL-57002).
|
||||
$path = $this->prepare_file(uniqid());
|
||||
$this->initiate_webdavclient();
|
||||
if (!$this->dav->open()) {
|
||||
return false;
|
||||
}
|
||||
$this->dav->get_file($this->davbasepath . $reference, $path);
|
||||
$this->dav->close();
|
||||
|
||||
return array('path' => $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does exactly the same as in the WebDAV repository. The only difference is, that
|
||||
* the nextcloud OAuth2 client uses OAuth2 instead of Basic Authentication.
|
||||
*
|
||||
* @param string $path relative path to the directory or file.
|
||||
* @param string $page page number (given multiple pages of elements).
|
||||
* @return array directory properties.
|
||||
*/
|
||||
public function get_listing($path='', $page = '') {
|
||||
if (empty($path)) {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
$ret = $this->get_listing_prepare_response($path);
|
||||
|
||||
// Before any WebDAV method can be executed, a WebDAV client socket needs to be opened
|
||||
// which connects to the server.
|
||||
$this->initiate_webdavclient();
|
||||
if (!$this->dav->open()) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Since the paths which are received from the PROPFIND WebDAV method are url encoded
|
||||
// (because they depict actual web-paths), the received paths need to be decoded back
|
||||
// for the plugin to be able to work with them.
|
||||
$ls = $this->dav->ls($this->davbasepath . urldecode($path));
|
||||
$this->dav->close();
|
||||
|
||||
// The method get_listing return all information about all child files/folders of the
|
||||
// current directory. If no information was received, the directory must be empty.
|
||||
if (!is_array($ls)) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Process WebDAV output and convert it into Moodle format.
|
||||
$ret['list'] = $this->get_listing_convert_response($path, $ls);
|
||||
return $ret;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use OCS to generate a public share to the requested file.
|
||||
* This method derives a download link from the public share URL.
|
||||
*
|
||||
* @param string $url relative path to the chosen file
|
||||
* @return string the generated download link.
|
||||
* @throws \repository_nextcloud\request_exception If nextcloud responded badly
|
||||
*
|
||||
*/
|
||||
public function get_link($url) {
|
||||
$ocsparams = [
|
||||
'path' => $url,
|
||||
'shareType' => ocs_client::SHARE_TYPE_PUBLIC,
|
||||
'publicUpload' => false,
|
||||
'permissions' => ocs_client::SHARE_PERMISSION_READ
|
||||
];
|
||||
|
||||
$response = $this->ocsclient->call('create_share', $ocsparams);
|
||||
$xml = simplexml_load_string($response);
|
||||
$repositoryname = get_string('pluginname', 'repository_nextcloud');
|
||||
|
||||
if ($xml === false ) {
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname,
|
||||
'errormessage' => 'Invalid response'));
|
||||
}
|
||||
|
||||
if ((string)$xml->meta->status !== 'ok') {
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname, 'errormessage' => sprintf(
|
||||
'(%s) %s', $xml->meta->statuscode, $xml->meta->message)));
|
||||
}
|
||||
|
||||
// Take the share link and convert it into a download link.
|
||||
return ((string)$xml->data[0]->url) . '/download';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the source from the file picker (chosen by the user) into
|
||||
* information, which will be received by methods that fetch files/references from
|
||||
* the Nextcloud server.
|
||||
*
|
||||
* @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
|
||||
* @return string file reference, ready to be stored
|
||||
*/
|
||||
public function get_file_reference($source) {
|
||||
$usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
|
||||
|
||||
// A filereference is requested if an alias/shortcut shall be created, i.e. a FILE_REFERENCE option is selected.
|
||||
// Therefore, generate and return a public link to the file.
|
||||
if ($usefilereference) {
|
||||
$reference = $this->get_link($source);
|
||||
$filereturn = new stdClass();
|
||||
$filereturn->type = 'FILE_REFERENCE';
|
||||
$filereturn->link = $reference;
|
||||
return json_encode($filereturn);
|
||||
}
|
||||
|
||||
// Otherwise, the simple relative path to the file is enough.
|
||||
return $source;
|
||||
}
|
||||
|
||||
/** Called when a file is selected as a "access control link".
|
||||
* Invoked at MOODLE/repository/repository_ajax.php
|
||||
*
|
||||
* This is called at the point the reference files are being copied from the draft area to the real area.
|
||||
* What is done here is transfer ownership to the system user (by copying) then delete the intermediate share
|
||||
* used for that. Finally update the reference to point to new file name.
|
||||
*
|
||||
* @param string $reference this reference is generated by repository::get_file_reference()
|
||||
* @param context $context the target context for this new file.
|
||||
* @param string $component the target component for this new file.
|
||||
* @param string $filearea the target filearea for this new file.
|
||||
* @param string $itemid the target itemid for this new file.
|
||||
* @return string updated reference (final one before it's saved to db).
|
||||
* @throws \repository_nextcloud\configuration_exception
|
||||
* @throws \repository_nextcloud\request_exception
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
* @throws repository_exception
|
||||
*/
|
||||
public function reference_file_selected($reference, $context, $component, $filearea, $itemid) {
|
||||
$source = json_decode($reference);
|
||||
|
||||
if (is_object($source)) {
|
||||
if ($source->type != 'FILE_CONTROLLED_LINK') {
|
||||
// Only access controlled links need special handling; we are done.
|
||||
return $reference;
|
||||
}
|
||||
if (!empty($source->usesystem)) {
|
||||
// If we already copied this file to the system account - we are done.
|
||||
return $reference;
|
||||
}
|
||||
}
|
||||
|
||||
// Check this issuer is enabled.
|
||||
if ($this->disabled || $this->get_system_oauth_client() === false || $this->get_system_ocs_client() === null) {
|
||||
throw new repository_exception('cannotdownload', 'repository');
|
||||
}
|
||||
|
||||
$linkmanager = new \repository_nextcloud\access_controlled_link_manager($this->ocsclient, $this->get_system_oauth_client(),
|
||||
$this->get_system_ocs_client(), $this->issuer, $this->get_name());
|
||||
|
||||
// Get the current user.
|
||||
$userauth = $this->get_user_oauth_client();
|
||||
if ($userauth === false) {
|
||||
$details = get_string('cannotconnect', 'repository_nextcloud');
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $this->get_name(), 'errormessage' => $details));
|
||||
}
|
||||
// 1. Share the File with the system account.
|
||||
$responsecreateshare = $linkmanager->create_share_user_sysaccount($reference);
|
||||
if ($responsecreateshare['statuscode'] == 403) {
|
||||
// File has already been shared previously => find file in system account and use that.
|
||||
$responsecreateshare = $linkmanager->find_share_in_sysaccount($reference);
|
||||
}
|
||||
|
||||
// 2. Create a unique path in the system account.
|
||||
$createdfolder = $linkmanager->create_folder_path_access_controlled_links($context, $component, $filearea,
|
||||
$itemid);
|
||||
|
||||
// 3. Copy File to the new folder path.
|
||||
$linkmanager->transfer_file_to_path($responsecreateshare['filetarget'], $createdfolder, 'copy');
|
||||
|
||||
// 4. Delete the share.
|
||||
$linkmanager->delete_share_dataowner_sysaccount($responsecreateshare['shareid']);
|
||||
|
||||
// Update the returned reference so that the stored_file in moodle points to the newly copied file.
|
||||
$filereturn = new stdClass();
|
||||
$filereturn->type = 'FILE_CONTROLLED_LINK';
|
||||
$filereturn->link = $createdfolder . $responsecreateshare['filetarget'];
|
||||
$filereturn->name = $reference;
|
||||
$filereturn->usesystem = true;
|
||||
$filereturn = json_encode($filereturn);
|
||||
|
||||
return $filereturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository method that serves the referenced file (created e.g. via get_link).
|
||||
* All parameters are there for compatibility with superclass, but they are ignored.
|
||||
*
|
||||
* @param stored_file $storedfile
|
||||
* @param int $lifetime (ignored)
|
||||
* @param int $filter (ignored)
|
||||
* @param bool $forcedownload (ignored)
|
||||
* @param array $options (ignored)
|
||||
* @throws \repository_nextcloud\configuration_exception
|
||||
* @throws \repository_nextcloud\request_exception
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function send_file($storedfile, $lifetime=null , $filter=0, $forcedownload=false, array $options = null) {
|
||||
$repositoryname = $this->get_name();
|
||||
$reference = json_decode($storedfile->get_reference());
|
||||
|
||||
if ($reference->type == 'FILE_REFERENCE') {
|
||||
redirect($reference->link);
|
||||
}
|
||||
|
||||
// 1. assure the client and user is logged in.
|
||||
if (empty($this->client) || $this->get_system_oauth_client() === false || $this->get_system_ocs_client() === null) {
|
||||
$details = get_string('contactadminwith', 'repository_nextcloud',
|
||||
'The OAuth client could not be connected.');
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname, 'errormessage' => $details));
|
||||
}
|
||||
|
||||
if (!$this->client->is_logged_in()) {
|
||||
$this->print_login_popup(['style' => 'margin-top: 250px']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determining writeability of file from the using context.
|
||||
// Variable $info is null|\file_info. file_info::is_writable is only true if user may write for any reason.
|
||||
$fb = get_file_browser();
|
||||
$context = context::instance_by_id($storedfile->get_contextid(), MUST_EXIST);
|
||||
$info = $fb->get_file_info($context,
|
||||
$storedfile->get_component(),
|
||||
$storedfile->get_filearea(),
|
||||
$storedfile->get_itemid(),
|
||||
$storedfile->get_filepath(),
|
||||
$storedfile->get_filename());
|
||||
$maywrite = !empty($info) && $info->is_writable();
|
||||
|
||||
$this->initiate_webdavclient();
|
||||
|
||||
// Create the a manager to handle steps.
|
||||
$linkmanager = new \repository_nextcloud\access_controlled_link_manager($this->ocsclient, $this->get_system_oauth_client(),
|
||||
$this->get_system_ocs_client(), $this->issuer, $repositoryname);
|
||||
|
||||
// 2. Check whether user has folder for files otherwise create it.
|
||||
$linkmanager->create_storage_folder($this->controlledlinkfoldername, $this->dav);
|
||||
|
||||
$userinfo = $this->client->get_userinfo();
|
||||
$username = $userinfo['username'];
|
||||
|
||||
// Creates a share between the systemaccount and the user.
|
||||
$responsecreateshare = $linkmanager->create_share_user_sysaccount($reference->link, $username, $maywrite);
|
||||
|
||||
$statuscode = $responsecreateshare['statuscode'];
|
||||
|
||||
if ($statuscode == 403) {
|
||||
$shareid = $linkmanager->get_shares_from_path($reference->link, $username);
|
||||
} else if ($statuscode == 100) {
|
||||
$filetarget = $linkmanager->get_share_information_from_shareid($responsecreateshare['shareid'], $username);
|
||||
$copyresult = $linkmanager->transfer_file_to_path($filetarget, $this->controlledlinkfoldername,
|
||||
'move', $this->dav);
|
||||
if (!($copyresult == 201 || $copyresult == 412)) {
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $this->repositoryname,
|
||||
'errormessage' => get_string('couldnotmove', 'repository_nextcloud', $this->controlledlinkfoldername)));
|
||||
}
|
||||
$shareid = $responsecreateshare['shareid'];
|
||||
} else if ($statuscode == 997) {
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname,
|
||||
'errormessage' => get_string('notauthorized', 'repository_nextcloud')));
|
||||
} else {
|
||||
$details = get_string('filenotaccessed', 'repository_nextcloud');
|
||||
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname, 'errormessage' => $details));
|
||||
}
|
||||
$filetarget = $linkmanager->get_share_information_from_shareid((int)$shareid, $username);
|
||||
|
||||
// Obtain the file from Nextcloud using a Bearer token authenticated connection because we cannot perform a redirect here.
|
||||
// The reason is that Nextcloud uses samesite cookie validation, i.e. a redirected request would not be authenticated.
|
||||
// (Also the browser might use the session of a Nextcloud user that is different from the one that is known to Moodle.)
|
||||
$filename = basename($filetarget);
|
||||
$tmppath = make_request_directory() . '/' . $filename;
|
||||
$this->dav->open();
|
||||
|
||||
// Concat webdav path with file path.
|
||||
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
$filetarget = ltrim($filetarget, '/');
|
||||
$filetarget = $webdavendpoint['path'] . $filetarget;
|
||||
|
||||
// Write file into temp location.
|
||||
if (!$this->dav->get_file($filetarget, $tmppath)) {
|
||||
$this->dav->close();
|
||||
throw new repository_exception('cannotdownload', 'repository');
|
||||
}
|
||||
$this->dav->close();
|
||||
|
||||
// Output the obtained file to the user and remove it from disk.
|
||||
send_temp_file($tmppath, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Which return type should be selected by default.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function default_returntype() {
|
||||
$setting = $this->get_option('defaultreturntype');
|
||||
$supported = $this->get_option('supportedreturntypes');
|
||||
if (($setting == FILE_INTERNAL && $supported !== 'external') || $supported === 'internal') {
|
||||
return FILE_INTERNAL;
|
||||
}
|
||||
return FILE_CONTROLLED_LINK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return names of the general options.
|
||||
* By default: no general option name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_type_option_names() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which checks whether the user is logged in on the Nextcloud instance.
|
||||
*
|
||||
* @return bool false, if no Access Token is set or can be requested.
|
||||
*/
|
||||
public function check_login() {
|
||||
$client = $this->get_user_oauth_client();
|
||||
return $client->is_logged_in();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached user authenticated oauth client.
|
||||
* @param bool|moodle_url $overrideurl Use this url instead of the repo callback.
|
||||
* @return \core\oauth2\client
|
||||
*/
|
||||
protected function get_user_oauth_client($overrideurl = false) {
|
||||
if ($this->client) {
|
||||
return $this->client;
|
||||
}
|
||||
if ($overrideurl) {
|
||||
$returnurl = $overrideurl;
|
||||
} else {
|
||||
$returnurl = new moodle_url('/repository/repository_callback.php');
|
||||
$returnurl->param('callback', 'yes');
|
||||
$returnurl->param('repo_id', $this->id);
|
||||
$returnurl->param('sesskey', sesskey());
|
||||
}
|
||||
$this->client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, self::SCOPES);
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prints a simple Login Button which redirects to an authorization window from Nextcloud.
|
||||
*
|
||||
* @return mixed login window properties.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function print_login() {
|
||||
$client = $this->get_user_oauth_client();
|
||||
$loginurl = $client->get_login_url();
|
||||
if ($this->options['ajax']) {
|
||||
$ret = array();
|
||||
$btn = new \stdClass();
|
||||
$btn->type = 'popup';
|
||||
$btn->url = $loginurl->out(false);
|
||||
$ret['login'] = array($btn);
|
||||
return $ret;
|
||||
} else {
|
||||
echo html_writer::link($loginurl, get_string('login', 'repository'),
|
||||
array('target' => '_blank', 'rel' => 'noopener noreferrer'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the held Access Token and prints the Login window.
|
||||
*
|
||||
* @return array login window properties.
|
||||
*/
|
||||
public function logout() {
|
||||
$client = $this->get_user_oauth_client();
|
||||
$client->log_out();
|
||||
return parent::logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up access token after the redirection from Nextcloud.
|
||||
*/
|
||||
public function callback() {
|
||||
$client = $this->get_user_oauth_client();
|
||||
// If an Access Token is stored within the client, it has to be deleted to prevent the addition
|
||||
// of an Bearer authorization header in the request method.
|
||||
$client->log_out();
|
||||
|
||||
// This will upgrade to an access token if we have an authorization code and save the access token in the session.
|
||||
$client->is_logged_in();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance for this plug-in
|
||||
*
|
||||
* @param string $type the type of the repository
|
||||
* @param int $userid the user id
|
||||
* @param stdClass $context the context
|
||||
* @param array $params the options for this instance
|
||||
* @param int $readonly whether to create it readonly or not (defaults to not)
|
||||
* @return mixed
|
||||
* @throws dml_exception
|
||||
* @throws required_capability_exception
|
||||
*/
|
||||
public static function create($type, $userid, $context, $params, $readonly=0) {
|
||||
require_capability('moodle/site:config', context_system::instance());
|
||||
return parent::create($type, $userid, $context, $params, $readonly);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method adds a select form and additional information to the settings form..
|
||||
*
|
||||
* @param \moodleform $mform Moodle form (passed by reference)
|
||||
* @return bool|void
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function instance_config_form($mform) {
|
||||
if (!has_capability('moodle/site:config', context_system::instance())) {
|
||||
$mform->addElement('static', null, '', get_string('nopermissions', 'error', get_string('configplugin',
|
||||
'repository_nextcloud')));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load configured issuers.
|
||||
$issuers = core\oauth2\api::get_all_issuers();
|
||||
$types = array();
|
||||
|
||||
// Validates which issuers implement the right endpoints. WebDav is necessary for Nextcloud.
|
||||
$validissuers = [];
|
||||
foreach ($issuers as $issuer) {
|
||||
$types[$issuer->get('id')] = $issuer->get('name');
|
||||
if (\repository_nextcloud\issuer_management::is_valid_issuer($issuer)) {
|
||||
$validissuers[] = $issuer->get('name');
|
||||
}
|
||||
}
|
||||
|
||||
// Render the form.
|
||||
$url = new \moodle_url('/admin/tool/oauth2/issuers.php');
|
||||
$mform->addElement('static', null, '', get_string('oauth2serviceslink', 'repository_nextcloud', $url->out()));
|
||||
|
||||
$mform->addElement('select', 'issuerid', get_string('chooseissuer', 'repository_nextcloud'), $types);
|
||||
$mform->addRule('issuerid', get_string('required'), 'required', null, 'issuer');
|
||||
$mform->addHelpButton('issuerid', 'chooseissuer', 'repository_nextcloud');
|
||||
$mform->setType('issuerid', PARAM_INT);
|
||||
|
||||
// All issuers that are valid are displayed seperately (if any).
|
||||
if (count($validissuers) === 0) {
|
||||
$mform->addElement('static', null, '', get_string('no_right_issuers', 'repository_nextcloud'));
|
||||
} else {
|
||||
$mform->addElement('static', null, '', get_string('right_issuers', 'repository_nextcloud',
|
||||
implode(', ', $validissuers)));
|
||||
}
|
||||
|
||||
$mform->addElement('text', 'controlledlinkfoldername', get_string('foldername', 'repository_nextcloud'));
|
||||
$mform->addHelpButton('controlledlinkfoldername', 'foldername', 'repository_nextcloud');
|
||||
$mform->setType('controlledlinkfoldername', PARAM_TEXT);
|
||||
$mform->setDefault('controlledlinkfoldername', 'Moodlefiles');
|
||||
|
||||
$mform->addElement('static', null, '', get_string('fileoptions', 'repository_nextcloud'));
|
||||
$choices = [
|
||||
'both' => get_string('both', 'repository_nextcloud'),
|
||||
'internal' => get_string('internal', 'repository_nextcloud'),
|
||||
'external' => get_string('external', 'repository_nextcloud'),
|
||||
];
|
||||
$mform->addElement('select', 'supportedreturntypes', get_string('supportedreturntypes', 'repository_nextcloud'), $choices);
|
||||
|
||||
$choices = [
|
||||
FILE_INTERNAL => get_string('internal', 'repository_nextcloud'),
|
||||
FILE_CONTROLLED_LINK => get_string('external', 'repository_nextcloud'),
|
||||
];
|
||||
$mform->addElement('select', 'defaultreturntype', get_string('defaultreturntype', 'repository_nextcloud'), $choices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings for repository instance
|
||||
*
|
||||
* @param array $options settings
|
||||
* @return bool
|
||||
*/
|
||||
public function set_option($options = array()) {
|
||||
$options['issuerid'] = clean_param($options['issuerid'], PARAM_INT);
|
||||
$options['controlledlinkfoldername'] = clean_param($options['controlledlinkfoldername'], PARAM_TEXT);
|
||||
|
||||
$ret = parent::set_option($options);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Names of the plugin settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_instance_option_names() {
|
||||
return ['issuerid', 'controlledlinkfoldername',
|
||||
'defaultreturntype', 'supportedreturntypes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to define which file-types are supported (hardcoded can not be changed in Admin Menu)
|
||||
* By default FILE_INTERNAL is supported. In case a system account is connected and an issuer exist,
|
||||
* FILE_CONTROLLED_LINK is supported.
|
||||
* FILE_INTERNAL - the file is uploaded/downloaded and stored directly within the Moodle file system.
|
||||
* FILE_CONTROLLED_LINK - creates a copy of the file in Nextcloud from which private shares to permitted users will be
|
||||
* created. The file itself can not be changed any longer by the owner.
|
||||
* @return int return type bitmask supported
|
||||
*/
|
||||
public function supported_returntypes() {
|
||||
// We can only support access controlled links if the system account is connected.
|
||||
$setting = $this->get_option('supportedreturntypes');
|
||||
$sysisconnected = !empty($this->issuer) && $this->issuer->is_system_account_connected();
|
||||
if ($setting === 'internal') {
|
||||
return FILE_INTERNAL;
|
||||
}
|
||||
if ($setting === 'external') {
|
||||
if ($sysisconnected) {
|
||||
return FILE_CONTROLLED_LINK | FILE_REFERENCE | FILE_EXTERNAL;
|
||||
}
|
||||
return FILE_REFERENCE | FILE_EXTERNAL;
|
||||
}
|
||||
// Otherwise all of them are supported (controlled link only with system account).
|
||||
if ($sysisconnected) {
|
||||
return FILE_CONTROLLED_LINK | FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
|
||||
}
|
||||
return FILE_INTERNAL | FILE_REFERENCE | FILE_EXTERNAL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the WebDAV `ls()' output and convert it into a format that Moodle's filepicker understands.
|
||||
*
|
||||
* @param string $dirpath Relative (urlencoded) path of the folder of interest.
|
||||
* @param array $ls Output by WebDAV
|
||||
* @return array Moodle-formatted list of directory contents; ready for use as $ret['list'] in get_listings
|
||||
*/
|
||||
private function get_listing_convert_response($dirpath, $ls) {
|
||||
global $OUTPUT;
|
||||
$folders = array();
|
||||
$files = array();
|
||||
$parsedurl = issuer_management::parse_endpoint_url('webdav', $this->issuer);
|
||||
$basepath = rtrim('/' . ltrim($parsedurl['path'], '/ '), '/ ');
|
||||
|
||||
foreach ($ls as $item) {
|
||||
if (!empty($item['lastmodified'])) {
|
||||
$item['lastmodified'] = strtotime($item['lastmodified']);
|
||||
} else {
|
||||
$item['lastmodified'] = null;
|
||||
}
|
||||
|
||||
// Extracting object title from absolute path: First remove Nextcloud basepath.
|
||||
$item['href'] = substr(urldecode($item['href']), strlen($basepath));
|
||||
// Then remove relative path to current folder.
|
||||
$title = substr($item['href'], strlen($dirpath));
|
||||
|
||||
if (!empty($item['resourcetype']) && $item['resourcetype'] == 'collection') {
|
||||
// A folder.
|
||||
if ($dirpath == $item['href']) {
|
||||
// Skip "." listing.
|
||||
continue;
|
||||
}
|
||||
|
||||
$folders[strtoupper($title)] = array(
|
||||
'title' => rtrim($title, '/'),
|
||||
'thumbnail' => $OUTPUT->image_url(file_folder_icon(90))->out(false),
|
||||
'children' => array(),
|
||||
'datemodified' => $item['lastmodified'],
|
||||
'path' => $item['href']
|
||||
);
|
||||
} else {
|
||||
// A file.
|
||||
$size = !empty($item['getcontentlength']) ? $item['getcontentlength'] : '';
|
||||
$files[strtoupper($title)] = array(
|
||||
'title' => $title,
|
||||
'thumbnail' => $OUTPUT->image_url(file_extension_icon($title, 90))->out(false),
|
||||
'size' => $size,
|
||||
'datemodified' => $item['lastmodified'],
|
||||
'source' => $item['href']
|
||||
);
|
||||
}
|
||||
}
|
||||
ksort($files);
|
||||
ksort($folders);
|
||||
return array_merge($folders, $files);
|
||||
}
|
||||
/**
|
||||
* Print the login in a popup.
|
||||
*
|
||||
* @param array|null $attr Custom attributes to be applied to popup div.
|
||||
*/
|
||||
private function print_login_popup($attr = null) {
|
||||
global $OUTPUT;
|
||||
|
||||
$this->client = $this->get_user_oauth_client();
|
||||
$url = new moodle_url($this->client->get_login_url());
|
||||
$state = $url->get_param('state') . '&reloadparent=true';
|
||||
$url->param('state', $state);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$repositoryname = get_string('pluginname', 'repository_nextcloud');
|
||||
|
||||
$button = new single_button($url, get_string('logintoaccount', 'repository', $repositoryname),
|
||||
'post', true);
|
||||
$button->add_action(new popup_action('click', $url, 'Login'));
|
||||
$button->class = 'mdl-align';
|
||||
$button = $OUTPUT->render($button);
|
||||
echo html_writer::div($button, '', $attr);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
}
|
||||
/**
|
||||
* Prepare response of get_listing; namely
|
||||
* - defining setting elements,
|
||||
* - filling in the parent path of the currently-viewed directory.
|
||||
* @param string $path Relative path
|
||||
* @return array ret array for use as get_listing's $ret
|
||||
*/
|
||||
private function get_listing_prepare_response($path) {
|
||||
$ret = [
|
||||
// Fetch the list dynamically. An AJAX request is sent to the server as soon as the user opens a folder.
|
||||
'dynload' => true,
|
||||
'nosearch' => true, // Disable search.
|
||||
'nologin' => false, // Provide a login link because a user logs into his/her private Nextcloud storage.
|
||||
'path' => array([ // Contains all parent paths to the current path.
|
||||
'name' => $this->get_meta()->name,
|
||||
'path' => '',
|
||||
]),
|
||||
'defaultreturntype' => $this->default_returntype(),
|
||||
'manage' => $this->issuer->get('baseurl'), // Provide button to go into file management interface quickly.
|
||||
'list' => array(), // Contains all file/folder information and is required to build the file/folder tree.
|
||||
];
|
||||
|
||||
// If relative path is a non-top-level path, calculate all its parents' paths.
|
||||
// This is used for navigation in the file picker.
|
||||
if ($path != '/') {
|
||||
$chunks = explode('/', trim($path, '/'));
|
||||
$parent = '/';
|
||||
// Every sub-path to the last part of the current path is a parent path.
|
||||
foreach ($chunks as $chunk) {
|
||||
$subpath = $parent . $chunk . '/';
|
||||
$ret['path'][] = [
|
||||
'name' => urldecode($chunk),
|
||||
'path' => $subpath
|
||||
];
|
||||
// Prepare next iteration.
|
||||
$parent = $subpath;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32"><rect rx="5" ry="5" height="32" width="32" fill="#0082c9"/><path style="text-decoration-color:#000;isolation:auto;mix-blend-mode:normal;block-progression:tb;text-decoration-line:none;text-indent:0;text-transform:none;text-decoration-style:solid" d="M16.023 9.342c-3.126 0-5.75 2.14-6.552 5.02-.7-1.54-2.24-2.632-4.03-2.632-2.436 0-4.44 2.004-4.44 4.44 0 2.438 2.003 4.442 4.44 4.442 1.79 0 3.33-1.092 4.03-2.632.802 2.88 3.427 5.02 6.553 5.02 3.11 0 5.72-2.117 6.538-4.972.713 1.512 2.23 2.584 4 2.584 2.437 0 4.44-2.004 4.44-4.442 0-2.437-2.004-4.44-4.44-4.44-1.77 0-3.288 1.072-4 2.584-.818-2.855-3.428-4.972-6.537-4.972zm0 2.607a4.203 4.203 0 0 1 4.223 4.22 4.203 4.203 0 0 1-4.223 4.223A4.203 4.203 0 0 1 11.8 16.17a4.203 4.203 0 0 1 4.223-4.22zM5.44 14.336c1.028 0 1.834.805 1.834 1.834a1.815 1.815 0 0 1-1.834 1.835 1.815 1.815 0 0 1-1.834-1.834c0-1.028.806-1.833 1.834-1.833zm21.12 0c1.027 0 1.833.805 1.834 1.834 0 1.03-.806 1.835-1.835 1.835a1.815 1.815 0 0 1-1.835-1.834c0-1.028.806-1.833 1.834-1.833z" fill="#fff" color="#000" white-space="normal"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,633 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains tests for the repository_nextcloud class.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core\oauth2\system_account;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/webdavlib.php');
|
||||
require_once($CFG->dirroot . '/repository/nextcloud/tests/fixtures/testable_access_controlled_link_manager.php');
|
||||
|
||||
/**
|
||||
* Class repository_nextcloud_testcase
|
||||
* @group repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_nextcloud_access_controlled_link_manager_testcase extends advanced_testcase {
|
||||
|
||||
/** @var null|testable_access_controlled_link_manager a malleable variant of the access_controlled_link_manager. */
|
||||
public $linkmanager = null;
|
||||
|
||||
/** @var null|\repository_nextcloud\ocs_client The ocs_client used to send requests. */
|
||||
public $ocsmockclient = null;
|
||||
|
||||
/** @var null|\core\oauth2\client Mock oauth client for the system account. */
|
||||
private $oauthsystemmock = null;
|
||||
|
||||
/** @var null|\core\oauth2\issuer which belongs to the repository_nextcloud object. */
|
||||
public $issuer = null;
|
||||
|
||||
/**
|
||||
* SetUp to create an repository instance.
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Admin is necessary to create issuer object.
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('repository_nextcloud');
|
||||
$this->issuer = $generator->test_create_issuer();
|
||||
$generator->test_create_endpoints($this->issuer->get('id'));
|
||||
|
||||
// Mock clients.
|
||||
$this->ocsmockclient = $this->getMockBuilder(repository_nextcloud\ocs_client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$this->oauthsystemmock = $this->getMockBuilder(\core\oauth2\client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$systemwebdavclient = $this->getMockBuilder(\webdav_client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$systemocsclient = $systemocsclient = $this->getMockBuilder(repository_nextcloud\ocs_client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
|
||||
// Pseudo system account user.
|
||||
$this->systemaccountusername = 'pseudouser';
|
||||
$record = new stdClass();
|
||||
$record->issuerid = $this->issuer->get('id');
|
||||
$record->refreshtoken = 'pseudotoken';
|
||||
$record->grantedscopes = 'scopes';
|
||||
$record->email = '';
|
||||
$record->username = $this->systemaccountusername;
|
||||
$systemaccount = new system_account(0, $record);
|
||||
$systemaccount->create();
|
||||
|
||||
$this->linkmanager = new testable_access_controlled_link_manager($this->ocsmockclient,
|
||||
$this->oauthsystemmock, $systemocsclient,
|
||||
$this->issuer, 'Nextcloud', $systemwebdavclient);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to test the private function create_share_user_sysaccount.
|
||||
*/
|
||||
public function test_create_share_user_sysaccount_user_shares() {
|
||||
$params = [
|
||||
'path' => "/ambient.txt",
|
||||
'shareType' => \repository_nextcloud\ocs_client::SHARE_TYPE_USER,
|
||||
'publicUpload' => false,
|
||||
'shareWith' => $this->systemaccountusername,
|
||||
'permissions' => \repository_nextcloud\ocs_client::SHARE_PERMISSION_READ,
|
||||
];
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<id>207</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>user1</uid_owner>
|
||||
<displayname_owner>user1</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1511532198</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>user1</uid_file_owner>
|
||||
<displayname_file_owner>user1</displayname_file_owner>
|
||||
<path>/ambient.txt</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>text/plain</mimetype>
|
||||
<storage_id>home::user1</storage_id>
|
||||
<storage>3</storage>
|
||||
<item_source>545</item_source>
|
||||
<file_source>545</file_source>
|
||||
<file_parent>20</file_parent>
|
||||
<file_target>/ambient.txt</file_target>
|
||||
<share_with>tech</share_with>
|
||||
<share_with_displayname>tech</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
$this->ocsmockclient->expects($this->once())->method('call')->with('create_share', $params)->will(
|
||||
$this->returnValue($expectedresponse));
|
||||
|
||||
$result = $this->linkmanager->create_share_user_sysaccount("/ambient.txt");
|
||||
$xml = simplexml_load_string($expectedresponse);
|
||||
$expected = array();
|
||||
$expected['statuscode'] = (int)$xml->meta->statuscode;
|
||||
$expected['shareid'] = (int)$xml->data->id;
|
||||
$expected['filetarget'] = (string)$xml->data[0]->file_target;
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
/**
|
||||
* Test the delete_share_function. In case the request fails, the function throws an exception, however this
|
||||
* can not be tested in phpUnit since it is javascript.
|
||||
*/
|
||||
public function test_delete_share_dataowner_sysaccount() {
|
||||
$shareid = 5;
|
||||
$deleteshareparams = [
|
||||
'share_id' => $shareid
|
||||
];
|
||||
$returnxml = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data/>
|
||||
</ocs>
|
||||
XML;
|
||||
$this->ocsmockclient->expects($this->once())->method('call')->with('delete_share', $deleteshareparams)->will(
|
||||
$this->returnValue($returnxml));
|
||||
$this->linkmanager->delete_share_dataowner_sysaccount($shareid, 'repository_nextcloud');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which test that create folder path does return the adequate results (path and success).
|
||||
* Additionally mock checks whether the right params are passed to the corresponding functions.
|
||||
*/
|
||||
public function test_create_folder_path_folders_are_not_created() {
|
||||
|
||||
$mocks = $this->set_up_mocks_for_create_folder_path(true, 'somename');
|
||||
$this->set_private_property($mocks['mockclient'], 'systemwebdavclient', $this->linkmanager);
|
||||
$result = $this->linkmanager->create_folder_path_access_controlled_links($mocks['mockcontext'], "mod_resource",
|
||||
'content', 0);
|
||||
$this->assertEquals('/somename (ctx )/mod_resource/content/0', $result);
|
||||
}
|
||||
/**
|
||||
* Function which test that create folder path does return the adequate results (path and success).
|
||||
* Additionally mock checks whether the right params are passed to the corresponding functions.
|
||||
*/
|
||||
public function test_create_folder_path_folders_are_created() {
|
||||
|
||||
// In Context is okay, number of context counts for number of iterations.
|
||||
$mocks = $this->set_up_mocks_for_create_folder_path(false, 'somename/withslash', true, 201);
|
||||
$this->set_private_property($mocks['mockclient'], 'systemwebdavclient', $this->linkmanager);
|
||||
$result = $this->linkmanager->create_folder_path_access_controlled_links($mocks['mockcontext'], "mod_resource",
|
||||
'content', 0);
|
||||
$this->assertEquals('/somenamewithslash (ctx )/mod_resource/content/0', $result);
|
||||
}
|
||||
/**
|
||||
* Test whether the create_folder_path methode throws exception.
|
||||
*/
|
||||
public function test_create_folder_path_folder_creation_fails() {
|
||||
|
||||
$mocks = $this->set_up_mocks_for_create_folder_path(false, 'somename', true, 400);
|
||||
$this->set_private_property($mocks['mockclient'], 'systemwebdavclient', $this->linkmanager);
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->linkmanager->create_folder_path_access_controlled_links($mocks['mockcontext'], "mod_resource",
|
||||
'content', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate mocks for testing create folder path.
|
||||
* @param bool $returnisdir Return value mocking the result of invoking is_dir
|
||||
* @param bool $returnestedcontext Name of the folder that is simulated to be checked/created
|
||||
* @param bool $callmkcol Also mock creation of the folder
|
||||
* @param int $returnmkcol Return value mocking the result of invoking mkcol
|
||||
* @return array ['mockcontext' context_module mock, 'mockclient' => webdav client mock]
|
||||
*/
|
||||
protected function set_up_mocks_for_create_folder_path($returnisdir, $returnestedcontext, $callmkcol = false,
|
||||
$returnmkcol = 201) {
|
||||
$mockcontext = $this->createMock(context_module::class);
|
||||
$mockclient = $this->getMockBuilder(\webdav_client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$parsedwebdavurl = parse_url($this->issuer->get_endpoint_url('webdav'));
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
// Empty ctx 'id' expected because using code will not be able to access $ctx->id.
|
||||
$cleanedcontextname = clean_param($returnestedcontext, PARAM_FILE);
|
||||
$dirstring = $webdavprefix . '/' . $cleanedcontextname . ' (ctx )';
|
||||
$mockclient->expects($this->atMost(4))->method('is_dir')->with($this->logicalOr(
|
||||
$dirstring, $dirstring . '/mod_resource', $dirstring . '/mod_resource/content',
|
||||
$dirstring . '/mod_resource/content/0'))->willReturn($returnisdir);
|
||||
if ($callmkcol == true) {
|
||||
$mockclient->expects($this->atMost(4))->method('mkcol')->willReturn($returnmkcol);
|
||||
}
|
||||
$mockcontext->method('get_parent_contexts')->willReturn(array('1' => $mockcontext));
|
||||
$mockcontext->method('get_context_name')->willReturn($returnestedcontext);
|
||||
|
||||
return array('mockcontext' => $mockcontext, 'mockclient' => $mockclient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the right methods from the webdavclient are called when the storage_folder is created.
|
||||
* 1. Directory already exist -> no further action needed.
|
||||
*/
|
||||
public function test_create_storage_folder_success() {
|
||||
$mockwebdavclient = $this->createMock(\webdav_client::class);
|
||||
$url = $this->issuer->get_endpoint_url('webdav');
|
||||
$parsedwebdavurl = parse_url($url);
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$mockwebdavclient->expects($this->once())->method('open')->willReturn(true);
|
||||
$mockwebdavclient->expects($this->once())->method('is_dir')->with($webdavprefix . 'myname')->willReturn(true);
|
||||
$mockwebdavclient->expects($this->once())->method('close');
|
||||
$this->linkmanager->create_storage_folder('myname', $mockwebdavclient);
|
||||
|
||||
}
|
||||
/**
|
||||
* Test whether the right methods from the webdavclient are called when the storage_folder is created.
|
||||
* 2. Directory does not exist. It is created with mkcol and returns a success.
|
||||
*
|
||||
*/
|
||||
public function test_create_storage_folder_success_mkcol() {
|
||||
$mockwebdavclient = $this->createMock(\webdav_client::class);
|
||||
$url = $this->issuer->get_endpoint_url('webdav');
|
||||
$parsedwebdavurl = parse_url($url);
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$mockwebdavclient->expects($this->once())->method('open')->willReturn(true);
|
||||
$mockwebdavclient->expects($this->once())->method('is_dir')->with($webdavprefix . 'myname')->willReturn(false);
|
||||
$mockwebdavclient->expects($this->once())->method('mkcol')->with($webdavprefix . 'myname')->willReturn(201);
|
||||
$mockwebdavclient->expects($this->once())->method('close');
|
||||
|
||||
$this->linkmanager->create_storage_folder('myname', $mockwebdavclient);
|
||||
}
|
||||
/**
|
||||
* Test whether the right methods from the webdavclient are called when the storage_folder is created.
|
||||
* 3. Request to create Folder fails.
|
||||
*/
|
||||
public function test_create_storage_folder_failure() {
|
||||
$mockwebdavclient = $this->createMock(\webdav_client::class);
|
||||
$url = $this->issuer->get_endpoint_url('webdav');
|
||||
$parsedwebdavurl = parse_url($url);
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$mockwebdavclient->expects($this->once())->method('open')->willReturn(true);
|
||||
$mockwebdavclient->expects($this->once())->method('is_dir')->with($webdavprefix . 'myname')->willReturn(false);
|
||||
$mockwebdavclient->expects($this->once())->method('mkcol')->with($webdavprefix . 'myname')->willReturn(400);
|
||||
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->linkmanager->create_storage_folder('myname', $mockwebdavclient);
|
||||
}
|
||||
/**
|
||||
* Test whether the webdav client gets the right params and whether function differentiates between move and copy.
|
||||
*/
|
||||
public function test_transfer_file_to_path_copyfile() {
|
||||
// Initialize params.
|
||||
$parsedwebdavurl = parse_url($this->issuer->get_endpoint_url('webdav'));
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$srcpath = 'sourcepath';
|
||||
$dstpath = "destinationpath/another/path";
|
||||
|
||||
// Mock the Webdavclient and set expected methods.
|
||||
$systemwebdavclientmock = $this->createMock(\webdav_client::class);
|
||||
$systemwebdavclientmock->expects($this->once())->method('open')->willReturn(true);
|
||||
$systemwebdavclientmock->expects($this->once())->method('copy_file')->with($webdavprefix . $srcpath,
|
||||
$webdavprefix . $dstpath . '/' . $srcpath, true)->willReturn(201);
|
||||
$this->set_private_property($systemwebdavclientmock, 'systemwebdavclient', $this->linkmanager);
|
||||
|
||||
// Call of function.
|
||||
$result = $this->linkmanager->transfer_file_to_path($srcpath, $dstpath, 'copy');
|
||||
|
||||
$this->assertEquals(201, $result);
|
||||
}
|
||||
/**
|
||||
* This function tests whether the function transfer_file_to_path() moves or copies a given file to a given path
|
||||
* It tests whether the webdav_client gets the right parameter and whether function distinguishes between move and copy.
|
||||
*
|
||||
*/
|
||||
public function test_transfer_file_to_path_copyfile_movefile() {
|
||||
// Initialize params.
|
||||
$parsedwebdavurl = parse_url($this->issuer->get_endpoint_url('webdav'));
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$srcpath = 'sourcepath';
|
||||
$dstpath = "destinationpath/another/path";
|
||||
|
||||
$systemwebdavclientmock = $this->createMock(\webdav_client::class);
|
||||
|
||||
$systemwebdavclientmock->expects($this->once())->method('open')->willReturn(true);
|
||||
$this->set_private_property($systemwebdavclientmock, 'systemwebdavclient', $this->linkmanager);
|
||||
$webdavclientmock = $this->createMock(\webdav_client::class);
|
||||
|
||||
$webdavclientmock->expects($this->once())->method('move')->with($webdavprefix . $srcpath,
|
||||
$webdavprefix . $dstpath . '/' . $srcpath, false)->willReturn(201);
|
||||
$result = $this->linkmanager->transfer_file_to_path($srcpath, $dstpath, 'move', $webdavclientmock);
|
||||
$this->assertEquals(201, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_shares_from path() function. This function extracts from an list of shares the share of a given user
|
||||
* (the username is a parameter in the function call) and returns the id. The test firstly test whether the right fileid
|
||||
* for user1 is extracted then for user2 and last but least whether an error is thrown if the user does not have a share.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function test_get_shares_from_path() {
|
||||
$params = [
|
||||
'path' => '/Kernsystem/Kursbereich Miscellaneous/Kurs Example Course/Datei zet/mod_resource/content/0/picture.png',
|
||||
'reshares' => true
|
||||
];
|
||||
$reference = new stdClass();
|
||||
$reference->link = "/Kernsystem/Kursbereich Miscellaneous/Kurs Example Course/Datei zet/mod_resource/content/0/picture.png";
|
||||
$reference->name = "f\u00fcrdennis.png";
|
||||
$reference->usesystem = true;
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<element>
|
||||
<id>292</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1515752494</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>some/path/of/some/file.pdf</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>image/png</mimetype>
|
||||
<storage_id>home::tech</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>1085</item_source>
|
||||
<file_source>1085</file_source>
|
||||
<file_parent>1084</file_parent>
|
||||
<file_target>/fehler (3).png</file_target>
|
||||
<share_with>user1</share_with>
|
||||
<share_with_displayname>user1</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
<element>
|
||||
<id>293</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1515752494</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>some/path/of/some/file.pdf</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>image/png</mimetype>
|
||||
<storage_id>home::tech</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>1085</item_source>
|
||||
<file_source>1085</file_source>
|
||||
<file_parent>1084</file_parent>
|
||||
<file_target>/fehler (3).png</file_target>
|
||||
<share_with>user2</share_with>
|
||||
<share_with_displayname>user2</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
$this->set_private_property($this->ocsmockclient, 'systemocsclient', $this->linkmanager);
|
||||
|
||||
$this->ocsmockclient->expects($this->exactly(3))->method('call')->with('get_shares', $params)->will(
|
||||
$this->returnValue($expectedresponse));
|
||||
$xmlobjuser1 = (int) $this->linkmanager->get_shares_from_path($reference->link, 'user2');
|
||||
$xmlobjuser2 = (int) $this->linkmanager->get_shares_from_path($reference->link, 'user1');
|
||||
|
||||
$this->assertEquals(293, $xmlobjuser1);
|
||||
$this->assertEquals(292, $xmlobjuser2);
|
||||
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
|
||||
$this->expectExceptionMessage('A request to Nextcloud has failed. The requested file could not be accessed. Please ' .
|
||||
'check whether you have chosen a valid file and you are authenticated with the right account.');
|
||||
$this->linkmanager->get_shares_from_path($reference->link, 'user3');
|
||||
|
||||
}
|
||||
/** Test whether the systemwebdav client is constructed correctly. Port is set to 443 in case of https, to 80 in
|
||||
* case of http and exception is thrown when endpoint does not exist.
|
||||
* @throws \repository_nextcloud\configuration_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function test_create_system_dav() {
|
||||
// Initialize mock and params.
|
||||
$fakeaccesstoken = new stdClass();
|
||||
$fakeaccesstoken->token = "fake access token";
|
||||
// Use `atLeastOnce` instead of `exactly(2)` because it is only called a second time on dev systems that allow http://.
|
||||
$this->oauthsystemmock->expects($this->atLeastOnce())->method('get_accesstoken')->willReturn($fakeaccesstoken);
|
||||
$parsedwebdavurl = parse_url($this->issuer->get_endpoint_url('webdav'));
|
||||
|
||||
// Call function and create own client.
|
||||
$dav = $this->linkmanager->create_system_dav();
|
||||
$mydav = new \webdav_client($parsedwebdavurl['host'], '', '', 'bearer', 'ssl://',
|
||||
"fake access token", $parsedwebdavurl['path']);
|
||||
$mydav->port = 443;
|
||||
$mydav->debug = false;
|
||||
$this->assertEquals($mydav, $dav);
|
||||
|
||||
// Deletes the old webdav endpoint and ...
|
||||
$this->delete_endpoints('webdav_endpoint');
|
||||
// Creates a new one which requires different ports.
|
||||
try {
|
||||
$endpoint = new stdClass();
|
||||
$endpoint->name = "webdav_endpoint";
|
||||
$endpoint->url = 'http://www.default.test/webdav/index.php';
|
||||
$endpoint->issuerid = $this->issuer->get('id');
|
||||
\core\oauth2\api::create_endpoint($endpoint);
|
||||
|
||||
// Call function and create own client.
|
||||
$dav = $this->linkmanager->create_system_dav();
|
||||
$mydav = new \webdav_client($parsedwebdavurl['host'], '', '', 'bearer', '',
|
||||
"fake access token");
|
||||
$mydav->port = 80;
|
||||
$mydav->debug = false;
|
||||
$this->assertEquals($mydav, $dav);
|
||||
} catch (core\invalid_persistent_exception $e) {
|
||||
// In some cases Moodle does not allow to create http connections. In those cases the exception
|
||||
// is catched here and the test are executed.
|
||||
$this->expectException(\core\invalid_persistent_exception::class);
|
||||
$this->linkmanager->create_system_dav();
|
||||
} finally {
|
||||
|
||||
// Delte endpoints and ...
|
||||
$this->delete_endpoints('webdav_endpoint');
|
||||
|
||||
// Do not insert new ones, therefore exception is thrown.
|
||||
$this->expectException(\repository_nextcloud\configuration_exception::class);
|
||||
$this->linkmanager->create_system_dav();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function get_share_information_from_shareid(). From a response with two element it is tested
|
||||
* whether the right file_target is extracted and lastly it is checked whether an error is thrown in case no suitable
|
||||
* element exists.
|
||||
* @throws \repository_nextcloud\request_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function test_get_share_information_from_shareid() {
|
||||
$params303 = [
|
||||
'share_id' => 303,
|
||||
];
|
||||
$params302 = [
|
||||
'share_id' => 302,
|
||||
];
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<element>
|
||||
<id>302</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1516096325</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>/some/target (2).png</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>image/png</mimetype>
|
||||
<storage_id>shared::/some/target.png</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>1125</item_source>
|
||||
<file_source>1125</file_source>
|
||||
<file_parent>20</file_parent>
|
||||
<file_target>/some/target.png</file_target>
|
||||
<share_with>user1</share_with>
|
||||
<share_with_displayname>user1</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
<element>
|
||||
<id>303</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1516096325</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>/some/target (2).pdf</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>image/png</mimetype>
|
||||
<storage_id>shared::/some/target.pdf</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>1125</item_source>
|
||||
<file_source>1125</file_source>
|
||||
<file_parent>20</file_parent>
|
||||
<file_target>/some/target.pdf</file_target>
|
||||
<share_with>user2</share_with>
|
||||
<share_with_displayname>user1</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
$this->set_private_property($this->ocsmockclient, 'systemocsclient', $this->linkmanager);
|
||||
|
||||
$this->ocsmockclient->expects($this->exactly(3))->method('call')->with('get_information_of_share',
|
||||
$this->logicalOr($params303, $params302))->will($this->returnValue($expectedresponse));
|
||||
|
||||
// Test function for two different users. Setting the id is just a dummy value since always $expectedresponse ...
|
||||
// ... is returned.
|
||||
$filetarget = $this->linkmanager->get_share_information_from_shareid(303, 'user2');
|
||||
$this->assertEquals('/some/target.pdf', $filetarget);
|
||||
|
||||
$filetarget = $this->linkmanager->get_share_information_from_shareid(302, 'user1');
|
||||
$this->assertEquals('/some/target.png', $filetarget);
|
||||
|
||||
// Expect exception in case no suitable elemtn exist in the response.
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->expectExceptionMessage('A request to Nextcloud has failed. The requested file could not be accessed. Please ' .
|
||||
'check whether you have chosen a valid file and you are authenticated with the right account.');
|
||||
$this->linkmanager->get_share_information_from_shareid(302, 'user3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which inserts a value into a non-public field of an object.
|
||||
*
|
||||
* @param mixed $value mock value that will be inserted.
|
||||
* @param string $propertyname name of the private property.
|
||||
* @param object $class Instance that is being modified.
|
||||
* @return ReflectionProperty the resulting reflection property.
|
||||
*/
|
||||
protected function set_private_property($value, $propertyname, $class) {
|
||||
$refclient = new ReflectionClass($class);
|
||||
$private = $refclient->getProperty($propertyname);
|
||||
$private->setAccessible(true);
|
||||
$private->setValue($class, $value);
|
||||
return $private;
|
||||
}
|
||||
/**
|
||||
* Helper method which gets a value from a non-public field of an object.
|
||||
*
|
||||
* @param string $propertyname name of the private property.
|
||||
* @param object $class Instance that is being modified.
|
||||
* @return mixed the resulting value.
|
||||
*/
|
||||
protected function get_private_property($propertyname, $class) {
|
||||
$refclient = new ReflectionClass($class);
|
||||
$private = $refclient->getProperty($propertyname);
|
||||
$private->setAccessible(true);
|
||||
$property = $private->getValue($private);
|
||||
return $property;
|
||||
}
|
||||
/**
|
||||
* Deletes all endpoint with the given name.
|
||||
* @param string $endpointname
|
||||
* @return array|null
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
protected function delete_endpoints($endpointname) {
|
||||
$endpoints = \core\oauth2\api::get_endpoints($this->issuer);
|
||||
$arrayofids = array();
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$name = $endpoint->get('name');
|
||||
if ($name === $endpointname) {
|
||||
$arrayofids[$endpoint->get('id')] = $endpoint->get('id');
|
||||
}
|
||||
}
|
||||
if (empty($arrayofids)) {
|
||||
return;
|
||||
}
|
||||
foreach ($arrayofids as $id) {
|
||||
core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Test support class for testing access_controlled_link_manager.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\oauth2\client;
|
||||
use repository_nextcloud\access_controlled_link_manager;
|
||||
use repository_nextcloud\ocs_client;
|
||||
|
||||
/**
|
||||
* Test support class for testing access_controlled_link_manager.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2018 Nina Herrmann (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_access_controlled_link_manager extends access_controlled_link_manager {
|
||||
|
||||
/**
|
||||
* Access_controlled_link_manager constructor.
|
||||
* @param ocs_client $ocsclient
|
||||
* @param client $systemoauthclient
|
||||
* @param ocs_client $systemocsclient
|
||||
* @param \core\oauth2\issuer $issuer
|
||||
* @param string $repositoryname
|
||||
* @param \webdav_client $systemdav
|
||||
*/
|
||||
public function __construct($ocsclient, $systemoauthclient, $systemocsclient, \core\oauth2\issuer $issuer, $repositoryname,
|
||||
$systemdav) {
|
||||
$this->ocsclient = $ocsclient;
|
||||
$this->systemoauthclient = $systemoauthclient;
|
||||
$this->systemocsclient = $systemocsclient;
|
||||
$this->repositoryname = $repositoryname;
|
||||
$this->issuer = $issuer;
|
||||
$this->systemwebdavclient = $systemdav;
|
||||
}
|
||||
}
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Data generator for repository plugin.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Data generator for repository plugin.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_nextcloud_generator extends testing_repository_generator {
|
||||
|
||||
/**
|
||||
* Creates an issuer and a user.
|
||||
* @return \core\oauth2\issuer
|
||||
*/
|
||||
public function test_create_issuer () {
|
||||
$issuerdata = new stdClass();
|
||||
$issuerdata->name = "Service";
|
||||
$issuerdata->clientid = "Clientid";
|
||||
$issuerdata->clientsecret = "Secret";
|
||||
$issuerdata->loginscopes = "openid profile email";
|
||||
$issuerdata->loginscopesoffline = "openid profile email";
|
||||
$issuerdata->baseurl = "";
|
||||
$issuerdata->image = "aswdf";
|
||||
|
||||
// Create the issuer.
|
||||
$issuer = \core\oauth2\api::create_issuer($issuerdata);
|
||||
return $issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the required endpoints.
|
||||
* @param int $issuerid
|
||||
* @return \core\oauth2\issuer
|
||||
*/
|
||||
public function test_create_endpoints ($issuerid) {
|
||||
$this->test_create_single_endpoint($issuerid, "ocs_endpoint");
|
||||
$this->test_create_single_endpoint($issuerid, "authorization_endpoint");
|
||||
$this->test_create_single_endpoint($issuerid, "webdav_endpoint", "https://www.default.test/webdav/index.php");
|
||||
$this->test_create_single_endpoint($issuerid, "token_endpoint");
|
||||
$this->test_create_single_endpoint($issuerid, "userinfo_endpoint");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single endpoint.
|
||||
*
|
||||
* @param int $issuerid
|
||||
* @param string $endpointtype
|
||||
* @param string $url
|
||||
* @return \core\oauth2\endpoint An instantiated endpoint
|
||||
*/
|
||||
public function test_create_single_endpoint($issuerid, $endpointtype, $url="https://www.default.test") {
|
||||
$endpoint = new stdClass();
|
||||
$endpoint->name = $endpointtype;
|
||||
$endpoint->url = $url;
|
||||
$endpoint->issuerid = $issuerid;
|
||||
$return = \core\oauth2\api::create_endpoint($endpoint);
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,948 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains tests for the repository_nextcloud class.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/repository/lib.php');
|
||||
require_once($CFG->libdir . '/webdavlib.php');
|
||||
|
||||
/**
|
||||
* Class repository_nextcloud_lib_testcase
|
||||
* @group repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_nextcloud_lib_testcase extends advanced_testcase {
|
||||
|
||||
/** @var null|\repository_nextcloud the repository_nextcloud object, which the tests are run on. */
|
||||
private $repo = null;
|
||||
|
||||
/** @var null|\core\oauth2\issuer which belongs to the repository_nextcloud object.*/
|
||||
private $issuer = null;
|
||||
|
||||
/**
|
||||
* SetUp to create an repository instance.
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Admin is neccessary to create api and issuer objects.
|
||||
$this->setAdminUser();
|
||||
|
||||
/** @var repository_nextcloud_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('repository_nextcloud');
|
||||
$this->issuer = $generator->test_create_issuer();
|
||||
|
||||
// Create Endpoints for issuer.
|
||||
$generator->test_create_endpoints($this->issuer->get('id'));
|
||||
|
||||
// Params for the config form.
|
||||
$reptype = $generator->create_type([
|
||||
'visible' => 1,
|
||||
'enableuserinstances' => 0,
|
||||
'enablecourseinstances' => 0,
|
||||
]);
|
||||
|
||||
$instance = $generator->create_instance([
|
||||
'issuerid' => $this->issuer->get('id'),
|
||||
'pluginname' => 'Nextcloud',
|
||||
'controlledlinkfoldername' => 'Moodlefiles',
|
||||
'supportedreturntypes' => 'both',
|
||||
'defaultreturntype' => FILE_INTERNAL,
|
||||
]);
|
||||
|
||||
// At last, create a repository_nextcloud object from the instance id.
|
||||
$this->repo = new repository_nextcloud($instance->id);
|
||||
$this->repo->options['typeid'] = $reptype->id;
|
||||
$this->repo->options['sortorder'] = 1;
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the is_visible method in case the repository is set to hidden in the database.
|
||||
*/
|
||||
public function test_is_visible_parent_false() {
|
||||
global $DB;
|
||||
$id = $this->repo->options['typeid'];
|
||||
|
||||
// Check, if the method returns false, when the repository is set to visible in the database
|
||||
// and the client configuration data is complete.
|
||||
$DB->update_record('repository', (object) array('id' => $id, 'visible' => 0));
|
||||
|
||||
$this->assertFalse($this->repo->is_visible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the repo is disabled.
|
||||
*/
|
||||
public function test_repo_creation() {
|
||||
$issuerid = $this->repo->get_option('issuerid');
|
||||
|
||||
// Config saves the right id.
|
||||
$this->assertEquals($this->issuer->get('id'), $issuerid);
|
||||
|
||||
// Function that is used in construct method returns the right id.
|
||||
$constructissuer = \core\oauth2\api::get_issuer($issuerid);
|
||||
$this->assertEquals($this->issuer->get('id'), $constructissuer->get('id'));
|
||||
|
||||
$this->assertEquals(true, $constructissuer->get('enabled'));
|
||||
$this->assertFalse($this->repo->disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of endpoints or null.
|
||||
* @param string $endpointname
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_endpoint_id($endpointname) {
|
||||
$endpoints = \core\oauth2\api::get_endpoints($this->issuer);
|
||||
$id = array();
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$name = $endpoint->get('name');
|
||||
if ($name === $endpointname) {
|
||||
$id[$endpoint->get('id')] = $endpoint->get('id');
|
||||
}
|
||||
}
|
||||
if (empty($id)) {
|
||||
return null;
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
/**
|
||||
* Test if repository is disabled when webdav_endpoint is deleted.
|
||||
*/
|
||||
public function test_issuer_webdav() {
|
||||
$idwebdav = $this->get_endpoint_id('webdav_endpoint');
|
||||
if (!empty($idwebdav)) {
|
||||
foreach ($idwebdav as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
$this->assertFalse(\repository_nextcloud\issuer_management::is_valid_issuer($this->issuer));
|
||||
}
|
||||
/**
|
||||
* Test if repository is disabled when ocs_endpoint is deleted.
|
||||
*/
|
||||
public function test_issuer_ocs() {
|
||||
$idocs = $this->get_endpoint_id('ocs_endpoint');
|
||||
if (!empty($idocs)) {
|
||||
foreach ($idocs as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
$this->assertFalse(\repository_nextcloud\issuer_management::is_valid_issuer($this->issuer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if repository is disabled when userinfo_endpoint is deleted.
|
||||
*/
|
||||
public function test_issuer_userinfo() {
|
||||
$idtoken = $this->get_endpoint_id('userinfo_endpoint');
|
||||
if (!empty($idtoken)) {
|
||||
foreach ($idtoken as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
$this->assertFalse(\repository_nextcloud\issuer_management::is_valid_issuer($this->issuer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if repository is disabled when token_endpoint is deleted.
|
||||
*/
|
||||
public function test_issuer_token() {
|
||||
$idtoken = $this->get_endpoint_id('token_endpoint');
|
||||
if (!empty($idtoken)) {
|
||||
foreach ($idtoken as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
$this->assertFalse(\repository_nextcloud\issuer_management::is_valid_issuer($this->issuer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if repository is disabled when auth_endpoint is deleted.
|
||||
*/
|
||||
public function test_issuer_authorization() {
|
||||
$idauth = $this->get_endpoint_id('authorization_endpoint');
|
||||
if (!empty($idauth)) {
|
||||
foreach ($idauth as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
$this->assertFalse(\repository_nextcloud\issuer_management::is_valid_issuer($this->issuer));
|
||||
}
|
||||
/**
|
||||
* Test if repository throws an error when endpoint does not exist.
|
||||
*/
|
||||
public function test_parse_endpoint_url_error() {
|
||||
$this->expectException(\repository_nextcloud\configuration_exception::class);
|
||||
\repository_nextcloud\issuer_management::parse_endpoint_url('notexisting', $this->issuer);
|
||||
}
|
||||
/**
|
||||
* Test get_listing method with an example directory. Tests error cases.
|
||||
*/
|
||||
public function test_get_listing_error() {
|
||||
$ret = $this->get_initialised_return_array();
|
||||
$this->setUser();
|
||||
// WebDAV socket is not opened.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(false));
|
||||
$private = $this->set_private_property($mock, 'dav');
|
||||
|
||||
$this->assertEquals($ret, $this->repo->get_listing('/'));
|
||||
|
||||
// Response is not an array.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(true));
|
||||
$mock->expects($this->once())->method('ls')->will($this->returnValue('notanarray'));
|
||||
$private->setValue($this->repo, $mock);
|
||||
|
||||
$this->assertEquals($ret, $this->repo->get_listing('/'));
|
||||
}
|
||||
/**
|
||||
* Test get_listing method with an example directory. Tests the root directory.
|
||||
*/
|
||||
public function test_get_listing_root() {
|
||||
$this->setUser();
|
||||
$ret = $this->get_initialised_return_array();
|
||||
|
||||
// This is the expected response from the ls method.
|
||||
$response = array(
|
||||
array(
|
||||
'href' => 'remote.php/webdav/',
|
||||
'lastmodified' => 'Thu, 08 Dec 2016 16:06:26 GMT',
|
||||
'resourcetype' => 'collection',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => ''
|
||||
),
|
||||
array(
|
||||
'href' => 'remote.php/webdav/Documents/',
|
||||
'lastmodified' => 'Thu, 08 Dec 2016 16:06:26 GMT',
|
||||
'resourcetype' => 'collection',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => ''
|
||||
),
|
||||
array(
|
||||
'href' => 'remote.php/webdav/welcome.txt',
|
||||
'lastmodified' => 'Thu, 08 Dec 2016 16:06:26 GMT',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => '163'
|
||||
)
|
||||
);
|
||||
|
||||
// The expected result from the get_listing method in the repository_nextcloud class.
|
||||
$ret['list'] = array(
|
||||
'DOCUMENTS/' => array(
|
||||
'title' => 'Documents',
|
||||
'thumbnail' => null,
|
||||
'children' => array(),
|
||||
'datemodified' => 1481213186,
|
||||
'path' => '/Documents/'
|
||||
),
|
||||
'WELCOME.TXT' => array(
|
||||
'title' => 'welcome.txt',
|
||||
'thumbnail' => null,
|
||||
'size' => '163',
|
||||
'datemodified' => 1481213186,
|
||||
'source' => '/welcome.txt'
|
||||
)
|
||||
);
|
||||
|
||||
// Valid response from the client.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(true));
|
||||
$mock->expects($this->once())->method('ls')->will($this->returnValue($response));
|
||||
$this->set_private_property($mock, 'dav');
|
||||
|
||||
$ls = $this->repo->get_listing('/');
|
||||
|
||||
// Those attributes can not be tested properly.
|
||||
$ls['list']['DOCUMENTS/']['thumbnail'] = null;
|
||||
$ls['list']['WELCOME.TXT']['thumbnail'] = null;
|
||||
|
||||
$this->assertEquals($ret, $ls);
|
||||
}
|
||||
/**
|
||||
* Test get_listing method with an example directory. Tests a different directory than the root
|
||||
* directory.
|
||||
*/
|
||||
public function test_get_listing_directory() {
|
||||
$ret = $this->get_initialised_return_array();
|
||||
$this->setUser();
|
||||
|
||||
// An additional directory path has to be added to the 'path' field within the returned array.
|
||||
$ret['path'][1] = array(
|
||||
'name' => 'dir',
|
||||
'path' => '/dir/'
|
||||
);
|
||||
|
||||
// This is the expected response from the get_listing method in the Nextcloud client.
|
||||
$response = array(
|
||||
array(
|
||||
'href' => 'remote.php/webdav/dir/',
|
||||
'lastmodified' => 'Thu, 08 Dec 2016 16:06:26 GMT',
|
||||
'resourcetype' => 'collection',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => ''
|
||||
),
|
||||
array(
|
||||
'href' => 'remote.php/webdav/dir/Documents/',
|
||||
'lastmodified' => null,
|
||||
'resourcetype' => 'collection',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => ''
|
||||
),
|
||||
array(
|
||||
'href' => 'remote.php/webdav/dir/welcome.txt',
|
||||
'lastmodified' => 'Thu, 08 Dec 2016 16:06:26 GMT',
|
||||
'status' => 'HTTP/1.1 200 OK',
|
||||
'getcontentlength' => '163'
|
||||
)
|
||||
);
|
||||
|
||||
// The expected result from the get_listing method in the repository_nextcloud class.
|
||||
$ret['list'] = array(
|
||||
'DOCUMENTS/' => array(
|
||||
'title' => 'Documents',
|
||||
'thumbnail' => null,
|
||||
'children' => array(),
|
||||
'datemodified' => null,
|
||||
'path' => '/dir/Documents/'
|
||||
),
|
||||
'WELCOME.TXT' => array(
|
||||
'title' => 'welcome.txt',
|
||||
'thumbnail' => null,
|
||||
'size' => '163',
|
||||
'datemodified' => 1481213186,
|
||||
'source' => '/dir/welcome.txt'
|
||||
)
|
||||
);
|
||||
|
||||
// Valid response from the client.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(true));
|
||||
$mock->expects($this->once())->method('ls')->will($this->returnValue($response));
|
||||
$this->set_private_property($mock, 'dav');
|
||||
|
||||
$ls = $this->repo->get_listing('/dir/');
|
||||
|
||||
// Can not be tested properly.
|
||||
$ls['list']['DOCUMENTS/']['thumbnail'] = null;
|
||||
$ls['list']['WELCOME.TXT']['thumbnail'] = null;
|
||||
|
||||
$this->assertEquals($ret, $ls);
|
||||
}
|
||||
/**
|
||||
* Test the get_link method.
|
||||
*/
|
||||
public function test_get_link_success() {
|
||||
$mock = $this->getMockBuilder(\repository_nextcloud\ocs_client::class)->disableOriginalConstructor()->disableOriginalClone(
|
||||
)->getMock();
|
||||
$file = '/datei';
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<id>2</id>
|
||||
<share_type>3</share_type>
|
||||
<uid_owner>admin</uid_owner>
|
||||
<displayname_owner>admin</displayname_owner>
|
||||
<permissions>1</permissions>
|
||||
<stime>1502883721</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token>QXbqrJj8DcMaXen</token>
|
||||
<uid_file_owner>admin</uid_file_owner>
|
||||
<displayname_file_owner>admin</displayname_file_owner>
|
||||
<path>/somefile</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>application/pdf</mimetype>
|
||||
<storage_id>home::admin</storage_id>
|
||||
<storage>1</storage>
|
||||
<item_source>6</item_source>
|
||||
<file_source>6</file_source>
|
||||
<file_parent>4</file_parent>
|
||||
<file_target>/somefile</file_target>
|
||||
<share_with/>
|
||||
<share_with_displayname/>
|
||||
<name/>
|
||||
<url>https://www.default.test/somefile</url>
|
||||
<mail_send>0</mail_send>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
// Expected Parameters.
|
||||
$ocsquery = [
|
||||
'path' => $file,
|
||||
'shareType' => \repository_nextcloud\ocs_client::SHARE_TYPE_PUBLIC,
|
||||
'publicUpload' => false,
|
||||
'permissions' => \repository_nextcloud\ocs_client::SHARE_PERMISSION_READ
|
||||
];
|
||||
|
||||
// With test whether mock is called with right parameters.
|
||||
$mock->expects($this->once())->method('call')->with('create_share', $ocsquery)->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
|
||||
// Method does extract the link from the xml format.
|
||||
$this->assertEquals('https://www.default.test/somefile/download', $this->repo->get_link($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* get_link can get OCS failure responses. Test that this is handled appropriately.
|
||||
*/
|
||||
public function test_get_link_failure() {
|
||||
$mock = $this->getMockBuilder(\repository_nextcloud\ocs_client::class)->disableOriginalConstructor()->disableOriginalClone(
|
||||
)->getMock();
|
||||
$file = '/datei';
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>failure</status>
|
||||
<statuscode>404</statuscode>
|
||||
<message>Msg</message>
|
||||
</meta>
|
||||
<data/>
|
||||
</ocs>
|
||||
XML;
|
||||
// Expected Parameters.
|
||||
$ocsquery = [
|
||||
'path' => $file,
|
||||
'shareType' => \repository_nextcloud\ocs_client::SHARE_TYPE_PUBLIC,
|
||||
'publicUpload' => false,
|
||||
'permissions' => \repository_nextcloud\ocs_client::SHARE_PERMISSION_READ
|
||||
];
|
||||
|
||||
// With test whether mock is called with right parameters.
|
||||
$mock->expects($this->once())->method('call')->with('create_share', $ocsquery)->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
|
||||
// Suppress (expected) XML parse error... Nextcloud sometimes returns JSON on extremely bad errors.
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// Method get_link correctly raises an exception that contains error code and message.
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->expectExceptionMessage(get_string('request_exception', 'repository_nextcloud', array('instance' => 'Nextcloud',
|
||||
'errormessage' => sprintf('(%s) %s', '404', 'Msg'))));
|
||||
$this->repo->get_link($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_link can get OCS responses that are not actually XML. Test that this is handled appropriately.
|
||||
*/
|
||||
public function test_get_link_problem() {
|
||||
$mock = $this->getMockBuilder(\repository_nextcloud\ocs_client::class)->disableOriginalConstructor()->disableOriginalClone(
|
||||
)->getMock();
|
||||
$file = '/datei';
|
||||
$expectedresponse = <<<JSON
|
||||
{"message":"CSRF check failed"}
|
||||
JSON;
|
||||
// Expected Parameters.
|
||||
$ocsquery = [
|
||||
'path' => $file,
|
||||
'shareType' => \repository_nextcloud\ocs_client::SHARE_TYPE_PUBLIC,
|
||||
'publicUpload' => false,
|
||||
'permissions' => \repository_nextcloud\ocs_client::SHARE_PERMISSION_READ
|
||||
];
|
||||
|
||||
// With test whether mock is called with right parameters.
|
||||
$mock->expects($this->once())->method('call')->with('create_share', $ocsquery)->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
|
||||
// Suppress (expected) XML parse error... Nextcloud sometimes returns JSON on extremely bad errors.
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// Method get_link correctly raises an exception.
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->repo->get_link($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_file reference, merely returns the input if no optional_param is set.
|
||||
*/
|
||||
public function test_get_file_reference_withoutoptionalparam() {
|
||||
$this->assertEquals('/somefile', $this->repo->get_file_reference('/somefile'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_file reference in case the optional param is set. Therefore has to simulate the get_link method.
|
||||
*/
|
||||
public function test_get_file_reference_withoptionalparam() {
|
||||
$_GET['usefilereference'] = true;
|
||||
$filename = '/somefile';
|
||||
// Calls for get link(). Therefore, mocks for get_link are build.
|
||||
$mock = $this->getMockBuilder(\repository_nextcloud\ocs_client::class)->disableOriginalConstructor()->disableOriginalClone(
|
||||
)->getMock();
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<id>2</id>
|
||||
<share_type>3</share_type>
|
||||
<uid_owner>admin</uid_owner>
|
||||
<displayname_owner>admin</displayname_owner>
|
||||
<permissions>1</permissions>
|
||||
<stime>1502883721</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token>QXbqrJj8DcMaXen</token>
|
||||
<uid_file_owner>admin</uid_file_owner>
|
||||
<displayname_file_owner>admin</displayname_file_owner>
|
||||
<path>/somefile</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>application/pdf</mimetype>
|
||||
<storage_id>home::admin</storage_id>
|
||||
<storage>1</storage>
|
||||
<item_source>6</item_source>
|
||||
<file_source>6</file_source>
|
||||
<file_parent>4</file_parent>
|
||||
<file_target>/somefile</file_target>
|
||||
<share_with/>
|
||||
<share_with_displayname/>
|
||||
<name/>
|
||||
<url>https://www.default.test/somefile</url>
|
||||
<mail_send>0</mail_send>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
// Expected Parameters.
|
||||
$ocsquery = ['path' => $filename,
|
||||
'shareType' => \repository_nextcloud\ocs_client::SHARE_TYPE_PUBLIC,
|
||||
'publicUpload' => false,
|
||||
'permissions' => \repository_nextcloud\ocs_client::SHARE_PERMISSION_READ,
|
||||
];
|
||||
|
||||
// With test whether mock is called with right parameters.
|
||||
$mock->expects($this->once())->method('call')->with('create_share', $ocsquery)->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
|
||||
$expected = new \stdClass();
|
||||
$expected->type = 'FILE_REFERENCE';
|
||||
$expected->link = 'https://www.default.test' . $filename . '/download';
|
||||
// Method redirects to get_link() and return the suitable value.
|
||||
$this->assertEquals(json_encode($expected), $this->repo->get_file_reference($filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logout.
|
||||
*/
|
||||
public function test_logout() {
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
|
||||
$mock->expects($this->exactly(2))->method('log_out');
|
||||
$this->set_private_property($mock, 'client');
|
||||
$this->repo->options['ajax'] = false;
|
||||
$this->expectOutputString('<a target="_blank" rel="noopener noreferrer">Log in to your account</a>' .
|
||||
'<a target="_blank" rel="noopener noreferrer">Log in to your account</a>');
|
||||
|
||||
$this->assertEquals($this->repo->print_login(), $this->repo->logout());
|
||||
|
||||
$mock->expects($this->exactly(2))->method('get_login_url')->will($this->returnValue(new moodle_url('url')));
|
||||
|
||||
$this->repo->options['ajax'] = true;
|
||||
$this->assertEquals($this->repo->print_login(), $this->repo->logout());
|
||||
|
||||
}
|
||||
/**
|
||||
* Test for the get_file method from the repository_nextcloud class.
|
||||
*/
|
||||
public function test_get_file() {
|
||||
// WebDAV socket is not open.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(false));
|
||||
$private = $this->set_private_property($mock, 'dav');
|
||||
|
||||
$this->assertFalse($this->repo->get_file('path'));
|
||||
|
||||
// WebDAV socket is open and the request successful.
|
||||
$mock = $this->createMock(\webdav_client::class);
|
||||
$mock->expects($this->once())->method('open')->will($this->returnValue(true));
|
||||
$mock->expects($this->once())->method('get_file')->will($this->returnValue(true));
|
||||
$private->setValue($this->repo, $mock);
|
||||
|
||||
$result = $this->repo->get_file('path', 'file');
|
||||
|
||||
$this->assertNotNull($result['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test callback.
|
||||
*/
|
||||
public function test_callback() {
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
// Should call check_login exactly once.
|
||||
$mock->expects($this->once())->method('log_out');
|
||||
$mock->expects($this->once())->method('is_logged_in');
|
||||
|
||||
$this->set_private_property($mock, 'client');
|
||||
|
||||
$this->repo->callback();
|
||||
}
|
||||
/**
|
||||
* Test check_login.
|
||||
*/
|
||||
public function test_check_login() {
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
$mock->expects($this->once())->method('is_logged_in')->will($this->returnValue(true));
|
||||
$this->set_private_property($mock, 'client');
|
||||
|
||||
$this->assertTrue($this->repo->check_login());
|
||||
}
|
||||
/**
|
||||
* Test print_login.
|
||||
*/
|
||||
public function test_print_login() {
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
$mock->expects($this->exactly(2))->method('get_login_url')->will($this->returnValue(new moodle_url('url')));
|
||||
$this->set_private_property($mock, 'client');
|
||||
|
||||
// Test with ajax activated.
|
||||
$this->repo->options['ajax'] = true;
|
||||
|
||||
$url = new moodle_url('url');
|
||||
$ret = array();
|
||||
$btn = new \stdClass();
|
||||
$btn->type = 'popup';
|
||||
$btn->url = $url->out(false);
|
||||
$ret['login'] = array($btn);
|
||||
|
||||
$this->assertEquals($ret, $this->repo->print_login());
|
||||
|
||||
// Test without ajax.
|
||||
$this->repo->options['ajax'] = false;
|
||||
|
||||
$output = html_writer::link($url, get_string('login', 'repository'),
|
||||
array('target' => '_blank', 'rel' => 'noopener noreferrer'));
|
||||
$this->expectOutputString($output);
|
||||
$this->repo->print_login();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the initiate_webdavclient function.
|
||||
*/
|
||||
public function test_initiate_webdavclient() {
|
||||
global $CFG;
|
||||
|
||||
$idwebdav = $this->get_endpoint_id('webdav_endpoint');
|
||||
if (!empty($idwebdav)) {
|
||||
foreach ($idwebdav as $id) {
|
||||
\core\oauth2\api::delete_endpoint($id);
|
||||
}
|
||||
}
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('repository_nextcloud');
|
||||
$generator->test_create_single_endpoint($this->issuer->get('id'), "webdav_endpoint",
|
||||
"https://www.default.test:8080/webdav/index.php");
|
||||
|
||||
$fakeaccesstoken = new stdClass();
|
||||
$fakeaccesstoken->token = "fake access token";
|
||||
$oauthmock = $this->createMock(\core\oauth2\client::class);
|
||||
$oauthmock->expects($this->once())->method('get_accesstoken')->will($this->returnValue($fakeaccesstoken));
|
||||
$this->set_private_property($oauthmock, 'client');
|
||||
|
||||
$dav = phpunit_util::call_internal_method($this->repo, "initiate_webdavclient", [], 'repository_nextcloud');
|
||||
|
||||
// Verify that port is set correctly (private property).
|
||||
$refclient = new ReflectionClass($dav);
|
||||
|
||||
$property = $refclient->getProperty('_port');
|
||||
$property->setAccessible(true);
|
||||
|
||||
$port = $property->getValue($dav);
|
||||
|
||||
$this->assertEquals('8080', $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test supported_returntypes.
|
||||
* FILE_INTERNAL when no system account is connected.
|
||||
* FILE_INTERNAL | FILE_CONTROLLED_LINK | FILE_EXTERNAL | FILE_REFERENCE when a system account is connected.
|
||||
*/
|
||||
public function test_supported_returntypes() {
|
||||
global $DB;
|
||||
$this->assertEquals(FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE, $this->repo->supported_returntypes());
|
||||
$dataobject = new stdClass();
|
||||
$dataobject->timecreated = time();
|
||||
$dataobject->timemodified = time();
|
||||
$dataobject->usermodified = 2;
|
||||
$dataobject->issuerid = $this->issuer->get('id');
|
||||
$dataobject->refreshtoken = 'sometokenthatwillnotbeused';
|
||||
$dataobject->grantedscopes = 'openid profile email';
|
||||
$dataobject->email = '[email protected]';
|
||||
$dataobject->username = 'someusername';
|
||||
|
||||
$DB->insert_record('oauth2_system_account', $dataobject);
|
||||
// When a system account is registered the file_type FILE_CONTROLLED_LINK is supported.
|
||||
$this->assertEquals(FILE_INTERNAL | FILE_EXTERNAL | FILE_CONTROLLED_LINK | FILE_REFERENCE,
|
||||
$this->repo->supported_returntypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* The reference_file_selected() methode is called every time a FILE_CONTROLLED_LINK is chosen for upload.
|
||||
* Since the function is very long the private function are tested separately, and merely the abortion of the
|
||||
* function are tested.
|
||||
*
|
||||
*/
|
||||
public function test_reference_file_selected_error() {
|
||||
$this->repo->disabled = true;
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->disabled = false;
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('Cannot connect as system user');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
$mock->expects($this->once())->method('get_system_oauth_client')->with($this->issuer)->willReturn(true);
|
||||
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('Cannot connect as current user');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->expects($this->once())->method('get_user_oauth_client')->willReturn(true);
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('cannotdownload');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->expects($this->once())->method('get_user_oauth_client')->willReturn(true);
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('cannotdownload');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->expects($this->once())->method('get_user_oauth_client')->willReturn(true);
|
||||
$this->repo->expects($this->once())->method('copy_file_to_path')->willReturn(array('statuscode' =>
|
||||
array('success' => 400)));
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('Could not copy file');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->expects($this->once())->method('get_user_oauth_client')->willReturn(true);
|
||||
$this->repo->expects($this->once())->method('copy_file_to_path')->willReturn(array('statuscode' =>
|
||||
array('success' => 201)));
|
||||
$this->repo->expects($this->once())->method('delete_share_dataowner_sysaccount')->willReturn(
|
||||
array('statuscode' => array('success' => 400)));
|
||||
$this->expectException(\repository_exception::class);
|
||||
$this->expectExceptionMessage('Share is still present');
|
||||
$this->repo->reference_file_selected('', context_system::instance(), '', '', '');
|
||||
|
||||
$this->repo->expects($this->once())->method('get_user_oauth_client')->willReturn(true);
|
||||
$this->repo->expects($this->once())->method('copy_file_to_path')->willReturn(array('statuscode' =>
|
||||
array('success' => 201)));
|
||||
$this->repo->expects($this->once())->method('delete_share_dataowner_sysaccount')->willReturn(
|
||||
array('statuscode' => array('success' => 100)));
|
||||
$filereturn = array();
|
||||
$filereturn->link = 'some/fullpath' . 'some/target/path';
|
||||
$filereturn->name = 'mysource';
|
||||
$filereturn->usesystem = true;
|
||||
$filereturn = json_encode($filereturn);
|
||||
$return = $this->repo->reference_file_selected('mysource', context_system::instance(), '', '', '');
|
||||
$this->assertEquals($filereturn, $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the send_file function for access controlled links.
|
||||
*/
|
||||
public function test_send_file_errors() {
|
||||
$fs = get_file_storage();
|
||||
$storedfile = $fs->create_file_from_reference([
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => 'testfile.txt',
|
||||
], $this->repo->id, json_encode([
|
||||
'type' => 'FILE_CONTROLLED_LINK',
|
||||
'link' => 'https://test.local/fakelink/',
|
||||
'usesystem' => true,
|
||||
]));
|
||||
$this->set_private_property('', 'client');
|
||||
$this->expectException(repository_nextcloud\request_exception::class);
|
||||
$this->expectExceptionMessage(get_string('contactadminwith', 'repository_nextcloud',
|
||||
'The OAuth client could not be connected.'));
|
||||
|
||||
$this->repo->send_file($storedfile, '', '', '');
|
||||
|
||||
// Testing whether the mock up appears is topic to behat.
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
$mock->expects($this->once())->method('is_logged_in')->willReturn(true);
|
||||
$this->repo->send_file($storedfile, '', '', '');
|
||||
|
||||
// Checks that setting for foldername are used.
|
||||
$mock->expects($this->once())->method('is_dir')->with('Moodlefiles')->willReturn(false);
|
||||
// In case of false as return value mkcol is called to create the folder.
|
||||
$parsedwebdavurl = parse_url($this->issuer->get_endpoint_url('webdav'));
|
||||
$webdavprefix = $parsedwebdavurl['path'];
|
||||
$mock->expects($this->once())->method('mkcol')->with(
|
||||
$webdavprefix . 'Moodlefiles')->willReturn(400);
|
||||
$this->expectException(\repository_nextcloud\request_exception::class);
|
||||
$this->expectExceptionMessage(get_string('requestnotexecuted', 'repository_nextcloud'));
|
||||
$this->repo->send_file($storedfile, '', '', '');
|
||||
|
||||
$expectedresponse = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<ocs>
|
||||
<meta>
|
||||
<status>ok</status>
|
||||
<statuscode>100</statuscode>
|
||||
<message/>
|
||||
</meta>
|
||||
<data>
|
||||
<element>
|
||||
<id>6</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1511877999</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>/System/Category Miscellaneous/Course Example Course/File morefiles/mod_resource/content/0/merge.txt</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>text/plain</mimetype>
|
||||
<storage_id>home::tech</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>824</item_source>
|
||||
<file_source>824</file_source>
|
||||
<file_parent>823</file_parent>
|
||||
<file_target>/merge (3).txt</file_target>
|
||||
<share_with>user2</share_with>
|
||||
<share_with_displayname>user1</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
<element>
|
||||
<id>5</id>
|
||||
<share_type>0</share_type>
|
||||
<uid_owner>tech</uid_owner>
|
||||
<displayname_owner>tech</displayname_owner>
|
||||
<permissions>19</permissions>
|
||||
<stime>1511877999</stime>
|
||||
<parent/>
|
||||
<expiration/>
|
||||
<token/>
|
||||
<uid_file_owner>tech</uid_file_owner>
|
||||
<displayname_file_owner>tech</displayname_file_owner>
|
||||
<path>/System/Category Miscellaneous/Course Example Course/File morefiles/mod_resource/content/0/merge.txt</path>
|
||||
<item_type>file</item_type>
|
||||
<mimetype>text/plain</mimetype>
|
||||
<storage_id>home::tech</storage_id>
|
||||
<storage>4</storage>
|
||||
<item_source>824</item_source>
|
||||
<file_source>824</file_source>
|
||||
<file_parent>823</file_parent>
|
||||
<file_target>/merged (3).txt</file_target>
|
||||
<share_with>user1</share_with>
|
||||
<share_with_displayname>user1</share_with_displayname>
|
||||
<mail_send>0</mail_send>
|
||||
</element>
|
||||
</data>
|
||||
</ocs>
|
||||
XML;
|
||||
|
||||
// Checks that setting for foldername are used.
|
||||
$mock->expects($this->once())->method('is_dir')->with('Moodlefiles')->willReturn(true);
|
||||
// In case of true as return value mkcol is not called to create the folder.
|
||||
$shareid = 5;
|
||||
|
||||
$mockocsclient = $this->getMockBuilder(
|
||||
\repository_nextcloud\ocs_client::class)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$mockocsclient->expects($this->exactly(2))->method('call')->with('get_information_of_share',
|
||||
array('share_id' => $shareid))->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
$this->repo->expects($this->once())->method('move_file_to_folder')->with('/merged (3).txt', 'Moodlefiles',
|
||||
$mock)->willReturn(array('success' => 201));
|
||||
|
||||
$this->repo->send_file('', '', '', '');
|
||||
|
||||
// Create test for statuscode 403.
|
||||
|
||||
// Checks that setting for foldername are used.
|
||||
$mock->expects($this->once())->method('is_dir')->with('Moodlefiles')->willReturn(true);
|
||||
// In case of true as return value mkcol is not called to create the folder.
|
||||
$shareid = 5;
|
||||
$mockocsclient = $this->getMockBuilder(\repository_nextcloud\ocs_client::class
|
||||
)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
$mockocsclient->expects($this->exactly(1))->method('call')->with('get_shares',
|
||||
array('path' => '/merged (3).txt', 'reshares' => true))->will($this->returnValue($expectedresponse));
|
||||
$mockocsclient->expects($this->exactly(1))->method('call')->with('get_information_of_share',
|
||||
array('share_id' => $shareid))->will($this->returnValue($expectedresponse));
|
||||
$this->set_private_property($mock, 'ocsclient');
|
||||
$this->repo->expects($this->once())->method('move_file_to_folder')->with('/merged (3).txt', 'Moodlefiles',
|
||||
$mock)->willReturn(array('success' => 201));
|
||||
$this->repo->send_file('', '', '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method, which inserts a given mock value into the repository_nextcloud object.
|
||||
*
|
||||
* @param mixed $value mock value that will be inserted.
|
||||
* @param string $propertyname name of the private property.
|
||||
* @return ReflectionProperty the resulting reflection property.
|
||||
*/
|
||||
protected function set_private_property($value, $propertyname) {
|
||||
$refclient = new ReflectionClass($this->repo);
|
||||
$private = $refclient->getProperty($propertyname);
|
||||
$private->setAccessible(true);
|
||||
$private->setValue($this->repo, $value);
|
||||
|
||||
return $private;
|
||||
}
|
||||
/**
|
||||
* Helper method to set required return parameters for get_listing.
|
||||
*
|
||||
* @return array array, which contains the parameters.
|
||||
*/
|
||||
protected function get_initialised_return_array() {
|
||||
$ret = array();
|
||||
$ret['dynload'] = true;
|
||||
$ret['nosearch'] = true;
|
||||
$ret['nologin'] = false;
|
||||
$ret['path'] = [
|
||||
[
|
||||
'name' => $this->repo->get_meta()->name,
|
||||
'path' => '',
|
||||
]
|
||||
];
|
||||
$ret['manage'] = '';
|
||||
$ret['defaultreturntype'] = FILE_INTERNAL;
|
||||
$ret['list'] = array();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains tests for the repository_nextcloud class.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class repository_nextcloud_ocs_testcase
|
||||
* @group repository_nextcloud
|
||||
* @copyright 2017 Jan Dageförde (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class repository_nextcloud_ocs_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var \core\oauth2\issuer
|
||||
*/
|
||||
private $issuer;
|
||||
|
||||
/**
|
||||
* SetUp to create issuer and endpoints for OCS testing.
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Admin is neccessary to create issuer object.
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('repository_nextcloud');
|
||||
$this->issuer = $generator->test_create_issuer();
|
||||
$generator->test_create_endpoints($this->issuer->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether required REST API functions are declared.
|
||||
*/
|
||||
public function test_api_functions() {
|
||||
$mock = $this->createMock(\core\oauth2\client::class);
|
||||
$mock->expects($this->once())->method('get_issuer')->willReturn($this->issuer);
|
||||
|
||||
$client = new \repository_nextcloud\ocs_client($mock);
|
||||
$functions = $client->get_api_functions();
|
||||
|
||||
// Assert that relevant (and used) functions are actually present.
|
||||
$this->assertArrayHasKey('create_share', $functions);
|
||||
}
|
||||
}
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Nextcloud repository version details.
|
||||
*
|
||||
* @package repository_nextcloud
|
||||
* @copyright 2017 Project seminar (Learnweb, University of Münster)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018091300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018090700; // Requires this Moodle version.
|
||||
$plugin->component = 'repository_nextcloud'; // Full name of the plugin (used for diagnostics).
|
||||
Reference in New Issue
Block a user