Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion classes/local/store/object_file_system.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@

abstract class object_file_system extends \file_system_filedir {

const PLUGINFILE_ORIGIN_SEGMENT_PREFIX = 'objectfs-origin=';

public $objectfs = true;
public $externalclient;
private $preferexternal;
private $deleteexternally;
Expand Down Expand Up @@ -768,13 +771,60 @@ public function redirect_to_presigned_url($contenthash, $headers = array()) {
}
header('Cache-Control: ' . $cachevisibility . ', max-age=' . ($signedurl->expiresat - time()));
}
redirect($signedurl->url);
redirect($this->embed_origin_url(
$signedurl->url
));
} catch (\Exception $e) {
debugging('Failed to redirect to pre-signed url: ' . $e->getMessage());
return false;
}
}

/**
* Embed origin URL in presigned URL.
*
* @param string $presignedurl
* @return string $presignedurl with embedded origin
* @throws moodle_exception
*/
public function embed_origin_url($presignedurl): string {
$me = me();
if (empty($me)) {
throw new moodle_exception('invalidurl');
}

return sprintf(
"%s#%s%s",
$presignedurl,
self::PLUGINFILE_ORIGIN_SEGMENT_PREFIX,
urlencode($me)
);
}

/**
* Convert redirected URLs in $text.
*
* @param string $text The content that may contain URLs in need of rewriting.
* @return string The processed text.
*/
public function rewrite_pluginfile_urls($text): string {
if (!$this->presigned_url_configured()) { // Do nothing.
return $text;
}

$re = sprintf(
'!https?://\S*?\#%s(\S+\w)!',
preg_quote(self::PLUGINFILE_ORIGIN_SEGMENT_PREFIX),
);
return preg_replace_callback(
$re,
function ($matches) {
global $CFG;
return sprintf('%s%s', $CFG->wwwroot, urldecode($matches[1]));
},
$text
);
}

/**
* Return if the file system supports presigned_urls.
Expand Down
12 changes: 9 additions & 3 deletions classes/local/store/s3/client.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ class client extends object_client_base {
const MAX_TEMP_LIMIT = 2097152;

protected $client;
protected $bucket;
private $signingmethod;

public function __construct($config) {
global $CFG;
Expand All @@ -64,6 +62,14 @@ public function __construct($config) {
$this->enablepresignedurls = $config->enablepresignedurls;
$this->signingmethod = $config->signingmethod;
$this->bucketkeyprefix = $config->key_prefix;
$this->cloudfrontresourcedomain = $config->cloudfrontresourcedomain;

if ('cf' === $this->signingmethod) {
if (!$this->cloudfrontresourcedomain) {
throw new \moodle_exception(OBJECTFS_PLUGIN_NAME . ': cloudfrontresourcedomain not configured');
}
}

$this->set_client($config);
} else {
parent::__construct($config);
Expand Down Expand Up @@ -536,7 +542,7 @@ private function generate_presigned_url_cloudfront($contenthash, array $headers
if ($nicefilename) {
$key .= $this->get_nice_filename($headers);
}
$resource = $this->config->cloudfrontresourcedomain . '/' . $key;
$resource = $this->cloudfrontresourcedomain . '/' . $key;
// This is the id of the Cloudfront key pair you generated.
$keypairid = $this->config->cloudfrontkeypairid;

Expand Down
17 changes: 17 additions & 0 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,20 @@ function tool_objectfs_status_checks() {

return [];
}

/**
* Convert presigned URLs in $text to the origin URL.
*
* @param string $text The content that may contain ULRs in need of rewriting.
* @param int $contextid This parameter and the next two identify the file area to use.
* @return string The processed text.
*/
function tool_objectfs_file_rewrite_urls($text, $contextid) {
$fs = get_file_storage()->get_file_system();

if (!isset($fs->objectfs)) {
return $text;
}

return $fs->rewrite_pluginfile_urls($text);
}
85 changes: 84 additions & 1 deletion tests/object_file_system_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
require_once(__DIR__ . '/classes/test_client.php');
require_once(__DIR__ . '/tool_objectfs_testcase.php');

class object_file_system_testcase extends tool_objectfs_testcase {
class object_file_system_test extends tool_objectfs_testcase {

public function set_externalclient_config($key, $value) {
// Get a reflection of externalclient object as a property.
Expand Down Expand Up @@ -899,4 +899,87 @@ public function test_is_configured_alternative_file_system_class_is_not_set() {
unset($CFG->alternative_file_system_class);
$this->assertFalse($this->filesystem->is_configured());
}

/**
* @return array
*/
public function rewrite_pluginfile_urls_s3_provider() {
return [
['/pluginfile.php', '/3854/tool_objectfs/content/1/test.pdf'],
];
}

/**
* Test rewrite_pluginfile_urls() S3
*
* @dataProvider rewrite_pluginfile_urls_s3_provider
*
* @param string $script
* @param int $pluginfile
*/
public function test_rewrite_pluginfile_urls_s3_proper($script, $pluginfile) {
global $CFG, $ME;

if (!$CFG->phpunit_objectfs_s3_integration_test_credentials) {
$this->markTestSkipped('No S3 test credentials in config file');
}

$ME = "$script$pluginfile";
$bucket = $CFG->phpunit_objectfs_s3_integration_test_credentials['s3_bucket'];
$s3_region = $CFG->phpunit_objectfs_s3_integration_test_credentials['s3_region'];
$bucketkeyprefix = 'test/';
$presignedurl = "https://$bucket.s3.$s3_region.amazonaws.com/$bucketkeyprefix?param1=val1&param2=val2";

$this->filesystem = new test_file_system();
$externalclient = $this->filesystem->get_external_client();
$this->set_externalclient_config('enablepresignedurls', '1');
$this->set_externalclient_config('signingmethod', 's3');
$this->set_externalclient_config('bucketkeyprefix', $bucketkeyprefix);
$this->assertTrue($this->filesystem->presigned_url_configured());

$embedded = $this->filesystem->embed_origin_url($presignedurl);
$textformat = 'Fake test with pdf %s';
$originaltext = sprintf($textformat, $embedded);

$this->assertEquals(
sprintf($textformat, $CFG->wwwroot.$ME),
$this->filesystem->rewrite_pluginfile_urls($originaltext)
);
}

/**
* Test rewrite_pluginfile_urls() CloudFront
*
* @dataProvider rewrite_pluginfile_urls_s3_provider
*
* @param string $script
* @param int $pluginfile
*/
public function test_rewrite_pluginfile_urls_s3_cf($script, $pluginfile) {
global $CFG, $ME;

if (!$CFG->phpunit_objectfs_s3_integration_test_credentials) {
$this->markTestSkipped('No S3 test credentials in config file');
}

$ME = "$script$pluginfile";
$cloudfrontresourcedomain = 'https://presigned.url';
$presignedurl = "$cloudfrontresourcedomain/x?param1=val1&param2=val2";

$this->filesystem = new test_file_system();
$externalclient = $this->filesystem->get_external_client();
$this->set_externalclient_config('enablepresignedurls', '1');
$this->set_externalclient_config('signingmethod', 'cf');
$this->set_externalclient_config('cloudfrontresourcedomain', $cloudfrontresourcedomain);
$this->assertTrue($this->filesystem->presigned_url_configured());

$embedded = $this->filesystem->embed_origin_url($presignedurl);
$textformat = 'Fake test with pdf %s';
$originaltext = sprintf($textformat, $embedded);

$this->assertEquals(
sprintf($textformat, $CFG->wwwroot.$ME),
$this->filesystem->rewrite_pluginfile_urls($originaltext)
);
}
}