--- loncom/lti/ltipassback.pm 2017/12/09 16:20:24 1.2 +++ loncom/lti/ltipassback.pm 2023/06/02 00:28:03 1.8 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # LTI Consumer Module to receive grades passed back by Provider # -# $Id: ltipassback.pm,v 1.2 2017/12/09 16:20:24 raeburn Exp $ +# $Id: ltipassback.pm,v 1.8 2023/06/02 00:28:03 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -29,6 +29,7 @@ package Apache::ltipassback; use strict; +use URI::Escape; use Apache::Constants qw(:common :http); use Apache::lonnet; use Apache::loncommon; @@ -38,14 +39,56 @@ use LONCAPA::ltiutils; sub handler { my $r = shift; my %errors; + my $params = {}; + my ($oauthtype,$authheader,$xmlbody); +# +# Retrieve content type from headers +# + my $content_type = $r->headers_in->get('Content-Type'); + if ($content_type eq 'application/xml') { + $oauthtype = 'consumer'; +# +# Retrieve OAuth data from Authorization header sent by LTI Provider +# + $authheader = $r->headers_in->get('Authorization'); + my ($authtype,$valuestr) = ($authheader =~ /^(OAuth)\s+(.+)$/i); + if (lc($authtype) eq 'oauth') { + foreach my $pair (split(/\s*,\s*/,$valuestr)) { + my ($key,$value) = split(/=/,$pair); + $value =~ s /(^"|"$)//g; + $params->{$key} = URI::Escape::uri_unescape($value); + } + } +# +# Retrieve message body +# + my $length = $r->headers_in->get('Content-length'); + if ($length) { + $r->read($xmlbody,$length,0); + if ($xmlbody ne '') { + my %grades = &LONCAPA::ltiutils::parse_grade_xml($xmlbody); + foreach my $num (sort { $a <=> $b } (keys(%grades))) { + if (ref($grades{$num}) eq 'HASH') { + if (($grades{$num}{'sourcedid'} ne '') && ($grades{$num}{'score'} ne '')) { + $params->{'sourcedid'} = $grades{$num}{'sourcedid'}; + $params->{'result_resultscore_textstring'} = $grades{$num}{'score'}; + $params->{'result_resultscore_language'} = $grades{$num}{'language'}; + $params->{'result_resultvaluesourcedid'} = 'decimal'; + } + } + } + } + } + } else { + $oauthtype = 'request token'; # # Retrieve data POSTed by LTI Provider # - &Apache::lonacc::get_posted_cgi($r); - my $params = {}; - foreach my $key (sort(keys(%env))) { - if ($key =~ /^form\.(.+)$/) { - $params->{$1} = $env{$key}; + &Apache::lonacc::get_posted_cgi($r); + foreach my $key (sort(keys(%env))) { + if ($key =~ /^form\.(.+)$/) { + $params->{$1} = $env{$key}; + } } } @@ -114,11 +157,16 @@ sub handler { # my (%toolsettings,%ltitools); - my ($consumer_secret,$nonce_lifetime) = + my ($consumer_secret,$nonce_lifetime) = &LONCAPA::ltiutils::get_tool_secret($params->{'oauth_consumer_key'}, $marker,$symb,$cdom,$cnum, \%toolsettings,\%ltitools,\%errors); + if (keys(%errors) > 0) { + &invalid_request($r,$params,\%errors); + return OK; + } + # # Verify the signed request using the consumer_key and # secret for the specific LTI Provider. @@ -128,20 +176,38 @@ sub handler { if ($ENV{'SERVER_PORT'} == 443) { $protocol = 'https'; } - unless (LONCAPA::ltiutils::verify_request($params,$protocol,$r->hostname,$r->uri, - $env{'request.method'},$consumer_secret, - \%errors)) { + + unless (LONCAPA::ltiutils::verify_request($oauthtype,$protocol,$r->hostname,$r->uri, + $r->method,$consumer_secret,$params, + $authheader,\%errors)) { &invalid_request($r,$params,\%errors); return OK; } # +# Verify XML in request body has not been tampered with +# + + if ($content_type eq 'application/xml') { + my $bodyhash = Digest::SHA::sha1_base64($xmlbody); + while (length($bodyhash) % 4) { + $bodyhash .= '='; + } + unless ($bodyhash eq $params->{oauth_body_hash}) { + $errors{16} = 1; + &invalid_request($r,$params,\%errors); + return OK; + } + } + +# # Determine if nonce in POSTed data has expired. # If unexpired, confirm it has not already been used. +# unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'}, $ltitools{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) { - $errors{15} = 1; + $errors{17} = 1; &invalid_request($r,$params,\%errors); return OK; } @@ -168,7 +234,7 @@ sub handler { %maproles = %{$ltitools{'roles'}}; } unless (keys(%maproles)) { - $errors{20} = 1; + $errors{22} = 1; &invalid_request($r,$params,\%errors); return OK; } @@ -205,12 +271,12 @@ sub handler { } } unless ($hasrole) { - $errors{21} = 1; + $errors{23} = 1; &invalid_request($r,$params,\%errors); return OK; } } else { - $errors{22} = 1; + $errors{24} = 1; &invalid_request($r,$params,\%errors); return OK; } @@ -219,14 +285,13 @@ sub handler { # Store result if one was sent in a valid format. # - my ($result,$resulttype,$lang,$pcf); if (exists($params->{'result_resultvaluesourcedid'})) { $resulttype = $params->{'result_resultvaluesourcedid'}; $resulttype =~ s/(^\s+|\s+)$//g; } else { $resulttype = 'decimal'; - } + } $result = $params->{'result_resultscore_textstring'}; $result =~ s/(^\s+|\s+)$//g; my $posslang = $params->{'result_resultscore_language'}; @@ -245,7 +310,7 @@ sub handler { }; } if ($@) { - $errors{22} = 1; + $errors{24} = 1; &invalid_request($r,$params,\%errors); return OK; } @@ -270,34 +335,43 @@ sub handler { my %newrecord=(); my $reckey = 'resource.0.solved'; my %record = &Apache::lonnet::restore($symb,$cdom.'_'.$cnum,$udom,$uname); + my $tries = 0; + if ($record{'resource.0.tries'} =~ /^\d$/) { + $tries = $record{'resource.0.tries'}; + } if ($record{'resource.0.awarded'} ne $pcf) { $newrecord{'resource.0.awarded'} = $pcf; } if ($pcf == 0) { - if ($record{$reckey} ne 'incorrect_by_override') { - $newrecord{$reckey} = 'incorrect_by_override'; + if ($record{$reckey} ne 'incorrect_by_passback') { + $newrecord{$reckey} = 'incorrect_by_passback'; } } else { - if ($record{$reckey} ne 'correct_by_override') { - $newrecord{$reckey} = 'correct_by_override'; + if ($record{$reckey} ne 'correct_by_passback') { + $newrecord{$reckey} = 'correct_by_passback'; } } if (%newrecord) { + $newrecord{'resource.0.tries'} = 1 + $tries; + $env{'request.course.id'} = $cdom.'_'.$cnum; my $result = &Apache::lonnet::cstore(\%newrecord,$symb,$cdom.'_'.$cnum, $udom,$uname); + delete($env{'request.course.id'}); if (($result eq 'ok') || ($result eq 'con_delayed')) { &success($r,$params->{'sourcedid'},$resulttype,$result,$lang); } else { - $errors{23} = 1; + $errors{25} = 1; &invalid_request($r,$params,\%errors); } + } else { + &success($r,$params->{'sourcedid'},$resulttype,$result,$lang); } } else { - $errors{24} = 1; + $errors{26} = 1; &invalid_request($r,$params,\%errors); } } else { - $errors{25} = 1; + $errors{27} = 1; &invalid_request($r,$params,\%errors); } return OK;