first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,477 @@
<?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;
use repository_exception;
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 == 204 || $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',
get_string('pathnotcreated', 'repository_nextcloud', $fullpath));
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
];
}
/**
* Download a file from the system account for the purpose of offline usage.
* @param string $srcpath Name of a file owned by the system account
* @param string $targetpath Temporary filename in Moodle
* @throws repository_exception The download was unsuccessful, maybe the file does not exist.
*/
public function download_for_offline_usage(string $srcpath, string $targetpath): void {
$this->systemwebdavclient->open();
$webdavendpoint = issuer_management::parse_endpoint_url('webdav', $this->issuer);
$srcpath = ltrim($srcpath, '/');
$sourcepath = $webdavendpoint['path'] . $srcpath;
// Write file into temp location.
if (!$this->systemwebdavclient->get_file($sourcepath, $targetpath)) {
$this->systemwebdavclient->close();
throw new repository_exception('cannotdownload', 'repository');
}
$this->systemwebdavclient->close();
}
}
@@ -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(get_string('endpointnotdefined', 'repository_nextcloud', $endpointname));
}
return parse_url($url);
}
}
+179
View File
@@ -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';
}
}
@@ -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);
}
}
+35
View File
@@ -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
)
)
);
@@ -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/>.
/**
* 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['pluginname_help'] = 'Nextcloud repository';
// Settings.
$string['issuervalidation_without'] = 'You have not yet selected a Nextcloud server as the OAuth 2 issuer.';
$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, go to Site administration / Server / OAuth 2 services.';
$string['foldername'] = 'Name of folder created in Nextcloud users\' private space that holds all access-controlled links.';
$string['foldername_help'] = 'To ensure that users find files shared with them, shares are saved into a specific folder.
This setting determines the name of the folder. It is recommended to choose 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 repeatedly, please contact the site administrator.';
$string['notauthorized'] = 'You are not authorised to execute this request. Please ensure you are authenticated with the correct account.';
$string['contactadminwith'] = 'The requested action could not be executed. If this happens repeatedly, please contact the site 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.';
$string['invalidresponse'] = 'Invalid server response.';
$string['noclientconnection'] = 'The OAuth clients could not be connected.';
$string['pathnotcreated'] = 'Folder path {$a} could not be created in the system account.';
$string['endpointnotdefined'] = 'Endpoint {$a} not defined.';
// Warnings.
$string['externalpubliclinkwarning'] = 'Warning: This file will become public.';
+995
View File
@@ -0,0 +1,995 @@
<?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;
/**
* Curl instance that can be used to fetch file from nextcloud instance.
* @var curl
*/
private $curl;
/**
* 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());
$this->curl = new curl();
}
/**
* 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) {
// Create a read only public link, remember no update possible in this file/folder.
$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);
if ($xml === false ) {
throw new \repository_nextcloud\request_exception(array('instance' => $this->get_name(),
'errormessage' => get_string('invalidresponse', 'repository_nextcloud')));
}
if ((string)$xml->meta->status !== 'ok') {
throw new \repository_nextcloud\request_exception(array('instance' => $this->get_name(), '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 does not do any translation of the file source.
*
* @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 or json encoded string for public link reference
*/
public function get_file_reference($source) {
$usefilereference = optional_param('usefilereference', false, PARAM_BOOL);
if ($usefilereference) {
return json_encode([
'type' => 'FILE_REFERENCE',
'link' => $this->get_link($source),
]);
}
// 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 additional options affecting the file serving
* @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 the file is a reference which means its a public link in nextcloud.
if ($reference->type === 'FILE_REFERENCE') {
// This file points to the public link just fetch the latest one from nextcloud repo.
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',
get_string('noclientconnection', 'repository_nextcloud'));
throw new \repository_nextcloud\request_exception(array('instance' => $repositoryname, 'errormessage' => $details));
}
// Download for offline usage. This is strictly read-only, so the file need not be shared.
if (!empty($options['offline'])) {
// Download from system account and provide the file to the user.
$linkmanager = new \repository_nextcloud\access_controlled_link_manager($this->ocsclient,
$this->get_system_oauth_client(), $this->get_system_ocs_client(), $this->issuer, $repositoryname);
// Create temp path, then download into it.
$filename = basename($reference->link);
$tmppath = make_request_directory() . '/' . $filename;
$linkmanager->download_for_offline_usage($reference->link, $tmppath);
// Output the obtained file to the user and remove it from disk.
send_temp_file($tmppath, $filename);
// That's all.
return;
}
if (!$this->client->is_logged_in()) {
$this->print_login_popup(['style' => 'margin-top: 250px'], $options['embed']);
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' => $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, true);
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 references if the system account is connected.
if (!empty($this->issuer) && $this->issuer->is_system_account_connected()) {
$setting = $this->get_option('supportedreturntypes');
if ($setting === 'internal') {
return FILE_INTERNAL;
} else if ($setting === 'external') {
return FILE_CONTROLLED_LINK;
} else {
return FILE_CONTROLLED_LINK | FILE_INTERNAL | FILE_REFERENCE;
}
} else {
return FILE_INTERNAL | FILE_REFERENCE;
}
}
/**
* 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())->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))->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, $embed = false) {
global $OUTPUT, $PAGE;
if ($embed) {
$PAGE->set_pagelayout('embedded');
}
$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();
$button = new single_button($url, get_string('logintoaccount', 'repository', $this->get_name()),
'post', single_button::BUTTON_PRIMARY);
$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.
'filereferencewarning' => get_string('externalpubliclinkwarning', 'repository_nextcloud'),
];
// 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;
}
/**
* When a controlled link is clicked in the file picker get the human readable info about this file.
*
* @param string $reference
* @param int $filestatus
* @return string
*/
public function get_reference_details($reference, $filestatus = 0) {
if ($this->disabled) {
throw new repository_exception('cannotdownload', 'repository');
}
if (empty($reference)) {
return get_string('unknownsource', 'repository');
}
$source = json_decode($reference);
$path = '';
if (!empty($source->usesystem) && !empty($source->name)) {
$path = $source->name;
}
return $path;
}
/**
* Synchronize the external file if there is an update happened to it.
*
* If the file has been updated in the nextcloud instance, this method
* would take care of the file we copy into the moodle file pool.
*
* The call to this method reaches from stored_file::sync_external_file()
*
* @param stored_file $file
* @return bool true if synced successfully else false if not ready to sync or reference link not set
*/
public function sync_reference(stored_file $file): bool {
global $CFG;
if ($file->get_referencelastsync() + DAYSECS > time()) {
// Synchronize once per day.
return false;
}
$reference = json_decode($file->get_reference());
if (!isset($reference->link)) {
return false;
}
$url = $reference->link;
if (file_extension_in_typegroup($file->get_filepath() . $file->get_filename(), 'web_image')) {
$saveas = $this->prepare_file(uniqid());
try {
$result = $this->curl->download_one($url, [], [
'filepath' => $saveas,
'timeout' => $CFG->repositorysyncimagetimeout,
'followlocation' => true,
]);
$info = $this->curl->get_info();
if ($result === true && isset($info['http_code']) && $info['http_code'] === 200) {
$file->set_synchronised_content_from_file($saveas);
return true;
}
} catch (Exception $e) {
// If the download fails lets download with get().
$this->curl->get($url, null, ['timeout' => $CFG->repositorysyncimagetimeout, 'followlocation' => true, 'nobody' => true]);
$info = $this->curl->get_info();
if (isset($info['http_code']) && $info['http_code'] === 200 &&
array_key_exists('download_content_length', $info) &&
$info['download_content_length'] >= 0) {
$filesize = (int)$info['download_content_length'];
$file->set_synchronized(null, $filesize);
return true;
}
$file->set_missingsource();
return true;
}
}
return false;
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 32 32" preserveAspectRatio="xMinYMid meet"><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.2 KiB

@@ -0,0 +1,654 @@
<?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/>.
namespace repository_nextcloud;
use testable_access_controlled_link_manager;
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
*
* @package repository_nextcloud
* @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 access_controlled_link_manager_test 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;
/** @var string system account username. */
public $systemaccountusername;
/**
* SetUp to create an repository instance.
*/
protected function setUp(): void {
$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(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(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 \core\oauth2\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(): void {
$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(): void {
$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(): void {
$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(): void {
// 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(): void {
$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(): void {
$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(): void {
$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(): void {
$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(): void {
// 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);
}
/**
* Test whether the webdav client gets the right params and whether function handles overwrite.
*
* @covers \repository_nextcloud\access_controlled_link_manager::transfer_file_to_path
*/
public function test_transfer_file_to_path_overwritefile(): void {
// 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(204);
$this->set_private_property($systemwebdavclientmock, 'systemwebdavclient', $this->linkmanager);
// Call of function.
$result = $this->linkmanager->transfer_file_to_path($srcpath, $dstpath, 'copy');
$this->assertEquals(204, $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(): void {
// 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(): void {
$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(): void {
// 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(): void {
$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->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);
$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);
}
}
}
@@ -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;
}
}
@@ -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;
}
}
File diff suppressed because it is too large Load Diff
+61
View File
@@ -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/>.
namespace repository_nextcloud;
/**
* Class repository_nextcloud_ocs_testcase
*
* @package repository_nextcloud
* @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 ocs_test extends \advanced_testcase {
/**
* @var \core\oauth2\issuer
*/
private $issuer;
/**
* SetUp to create issuer and endpoints for OCS testing.
*/
protected function setUp(): void {
$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(): void {
$mock = $this->createMock(\core\oauth2\client::class);
$mock->expects($this->once())->method('get_issuer')->willReturn($this->issuer);
$client = new ocs_client($mock);
$functions = $client->get_api_functions();
// Assert that relevant (and used) functions are actually present.
$this->assertArrayHasKey('create_share', $functions);
}
}
+29
View File
@@ -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 = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'repository_nextcloud'; // Full name of the plugin (used for diagnostics).