LTI, Perl, CGI, LMS, Moodle
Image generated by DALL-E
I needed to call a Perl CGI script from Moodle via the LTI (Learning Tools Interoperability) interface. However, at that time (spring 2021), there were only samples of the "call an outer tool using LTI" side, and there were not many references on how to create a tool provider. Since I was able to get it to work through trial and error, I have posted the sample code I used to check the operation.
I remember wondering whether to use & or & for the delimiter & in the $text and $key in the code below.
All CGI parameters are listed for debugging purposes. Please select the parameters you need.
It provides a simple authentication function, so if you only use this authentication part, you can apply it to a variety of applications. For example, if you want to show a web page only to students enrolled in a particular Moodle course, you can write a script that outputs HTML after authentication. This is more appropriate than checking whether or not the client's IP address is within the university.
#!/usr/bin/perl
use utf8;
use CGI;
use Digest::HMAC_SHA1 qw(hmac_sha1);
use MIME::Base64 qw(encode_base64);
use URI::Escape qw(uri_escape);
use POSIX qw(strftime);
# URL where this script is located
$cgi_url = "https://www.example.com/cgi-bin/lti_test.cgi";
# Consumer Key (like a User ID)
$consumer_key = "ltitest";
# Shared Secret Key (like a password corresponding to a user ID)
$shared_secret = "testtesttest";
my $q = CGI->new;
print $q->header(-type=>'text/html', -charset=>'utf-8');
print $q->start_html(-title=>"LTI test");
print $q->Dump(); # Dump parameters passed to this CGI
print "<hr>\n";
# In practice, compare $consumer_key (user ID assumed by this script)
# with the value of $q->param('oauth_consumer_key'), and if there is a
# mismatch, an error is raised. (Omitted here)
my @key_list = $q->param;
$param="";
foreach (sort @key_list) {
# Sort parameters (other than oauth_signature) passed to CGI (REQUIRED)
if (! ( /^oauth_signature$/) ) {
$param .= $_ . "=".uri_escape(scalar $q->param($_))."&";
}
}
chop($param);
$param = uri_escape($param);
$myurl = uri_escape($cgi_url);
# Concatenate the contents of the environment variable REQUEST_METHOD
# with the contents of $myurl and $param, separated by &
# (this will be the text part $text)
$text = uri_escape($ENV{'REQUEST_METHOD'})."&".$myurl."&".$param;
# Append & to the end of the uri_escaped shared secret key $shared_secret.
# (this will be the key part $key)
$key = uri_escape($shared_secret) . "&";
# Call hmac_sha1 with the generated $text and $key. Pass the result to
# encode_base64 to obtain a signature.
$digest = hmac_sha1($text, $key);
$enc_digest = encode_base64($digest);
chomp $enc_digest;
$timestmp = $q->param('oauth_timestamp');
$sec_from_epoch = time;
if ($timestmp < $sec_from_epoch - 60*3) {
# Error if this request has expired
exit 1;
}
$timestr = strftime "%Y/%m/%d %H:%M:%S", localtime($timestmp);
print "<p>Timestamp: $timestr</p>\n";
print "<p>Current Time: ". strftime("%Y/%m/%d %H:%M:%S", localtime(time)) ."</p>\n";
print "<p>Text: $text</p><p>Key: $key</p>\n";
print "Generated signature: <strong>".$enc_digest."</strong>\n";
if ($enc_digest eq $q->param('oauth_signature')) {
# OK if the signature generated by this script matches the value received
# as oauth_signature
print "<p><strong>Signature matched.</strong></p>\n";
} else {
# Error if not matched
print "<p><strong>Signature mismatched.</strong> oauth_signature = "
. $q->param('oauth_signature') . "</p>\n";
}
print $q->end_html;
Atsushi NUNOME @ Computer System Laboratory at Kyoto Institute of Technology
(nunomekit.ac.jp)