--- loncom/lti/ltipassback.pm	2017/12/15 17:07:09	1.5
+++ 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.5 2017/12/15 17:07:09 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{16} = 1;
+        $errors{17} = 1;
         &invalid_request($r,$params,\%errors);
         return OK;
     }
@@ -168,7 +234,7 @@ sub handler {
             %maproles = %{$ltitools{'roles'}};
         }
         unless (keys(%maproles)) {
-            $errors{21} = 1;
+            $errors{22} = 1;
             &invalid_request($r,$params,\%errors);
             return OK;
         }
@@ -205,12 +271,12 @@ sub handler {
             }
         }
         unless ($hasrole) {
-            $errors{22} = 1;
+            $errors{23} = 1;
             &invalid_request($r,$params,\%errors);
             return OK;
         }
     } else {
-        $errors{23} = 1;
+        $errors{24} = 1;
         &invalid_request($r,$params,\%errors);
         return OK;
     }
@@ -219,7 +285,6 @@ 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'};