Skip to content

Commit

Permalink
object_file_system: embed_origin_url(), tool_objectfs_file_rewrite_ur…
Browse files Browse the repository at this point in the history
…ls()

redirect_to_presigned_url(): embed origin url into presigned url,
and the corresponding reversal hook.
  • Loading branch information
srdjan-catalyst committed Jan 19, 2022
1 parent caa0d12 commit e97c969
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 5 deletions.
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 @@ -804,13 +807,60 @@ protected function copy_from_external_to_local($contenthash) {
*/
public function redirect_to_presigned_url($contenthash, $headers = array()) {
try {
redirect($this->externalclient->generate_presigned_url($contenthash, $headers));
redirect($this->embed_origin_url(
$this->externalclient->generate_presigned_url($contenthash, $headers),
));
} 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 ULRs 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 @@ -46,8 +46,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 @@ -65,6 +63,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 @@ -491,7 +497,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 @@ -112,3 +112,20 @@ function tool_objectfs_pluginfile($course, $cm, context $context, $filearea, arr
send_stored_file($file, $lifetime, 0, $forcedownload, $options);
return true;
}

/**
* 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 @@ -930,4 +930,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)
);
}
}

0 comments on commit e97c969

Please sign in to comment.