Skip to content

Commit e97c969

Browse files
object_file_system: embed_origin_url(), tool_objectfs_file_rewrite_urls()
redirect_to_presigned_url(): embed origin url into presigned url, and the corresponding reversal hook.
1 parent caa0d12 commit e97c969

4 files changed

Lines changed: 161 additions & 5 deletions

File tree

classes/local/store/object_file_system.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747

4848
abstract class object_file_system extends \file_system_filedir {
4949

50+
const PLUGINFILE_ORIGIN_SEGMENT_PREFIX = 'objectfs-origin=';
51+
52+
public $objectfs = true;
5053
public $externalclient;
5154
private $preferexternal;
5255
private $deleteexternally;
@@ -804,13 +807,60 @@ protected function copy_from_external_to_local($contenthash) {
804807
*/
805808
public function redirect_to_presigned_url($contenthash, $headers = array()) {
806809
try {
807-
redirect($this->externalclient->generate_presigned_url($contenthash, $headers));
810+
redirect($this->embed_origin_url(
811+
$this->externalclient->generate_presigned_url($contenthash, $headers),
812+
));
808813
} catch (\Exception $e) {
809814
debugging('Failed to redirect to pre-signed url: ' . $e->getMessage());
810815
return false;
811816
}
812817
}
813818

819+
/**
820+
* Embed origin URL in presigned URL.
821+
*
822+
* @param string $presignedurl
823+
* @return string $presignedurl with embedded origin
824+
* @throws moodle_exception
825+
*/
826+
public function embed_origin_url($presignedurl): string {
827+
$me = me();
828+
if (empty($me)) {
829+
throw new moodle_exception('invalidurl');
830+
}
831+
832+
return sprintf(
833+
"%s#%s%s",
834+
$presignedurl,
835+
self::PLUGINFILE_ORIGIN_SEGMENT_PREFIX,
836+
urlencode($me)
837+
);
838+
}
839+
840+
/**
841+
* Convert redirected URLs in $text.
842+
*
843+
* @param string $text The content that may contain ULRs in need of rewriting.
844+
* @return string The processed text.
845+
*/
846+
public function rewrite_pluginfile_urls($text): string {
847+
if (!$this->presigned_url_configured()) { // Do nothing.
848+
return $text;
849+
}
850+
851+
$re = sprintf(
852+
'!https?://\S*?\#%s(\S+\w)!',
853+
preg_quote(self::PLUGINFILE_ORIGIN_SEGMENT_PREFIX),
854+
);
855+
return preg_replace_callback(
856+
$re,
857+
function ($matches) {
858+
global $CFG;
859+
return sprintf('%s%s', $CFG->wwwroot, urldecode($matches[1]));
860+
},
861+
$text
862+
);
863+
}
814864

815865
/**
816866
* Return if the file system supports presigned_urls.

classes/local/store/s3/client.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ class client extends object_client_base {
4646
const MAX_TEMP_LIMIT = 2097152;
4747

4848
protected $client;
49-
protected $bucket;
50-
private $signingmethod;
5149

5250
public function __construct($config) {
5351
global $CFG;
@@ -65,6 +63,14 @@ public function __construct($config) {
6563
$this->enablepresignedurls = $config->enablepresignedurls;
6664
$this->signingmethod = $config->signingmethod;
6765
$this->bucketkeyprefix = $config->key_prefix;
66+
$this->cloudfrontresourcedomain = $config->cloudfrontresourcedomain;
67+
68+
if ('cf' === $this->signingmethod) {
69+
if (!$this->cloudfrontresourcedomain) {
70+
throw new \moodle_exception(OBJECTFS_PLUGIN_NAME . ': cloudfrontresourcedomain not configured');
71+
}
72+
}
73+
6874
$this->set_client($config);
6975
} else {
7076
parent::__construct($config);
@@ -491,7 +497,7 @@ private function generate_presigned_url_cloudfront($contenthash, array $headers
491497
if ($nicefilename) {
492498
$key .= $this->get_nice_filename($headers);
493499
}
494-
$resource = $this->config->cloudfrontresourcedomain . '/' . $key;
500+
$resource = $this->cloudfrontresourcedomain . '/' . $key;
495501
// This is the id of the Cloudfront key pair you generated.
496502
$keypairid = $this->config->cloudfrontkeypairid;
497503

lib.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,20 @@ function tool_objectfs_pluginfile($course, $cm, context $context, $filearea, arr
112112
send_stored_file($file, $lifetime, 0, $forcedownload, $options);
113113
return true;
114114
}
115+
116+
/**
117+
* Convert presigned URLs in $text to the origin URL.
118+
*
119+
* @param string $text The content that may contain ULRs in need of rewriting.
120+
* @param int $contextid This parameter and the next two identify the file area to use.
121+
* @return string The processed text.
122+
*/
123+
function tool_objectfs_file_rewrite_urls($text, $contextid) {
124+
$fs = get_file_storage()->get_file_system();
125+
126+
if (!isset($fs->objectfs)) {
127+
return $text;
128+
}
129+
130+
return $fs->rewrite_pluginfile_urls($text);
131+
}

tests/object_file_system_test.php

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
require_once(__DIR__ . '/classes/test_client.php');
2525
require_once(__DIR__ . '/tool_objectfs_testcase.php');
2626

27-
class object_file_system_testcase extends tool_objectfs_testcase {
27+
class object_file_system_test extends tool_objectfs_testcase {
2828

2929
public function set_externalclient_config($key, $value) {
3030
// Get a reflection of externalclient object as a property.
@@ -930,4 +930,87 @@ public function test_is_configured_alternative_file_system_class_is_not_set() {
930930
unset($CFG->alternative_file_system_class);
931931
$this->assertFalse($this->filesystem->is_configured());
932932
}
933+
934+
/**
935+
* @return array
936+
*/
937+
public function rewrite_pluginfile_urls_s3_provider() {
938+
return [
939+
['/pluginfile.php', '/3854/tool_objectfs/content/1/test.pdf'],
940+
];
941+
}
942+
943+
/**
944+
* Test rewrite_pluginfile_urls() S3
945+
*
946+
* @dataProvider rewrite_pluginfile_urls_s3_provider
947+
*
948+
* @param string $script
949+
* @param int $pluginfile
950+
*/
951+
public function test_rewrite_pluginfile_urls_s3_proper($script, $pluginfile) {
952+
global $CFG, $ME;
953+
954+
if (!$CFG->phpunit_objectfs_s3_integration_test_credentials) {
955+
$this->markTestSkipped('No S3 test credentials in config file');
956+
}
957+
958+
$ME = "$script$pluginfile";
959+
$bucket = $CFG->phpunit_objectfs_s3_integration_test_credentials['s3_bucket'];
960+
$s3_region = $CFG->phpunit_objectfs_s3_integration_test_credentials['s3_region'];
961+
$bucketkeyprefix = 'test/';
962+
$presignedurl = "https://$bucket.s3.$s3_region.amazonaws.com/$bucketkeyprefix?param1=val1&param2=val2";
963+
964+
$this->filesystem = new test_file_system();
965+
$externalclient = $this->filesystem->get_external_client();
966+
$this->set_externalclient_config('enablepresignedurls', '1');
967+
$this->set_externalclient_config('signingmethod', 's3');
968+
$this->set_externalclient_config('bucketkeyprefix', $bucketkeyprefix);
969+
$this->assertTrue($this->filesystem->presigned_url_configured());
970+
971+
$embedded = $this->filesystem->embed_origin_url($presignedurl);
972+
$textformat = 'Fake test with pdf %s';
973+
$originaltext = sprintf($textformat, $embedded);
974+
975+
$this->assertEquals(
976+
sprintf($textformat, $CFG->wwwroot.$ME),
977+
$this->filesystem->rewrite_pluginfile_urls($originaltext)
978+
);
979+
}
980+
981+
/**
982+
* Test rewrite_pluginfile_urls() CloudFront
983+
*
984+
* @dataProvider rewrite_pluginfile_urls_s3_provider
985+
*
986+
* @param string $script
987+
* @param int $pluginfile
988+
*/
989+
public function test_rewrite_pluginfile_urls_s3_cf($script, $pluginfile) {
990+
global $CFG, $ME;
991+
992+
if (!$CFG->phpunit_objectfs_s3_integration_test_credentials) {
993+
$this->markTestSkipped('No S3 test credentials in config file');
994+
}
995+
996+
$ME = "$script$pluginfile";
997+
$cloudfrontresourcedomain = 'https://presigned.url';
998+
$presignedurl = "$cloudfrontresourcedomain/x?param1=val1&param2=val2";
999+
1000+
$this->filesystem = new test_file_system();
1001+
$externalclient = $this->filesystem->get_external_client();
1002+
$this->set_externalclient_config('enablepresignedurls', '1');
1003+
$this->set_externalclient_config('signingmethod', 'cf');
1004+
$this->set_externalclient_config('cloudfrontresourcedomain', $cloudfrontresourcedomain);
1005+
$this->assertTrue($this->filesystem->presigned_url_configured());
1006+
1007+
$embedded = $this->filesystem->embed_origin_url($presignedurl);
1008+
$textformat = 'Fake test with pdf %s';
1009+
$originaltext = sprintf($textformat, $embedded);
1010+
1011+
$this->assertEquals(
1012+
sprintf($textformat, $CFG->wwwroot.$ME),
1013+
$this->filesystem->rewrite_pluginfile_urls($originaltext)
1014+
);
1015+
}
9331016
}

0 commit comments

Comments
 (0)