[LON-CAPA-cvs] cvs: loncom / Lond.pm lond /interface courseprefs.pm lonconfigsettings.pm /lonnet/perl lonnet.pm /lti ltiauth.pm

raeburn raeburn at source.lon-capa.org
Tue Feb 1 18:13:21 EST 2022


raeburn		Tue Feb  1 23:13:21 2022 EDT

  Modified files:              
    /loncom/interface	courseprefs.pm lonconfigsettings.pm 
    /loncom/lonnet/perl	lonnet.pm 
    /loncom/lti	ltiauth.pm 
    /loncom	Lond.pm lond 
  Log:
  - Bug 6907
    - Extraction of LTI itemID based on consumer key in signed LTI payload, and 
      verification with available secret moved from ltiauth.pm to Lond.pm.
    - Verification will now occur on course's home server or domain's primary
      library server, for course-defined LTI ID, and domain-defined LTI ID
      respectively.
    - Setting and modifying link protection key and secret now requires user
      session on course's home server.
    - Display of existing LTI key only available of course's home server.
    - Display of stored LTI secret eliminated so a Course Coordinator will 
      need to record it offline or commit it to memory. 
  
  
-------------- next part --------------
Index: loncom/interface/courseprefs.pm
diff -u loncom/interface/courseprefs.pm:1.98 loncom/interface/courseprefs.pm:1.99
--- loncom/interface/courseprefs.pm:1.98	Tue Feb  1 18:23:24 2022
+++ loncom/interface/courseprefs.pm	Tue Feb  1 23:13:19 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set configuration settings for a course
 #
-# $Id: courseprefs.pm,v 1.98 2022/02/01 18:23:24 raeburn Exp $
+# $Id: courseprefs.pm,v 1.99 2022/02/01 23:13:19 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -365,11 +365,25 @@
     }
 
     my %values=&Apache::lonnet::dump('environment',$cdom,$cnum);
