Last modified: Thursday, August 08, 2024

LTI tool providerのサンプルPerlコード

LTI, Perl, CGI, LMS, Moodle

Illustration of a camel with a board with LTI written on it in its mouth. This image is generated by DALL-E.

Image generated by DALL-E

とあるPerlのCGIスクリプトをMoodleからLTI (Learning Tools Interoperability)経由で呼び出す必要が出てきたのですが、当時(2021年春)は「LTIを使って呼び出す」側のサンプルばかりで、tool providerの作り方に関してはあまり参考になる資料が無かったようです。

下記のコードにある$text$keyで、区切り文字&を&とするか&とするかで悩んだ記憶があります…

試行錯誤しながらうまく行くようにできましたので、とりあえず動作確認のサンプルコードだけ置いておきます。

デバッグ目的ですべてのCGIパラメータをリストアップしています。 必要なパラメータを選んでご利用ください。

簡易認証機能がありますので、この認証部分だけ使い回せば様々な用途に応用可能かと思います。 例えば、「特定のMoodleコースに登録している学生にだけ見せたいWebページ」であれば、 認証後にHTMLを出力するようなスクリプトを書くことで対応可能です。 「クライアントのIPアドレスが学内かどうか」を調べるよりも適切に制限できます。

ダウンロード(lti_sample.pl) [Download]



#!/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;



布目 淳@京都工芸繊維大学コンピュータシステム研究室 (nunome@kit.ac.jp)