LTI, Perl, CGI, LMS, Moodle
Image generated by DALL-E
とあるPerlのCGIスクリプトをMoodleからLTI (Learning Tools Interoperability)経由で呼び出す必要が出てきたのですが、当時(2021年春)は「LTIを使って呼び出す」側のサンプルばかりで、tool providerの作り方に関してはあまり参考になる資料が無かったようです。
下記のコードにある$textと$keyで、区切り文字&を&とするか&とするかで悩んだ記憶があります…
試行錯誤しながらうまく行くようにできましたので、とりあえず動作確認のサンプルコードだけ置いておきます。
デバッグ目的ですべてのCGIパラメータをリストアップしています。 必要なパラメータを選んでご利用ください。
簡易認証機能がありますので、この認証部分だけ使い回せば様々な用途に応用可能かと思います。 例えば、「特定のMoodleコースに登録している学生にだけ見せたいWebページ」であれば、 認証後にHTMLを出力するようなスクリプトを書くことで対応可能です。 「クライアントのIPアドレスが学内かどうか」を調べるよりも適切に制限できます。
#!/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
$cgi_url = "https://www.example.com/cgi-bin/lti_test.cgi";
# コンシューマーキー(ユーザIDみたいなもの)
$consumer_key = "ltitest";
# 共有シークレットキー(ユーザ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(); # CGIに渡されるパラメータをダンプする
print "<hr>\n";
# 実際には$consumer_key(CGIが想定しているユーザID)と
# $q->param('oauth_consumer_key') の値を比較して不一致であればエラーとする
# (ここでは省略)
my @key_list = $q->param;
$param="";
foreach (sort @key_list) {
# CGIに渡されるパラメータ(oauth_signature以外)をソートしておく(必須)
if (! ( /^oauth_signature$/) ) {
$param .= $_ . "=".uri_escape(scalar $q->param($_))."&";
}
}
chop($param);
$param = uri_escape($param);
$myurl = uri_escape($cgi_url);
# 環境変数REQUEST_METHODの内容と$myurl, $paramの内容を&区切りで連結する
# (これをテキスト部$textとする)
$text = uri_escape($ENV{'REQUEST_METHOD'})."&".$myurl."&".$param;
# 共有シークレットキー$shared_secretをuri_escapeしたものの末尾に&を付加
# (これをキー部$keyとする)
$key = uri_escape($shared_secret) . "&";
# 生成した$textと$keyを使ってhmac_sha1を呼び出す。この結果をencode_base64
# したものがシグネチャになる
$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) {
# リクエストの有効期限が過ぎている場合はエラー終了
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')) {
# 自分で生成したシグネチャと oauth_signature として受け取った値が一致していればOK
print "<p><strong>Signature matched.</strong></p>\n";
} else {
# 一致していなければエラー
print "<p><strong>Signature mismatched.</strong> oauth_signature = "
. $q->param('oauth_signature') . "</p>\n";
}
print $q->end_html;
布目 淳@京都工芸繊維大学コンピュータシステム研究室 (nunomekit.ac.jp)