-    my %courselti=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
-    if ($courselti{'lock'}) {
-        delete($courselti{'lock'});
+    my %lti=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
+    my %ltienc = &Apache::lonnet::dump('nohist_ltienc',$cdom,$cnum,undef,undef,undef,1);
+    foreach my $id (keys(%lti)) {
+        if (ref($lti{$id}) eq 'HASH') {
+            if (ref($ltienc{$id}) eq 'HASH') {
+                $values{'linkprotection'}{$id} = { %{$lti{$id}}, %{$ltienc{$id}} };
+            } else {
+                $values{'linkprotection'}{$id} = $lti{$id};
+            }
+        }
+        unless ($phase eq 'process') {
+            if (ref($values{'linkprotection'}{$id}) eq 'HASH') {
+                delete($values{'linkprotection'}{$id}{'secret'});
+            }
+        }
+    }
+    if ($lti{'lock'}) {
+        delete($lti{'lock'});
     }
-    $values{'linkprotection'} = \%courselti;
     my @prefs_order = ('courseinfo','localization','feedback','discussion',
                        'classlists','appearance','grading','printouts',
                        'menuitems','linkprotection','spreadsheet','bridgetasks',
@@ -596,7 +610,7 @@
         my $jscript = &get_jscript($cid,$cdom,$phase,$crstype,\%values,$noedit);
         my @allitems = &get_allitems(%prefs);
         &Apache::lonconfigsettings::display_settings($r,$cdom,$phase,$context,
-            \@prefs_order,\%prefs,\%values,undef,$jscript,\@allitems,$crstype,
+            \@prefs_order,\%prefs,\%values,$cnum,$jscript,\@allitems,$crstype,
             'coursepref',$parm_permission);
     } else {
         &Apache::lonconfigsettings::display_choices($r,$phase,$context,
@@ -649,7 +663,7 @@
 }
 
 sub print_config_box {
-    my ($r,$cdom,$phase,$action,$item,$settings,$allitems,$crstype,$parm_permission) = @_;
+    my ($r,$cdom,$cnum,$phase,$action,$item,$settings,$allitems,$crstype,$parm_permission) = @_;
     my $ordered = $item->{'ordered'};
     my $itemtext = $item->{'itemtext'};
     my $noedit;
@@ -780,7 +794,7 @@
     } elsif ($action eq 'menuitems') {
         $output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit);
     } elsif ($action eq 'linkprotection') {
-        $output .= &print_linkprotection($cdom,$settings,\$rowtotal,$crstype,$noedit);
+        $output .= &print_linkprotection($cdom,$cnum,$settings,\$rowtotal,$crstype,$noedit);
     } elsif ($action eq 'other') {
         $output .= &print_other($cdom,$settings,$allitems,\$rowtotal,$crstype,$noedit);
     }
@@ -794,7 +808,7 @@
 
 sub process_changes {
     my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
-    my (%newvalues,%courselti,$errors);
+    my (%newvalues,%lti,%ltienc,$errors);
     if (ref($item) eq 'HASH') {
         if (ref($changes) eq 'HASH') {
             my @ordered;
@@ -812,12 +826,12 @@
                     }
                 }
             } elsif ($action eq 'linkprotection') {
-                if (ref($values->{'linkprotection'}) eq 'HASH') {
-                    foreach my $id (keys(%{$values->{'linkprotection'}})) {
+                if (ref($values->{$action}) eq 'HASH') {
+                    foreach my $id (keys(%{$values->{$action}})) {
                         if ($id =~ /^\d+$/) {
                             push(@ordered,$id);
-                            unless (ref($values->{'linkprotection'}->{$id}) eq 'HASH') {
-                                $courselti{$id} = '';
+                            unless (ref($values->{$action}->{$id}) eq 'HASH') {
+                                $lti{$id} = '';
                             }
                         }
                     }
@@ -965,6 +979,7 @@
                     }
                 } elsif ($action eq 'linkprotection') {
                     my %menutitles = &ltimenu_titles();
+                    my $switchserver = &check_switchserver($cdom,$cnum);
                     my (@items,%deletions,%itemids,%haschanges);
                     if ($env{'form.linkprot_add'}) {
                         my $name = $env{'form.linkprot_name_add'};
@@ -980,18 +995,18 @@
                                        '</span>';
                         }
                     }
-                    if (ref($values->{'linkprotection'}) eq 'HASH') {
+                    if (ref($values->{$action}) eq 'HASH') {
                         my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del');
                         my $maxnum = $env{'form.linkprot_maxnum'};
                         for (my $i=0; $i<=$maxnum; $i++) {
                             my $itemid = $env{'form.linkprot_id_'.$i};
                             $itemid =~ s/\D+//g;
                             if ($itemid) {
-                                if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
+                                if (ref($values->{$action}->{$itemid}) eq 'HASH') {
                                     push(@items,$i);
                                     $itemids{$i} = $itemid;
                                     if ((@todelete > 0) && (grep(/^$i$/, at todelete))) {
-                                        $deletions{$itemid} = $values->{'linkprotection'}->{$itemid}->{'name'};
+                                        $deletions{$itemid} = $values->{$action}->{$itemid}->{'name'};
                                     }
                                 }
                             }
@@ -1002,19 +1017,19 @@
                         my $itemid = $itemids{$idx};
                         next unless ($itemid);
                         if (exists($deletions{$itemid})) {
-                            $courselti{$itemid} = $deletions{$itemid};
+                            $lti{$itemid} = $deletions{$itemid};
                             $haschanges{$itemid} = 1;
                             next;
                         }
                         my %current;
-                        if (ref($values->{'linkprotection'}) eq 'HASH') {
-                            if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
-                                foreach my $key (keys(%{$values->{'linkprotection'}->{$itemid}})) {
-                                    $current{$key} = $values->{'linkprotection'}->{$itemid}->{$key};
+                        if (ref($values->{$action}) eq 'HASH') {
+                            if (ref($values->{$action}->{$itemid}) eq 'HASH') {
+                                foreach my $key (keys(%{$values->{$action}->{$itemid}})) {
+                                    $current{$key} = $values->{$action}->{$itemid}->{$key};
                                 }
                             }
                         }
-                        foreach my $inner ('name','key','secret','lifetime','version') {
+                        foreach my $inner ('name','lifetime','version') {
                             my $formitem = 'form.linkprot_'.$inner.'_'.$idx;
                             $env{$formitem} =~ s/(`)/'/g;
                             if ($inner eq 'lifetime') {
@@ -1026,13 +1041,40 @@
                                 }
                             }
                             if ($env{$formitem} ne '') {
-                                $courselti{$itemid}{$inner} = $env{$formitem};
+                                $lti{$itemid}{$inner} = $env{$formitem};
+                            }
+                        }
+                        unless ($switchserver) {
+                            my $keyitem = 'form.linkprot_key_'.$idx;
+                            $env{$keyitem} =~ s/(`)/'/g;
+                            unless ($idx eq 'add') {
+                                if ($current{'key'} ne $env{$keyitem}) {
+                                    $haschanges{$itemid} = 1;
+                                }
+                            }
+                            if ($env{$keyitem} ne '') {
+                                $lti{$itemid}{'key'} = $env{$keyitem};
+                            }
+                            my $secretitem = 'form.linkprot_secret_'.$idx;
+                            $env{$secretitem} =~ s/(`)/'/g;
+                            if ($current{'usable'}) {
+                                if ($env{'form.linkprot_changesecret_'.$idx}) {
+                                    if ($env{$secretitem} ne '') {
+                                        $lti{$itemid}{'secret'} = $env{$secretitem};
+                                        $haschanges{$itemid} = 1;
+                                    }
+                                } else {
+                                    $lti{$itemid}{'secret'} = $current{'secret'};
+                                }
+                            } elsif ($env{$secretitem} ne '') {
+                                $lti{$itemid}{'secret'} = $env{$secretitem};
+                                $haschanges{$itemid} = 1;
                             }
                         }
                     }
                     if (keys(%haschanges)) {
                         foreach my $entry (keys(%haschanges)) {
-                            $changes->{$entry} = $courselti{$entry};
+                            $changes->{$entry} = $lti{$entry};
                         }
                     }
                 } else {
@@ -1655,7 +1697,7 @@
         if (grep(/^\Q$item\E$/,@{$actions})) {
             $output .= '<h3>'.&mt($prefs->{$item}{'text'}).'</h3>';
             if (ref($changes->{$item}) eq 'HASH') {
-                if ((keys(%{$changes->{$item}}) > 0) || ($item eq 'linkprotection')) {
+                if (keys(%{$changes->{$item}}) > 0) {
                     $output .= &mt('Changes made:').'<ul style="list-style:none;">';
                     if ($item eq 'other') {
                         foreach my $key (sort(keys(%{$changes->{$item}}))) {
@@ -1669,34 +1711,96 @@
                             }
                         }
                     } elsif ($item eq 'linkprotection') {
-                        if (&Apache::lonnet::put('lti',$changes->{'linkprotection'},$cdom,$cnum,1) eq 'ok') {
-                            my $hashid=$cdom.'_'.$cnum;
-                            &Apache::lonnet::devalidate_cache_new('courselti',$hashid);
-                            foreach my $itemid (sort { $a <=> $b } %{$changes->{'linkprotection'}}) {
-                                if (ref($changes->{'linkprotection'}->{$itemid}) eq 'HASH') {
-                                    my %values = %{$changes->{'linkprotection'}->{$itemid}};
-                                    my %desc = &linkprot_names();
-                                    my $display;
-                                    foreach my $title ('name','lifetime','version','key','secret') {
-                                        if ($title eq 'secret') {
-                                            my $length = length($values{$title});
-                                            $display .= $desc{$title}.': '.('*' x $length);
-                                        } elsif ($title eq 'version') {
-                                            if ($values{$title} eq 'LTI-1p0') {
-                                                $display .= $desc{$title}.': 1.1, ';
+                        my (%ltienc,$lti_save_error);
+                        if (ref($changes->{$item}) eq 'HASH') {
+                            foreach my $id (sort { $a <=> $b } keys(%{$changes->{$item}})) {
+                                if (ref($changes->{$item}->{$id}) eq 'HASH') {
+                                    if (exists($changes->{$item}->{$id}->{'key'})) {
+                                        $ltienc{$id}{'key'} = $changes->{$item}->{$id}->{'key'};
+                                        delete($changes->{$item}->{$id}->{'key'});
+                                    }
+                                    if (exists($changes->{$item}->{$id}->{'secret'})) {
+                                        $ltienc{$id}{'secret'} = $changes->{$item}->{$id}->{'secret'};
+                                        delete($changes->{$item}->{$id}->{'secret'});
+                                    } elsif (ref($oldlinkprot{$id}) eq 'HASH') {
+                                        if (exists($oldlinkprot{$id}{'usable'})) {
+                                            $changes->{$item}->{$id}->{'usable'} = 1;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        if (keys(%ltienc) > 0) {
+                            if (&Apache::lonnet::put('nohist_ltienc',\%ltienc,$cdom,$cnum,1) eq 'ok') {
+                                foreach my $id (keys(%ltienc)) {
+                                    if (exists($ltienc{$id}{'secret'})) {
+                                        $changes->{$item}->{$id}->{'usable'} = 1;
+                                    }
+                                }
+                            } else {
+                                $lti_save_error = 1;
+                            }
+                        }
+                        unless ($lti_save_error) {
+                            if (&Apache::lonnet::put('lti',$changes->{$item},$cdom,$cnum,1) eq 'ok') {
+                                my $hashid=$cdom.'_'.$cnum;
+                                &Apache::lonnet::devalidate_cache_new('courselti',$hashid);
+                                $chome = &Apache::lonnet::homeserver($cnum,$cdom);
+                                unless (($chome eq 'no_host') || ($chome eq '')) {
+                                    my @ids=&Apache::lonnet::current_machine_ids();
+                                    unless (grep(/^\Q$chome\E$/, at ids)) {
+                                        &Apache::lonnet::devalidate_cache_new('courseltienc',$hashid);
+                                    }
+                                }
+                                foreach my $id (sort { $a <=> $b } %{$changes->{$item}}) {
+                                    if (ref($changes->{$item}->{$id}) eq 'HASH') {
+                                        my %values = %{$changes->{$item}->{$id}};
+                                        my %desc = &linkprot_names();
+                                        my $display;
+                                        foreach my $title ('name','lifetime','version','key','secret') {
+                                            if (($title eq 'key') || ($title eq 'secret')) {
+                                                if (ref($ltienc{$id}) eq 'HASH') {
+                                                    if (exists($ltienc{$id}{$title})) {
+                                                        if ($title eq 'secret') {
+                                                            my $length = length($ltienc{$id}{$title});
+                                                            $display .= $desc{$title}.': '.('*' x $length);
+                                                        } else {
+                                                            $display .= $desc{$title}.': '.$ltienc{$id}{$title}.', ';
+                                                        }
+                                                    }
+                                                }
+                                            } elsif ($title eq 'version') {
+                                                if ($values{$title} eq 'LTI-1p0') {
+                                                    $display .= $desc{$title}.': 1.1, ';
+                                                }
+                                            } else {
+                                                $display .= $desc{$title}.': '.$values{$title}.', ';
                                             }
-                                        } else {
-                                            $display .= $desc{$title}.': '.$values{$title}.', ';
                                         }
+                                        $display =~ s/, $//;
+                                        $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]','<i>'.$id.'</i>',
+                                                   "'$display'")).'</li>';
+                                    } elsif (ref($oldlinkprot{$id}) eq 'HASH') {
+                                        my $oldname = $oldlinkprot{$id}{'name'};
+                                        $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]','<i>'."$id ($oldname)".'</i>')).'</li>';
                                     }
-                                    $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]','<i>'.$itemid.'</i>',
-                                               "'$display'")).'</li>';
-                                } elsif (ref($oldlinkprot{$itemid}) eq 'HASH') {
-                                    my $oldname = $oldlinkprot{$itemid}{'name'};
-                                    $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]','<i>'."$itemid ($oldname)".'</i>')).'</li>';
                                 }
+                            } else {
+                                $lti_save_error = 1;
                             }
-                        } else {
+                        }
+                        unless ($lti_save_error) {
+                            my @deletions;
+                            foreach my $id (sort { $a <=> $b } keys(%{$changes->{$item}})) {
+                                unless (ref($changes->{$item}->{$id}) eq 'HASH') {
+                                    push (@deletions,$id);
+                                }
+                            }
+                            if (@deletions) {
+                                &Apache::lonnet::del('nohist_ltienc',\@deletions,$cdom,$cnum);
+                            }
+                        }
+                        if ($lti_save_error) {
                             $output .= '<li>'.
                                        '<span class="LC_error">'.
                                        &mt('An error occurred when saving changes to link protection settings, which remain unchanged.').
@@ -2375,11 +2479,45 @@
 }
 ENDSCRIPT
     }
+    my $linkprotector_js = <<"ENDSCRIPT";
+function toggleLTI(form,num,item) {
+    var radioname = '';
+    var currdivid = '';
+    var newdivid = '';
+    if ((document.getElementById('linkprot_divcurr'+item+'_'+num)) &&
+        (document.getElementById('linkprot_divchg'+item+'_'+num))) {
+        currdivid = document.getElementById('linkprot_divcurr'+item+'_'+num);
+        newdivid = document.getElementById('linkprot_divchg'+item+'_'+num);
+        radioname = form.elements['linkprot_change'+item+'_'+num];
+        if (radioname) {
+            if (radioname.length > 0) {
+                var setvis;
+                for (var i=0; i<radioname.length; i++) {
+                    if (radioname[i].checked == true) {
+                        if (radioname[i].value == 1) {
+                            newdivid.style.display = 'inline-block';
+                            currdivid.style.display = 'none';
+                            setvis = 1;
+                        }
+                        break;
+                    }
+                }
+                if (!setvis) {
+                    newdivid.style.display = 'none';
+                    currdivid.style.display = 'inline-block';
+                }
+            }
+        }
+    }
+    return;
+}
+ENDSCRIPT
     $jscript = '<script type="text/javascript" language="Javascript">'."\n".
                '// <![CDATA['."\n".  
                $browse_js."\n".$categorize_js."\n".$loncaparev_js."\n".
                $cloners_js."\n".$instcode_js.
-               $syllabus_js."\n".$menuitems_js."\n".'//]]>'."\n".
+               $syllabus_js."\n".$menuitems_js."\n".
+               $linkprotector_js."\n".'//]]>'."\n".
                '</script>'."\n".$stubrowse_js."\n";
     return $jscript;
 }
@@ -5268,7 +5406,7 @@
 }
 
 sub print_linkprotection {
-    my ($cdom,$settings,$rowtotal,$crstype,$noedit) = @_;
+    my ($cdom,$cnum,$settings,$rowtotal,$crstype,$noedit) = @_;
     unless (ref($settings) eq 'HASH') {
         return;
     }
@@ -5283,6 +5421,8 @@
     my %lt = &linkprot_names();
     my $itemcount = 0;
 
+    my $switchserver = &check_switchserver($cdom,$cnum);
+
     if (ref($settings->{'linkprotection'}) eq 'HASH') {
         if (keys(%{$settings->{'linkprotection'}})) {
             my @current = sort { $a <=> $b } keys(%{$settings->{'linkprotection'}});
@@ -5307,20 +5447,65 @@
                     '<td><span class="LC_nobreak">'.$lt{'name'}.
                     ':<input type="text" size="15" name="linkprot_name_'.$i.'" value="'.$values{'name'}.'" autocomplete="off"'.$disabled.' /></span> '.
                     (' 'x2).
-                    '<span class="LC_nobreak">'.$lt{'version'}.':<select name="linkprot_version_'.$i.'">'.
+                    '<span class="LC_nobreak">'.$lt{'version'}.':<select name="linkprot_version_'.$i.'"'.$disabled.'>'.
                     '<option value="LTI-1p0" '.$selected.'>1.1</option></select></span> '."\n".
                     (' 'x2).
                     '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="linkprot_lifetime_'.$i.'"'.
                     'value="'.$values{'lifetime'}.'" size="3"'.$disabled.' /></span>'.
-                    '<br /><br />'.
-                    '<span class="LC_nobreak">'.$lt{'key'}.
-                    ':<input type="text" size="25" name="linkprot_key_'.$i.'" value="'.$values{'key'}.'" autocomplete="off"'.$disabled.' /></span> '.
-                    (' 'x2).
-                    '<span class="LC_nobreak">'.$lt{'secret'}.':'.
-                    '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="'.$values{'secret'}.'" autocomplete="off"'.$disabled.' />'.
-                    '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>'.
-                    '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span>'.
-                    '</td></tr>';
+                    '<br /><br />';
+                if ($values{'key'} ne '') {
+                    $datatable .= '<span class="LC_nobreak">'.$lt{'key'};
+                    if ($noedit) {
+                        $datatable .= ': ['.&mt('not shown').']';
+                    } elsif ($switchserver) {
+                        $datatable .= ': ['.&mt('[_1] to view/edit',$switchserver).']';
+                    } else {
+                        $datatable .= ':<input type="text" size="25" name="linkprot_key_'.$i.'" value="'.$values{'key'}.'" autocomplete="off"'.$disabled.' />';
+                    }
+                    $datatable .= '</span> '.(' 'x2);
+                } elsif (!$switchserver) {
+                    $datatable .= '<span class="LC_nobreak">'.$lt{'key'}.':'.
+                                  '<input type="text" size="25" name="linkprot_key_'.$i.'" value="'.$values{'key'}.'" autocomplete="off"'.$disabled.' />'.
+                                  '</span> '.(' 'x2);
+                }
+                if ($switchserver) {
+                    if ($values{'usable'} ne '') {
+                        $datatable .= '<div id="linkprot_divcurrsecret_'.$i.'" style="display:inline-block" /><span class="LC_nobreak">'.
+                                      $lt{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'</span></div>'.
+                                      '<span class="LC_nobreak">'.&mt('Change secret?').
+                                      '<label><input type="radio" value="0" name="linkprot_changesecret_'.$i.'" onclick="javascript:toggleLTI(this.form,'."'$i','secret'".');" checked="checked"'.$disabled.' />'.&mt('No').'</label>'.
+                                      (' 'x2).
+                                      '<label><input type="radio" value="1" name="linkprot_changesecret_'.$i.'" onclick="javascript:toggleLTI(this.form,'."'$i','secret'".');" '.$disabled.' />'.&mt('Yes').'</label>'.(' 'x2).
+                                      '</span><div id="linkprot_divchgsecret_'.$i.'" style="display:none" />'.
+                                      '<span class="LC_nobreak"> - '.&mt("submit from course's home server: [_1].",$switchserver).'</span>'.
+                                      '</div>';
+                    } elsif ($values{'key'} eq '') {
+                        $datatable .= '<span class="LC_nobreak">'.&mt('Key and Secret are required').' - '.&mt("submit from course's home server: [_1].",$switchserver).'</span>'."\n";
+                    } else {
+                        $datatable .= '<span class="LC_nobreak">'.&mt('Secret required').' - '.&mt("submit from course's home server: [_1].",$switchserver).'</span>'."\n";
+                    }
+                } else {
+                    if ($values{'usable'} ne '') {
+                        $datatable .= '<div id="linkprot_divcurrsecret_'.$i.'" style="display:inline-block" /><span class="LC_nobreak">'.
+                                      $lt{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'</span></div>'.
+                                      '<span class="LC_nobreak">'.&mt('Change?').
+                                      '<label><input type="radio" value="0" name="linkprot_changesecret_'.$i.'" onclick="javascript:toggleLTI(this.form,'."'$i','secret'".');" checked="checked"'.$disabled.' />'.&mt('No').'</label>'.
+                                      (' 'x2).
+                                      '<label><input type="radio" value="1" name="linkprot_changesecret_'.$i.'" onclick="javascript:toggleLTI(this.form,'."'$i','secret'".');"'.$disabled.' />'.&mt('Yes').
+                                      '</label>  </span><div id="linkprot_divchgsecret_'.$i.'" style="display:none" />'.
+                                      '<span class="LC_nobreak">'.&mt('New Secret').':'.
+                                      '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="off"'.$disabled.' />'.
+                                      '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>'.
+                                      '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span></div>';
+                    } else {
+                        $datatable .=
+                            '<span class="LC_nobreak">'.$lt{'secret'}.':'.
+                            '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="off"'.$disabled.' />'.
+                            '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>'.
+                            '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span>';
+                    }
+                }
+                $datatable .= '</td></tr>';
                 $itemcount ++;
             }
         }
@@ -5337,12 +5522,16 @@
                   '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
                   (' 'x2).
                   '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="3" name="linkprot_lifetime_add" value="300"'.$disabled.' /></span> '."\n".
-                  '<br /><br />'.
-                  '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="linkprot_key_add" value="" autocomplete="off"'.$disabled.' /></span> '."\n".
-                  (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" autocomplete="off"'.$disabled.' />'.
-                  '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_add.type='."'text'".' } else { this.form.linkprot_secret_add.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label></span> '."\n".
-                  '</td></tr>';
+                  '<br /><br />';
+    if ($switchserver) {
+        $datatable .= '<span class="LC_nobreak">'.&mt('Key and Secret are required').' - '.&mt("submit from course's home server: [_1].",$switchserver).'</span>'."\n";
+    } else {
+        $datatable .= '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="linkprot_key_add" value="" autocomplete="off"'.$disabled.' /></span> '."\n".
+                      (' 'x2).
+                      '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" autocomplete="off"'.$disabled.' />'.
+                      '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_add.type='."'text'".' } else { this.form.linkprot_secret_add.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label></span> '."\n";
+    }
+    $datatable .= '</td></tr>';
     $$rowtotal ++;
     return $datatable;;
 }
@@ -5358,6 +5547,22 @@
     return %lt;
 }
 
+sub check_switchserver {
+    my ($cdom,$cnum) = @_;
+    my ($allowed,$switchserver);
+    my $home = &Apache::lonnet::homeserver($cnum,$cdom);
+    unless ($home eq 'no_host') {
+        my @ids=&Apache::lonnet::current_machine_ids();
+        foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } }
+        if (!$allowed) {
+            $switchserver='<a href="/adm/switchserver?otherserver='.$home.'&role='.
+                          &HTML::Entities::encode($env{'request.role'},'\'<>"&').
+                          '&destinationurl=/adm/courseprefs">'.&mt('Switch Server').'</a>';
+        }
+    }
+    return $switchserver;
+}
+
 sub print_other {
     my ($cdom,$settings,$allitems,$rowtotal,$crstype,$noedit) = @_;
     unless ((ref($settings) eq 'HASH') && (ref($allitems) eq 'ARRAY')) {
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.55 loncom/interface/lonconfigsettings.pm:1.56
--- loncom/interface/lonconfigsettings.pm:1.55	Tue Dec 28 02:20:07 2021
+++ loncom/interface/lonconfigsettings.pm	Tue Feb  1 23:13:19 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.55 2021/12/28 02:20:07 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.56 2022/02/01 23:13:19 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -282,13 +282,30 @@
                 $additem = {'add_entries' => \%loaditems,};
             }
         } elsif ($context eq 'course') {
+            my $onload;
             if (grep(/^courseinfo$/, at actions)) {
-                if (@code_order) { 
-                    $additem = {
-                                   add_entries => {'onload' => "courseSet('','load');toggleCloners(document.display.cloners_instcode);"},
-                               };
+                if (@code_order) {
+                    $onload = "courseSet('','load');toggleCloners(document.display.cloners_instcode);";
+                }
+            }
+            if (grep(/^linkprotection$/, at actions)) {
+                if (ref($values) eq 'HASH') {
+                    if (ref($values->{'linkprotection'}) eq 'HASH') {
+                        my $posslti = scalar(keys(%{$values->{'linkprotection'}}));
+                        for (my $i=0; $i<$posslti; $i++) {
+                            if ($values->{'linkprotection'}->{'usable'}) {
+                                $onload .= "toggleLTI(document.display,'$i','secret');";
+                            }
+                        }
+                    }
                 }
             }
+            if ($onload) {
+                my %loaditems = (
+                                  'onload' => $onload,
+                                );
+                $additem = {'add_entries' => \%loaditems,};
+            }
         }
     }
     $r->print(&Apache::loncommon::start_page($pagetitle,$js,$additem));
@@ -495,7 +512,7 @@
                                 $phase,$item,$prefs->{$item},$settings);
                     } else {
                         ($output{$item},$rowtotal{$item}) =
-                            &Apache::courseprefs::print_config_box($r,$dom,$phase,
+                            &Apache::courseprefs::print_config_box($r,$dom,$confname,$phase,
                                 $item,$prefs->{$item},$values,$allitems,$crstype,$parm_permission);
                     }
                     $rowsum += $rowtotal{$item};
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1478 loncom/lonnet/perl/lonnet.pm:1.1479
--- loncom/lonnet/perl/lonnet.pm:1.1478	Tue Feb  1 19:10:54 2022
+++ loncom/lonnet/perl/lonnet.pm	Tue Feb  1 23:13:20 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1478 2022/02/01 19:10:54 raeburn Exp $
+# $Id: lonnet.pm,v 1.1479 2022/02/01 23:13:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -12266,6 +12266,55 @@
     return %courselti;
 }
 
+sub courselti_itemid {
+    my ($cnum,$cdom,$url,$method,$params,$context) = @_;
+    my ($chome,$itemid);
+    $chome = &homeserver($cnum,$cdom);
+    return if ($chome eq 'no_host');
+    if (ref($params) eq 'HASH') {
+        my $items = &freeze_escape($params);
+        my $rep;
+        if (grep { $_ eq $chome } current_machine_ids()) {
+            $rep = LONCAPA::Lond::crslti_itemid($cdom,$cnum,$url,$method,$params,$perlvar{'lonVersion'});
+        } else {
+            my $escurl = &escape($url);
+            my $escmethod = &escape($method);
+            my $items = &freeze_escape($params);
+            $rep = &reply("encrypt:lti:$cdom:$cnum:$context:$escurl:$escmethod:$items",$chome);
+        }
+        unless (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+                ($rep eq 'unknown_cmd')) {
+            $itemid = $rep;
+        }
+    }
+    return $itemid;
+}
+
+sub domainlti_itemid {
+    my ($cdom,$url,$method,$params,$context) = @_;
+    my ($primary_id,$itemid);
+    $primary_id = &domain($cdom,'primary');
+    return if ($primary_id eq '');
+    if (ref($params) eq 'HASH') {
+        my $items = &freeze_escape($params);
+        my $rep;
+        if (grep { $_ eq $primary_id } current_machine_ids()) {
+            $rep = LONCAPA::Lond::domlti_itemid($cdom,$context,$url,$method,$params,$perlvar{'lonVersion'});
+        } else {
+            my $cnum = '';
+            my $escurl = &escape($url);
+            my $escmethod = &escape($method);
+            my $items = &freeze_escape($params);
+            $rep = &reply("encrypt:lti:$cdom:$cnum:$context:$escurl:$escmethod:$items",$primary_id);
+        }
+        unless (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+                ($rep eq 'unknown_cmd')) {
+            $itemid = $rep;
+        }
+    }
+    return $itemid;
+}
+
 sub get_numsuppfiles {
     my ($cnum,$cdom,$ignorecache)=@_;
     my $hashid=$cnum.':'.$cdom;
Index: loncom/lti/ltiauth.pm
diff -u loncom/lti/ltiauth.pm:1.29 loncom/lti/ltiauth.pm:1.30
--- loncom/lti/ltiauth.pm:1.29	Tue Feb  1 19:54:36 2022
+++ loncom/lti/ltiauth.pm	Tue Feb  1 23:13:20 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Basic LTI Authentication Module
 #
-# $Id: ltiauth.pm,v 1.29 2022/02/01 19:54:36 raeburn Exp $
+# $Id: ltiauth.pm,v 1.30 2022/02/01 23:13:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -31,7 +31,6 @@
 use strict;
 use LONCAPA qw(:DEFAULT :match);
 use Apache::Constants qw(:common :http);
-use Net::OAuth;
 use Apache::lonlocal;
 use Apache::lonnet;
 use Apache::loncommon;
@@ -127,20 +126,6 @@
                     my $intdom = &Apache::lonnet::internet_dom($primary_id);
                     if (($intdom ne '') && (grep(/^\Q$intdom\E$/, at intdoms))) {
 #
-# Retrieve information for LTI link protectors in course
-# where url was /adm/launch/tiny/$cdom/$uniqueid
-#
-                        my (%crslti,%crslti_by_key,$itemid,$ltitype);
-                        %crslti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
-                        if (keys(%crslti)) {
-                            foreach my $id (keys(%crslti)) {
-                                if (ref($crslti{$id}) eq 'HASH') {
-                                    my $key = $crslti{$id}{'key'};
-                                    push(@{$crslti_by_key{$key}},$id);
-                                }
-                            }
-                        }
-#
 # Verify the signed request using the secret for LTI link
 # protectors for which the key in the POSTed data matches
 # keys in the course configuration.
@@ -153,8 +138,14 @@
 # Determine if nonce in POSTed data has expired.
 # If unexpired, confirm it has not already been used.
 #
-                        if (keys(%crslti)) {
-                            $itemid = &get_lti_itemid($requri,$hostname,$params,\%crslti,\%crslti_by_key);
+# Retrieve information for LTI link protectors in course
+# where url was /adm/launch/tiny/$cdom/$uniqueid
+#
+
+                        my ($itemid,$ltitype,%crslti);
+                        $itemid = &get_lti_itemid($requri,$hostname,$params,$cdom,$cnum,'deeplink');
+                        if ($itemid) {
+                            %crslti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
                         }
                         if (($itemid) && (ref($crslti{$itemid}) eq 'HASH')) {
                             $ltitype = 'c';
@@ -164,30 +155,17 @@
                                 return OK;
                             }
                         } else {
-                            my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
-                            unless (keys(%lti) > 0) {
-                                &invalid_request($r,4);
-                                return OK;
-                            }
-                            my (%domlti_by_key,%domlti);
-                            foreach my $id (keys(%lti)) {
-                                if (ref($lti{$id}) eq 'HASH') {
-                                    my $key = $lti{$id}{'key'};
-                                    if (!$lti{$itemid}{'requser'}) {
-                                        push(@{$domlti_by_key{$key}},$id);
-                                        $domlti{$id} = $lti{$id};
-                                    }
-                                }
-                            }
-                            if (keys(%domlti)) {
-                                $itemid = &get_lti_itemid($requri,$hostname,$params,\%domlti,\%domlti_by_key);
+                            my %lti;
+                            $itemid = &get_lti_itemid($requri,$hostname,$params,$cdom,'','deeplink');
+                            if ($itemid) {
+                                %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
                             }
-                            if (($itemid) && (ref($domlti{$itemid}) eq 'HASH')) {
+                            if (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
                                 $ltitype = 'd';
                                 unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
-                                                                        $domlti{$itemid}{'lifetime'},$cdom,
+                                                                        $lti{$itemid}{'lifetime'},$cdom,
                                                                         $r->dir_config('lonLTIDir'))) {
-                                    &invalid_request($r,5);
+                                    &invalid_request($r,4);
                                     return OK;
                                 }
                             }
@@ -202,22 +180,22 @@
                                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                                 $r->set_handlers('PerlHandler'=> undef);
                             } else {
-                                &invalid_request($r,6);
+                                &invalid_request($r,5);
                             }
                         } else {
-                            &invalid_request($r,7);
+                            &invalid_request($r,6);
                         }
                     } else {
-                        &invalid_request($r,8);
+                        &invalid_request($r,7);
                     }
                 } else {
-                    &invalid_request($r,9);
+                    &invalid_request($r,8);
                 }
             } else {
-                &invalid_request($r,10);
+                &invalid_request($r,9);
             }
         } else {
-            &invalid_request($r,11);
+            &invalid_request($r,10);
         }
         return OK;
     }
@@ -318,7 +296,7 @@
         if ($tail =~ m{^/uploaded/($match_domain)/($match_courseid)/(?:default|supplemental)(?:|_\d+)\.(?:sequence|page)(|___\d+___.+)$}) {
             ($urlcdom,$urlcnum,my $rest) = ($1,$2,$3);
             if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,12);
+                &invalid_request($r,11);
                 return OK;
             }
             if ($rest eq '') {
@@ -337,13 +315,13 @@
         } elsif ($tail =~ m{^/($match_domain)/($match_courseid)$}) {
             ($urlcdom,$urlcnum) = ($1,$2);
             if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,13);
+                &invalid_request($r,12);
                 return OK;
             }
         } elsif ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
             ($urlcdom,$urlcnum) = &course_from_tinyurl($tail);
             if (($urlcdom eq '') || ($urlcnum eq '')) {
-                &invalid_request($r,14);
+                &invalid_request($r,13);
                 return OK;
             }
         }
@@ -369,33 +347,19 @@
 
 #
 # Retrieve information for LTI Consumers in course's domain
-# and populate hash --  %lti_by_key -- for which keys
-# are those defined in domain configuration for LTI.
-#
- 
-    my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
-    unless (keys(%lti) > 0) {
-        &invalid_request($r,15);
-        return OK;
-    }
-    my %lti_by_key;
-    if (keys(%lti)) {
-        foreach my $id (keys(%lti)) {
-            if (ref($lti{$id}) eq 'HASH') {
-                my $key = $lti{$id}{'key'};
-                push(@{$lti_by_key{$key}},$id);
-            }
-        }
-    }
-
+# defined in domain configuration for LTI.
 #
 # Verify the signed request using the secret for those
-# Consumers for which the key in the POSTed data matches 
+# Consumers for which the key in the POSTed data matches
 # keys in the course configuration or the domain configuration
 # for LTI.
 #
 
-    my $itemid = &get_lti_itemid($requri,$hostname,$params,\%lti,\%lti_by_key);
+    my %lti;
+    my $itemid = &get_lti_itemid($requri,$hostname,$params,$cdom);
+    if ($itemid) {
+        %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
+    }
 
 #
 # Request is invalid if the signed request could not be verified
@@ -403,7 +367,7 @@
 # configuration in LON-CAPA for that LTI Consumer.
 #
     unless (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
-        &invalid_request($r,16);
+        &invalid_request($r,14);
         return OK;
     }
 
@@ -413,7 +377,7 @@
 #
     unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
                                             $lti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
-        &invalid_request($r,17);
+        &invalid_request($r,15);
         return OK;
     }
 
@@ -434,10 +398,10 @@
                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                 $r->set_handlers('PerlHandler'=> undef);
             } else {
-                &invalid_request($r,18);
+                &invalid_request($r,16);
             }
         } else {
-            &invalid_request($r,19);
+            &invalid_request($r,17);
         }
         return OK;
     }
@@ -499,7 +463,7 @@
                 my $storedcnum = $1;
                 my $crshome = &Apache::lonnet::homeserver($storedcnum,$cdom);
                 if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                    &invalid_request($r,20);
+                    &invalid_request($r,18);
                     return OK;
                 } else {
                     $posscnum = $storedcnum;
@@ -511,7 +475,7 @@
     if ($urlcnum ne '') {
         if ($posscnum ne '') {
             if ($posscnum ne $urlcnum) {
-                &invalid_request($r,21);
+                &invalid_request($r,19);
                 return OK;
             } else {
                 $cnum = $posscnum;
@@ -519,7 +483,7 @@
         } else {
             my $crshome = &Apache::lonnet::homeserver($urlcnum,$cdom);
             if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                &invalid_request($r,22);
+                &invalid_request($r,20);
                 return OK;
             } else {
                 $cnum = $urlcnum;
@@ -584,7 +548,7 @@
                                                     $domdesc,\%data,\%alerts,\%rulematch,
                                                     \%inst_results,\%curr_rules,%got_rules);
                 if ($result eq 'notallowed') {
-                    &invalid_request($r,23);
+                    &invalid_request($r,21);
                 } elsif ($result eq 'ok') {
                     if (($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'mapcrs'}) &&
                         ($lti{$itemid}{'makecrs'})) {
@@ -593,16 +557,16 @@
                         }
                     }
                 } else {
-                    &invalid_request($r,24);
+                    &invalid_request($r,22);
                     return OK;
                 }
             } else {
-                &invalid_request($r,25);
+                &invalid_request($r,23);
                 return OK;
             }
         }
     } else {
-        &invalid_request($r,26);
+        &invalid_request($r,24);
         return OK;
     }
 
@@ -625,10 +589,10 @@
                                  $symb,$cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,
                                  $reqcrs,$sourcecrs);
                 } else {
-                    &invalid_request($r,27);
+                    &invalid_request($r,25);
                 }
             } else {
-                &invalid_request($r,28);
+                &invalid_request($r,26);
             }
         } else {
             &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,undef,$mapurl,$tail,
@@ -719,7 +683,7 @@
             }
         }
         if ($reqrole eq '') {
-            &invalid_request($r,29);
+            &invalid_request($r,27);
             return OK;
         } else {
             unless (%crsenv) {
@@ -729,10 +693,10 @@
             my $default_enrollment_end_date   = $crsenv{'default_enrollment_end_date'};
             my $now = time;
             if ($default_enrollment_end_date && $default_enrollment_end_date <= $now) {
-                &invalid_request($r,30);
+                &invalid_request($r,28);
                 return OK;
             } elsif ($default_enrollment_start_date && $default_enrollment_start_date >$now) {
-                &invalid_request($r,31);
+                &invalid_request($r,29);
                 return OK;
             } else {
                 $selfenrollrole = $reqrole.'./'.$cdom.'/'.$cnum;
@@ -794,38 +758,19 @@
 }
 
 sub get_lti_itemid {
-    my ($requri,$hostname,$params,$lti,$lti_by_key) = @_;
-    return unless ((ref($params) eq 'HASH') && (ref($lti) eq 'HASH')  && (ref($lti_by_key) eq 'HASH'));
-
-    if (exists($params->{'oauth_callback'})) {
-        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
-    } else {
-        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0;
-    }
-
+    my ($requri,$hostname,$params,$cdom,$cnum,$context) = @_;
+    return unless ((ref($params) eq 'HASH');
     my $protocol = 'http';
     if ($ENV{'SERVER_PORT'} == 443) {
         $protocol = 'https';
     }
-
-    my ($itemid,$consumer_key,$secret);
-    my $consumer_key = $params->{'oauth_consumer_key'};
-    if (ref($lti_by_key->{$consumer_key}) eq 'ARRAY') {
-        foreach my $id (@{$lti_by_key->{$consumer_key}}) {
-            if (ref($lti->{$id}) eq 'HASH') {
-                $secret = $lti->{$id}{'secret'};
-                my $request = Net::OAuth->request('request token')->from_hash($params,
-                                                   request_url => $protocol.'://'.$hostname.$requri,
-                                                   request_method => $env{'request.method'},
-                                                   consumer_secret => $secret,);
-                if ($request->verify()) {
-                    $itemid = $id;
-                    last;
-                }
-            }
-        }
+    my $url = $protocol.'://'.$hostname.$requri;
+    my $method = $env{'request.method'};
+    if ($cnum ne '') {
+        return &Apache::lonnet::courselti_itemid($cnum,$cdom,$url,$method,$params,$context);
+    } else {
+        return &Apache::lonnet::domainlti_itemid($cdom,$url,$method,$params,$context);
     }
-    return $itemid;
 }
 
 sub lti_enroll {
Index: loncom/Lond.pm
diff -u loncom/Lond.pm:1.17 loncom/Lond.pm:1.18
--- loncom/Lond.pm:1.17	Wed Jan 19 16:02:59 2022
+++ loncom/Lond.pm	Tue Feb  1 23:13:21 2022
@@ -1,6 +1,6 @@
 # The LearningOnline Network
 #
-# $Id: Lond.pm,v 1.17 2022/01/19 16:02:59 raeburn Exp $
+# $Id: Lond.pm,v 1.18 2022/02/01 23:13:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -41,6 +41,7 @@
 use Crypt::OpenSSL::X509;
 use Crypt::X509::CRL;
 use Crypt::PKCS10;
+use Net::OAuth;
 
 sub dump_with_regexp {
     my ( $tail, $clientversion ) = @_;
@@ -1043,6 +1044,159 @@
     return $qresult;
 }
 
+sub crslti_itemid {
+    my ($cdom,$cnum,$url,$method,$params,$loncaparev) = @_;
+    unless (ref($params) eq 'HASH') {
+        return;
+    }
+    if (($cdom eq '') || ($cnum eq '')) {
+        return;
+    }
+    my ($itemid,$consumer_key,$secret);
+
+    if (exists($params->{'oauth_callback'})) {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
+    } else {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0;
+    }
+
+    my $consumer_key = $params->{'oauth_consumer_key'};
+    return if ($consumer_key eq '');
+
+    my (%crslti,%crslti_by_key);
+    my $hashid=$cdom.'_'.$cnum;
+    my ($result,$cached)=&Apache::lonnet::is_cached_new('courseltienc',$hashid);
+    if (defined($cached)) {
+        if (ref($result) eq 'HASH') {
+            %crslti = %{$result};
+        }
+    } else {
+        my $reply = &dump_with_regexp(join(":",($cdom,$cnum,'nohist_ltienc','','')),$loncaparev);
+        %crslti = %{&Apache::lonnet::unserialize($reply)};
+        my $cachetime = 24*60*60;
+        &Apache::lonnet::do_cache_new('courseltienc',$hashid,\%crslti,$cachetime);
+    }
+
+    return if (!keys(%crslti));
+
+    foreach my $id (keys(%crslti)) {
+        if (ref($crslti{$id}) eq 'HASH') {
+            my $key = $crslti{$id}{'key'};
+            if (($key ne '') && ($crslti{$id}{'secret'} ne '')) {
+                push(@{$crslti_by_key{$key}},$id);
+            }
+        }
+    }
+
+    return if (!keys(%crslti_by_key));
+
+    if (ref($crslti_by_key{$consumer_key}) eq 'ARRAY') {
+        foreach my $id (@{$crslti_by_key{$consumer_key}}) {
+            my $secret = $crslti{$id}{'secret'};
+            my $request = Net::OAuth->request('request token')->from_hash($params,
+                                              request_url => $url,
+                                              request_method => $method,
+                                              consumer_secret => $secret,);
+            if ($request->verify()) {
+                $itemid = $id;
+                last;
+            }
+        }
+    }
+    return $itemid;
+}
+
+sub domlti_itemid {
+    my ($dom,$context,$url,$method,$params,$loncaparev) = @_;
+    unless (ref($params) eq 'HASH') {
+        return;
+    }
+    if ($dom eq '') {
+        return;
+    }
+    my ($itemid,$consumer_key,$secret);
+
+    if (exists($params->{'oauth_callback'})) {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
+    } else {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0;
+    }
+
+    my $consumer_key = $params->{'oauth_consumer_key'};
+    return if ($consumer_key eq '');
+
+    my %ltienc;
+    my ($encresult,$enccached)=&Apache::lonnet::is_cached_new('ltienc',$dom);
+    if (defined($enccached)) {
+        if (ref($encresult) eq 'HASH') {
+            %ltienc = %{$encresult};
+        }
+    } else {
+        my $reply = &get_dom("getdom:$dom:encconfig:lti");
+        my $ltiencref = &Apache::lonnet::thaw_unescape($reply);
+        if (ref($ltiencref) eq 'HASH') {
+            %ltienc = %{$ltiencref};
+        }
+        my $cachetime = 24*60*60;
+        &Apache::lonnet::do_cache_new('ltienc',$dom,\%ltienc,$cachetime);
+    }
+
+    return if (!keys(%ltienc));
+
+    my %lti;
+    if ($context eq 'deeplink') {
+        my ($result,$cached)=&Apache::lonnet::is_cached_new('lti',$dom);
+        if (defined($cached)) {
+            if (ref($result) eq 'HASH') {
+                %lti = %{$result};
+            }
+        } else {
+            my $reply = &get_dom("getdom:$dom:configuration:lti");
+            my $ltiref = &Apache::lonnet::thaw_unescape($reply);
+            if (ref($ltiref) eq 'HASH') {
+                %lti = %{$ltiref};
+            }
+            my $cachetime = 24*60*60;
+            &Apache::lonnet::do_cache_new('lti',$dom,\%lti,$cachetime);
+        }
+    }
+    return if (!keys(%lti));
+
+    my %lti_by_key;
+    foreach my $id (keys(%ltienc)) {
+        if (ref($ltienc{$id}) eq 'HASH') {
+            my $key = $ltienc{$id}{'key'};
+            if (($key ne '') && ($ltienc{$id}{'secret'} ne '')) {
+                if ($context eq 'deeplink') {
+                    if (ref($lti{$id}) eq 'HASH') {
+                        if (!$lti{$id}{'requser'}) {
+                            push(@{$lti_by_key{$key}},$id);
+                        }
+                    }
+                } else {
+                    push(@{$lti_by_key{$key}},$id);
+                }
+            }
+        }
+    }
+    return if (!keys(%lti_by_key));
+
+    if (ref($lti_by_key{$consumer_key}) eq 'ARRAY') {
+        foreach my $id (@{$lti_by_key{$consumer_key}}) {
+            my $secret = $ltienc{$id}{'secret'};
+            my $request = Net::OAuth->request('request token')->from_hash($params,
+                                              request_url => $url,
+                                              request_method => $method,
+                                              consumer_secret => $secret,);
+            if ($request->verify()) {
+                $itemid = $id;
+                last;
+            }
+        }
+    }
+    return $itemid;
+}
+
 1;
 
 __END__
Index: loncom/lond
diff -u loncom/lond:1.571 loncom/lond:1.572
--- loncom/lond:1.571	Tue Dec 21 13:57:47 2021
+++ loncom/lond	Tue Feb  1 23:13:21 2022
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.571 2021/12/21 13:57:47 raeburn Exp $
+# $Id: lond,v 1.572 2022/02/01 23:13:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -65,7 +65,7 @@
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.571 $'; #' stupid emacs
+my $VERSION='$Revision: 1.572 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -266,6 +266,7 @@
                ls => {remote => 1, enroll => 1, content => 1,},
                ls2 => {remote => 1, enroll => 1, content => 1,},
                ls3 => {remote => 1, enroll => 1, content => 1,},
+               lti => {institutiononly => 1},
                makeuser => {remote => 1, enroll => 1, domroles => 1,},
                mkdiruserfile => {remote => 1, enroll => 1,},
                newput => {remote => 1, enroll => 1, reqcrs => 1, domroles => 1,},
@@ -5156,6 +5157,25 @@
 }
 &register_handler("getdom", \&get_domain_handler, 0, 1, 0);
 
+#
+# Encrypted get from the namespace database file at the domain level.
+# This function retrieves a keyed item from a specific named database in the
+# domain directory.
+#
+# Parameters:
+#   $cmd             - Command request keyword (egetdom).
+#   $tail            - Tail of the command.  This is a colon separated list
+#                      consisting of the domain and the 'namespace'
+#                      which selects the gdbm file to do the lookup in,
+#                      & separated list of keys to lookup.  Note that
+#                      the values are returned as an & separated list too.
+#   $client          - File descriptor open on the client.
+# Returns:
+#   1       - Continue processing.
+#   0       - Exit.
+#  Side effects:
+#     reply is encrypted before being written to $client.
+#
 sub encrypted_get_domain_handler {
     my ($cmd, $tail, $client) = @_;
 
@@ -5185,6 +5205,71 @@
 &register_handler("egetdom", \&encrypted_get_domain_handler, 1, 1, 0);
 
 #
+# Encrypted get from the namespace database file at the domain level.
+# This function retrieves a keyed item from a specific named database in the
+# domain directory.
+#
+# Parameters:
+#   $cmd             - Command request keyword (lti).
+#   $tail            - Tail of the command.  This is a colon-separated list
+#                      consisting of the domain, coursenum, if for LTI-
+#                      enabled deep-linking to course content using
+#                      link protection configured within a course,
+#                      context (=deeplink) if for LTI-enabled deep-linking
+#                      to course content using LTI Provider settings
+#                      configured within a course's domain, the (escaped)
+#                      launch URL, the (escaped) method (typically POST),
+#                      and a frozen hash of the LTI launch parameters
+#                      from the LTI payload.
+#   $client          - File descriptor open on the client.
+# Returns:
+#   1       - Continue processing.
+#   0       - Exit.
+#  Side effects:
+#     The reply will contain an LTI itemID, if the signed LTI payload
+#     could be verified using the consumer key and the shared secret 
+#     available for that key (for the itemID) for either the course or domain, 
+#     depending on values for cnum and context. The reply is encrypted before 
+#     being written to $client.
+#
+sub lti_handler {
+    my ($cmd, $tail, $client) = @_;
+
+    my $userinput = "$cmd:$tail";
+
+    my ($cdom,$cnum,$context,$escurl,$escmethod,$items) = split(/:/,$tail);
+    my $url = &unescape($escurl);
+    my $method = &unescape($escmethod);
+    my $params = &Apache::lonnet::thaw_unescape($items);
+    my $res;
+    if ($cnum ne '') {
+        $res = &LONCAPA::Lond::crslti_itemid($cdom,$cnum,$url,$method,$params,$perlvar{'lonVersion'});
+    } else {
+        $res = &LONCAPA::Lond::domlti_itemid($cdom,$context,$url,$method,$params,$perlvar{'lonVersion'});
+    }
+    if ($res =~ /^error:/) {
+        &Failure($client, \$res, $userinput);
+    } else {
+        if ($cipher) {
+            my $cmdlength=length($res);
+            $res.="         ";
+            my $encres='';
+            for (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
+                $encres.= unpack("H16",
+                                 $cipher->encrypt(substr($res,
+                                                         $encidx,
+                                                         8)));
+            }
+            &Reply( $client,"enc:$cmdlength:$encres\n",$userinput);
+        } else {
+            &Failure( $client, "error:no_key\n",$userinput);
+        }
+    }
+    return 1;
+}
+&register_handler("lti", \&lti_handler, 1, 1, 0);
+
+#
 #  Puts an id to a domains id database. 
 #
 #  Parameters:


More information about the LON-CAPA-cvs mailing list