[LON-CAPA-cvs] cvs: loncom /auth migrateuser.pm /interface courseprefs.pm domainprefs.pm lonconfigsettings.pm lonmodifycourse.pm /lonnet/perl lonnet.pm /lti ltiauth.pm

raeburn raeburn at source.lon-capa.org
Sun Feb 6 16:37:08 EST 2022


raeburn		Sun Feb  6 21:37:08 2022 EDT

  Modified files:              
    /loncom/interface	domainprefs.pm lonconfigsettings.pm 
                     	lonmodifycourse.pm courseprefs.pm 
    /loncom/lti	ltiauth.pm 
    /loncom/auth	migrateuser.pm 
    /loncom/lonnet/perl	lonnet.pm 
  Log:
  - Bug 6907
    - Domain default to determine whether LTI launch of deep-linked URL requires
      student to authenticate; can be overridden for specific course(s).
    - If domain config permits it, link protection setting in a course can 
      specify whether to accept username included in LTI payload, and action
      to take if username is not for an enrolled student.
    - Second arg passed to ltiauth::invalid_request() is text string stating why
      LTI launch was invalid.
  
  
-------------- next part --------------
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.403 loncom/interface/domainprefs.pm:1.404
--- loncom/interface/domainprefs.pm:1.403	Wed Jan 19 16:54:16 2022
+++ loncom/interface/domainprefs.pm	Sun Feb  6 21:36:59 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.403 2022/01/19 16:54:16 raeburn Exp $
+# $Id: domainprefs.pm,v 1.404 2022/02/06 21:36:59 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -6863,6 +6863,7 @@
         postsubmit           => 'Disable submit button/keypress following student submission',
         canclone             => "People who may clone a course (besides course's owner and coordinators)",
         mysqltables          => 'Lifetime (s) of "Temporary" MySQL tables (student performance data) on homeserver',
+        ltiauth              => 'Student username in LTI launch of deep-linked URL can be accepted without re-authentication', 
     );
     my %staticdefaults = (
                            anonsurvey_threshold => 10,
@@ -6985,8 +6986,12 @@
         my ($currdefresponder,%defcredits,%curruploadquota,%deftimeout,%currmysql);
         my $currusecredits = 0;
         my $postsubmitclient = 1;
+        my $ltiauth = 0; 
         my @types = ('official','unofficial','community','textbook','placement');
         if (ref($settings) eq 'HASH') {
+            if ($settings->{'ltiauth'}) {
+                $ltiauth = 1;
+            } 
             $currdefresponder = $settings->{'anonsurvey_threshold'};
             if (ref($settings->{'uploadquota'}) eq 'HASH') {
                 foreach my $type (keys(%{$settings->{'uploadquota'}})) {
@@ -7132,7 +7137,16 @@
         }
         $datatable .= '</tr></table></td></tr>'."\n";
         $itemcount ++;
-
+        %defaultchecked = ('ltiauth' => 'off');
+        @toggles = ('ltiauth');
+        $current = {
+                       'ltiauth' => $ltiauth,
+                   };
+        ($table,$itemcount) =
+            &radiobutton_prefs($current,\@toggles,\%defaultchecked,
+                               \%choices,$itemcount,undef,undef,'left');
+        $datatable .= $table;
+        $itemcount ++;
     }
     $$rowtotal += $itemcount;
     return $datatable;
@@ -20447,8 +20461,9 @@
                            'uselcmath'       => 'on',
                            'usejsme'         => 'on',
                            'inline_chem'     => 'on',
+                           'ltiauth'         => 'off',
                          );
-    my @toggles = ('canuse_pdfforms','uselcmath','usejsme','inline_chem');
+    my @toggles = ('canuse_pdfforms','uselcmath','usejsme','inline_chem','ltiauth');
     my @numbers = ('anonsurvey_threshold','uploadquota_official','uploadquota_unofficial',
                    'uploadquota_community','uploadquota_textbook','uploadquota_placement',
                    'mysqltables_official','mysqltables_unofficial','mysqltables_community',
@@ -20659,8 +20674,8 @@
             if (($changes{'canuse_pdfforms'}) || ($changes{'uploadquota'}) || ($changes{'postsubmit'}) ||
                 ($changes{'coursecredits'}) || ($changes{'uselcmath'}) || ($changes{'usejsme'}) ||
                 ($changes{'canclone'}) || ($changes{'mysqltables'}) || ($changes{'texengine'}) ||
-                ($changes{'inline_chem'})) {
-                foreach my $item ('canuse_pdfforms','uselcmath','usejsme','inline_chem','texengine') {
+                ($changes{'inline_chem'}) || ($changes{'ltiauth'})) {
+                foreach my $item ('canuse_pdfforms','uselcmath','usejsme','inline_chem','texengine','ltiauth') {
                     if ($changes{$item}) {
                         $domdefaults{$item}=$defaultshash{'coursedefaults'}{$item};
                     }
@@ -20834,6 +20849,12 @@
                     } else {
                         $resulttext .= '<li>'.&mt('By default, only course owner and coordinators may clone a course.').'</li>';
                     }
+                } elsif ($item eq 'ltiauth') {
+                    if ($env{'form.'.$item} eq '1') {
+                        $resulttext .= '<li>'.&mt('LTI launch of deep-linked URL need not require re-authentication').'</li>';
+                    } else {
+                        $resulttext .= '<li>'.&mt('LTI launch of deep-linked URL will require re-authentication').'</li>';
+                    }
                 }
             }
             $resulttext .= '</ul>';
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.56 loncom/interface/lonconfigsettings.pm:1.57
--- loncom/interface/lonconfigsettings.pm:1.56	Tue Feb  1 23:13:19 2022
+++ loncom/interface/lonconfigsettings.pm	Sun Feb  6 21:36:59 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.56 2022/02/01 23:13:19 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.57 2022/02/06 21:36:59 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -291,10 +291,27 @@
             if (grep(/^linkprotection$/, at actions)) {
                 if (ref($values) eq 'HASH') {
                     if (ref($values->{'linkprotection'}) eq 'HASH') {
+                        my $ltiauth;
+                        if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) {
+                            $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'};
+                        } else {
+                            my %domdefs = &Apache::lonnet::get_domain_defaults($dom);
+                            $ltiauth = $domdefs{'crsltiauth'};
+                        }
                         my $posslti = scalar(keys(%{$values->{'linkprotection'}}));
-                        for (my $i=0; $i<$posslti; $i++) {
-                            if ($values->{'linkprotection'}->{'usable'}) {
-                                $onload .= "toggleLTI(document.display,'$i','secret');";
+                        for (my $i=0; $i<=$posslti; $i++) {
+                            my $num = $i;
+                            if ($i == $posslti) {
+                                $num = 'add';
+                            }
+                            if (ref($values->{'linkprotection'}->{$i}) eq 'HASH') {
+                                if ($values->{'linkprotection'}->{$i}->{'usable'}) {
+                                    $onload .= "toggleLTI(document.display,'$num','secret');";
+                                }
+                            } 
+                            if ($ltiauth) {
+                                $onload .= "toggleLTIReqUser(document.display,'requser','optional','1','block','$num');".
+                                           "toggleLTIReqUser(document.display,'mapuser','userfield','other','inline-block','$num');";
                             }
                         }
                     }
Index: loncom/interface/lonmodifycourse.pm
diff -u loncom/interface/lonmodifycourse.pm:1.96 loncom/interface/lonmodifycourse.pm:1.97
--- loncom/interface/lonmodifycourse.pm:1.96	Tue Jun 15 20:52:26 2021
+++ loncom/interface/lonmodifycourse.pm	Sun Feb  6 21:36:59 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # handler for DC-only modifiable course settings
 #
-# $Id: lonmodifycourse.pm,v 1.96 2021/06/15 20:52:26 raeburn Exp $
+# $Id: lonmodifycourse.pm,v 1.97 2022/02/06 21:36:59 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -430,6 +430,7 @@
                       'setanon'       => 'View/Modify responders threshold for anonymous survey submissions display',
                       'selfenroll'    => 'View/Modify Self-Enrollment configuration',
                       'setpostsubmit' => 'View/Modify submit button behavior, post-submission',
+                      'setltiauth'    => 'View/Modify re-authentication requirement for LTI launch of deep-linked item',
                     );
     } else {
         %linktext = (
@@ -437,6 +438,7 @@
                       'setanon'       => 'View responders threshold for anonymous survey submissions display',
                       'selfenroll'    => 'View Self-Enrollment configuration',
                       'setpostsubmit' => 'View submit button behavior, post-submission',
+                      'setltiauth'    => 'View re-authentication requirement for LTI launch of deep-linked item',
                     );
     }
     if ($type eq 'Community') {
@@ -475,6 +477,7 @@
     my $anon_text = &mt('Responder threshold required to display anonymous survey submissions.');
     my $postsubmit_text = &mt('Override defaults for submit button behavior post-submission for this specific course.'); 
     my $mysqltables_text = &mt('Override default for lifetime of "temporary" MySQL tables containing student performance data.');
+    my $ltiauth_text = &mt('Override default for requirement for re-authentication for LTI-limited launch of deep-linked item');
     $linktext{'viewparms'} = 'Display current settings for automated enrollment';
 
     my %domconf = &Apache::lonnet::get_dom('configuration',['coursecategories'],$dom);
@@ -556,6 +559,14 @@
                 permission => $permission->{'setpostsubmit'},
                 linktitle => '',
             },
+            {
+                linktext => $linktext{'setltiauth'},
+                icon => 'system-lock-screen.png',
+                #help => '',
+                url => &phaseurl('setltiauth'),
+                permission => $permission->{'setltiauth'},
+                linktitle => '',
+            },
         ]
         },
         );
@@ -586,7 +597,8 @@
                   '<li>'.$setquota_text.'</li>'."\n".
                   '<li>'.$setuploadquota_text.'</li>'."\n".
                   '<li>'.$anon_text.'</li>'."\n".
-                  '<li>'.$postsubmit_text.'</li>'."\n";
+                  '<li>'.$postsubmit_text.'</li>'."\n".
+                  '<li>'.$ltiauth_text.'</li>'."\n";
     my ($categories_link_start,$categories_link_end);
     if ($permission->{'catsettings'} eq 'edit') {
         $categories_link_start = '<a href="/adm/domainprefs?actions=coursecategories&phase=display">';
@@ -1261,6 +1273,79 @@
     return;
 }
 
+sub print_set_ltiauth {
+    my ($r,$cdom,$cnum,$cdesc,$type,$readonly) = @_;
+    my %lt = &Apache::lonlocal::texthash(
+                'requ' => 'Requirement for re-authentication for student LTI-limited launch of deep-linked item',
+                'link' => 'Link protection can be set to accept username for an enrolled student (if sent by Consumer)',
+                'logi' => 'Login needed, regardless of user information sent by LTI Consumer in (signed) parameters',
+                'used' => 'Use domain default',
+                'cour' => 'Use course-specific setting',
+                'curd' => 'Current domain default is',
+                'valu' => 'Value for this course',
+                'modi' => 'Save',
+                'back' => 'Pick another action',
+    );
+    my ($domdef,$checkeddom,$checkedcrs,$domdefdisplay,$divsty,$authok,$authno);
+    $domdef = 0;
+    $checkeddom = 'checked="checked "';
+    $domdefdisplay = $lt{'logi'}; 
+    $divsty = 'display:none';
+    $authno = 'checked="checked" ';
+    my %domconfig =
+        &Apache::lonnet::get_dom('configuration',['coursedefaults'],$cdom);
+    if (ref($domconfig{'coursedefaults'}) eq 'HASH') {
+        $domdef = $domconfig{'coursedefaults'}{'ltiauth'};
+    }
+    if ($domdef) {
+        $domdefdisplay = $lt{'link'};
+    }
+    my %settings = &Apache::lonnet::get('environment',['internal.ltiauth'],$cdom,$cnum);
+    my $ltiauth = $settings{'internal.ltiauth'};
+
+    if ($ltiauth ne '') {
+        $checkedcrs = $checkeddom;
+        $checkeddom = '';
+        $divsty = 'display:inline-block';
+        if ($ltiauth) {
+            $authok = 'checked="checked "';
+        }
+    }
+    &print_header($r,$type);
+    my $hidden_elements = &hidden_form_elements();
+    my ($disabled,$submit);
+    if ($readonly) {
+        $disabled = ' disabled="disabled"';
+    } else {
+        $submit = '<input type="button" onclick="javascript:changePage(this.form,'."'processltiauth'".');" value="'.$lt{'modi'}.'" />';
+    }
+    my $helpitem = &Apache::loncommon::help_open_topic('Modify_Course_LTI_Authen');
+    $r->print(<<ENDDOCUMENT);
+<form action="/adm/modifycourse" method="post" name="setltiauth">
+<h3>$helpitem $lt{'requ'} <span class="LC_nobreak">$cdesc</span></h3>
+<p><span class="LC_nobreak">$lt{'curd'}: <span style="font-style:italic">'.$domdefdisplay</span></span</p>
+<p><span class="LC_nobreak">
+<label><input type="radio" name="ltiauthset" value="dom" onclick="toggleLTIOptions(this.form);" $checkeddom$disabled/>$lt{'used'}</label></span><br />
+<span class="LC_nobreak">
+<label><input type="radio" name="ltiauthset" value="course" onclick="toggleLTIOptions(this.form);" $checkedcrs$disabled/>$lt{'cour'}</label></span>
+<fieldset id="crsltiauth" style="$divsty">
+<legend>$lt{'valu'}</legend>
+<span class="LC_nobreak">
+<label><input type="radio" name="ltiauth" value="0" $authno$disabled/>$lt{'logi'}</label>
+</span><br />
+<span class="LC_nobreak">
+<label><input type="radio" name="ltiauth" value="1" $authok$disabled/>$lt{'link'}</label>
+</span>
+</fieldset>
+$submit
+</p>
+$hidden_elements
+<a href="javascript:changePage(document.setltiauth,'menu')">$lt{'back'}</a>
+</form>
+ENDDOCUMENT
+    return;
+}
+
 sub modify_selfenrollconfig {
     my ($r,$type,$cdesc,$coursehash) = @_;
     return unless(ref($coursehash) eq 'HASH');
@@ -2258,6 +2343,109 @@
     return;
 }
 
+sub modify_ltiauth {
+    my ($r,$cdom,$cnum,$cdesc,$domdesc,$type) = @_;
+    my %lt = &Apache::lonlocal::texthash(
+                'requ' => 'Requirement for re-authentication for student LTI-limited launch of deep-linked item',
+                'link' => 'Link protection can be set to accept username for an enrolled student (if sent by Consumer)',
+                'logi' => 'Login needed, regardless of user information sent by LTI Consumer in (signed) parameters',
+                'used' => 'Use domain default',
+                'cour' => 'Use course-specific setting',
+                'modi' => 'Save',
+                'back' => 'Pick another action',
+    );
+    &print_header($r,$type);
+    $r->print('<form action="/adm/modifycourse" method="post" name="processltiauth">'."\n".
+              '<h3>'.$lt{'requ'}.
+              ' <span class="LC_nobreak">'.$cdesc.'</span></h3>');
+    my %oldsettings = &Apache::lonnet::get('environment',['internal.ltiauth'],$cdom,$cnum);
+    my $oldltiauth = $oldsettings{'internal.ltiauth'};
+    my $domdef;
+    my %domconfig =
+        &Apache::lonnet::get_dom('configuration',['coursedefaults'],$cdom);
+    if (ref($domconfig{'coursedefaults'}) eq 'HASH') {
+        $domdef = $domconfig{'coursedefaults'}{'ltiauth'};
+    }
+    my ($newltiauth,$nochange,$change,$status,$error,$ltiauth);
+    if ($env{'form.ltiauthset'} eq 'dom') {
+        if ($oldltiauth eq '') {
+            $nochange = 1;
+        } else {
+            $change = 1;
+        }
+    } elsif ($env{'form.ltiauthset'} eq 'course') {
+        if ($env{'form.ltiauth'} =~ /^0|1$/) { 
+            $newltiauth = $env{'form.ltiauth'};
+        }
+        if ($oldltiauth == $newltiauth) {
+            $nochange = 1;
+        } else {
+            $change = 1;
+        }
+    }
+    if ($change) {
+        if ($newltiauth ne '') {
+            my %cenv = (
+                         'internal.ltiauth' => $newltiauth,
+                       );
+            if (&Apache::lonnet::put('environment',\%cenv,$cdom,$cnum) eq 'ok') {
+                if ($env{'course.'.$cdom.'_'.$cnum.'.description'} ne '') {
+                    &Apache::lonnet::appenv(
+                       {'course.'.$cdom.'_'.$cnum.'.internal.ltiauth' => $newltiauth});
+                }
+            } else {
+                $error = 1;
+            }
+        } else {
+            if (&Apache::lonnet::del('environment',['internal.ltiauth'],$cdom,$cnum) eq 'ok') {
+                if (exists($env{'course.'.$cdom.'_'.$cnum.'.internal.ltiauth'})) {
+                    &Apache::lonnet::delenv('course.'.$cdom.'_'.$cnum.'.internal.ltiauth');
+                }
+            } else {
+                $error = 1;
+            }
+        }
+    }
+    if ($error) {
+        $nochange = 1;
+    }
+    if ($nochange) {
+        $ltiauth = $oldltiauth;
+    } else {
+        $ltiauth = $newltiauth;
+    }
+    if ($ltiauth eq '') {
+        $status = $lt{'used'}.': ';
+        if ($domdef) {
+            $status .= '<span style="font-style:italic">'.$lt{'link'}.'</span>';
+        } else {
+            $status .= '<span style="font-style:italic">'.$lt{'logi'}.'</span>';
+        }
+    } else {
+        $status = $lt{'cour'}.': ';
+        if ($ltiauth) {
+            $status .= '<span style="font-style:italic">'.$lt{'link'}.'</span>';
+        } else {
+            $status .= '<span style="font-style:italic">'.$lt{'logi'}.'</span>';
+        }
+    }
+    if ($error) {
+        $r->print('<p class="LC_warning">'.&mt('An error occurred when saving your changes').'</p>');
+    }
+    $r->print('<p>');
+    if ($nochange) {
+        $r->print(&mt('Re-authentication requirement for LTI launch of deep-linked item is unchanged'));
+    } elsif ($change) {
+        $r->print(&mt('Re-authentication requirement for LTI launch of deep-linked changed'));
+    }
+    $r->print('<br />'.$status);
+    $r->print('</p><p>'.
+              '<a href="javascript:changePage(document.processltiauth,'."'menu'".')">'.
+              &mt('Pick another action').'</a></p>');
+    $r->print(&hidden_form_elements().'</form>');
+    return;
+}
+
 sub print_header {
     my ($r,$type,$javascript_validations) = @_;
     my $phase = "start";
@@ -2413,6 +2601,35 @@
 
 ENDSCRIPT
 
+    } elsif ($phase eq 'setltiauth') {
+        $js .= <<"ENDJS";
+function toggleLTIOptions(form) {
+    var radioname = 'ltiauthset';
+    var divid = 'crsltiauth';
+    var num = form.elements[radioname].length;
+    if (num) {
+        var setvis = '';
+        for (var i=0; i<num; i++) {
+            if (form.elements[radioname][i].checked) {
+                if (form.elements[radioname][i].value == 'course') {
+                    if (document.getElementById(divid)) {
+                        document.getElementById(divid).style.display = 'inline-block';
+                    }
+                    setvis = 1;
+                }
+                break;
+            }
+        }
+        if (!setvis) {
+            if (document.getElementById(divid)) {
+                document.getElementById(divid).style.display = 'none';
+            }
+        }
+    }
+    return;
+}
+
+ENDJS
     }
     my $starthash;
     if ($env{'form.phase'} eq 'adhocrole') {
@@ -2423,6 +2640,10 @@
         $starthash = {
            add_entries => {'onload' => "hide_searching(); courseSet(document.filterpicker.official, 'load');"},
                      };
+    } elsif ($env{'form.phase'} eq 'setltiauth') {
+        $starthash = {
+           add_entries => {'onload' => "toggleLTIOptions(document.setltiauth);"},
+                     };
     }
     $r->print(&Apache::loncommon::start_page('View/Modify Course/Community Settings',
 					     &Apache::lonhtmlcommon::scripttag($js),
@@ -2522,7 +2743,7 @@
           'locarg','krbarg','krbver','counter','hidefromcat','usecategory',
           'threshold','postsubmit','postsubtimeout','defaultcredits','uploadquota',
           'selfenrollmgrdc','selfenrollmgrcc','action','state','currsec_st',
-          'sections','newsec','mysqltables','nopasswdchg'],
+          'sections','newsec','mysqltables','nopasswdchg','ltiauth','ltiauthset'],
           ['^selfenrollmgr_','^selfenroll_'])."\n".
           '<input type="hidden" name="prevphase" value="'.$env{'form.phase'}.'" />';
     return $hidden_elements;
@@ -2556,6 +2777,8 @@
             processcat        => 'edit',
             selfenroll        => 'edit',
             adhocrole         => 'coord',
+            setltiauth        => 'edit',
+            processltiauth    => 'edit',
         );
         if ($passwdconf{'crsownerchg'}) {
             $permission{passwdchg} = 'edit';
@@ -2571,6 +2794,7 @@
             catsettings   => 'view',
             selfenroll    => 'view',
             adhocrole     => 'custom',
+            setltiauth    => 'view',
         );
         if ($passwdconf{'crsownerchg'}) {
             $permission{passwdchg} = 'view';
@@ -2774,6 +2998,19 @@
                                   text=>"Result"});
                                 &modify_selfenrollconfig($r,$type,$cdesc,$coursehash);
                             }
+                        } elsif (($phase eq 'setltiauth') && ($permission->{'setltiauth'})) {
+                            &Apache::lonhtmlcommon::add_breadcrumb
+                            ({href=>"javascript:changePage(document.$phase,'$phase')",
+                              text=>"Requirement for re-authentication for LTI launch of deep-linked item"});
+                            &print_set_ltiauth($r,$cdom,$cnum,$cdesc,$type,$readonly);
+                        } elsif (($phase eq 'processltiauth') && ($permission->{'processltiauth'})) {
+                            &Apache::lonhtmlcommon::add_breadcrumb
+                            ({href=>"javascript:changePage(document.$phase,'setltiauth')",
+                              text=>"Requirement for re-authentication for LTI launch of deep-linked item"});
+                            &Apache::lonhtmlcommon::add_breadcrumb
+                            ({href=>"javascript:changePage(document.$phase,'$phase')",
+                              text=>"Result"});
+                            &modify_ltiauth($r,$cdom,$cnum,$cdesc,$domdesc,$type);
                         }
                     }
                 } else {
Index: loncom/interface/courseprefs.pm
diff -u loncom/interface/courseprefs.pm:1.99 loncom/interface/courseprefs.pm:1.100
--- loncom/interface/courseprefs.pm:1.99	Tue Feb  1 23:13:19 2022
+++ loncom/interface/courseprefs.pm	Sun Feb  6 21:36:59 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set configuration settings for a course
 #
-# $Id: courseprefs.pm,v 1.99 2022/02/01 23:13:19 raeburn Exp $
+# $Id: courseprefs.pm,v 1.100 2022/02/06 21:36:59 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -808,7 +808,7 @@
 
 sub process_changes {
     my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
-    my (%newvalues,%lti,%ltienc,$errors);
+    my (%newvalues,%lti,%ltienc,$ltiauth,$errors);
     if (ref($item) eq 'HASH') {
         if (ref($changes) eq 'HASH') {
             my @ordered;
@@ -840,6 +840,12 @@
                 if (($env{'form.linkprot_add'}) && ($env{'form.linkprot_maxnum'} =~ /^\d+$/)) {
                     push(@ordered,$env{'form.linkprot_maxnum'});
                 }
+                if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) {
+                    $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'};
+                } else {
+                    my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
+                    $ltiauth = $domdefs{'crsltiauth'};
+                }
             } elsif (ref($item->{'ordered'}) eq 'ARRAY') {
                 if ($action eq 'courseinfo') {
                     my ($can_toggle_cat,$can_categorize) =
@@ -1044,6 +1050,46 @@
                                 $lti{$itemid}{$inner} = $env{$formitem};
                             }
                         }
+                        if ($ltiauth) {
+                            my $reqitem = 'form.linkprot_requser_'.$idx;
+                            $env{$reqitem} =~ s/(`)/'/g;
+                            unless ($idx eq 'add') {
+                                if ($current{'requser'} ne $env{$reqitem}) {
+                                    $haschanges{$itemid} = 1;
+                                }
+                            }
+                            if ($env{$reqitem} ne '') {
+                                $lti{$itemid}{'requser'} = $env{$reqitem};
+                                foreach my $inner ('mapuser','notstudent') {
+                                    my $formitem = 'form.linkprot_'.$inner.'_'.$idx;
+                                    $env{$formitem} =~ s/(`)/'/g;
+                                    if ($inner eq 'mapuser') {
+                                        if ($env{$formitem} eq 'other') {
+                                            my $mapuser = $env{'form.linkprot_customuser_'.$idx};
+                                            $mapuser =~ s/(`)/'/g;
+                                            $mapuser =~ s/^\s+|\s+$//g;
+                                            if ($mapuser ne '') {
+                                                $lti{$itemid}{$inner} = $mapuser;
+                                            } else {
+                                                delete($lti{$itemid}{'requser'});
+                                                last;
+                                            }
+                                        } elsif ($env{$formitem} eq 'sourcedid') {
+                                            $lti{$itemid}{$inner} = 'lis_person_sourcedid';
+                                        } elsif ($env{$formitem} eq 'email') {
+                                            $lti{$itemid}{$inner} = 'lis_person_contact_email_primary';
+                                        }
+                                    } else {
+                                        $lti{$itemid}{$inner} = $env{$formitem};
+                                    }
+                                    unless ($idx eq 'add') {
+                                        if ($current{$inner} ne $lti{$itemid}{$inner}) {
+                                            $haschanges{$itemid} = 1;
+                                        }
+                                    }
+                                }
+                            }
+                        }
                         unless ($switchserver) {
                             my $keyitem = 'form.linkprot_key_'.$idx;
                             $env{$keyitem} =~ s/(`)/'/g;
@@ -1711,7 +1757,13 @@
                             }
                         }
                     } elsif ($item eq 'linkprotection') {
-                        my (%ltienc,$lti_save_error);
+                        my ($ltiauth,%ltienc,$lti_save_error);
+                        if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) {
+                            $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'};
+                        } else {
+                            my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
+                            $ltiauth = $domdefs{'crsltiauth'};
+                        }
                         if (ref($changes->{$item}) eq 'HASH') {
                             foreach my $id (sort { $a <=> $b } keys(%{$changes->{$item}})) {
                                 if (ref($changes->{$item}->{$id}) eq 'HASH') {
@@ -1748,7 +1800,7 @@
                                 $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)) {
+                                    if (grep(/^\Q$chome\E$/, at ids)) {
                                         &Apache::lonnet::devalidate_cache_new('courseltienc',$hashid);
                                     }
                                 }
@@ -1763,7 +1815,7 @@
                                                     if (exists($ltienc{$id}{$title})) {
                                                         if ($title eq 'secret') {
                                                             my $length = length($ltienc{$id}{$title});
-                                                            $display .= $desc{$title}.': '.('*' x $length);
+                                                            $display .= $desc{$title}.': '.('*' x $length).', ';
                                                         } else {
                                                             $display .= $desc{$title}.': '.$ltienc{$id}{$title}.', ';
                                                         }
@@ -1777,6 +1829,24 @@
                                                 $display .= $desc{$title}.': '.$values{$title}.', ';
                                             }
                                         }
+                                        if ($ltiauth) {
+                                            if (($values{'requser'}) && ($values{'mapuser'} ne '')) {
+                                                if ($values{'mapuser'} eq 'lis_person_contact_email_primary') {
+                                                    $display .= &mt('Source of username: Email address [_1]',
+                                                                   '(lis_person_contact_email_primary)').', ';
+                                                } elsif ($values{'mapuser'} eq 'lis_person_sourcedid') {
+                                                    $display .= &mt('Source of username: User ID [_1]',
+                                                                    '(lis_person_sourcedid)').', ';
+                                                } else {
+                                                    $display .= &mt('Source of username: [_1]',$values{'mapuser'}).', ';
+                                                }
+                                                if ($values{'notstudent'} eq 'auth') {
+                                                    $display .= &mt('Display LON-CAPA login page if no match').', ';
+                                                } elsif ($values{'notstudent'} eq 'reject') {
+                                                    $display .= &mt('Discontinue launch if no match').', ';
+                                                }
+                                            }
+                                        }
                                         $display =~ s/, $//;
                                         $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]','<i>'.$id.'</i>',
                                                    "'$display'")).'</li>';
@@ -2511,6 +2581,31 @@
     }
     return;
 }
+
+function toggleLTIReqUser(form,item,extra,valon,styleon,num) {
+    if (document.getElementById('linkprot_'+extra+'_'+num)) {
+        var extraid = document.getElementById('linkprot_'+extra+'_'+num);
+        var itemname = form.elements['linkprot_'+item+'_'+num];
+        if (itemname) {
+            if (itemname.length > 0) {
+                var setvis;
+                for (var i=0; i<itemname.length; i++) {
+                    if (itemname[i].checked == true) {
+                        if (itemname[i].value == valon) {
+                            extraid.style.display = styleon;
+                            setvis = 1;
+                        }
+                        break;
+                    }
+                }
+                if (!setvis) {
+                    extraid.style.display = 'none';
+                }
+            }
+        }
+    }
+    return;
+}
 ENDSCRIPT
     $jscript = '<script type="text/javascript" language="Javascript">'."\n".
                '// <![CDATA['."\n".  
@@ -5418,9 +5513,20 @@
     if ($noedit) {
         $disabled = ' disabled="disabled"';
     }
-    my %lt = &linkprot_names();
+    my %desc = &linkprot_names();
+    my %lt = &Apache::lonlocal::texthash (
+       'requ'      => 'Required settings',
+       'opti'      => 'Optional settings',
+    );
     my $itemcount = 0;
 
+    my $ltiauth;
+    if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) {
+        $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'};
+    } else {
+        my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
+        $ltiauth = $domdefs{'crsltiauth'};
+    }
     my $switchserver = &check_switchserver($cdom,$cnum);
 
     if (ref($settings->{'linkprotection'}) eq 'HASH') {
@@ -5443,18 +5549,43 @@
                 $datatable .=
                     '<tr '.$css_class.'><td><span class="LC_nobreak">'.
                     '<label><input type="checkbox" name="linkprot_del" value="'.$i.'"'.$disabled.' />'.
-                    &mt('Delete?').'</label></span></td>'.
-                    '<td><span class="LC_nobreak">'.$lt{'name'}.
+                    &mt('Delete?').'</label></span></td><td>';
+                my ($usersty,$onclickrequser,%checkedrequser);
+                if ($ltiauth) {
+                    $usersty = 'display:none';
+                    $onclickrequser = ' onclick="toggleLTIReqUser(this.form,'."'requser','optional','1','block','$i'".');"';
+                    %checkedrequser = (
+                        no => ' checked="checked"',
+                        yes  => '',
+                    );
+                    if ($values{'requser'}) {
+                        $checkedrequser{'yes'} = $checkedrequser{'no'};
+                        $checkedrequser{'no'} = '';
+                    }
+                    $datatable .= '<fieldset><legend>'.$lt{'requ'}.'</legend>';
+                    if ($values{'requser'}) { 
+                        $usersty = 'display:inline-block';
+                    }
+                }
+                $datatable .=
+                    '<span class="LC_nobreak">'.$desc{'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.'"'.$disabled.'>'.
+                    '<span class="LC_nobreak">'.$desc{'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">'.$desc{'lifetime'}.':<input type="text" name="linkprot_lifetime_'.$i.'"'.
+                    ' value="'.$values{'lifetime'}.'" size="3"'.$disabled.' /></span>';
+                if ($ltiauth) {
+                    $datatable .= (' 'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
+                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="0"'.
+                                  $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label> '.
+                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="1"'.
+                                  $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>'.
+                                  '<br /><br />';
+                }
                 if ($values{'key'} ne '') {
-                    $datatable .= '<span class="LC_nobreak">'.$lt{'key'};
+                    $datatable .= '<span class="LC_nobreak">'.$desc{'key'};
                     if ($noedit) {
                         $datatable .= ': ['.&mt('not shown').']';
                     } elsif ($switchserver) {
@@ -5464,14 +5595,14 @@
                     }
                     $datatable .= '</span> '.(' 'x2);
                 } elsif (!$switchserver) {
-                    $datatable .= '<span class="LC_nobreak">'.$lt{'key'}.':'.
+                    $datatable .= '<span class="LC_nobreak">'.$desc{'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>'.
+                                      $desc{'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).
@@ -5487,7 +5618,7 @@
                 } 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>'.
+                                      $desc{'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).
@@ -5499,12 +5630,19 @@
                                       '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span></div>';
                     } else {
                         $datatable .=
-                            '<span class="LC_nobreak">'.$lt{'secret'}.':'.
+                            '<span class="LC_nobreak">'.$desc{'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>';
                     }
                 }
+                if ($ltiauth) {
+                    $datatable .= 
+                        '</fieldset>'.
+                        '<fieldset id="linkprot_optional_'.$i.'" style="'.$usersty.'"><legend>'.$lt{'opti'}.'</legend>'.
+                        &linkprot_options($i,$itemcount,$disabled,\%values,\%desc).
+                        '</fieldset>';
+                }
                 $datatable .= '</td></tr>';
                 $itemcount ++;
             }
@@ -5514,37 +5652,65 @@
     $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n".
                   '<input type="hidden" name="linkprot_maxnum" value="'.$next.'" />'."\n".
                   '<input type="checkbox" name="linkprot_add" value="1"'.$disabled.' />'.&mt('Add').'</span></td>'."\n".
-                  '<td>'.
-                  '<span class="LC_nobreak">'.$lt{'name'}.
+                  '<td>';
+    my ($usersty,$onclickrequser,%checkedrequser);
+    if ($ltiauth) {
+        $usersty = 'display:none';
+        $onclickrequser = ' onclick="toggleLTIReqUser(this.form,'."'requser','optional','1','block','add'".');"';
+        %checkedrequser = (
+            no => ' checked="checked"',
+            yes  => '',
+        );
+        $datatable .= '<fieldset><legend>'.$lt{'requ'}.'</legend>';
+    }
+    $datatable .= '<span class="LC_nobreak">'.$desc{'name'}.
                   ':<input type="text" size="15" name="linkprot_name_add" value="" autocomplete="off"'.$disabled.' /></span> '."\n".
                   (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'version'}.':<select name="linkprot_version_add"'.$disabled.'>'.
+                  '<span class="LC_nobreak">'.$desc{'version'}.':<select name="linkprot_version_add"'.$disabled.'>'.
                   '<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">'.$desc{'lifetime'}.':<input type="text" size="3" name="linkprot_lifetime_add" value="300"'.$disabled.' /></span> '."\n";
+    if ($ltiauth) {
+        $datatable .= (' 'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
+                      '<label><input type="radio" name="linkprot_requser_add" value="0"'.
+                      $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label> '.
+                      '<label><input type="radio" name="linkprot_requser_add" value="1"'.
+                      $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>';
+    }
+    $datatable .= '<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".
+        $datatable .= '<span class="LC_nobreak">'.$desc{'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.' />'.
+                      '<span class="LC_nobreak">'.$desc{'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";
     }
+    if ($ltiauth) {
+        $datatable .= '</fieldset>'.
+                      '<fieldset id="linkprot_optional_add" style="'.$usersty.'"><legend>'.$lt{'opti'}.'</legend>'.
+                      &linkprot_options('add',$itemcount,$disabled,{},\%desc).
+                     '</fieldset>';
+    }
     $datatable .= '</td></tr>';
     $$rowtotal ++;
     return $datatable;;
 }
 
 sub linkprot_names {
-    my %lt = &Apache::lonlocal::texthash(
+    return &Apache::lonlocal::texthash(
                                           'version'   => 'LTI Version',
                                           'key'       => 'Key',
                                           'lifetime'  => 'Nonce lifetime (s)',
-                                          'name'      => 'Launcher Application Name',
+                                          'name'      => 'Launcher Application',
                                           'secret'    => 'Secret',
+                                          'requser'   => 'Use identity',
+                                          'email'     => 'Email address',
+                                          'sourcedid' => 'User ID',
+                                          'other'     => 'Other',
+                                          'auth'      => 'Display LON-CAPA login page',
+                                          'reject'    => 'Discontinue launch process',
                                         );
-    return %lt;
 }
 
 sub check_switchserver {
@@ -5563,6 +5729,56 @@
     return $switchserver;
 }
 
+sub linkprot_options {
+    my ($num,$itemcount,$disabled,$current,$desc) = @_;
+    my %lt;
+    if (ref($desc) eq 'HASH') {
+        %lt = %{$desc};
+    }
+    my $userfieldsty = 'none';
+    my (%checked,$userfield);
+    $checked{'sourcedid'} = ' checked="checked"';
+    $checked{'reject'} = ' checked="checked"';
+    if (ref($current) eq 'HASH') {
+        if (($current->{'mapuser'} ne '') && ($current->{'mapuser'} ne 'lis_person_sourcedid')) {
+            $checked{'sourcedid'} = '';
+            if ($current->{'mapuser'} eq 'lis_person_contact_email_primary') {
+                $checked{'email'} = ' checked="checked"';
+            } else {
+                $checked{'other'} = ' checked="checked"';
+                $userfield = $current->{'mapuser'};
+                $userfieldsty = 'inline-block';
+            }
+        }
+        if (($current->{'notstudent'} ne '') && ($current->{'notstudent'} ne 'reject')) {
+            $checked{'reject'} = '';
+            $checked{'auth'} = ' checked="checked"';
+        }
+    }
+    my $onclickuser = ' onclick="toggleLTIReqUser(this.form,'."'mapuser','userfield','other','inline-block','$num'".');"';
+    my $output = '<div class="LC_floatleft"><span class="LC_nobreak">'.
+                 &mt('Source of LON-CAPA username in LTI request').': ';
+    foreach my $option ('sourcedid','email','other') {
+        $output .= '<label><input type="radio" name="linkprot_mapuser_'.$num.'" value="'.$option.'"'.
+                   $checked{$option}.$onclickuser.$disabled.' />'.$lt{$option}.'</label>'.
+                   ($option eq 'other' ? '' : (' 'x2) );
+    }
+    $output .= '</span></div>'.
+               '<div class="LC_floatleft" style="display:'.$userfieldsty.';" id="linkprot_userfield_'.$num.'">'.
+               '<input type="text" name="linkprot_customuser_'.$num.'" '.
+               'value="'.$userfield.'"'.$disabled.' /></div>';
+    $output .= '<br />'.
+               '<div class="LC_floatleft"><span class="LC_nobreak">'.
+               &mt('Action when username is not for an enrolled student').': ';
+    foreach my $option ('reject','auth') {
+        $output .= '<label><input type="radio" name="linkprot_notstudent_'.$num.'" value="'.$option.'"'.
+                   $checked{$option}.$disabled.' />'.$lt{$option}.'</label>'.
+                   ($option eq 'auth' ? '' : (' 'x2) );
+    }
+    $output .= '</span></div>';
+    return $output;
+}
+
 sub print_other {
     my ($cdom,$settings,$allitems,$rowtotal,$crstype,$noedit) = @_;
     unless ((ref($settings) eq 'HASH') && (ref($allitems) eq 'ARRAY')) {
Index: loncom/lti/ltiauth.pm
diff -u loncom/lti/ltiauth.pm:1.31 loncom/lti/ltiauth.pm:1.32
--- loncom/lti/ltiauth.pm:1.31	Wed Feb  2 00:31:16 2022
+++ loncom/lti/ltiauth.pm	Sun Feb  6 21:37:00 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Basic LTI Authentication Module
 #
-# $Id: ltiauth.pm,v 1.31 2022/02/02 00:31:16 raeburn Exp $
+# $Id: ltiauth.pm,v 1.32 2022/02/06 21:37:00 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -79,7 +79,7 @@
     }
 
     unless (keys(%{$params})) {
-        &invalid_request($r,1);
+        &invalid_request($r,'No parameters included in launch request');
         return OK;
     }
 
@@ -89,7 +89,7 @@
             $params->{'oauth_version'} &&
             $params->{'oauth_signature'} &&
             $params->{'oauth_signature_method'}) {
-        &invalid_request($r,2);
+        &invalid_request($r,'One or more required parameters missing from launch request');
         return OK;
     }
 
@@ -142,34 +142,99 @@
 # where url was /adm/launch/tiny/$cdom/$uniqueid
 #
 
-                        my ($itemid,$ltitype,%crslti);
+                        my ($itemid,$ltitype,%crslti,%lti_in_use);
                         $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';
-                            unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
-                                                                    $crslti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
-                                &invalid_request($r,3);
+                            if (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
+                                                                $crslti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
+                                %lti_in_use = %{$crslti{$itemid}};
+                            } else {
+                                &invalid_request($r,'Time limit exceeded for launch request credentials');
                                 return OK;
                             }
                         } else {
-                            my %lti;
                             $itemid = &get_lti_itemid($requri,$hostname,$params,$cdom,'','deeplink');
+                            my %lti;
                             if ($itemid) {
                                 %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
                             }
                             if (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
                                 $ltitype = 'd';
-                                unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
+                                if (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
                                                                         $lti{$itemid}{'lifetime'},$cdom,
                                                                         $r->dir_config('lonLTIDir'))) {
-                                    &invalid_request($r,4);
+                                    %lti_in_use = %{$lti{$itemid}};
+                                } else {
+                                    &invalid_request($r,'Time limit exceeded for launch request credentials');
                                     return OK;
                                 }
                             }
                         }
+                        if (($itemid) && ($lti_in_use{'requser'})) {
+                            my %courseinfo = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                            my $ltiauth;
+                            if (exists($courseinfo{'internal.ltiauth'})) {
+                                $ltiauth = $courseinfo{'internal.ltiauth'};
+                            } else {
+                                my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
+                                $ltiauth = $domdefs{'crsltiauth'};
+                            }
+                            if ($ltiauth) {
+                                my $possuname;
+                                my $mapuser = $crslti{$itemid}{'mapuser'};
+                                if ($mapuser eq 'sourcedid') {
+                                    if ($params->{'lis_person_sourcedid'} =~ /^$match_username$/) {
+                                        $possuname = $params->{'lis_person_sourcedid'};
+                                    }
+                                } elsif ($mapuser eq 'email') {
+                                    if ($params->{'lis_person_contact_email_primary'} =~ /^$match_username$/) {
+                                        $possuname = $params->{'lis_person_contact_email_primary'};
+                                    }
+                                } elsif (exists($params->{$mapuser})) {
+                                    if ($params->{$mapuser} =~ /^$match_username$/) {
+                                        $possuname = $params->{$mapuser};
+                                    }
+                                }
+                                if ($possuname ne '') {
+                                    my $uhome = &Apache::lonnet::homeserver($possuname,$cdom);
+                                    unless ($uhome eq 'no_host') {
+                                        my $uname = $possuname;
+                                        my ($is_student,$is_nonstudent);
+                                        my %course_roles =
+                                            &Apache::lonnet::get_my_roles($uname,$cdom,,'userroles',['active'],
+                                                                          ['cc','co','in','ta','ep','ad','st','cr'],
+                                                                          [$cdom]);
+                                        foreach my $key (keys(%course_roles)) {
+                                            my ($trest,$tdomain,$trole,$sec) = split(/:/,$key);
+                                            if (($trest eq $cnum) && ($tdomain eq $cdom)) {
+                                                if ($trole eq 'st') {
+                                                    $is_student = 1;
+                                                } else {
+                                                    $is_nonstudent = 1;
+                                                    last;
+                                                }
+                                            }
+                                        }
+                                        if (($is_student) && (!$is_nonstudent)) {
+                                            unless (&Apache::lonnet::is_advanced_user($uname,$cdom)) {
+                                                foreach my $key (%{$params}) {
+                                                    delete($env{'form.'.$key});
+                                                }
+                                                &linkprot_session($r,$uname,$cnum,$cdom,$uhome,$itemid,$ltitype,$tail,$lonhost);
+                                                return OK;
+                                            }
+                                        }
+                                    }
+                                }
+                                if ($lti_in_use{'notstudent'} eq 'reject') {
+                                    &invalid_request($r,'Information for valid user missing from launch request');
+                                }
+                            }
+                        }
                         if ($itemid) {
                             foreach my $key (%{$params}) {
                                 delete($env{'form.'.$key});
@@ -180,22 +245,22 @@
                                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                                 $r->set_handlers('PerlHandler'=> undef);
                             } else {
-                                &invalid_request($r,5);
+                                &invalid_request($r,'Failed to store information from launch request');
                             }
                         } else {
-                            &invalid_request($r,6);
+                            &invalid_request($r,'Launch request could not be validated');
                         }
                     } else {
-                        &invalid_request($r,7);
+                        &invalid_request($r,'Launch unavailable on this LON-CAPA server');
                     }
                 } else {
-                    &invalid_request($r,8);
+                    &invalid_request($r,'Launch unavailable for this domain');
                 }
             } else {
-                &invalid_request($r,9);
+                &invalid_request($r,'Invalid launch URL');
             }
         } else {
-            &invalid_request($r,10);
+            &invalid_request($r,'Invalid launch URL');
         }
         return OK;
     }
@@ -296,7 +361,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,11);
+                &invalid_request($r,'Incorrect domain in requested URL');
                 return OK;
             }
             if ($rest eq '') {
@@ -315,13 +380,13 @@
         } elsif ($tail =~ m{^/($match_domain)/($match_courseid)$}) {
             ($urlcdom,$urlcnum) = ($1,$2);
             if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,12);
+                &invalid_request($r,'Incorrect domain in requested URL');
                 return OK;
             }
         } elsif ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
             ($urlcdom,$urlcnum) = &course_from_tinyurl($tail);
             if (($urlcdom eq '') || ($urlcnum eq '')) {
-                &invalid_request($r,13);
+                &invalid_request($r,'Invalid URL shortcut');
                 return OK;
             }
         }
@@ -367,7 +432,7 @@
 # configuration in LON-CAPA for that LTI Consumer.
 #
     unless (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
-        &invalid_request($r,14);
+        &invalid_request($r,'Launch request could not be validated');
         return OK;
     }
 
@@ -377,7 +442,7 @@
 #
     unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
                                             $lti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
-        &invalid_request($r,15);
+        &invalid_request($r,'Time limit exceeded for launch request credentials');
         return OK;
     }
 
@@ -398,10 +463,10 @@
                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                 $r->set_handlers('PerlHandler'=> undef);
             } else {
-                &invalid_request($r,16);
+                &invalid_request($r,'Failed to store information from launch request');
             }
         } else {
-            &invalid_request($r,17);
+            &invalid_request($r,'Launch URL invalid for matched launch credentials');
         }
         return OK;
     }
@@ -463,7 +528,7 @@
                 my $storedcnum = $1;
                 my $crshome = &Apache::lonnet::homeserver($storedcnum,$cdom);
                 if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                    &invalid_request($r,18);
+                    &invalid_request($r,'Invalid courseID included in launch data');
                     return OK;
                 } else {
                     $posscnum = $storedcnum;
@@ -475,7 +540,7 @@
     if ($urlcnum ne '') {
         if ($posscnum ne '') {
             if ($posscnum ne $urlcnum) {
-                &invalid_request($r,19);
+                &invalid_request($r,'Course ID included in launch data incompatible with URL');
                 return OK;
             } else {
                 $cnum = $posscnum;
@@ -483,7 +548,7 @@
         } else {
             my $crshome = &Apache::lonnet::homeserver($urlcnum,$cdom);
             if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                &invalid_request($r,20);
+                &invalid_request($r,'Valid course ID could not be extracted from requested URL');
                 return OK;
             } else {
                 $cnum = $urlcnum;
@@ -548,7 +613,7 @@
                                                     $domdesc,\%data,\%alerts,\%rulematch,
                                                     \%inst_results,\%curr_rules,%got_rules);
                 if ($result eq 'notallowed') {
-                    &invalid_request($r,21);
+                    &invalid_request($r,'Account creation not permitted for this user');
                 } elsif ($result eq 'ok') {
                     if (($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'mapcrs'}) &&
                         ($lti{$itemid}{'makecrs'})) {
@@ -557,16 +622,16 @@
                         }
                     }
                 } else {
-                    &invalid_request($r,22);
+                    &invalid_request($r,'An error occurred during account creation');
                     return OK;
                 }
             } else {
-                &invalid_request($r,23);
+                &invalid_request($r,'Account creation not permitted');
                 return OK;
             }
         }
     } else {
-        &invalid_request($r,24);
+        &invalid_request($r,'Could not determine username and/or domain for user');
         return OK;
     }
 
@@ -589,10 +654,10 @@
                                  $symb,$cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,
                                  $reqcrs,$sourcecrs);
                 } else {
-                    &invalid_request($r,25);
+                    &invalid_request($r,'No LON-CAPA course available, and creation is not permitted for this user');
                 }
             } else {
-                &invalid_request($r,26);
+                &invalid_request($r,'No LON-CAPA course available, and creation is not permitted');
             }
         } else {
             &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,undef,$mapurl,$tail,
@@ -683,7 +748,7 @@
             }
         }
         if ($reqrole eq '') {
-            &invalid_request($r,27);
+            &invalid_request($r,'No matching role available in LON-CAPA course, and not permitted to self-enroll');
             return OK;
         } else {
             unless (%crsenv) {
@@ -693,10 +758,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,28);
+                &invalid_request($r,'No active role available in LON-CAPA course, and past end date for self-enrollment');
                 return OK;
             } elsif ($default_enrollment_start_date && $default_enrollment_start_date >$now) {
-                &invalid_request($r,29);
+                &invalid_request($r,'No active role available in LON-CAPA course, and brefor start date for self-enrollment');
                 return OK;
             } else {
                 $selfenrollrole = $reqrole.'./'.$cdom.'/'.$cnum;
@@ -828,24 +893,7 @@
     } else {
         &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, course dom: $cdom");
     }
-    my ($is_balancer,$otherserver,$hosthere);
-    ($is_balancer,$otherserver) =
-        &Apache::lonnet::check_loadbalancing($uname,$udom,'login');
-    if ($is_balancer) {
-        if ($otherserver eq '') {
-            my $lowest_load;
-            ($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($udom);
-            if ($lowest_load > 100) {
-                $otherserver = &Apache::lonnet::spareserver($r,$lowest_load,$lowest_load,1,$udom);
-            }
-        }
-        if ($otherserver ne '') {
-            my @hosts = &Apache::lonnet::current_machine_ids();
-            if (grep(/^\Q$otherserver\E$/, at hosts)) {
-                $hosthere = $otherserver;
-            }
-        }
-    }
+    my ($is_balancer,$otherserver,$hosthere) = &check_balancer($r,$uname,$udom);
     my $protocol = 'http';
     if ($ENV{'SERVER_PORT'} == 443) {
         $protocol = 'https';
@@ -924,8 +972,7 @@
         $r->internal_redirect($redirecturl);
         $r->set_handlers('PerlHandler'=> undef);
     } else {
-        # need to login them in, so generate the need data that
-        # migrate expects to do login
+        # need to login them in, so generate the data migrate expects to do login
         foreach my $key (keys(%{$params})) {
             delete($env{'form.'.$key});
         }
@@ -1003,8 +1050,71 @@
     return;
 }
 
+sub linkprot_session {
+    my ($r,$uname,$cnum,$cdom,$uhome,$itemid,$ltitype,$dest,$lonhost) = @_;
+    $r->user($uname);
+    if ($ltitype eq 'c') {
+        &Apache::lonnet::logthis("Course Link Protector ($itemid) authorized student: $uname:$cdom, course: $cdom\_$cnum");
+    } elsif ($ltitype eq 'd') {
+        &Apache::lonnet::logthis("Domain LTI for link protection ($itemid) authorized student: $uname:$cdom, course: $cdom\_$cnum");
+    }
+    my ($is_balancer,$otherserver,$hosthere) = &check_balancer($r,$uname,$cdom);
+    if (($is_balancer) && (!$hosthere)) {
+        # login but immediately go to switch server
+        $env{'form.origurl'} = $dest;
+        $env{'request.linkprot'} = $itemid.$ltitype.':'.$dest;
+        $env{'request.deeplink.login'} = $dest;
+        my $redirecturl = '/adm/switchserver';
+        if ($otherserver ne '') {
+            $redirecturl .= '?otherserver='.$otherserver;
+        }
+        $r->internal_redirect($redirecturl);
+        $r->set_handlers('PerlHandler'=> undef);
+    } else {
+        # need to login them in, so generate the data migrate expects to do login
+        my $ip = $r->get_remote_host();
+        my %info=('ip'             => $ip,
+                  'domain'         => $cdom,
+                  'username'       => $uname,
+                  'server'         => $lonhost,
+                  'linkprot'       => $itemid.$ltitype.':'.$dest,
+                  'home'           => $uhome,
+                  'origurl'        => $dest,
+                  'deeplink.login' => $dest,
+                 );
+        my $token = &Apache::lonnet::tmpput(\%info,$lonhost);
+        $env{'form.token'} = $token;
+        $r->internal_redirect('/adm/migrateuser');
+        $r->set_handlers('PerlHandler'=> undef);
+    }
+    return;
+}
+
+sub check_balancer {
+    my ($r,$uname,$udom) = @_;
+    my ($is_balancer,$otherserver,$hosthere);
+    ($is_balancer,$otherserver) =
+        &Apache::lonnet::check_loadbalancing($uname,$udom,'login');
+    if ($is_balancer) {
+        if ($otherserver eq '') {
+            my $lowest_load;
+            ($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($udom);
+            if ($lowest_load > 100) {
+                $otherserver = &Apache::lonnet::spareserver($r,$lowest_load,$lowest_load,1,$udom);
+            }
+        }
+        if ($otherserver ne '') {
+            my @hosts = &Apache::lonnet::current_machine_ids();
+            if (grep(/^\Q$otherserver\E$/, at hosts)) {
+                $hosthere = $otherserver;
+            }
+        }
+    }
+    return ($is_balancer,$otherserver,$hosthere);
+}
+
 sub invalid_request {
-    my ($r,$num) = @_;
+    my ($r,$msg) = @_;
     &Apache::loncommon::content_type($r,'text/html');
     $r->send_http_header;
     if ($r->header_only) {
@@ -1013,7 +1123,12 @@
     &Apache::lonlocal::get_language_handle($r);
     $r->print(
         &Apache::loncommon::start_page('Invalid LTI call','',{ 'only_body' => 1,}).
-        &mt('Invalid LTI call [_1]',$num).
+        '<h3>'.&mt('Invalid LTI launch request').'</h3>'.
+        '<p class="LC_warning">'.
+        &mt('Launch of LON-CAPA is unavailable from the "external tool" link you had followed in another web application.').
+        &mt('Launch failed for the following reason:').
+        '</p>'.
+        '<p class="LC_error">'.$msga'.</p>'.
         &Apache::loncommon::end_page());
     return;
 }
Index: loncom/auth/migrateuser.pm
diff -u loncom/auth/migrateuser.pm:1.56 loncom/auth/migrateuser.pm:1.57
--- loncom/auth/migrateuser.pm:1.56	Thu Nov 18 20:25:27 2021
+++ loncom/auth/migrateuser.pm	Sun Feb  6 21:37:04 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Starts a user off based of an existing token.
 #
-# $Id: migrateuser.pm,v 1.56 2021/11/18 20:25:27 raeburn Exp $
+# $Id: migrateuser.pm,v 1.57 2022/02/06 21:37:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -739,6 +739,17 @@
                 }
             }
             if ($data{'linkprot'} ne '') {
+                if (($env{'user.name'} ne $data{'username'}) ||
+                    ($env{'user.domain'} ne $data{'domain'})) {
+                    my %linkprot_env;
+                    foreach my $item ('linkprot','deeplink.login') {
+                        if ($data{$item}) {
+                            $linkprot_env{'request.'.$item} = $data{$item};
+                        }
+                    }
+                    &logout($r,$ip,$handle,\%data,\%linkprot_env);
+                    return OK;
+                }
                 &Apache::lonnet::appenv({'request.linkprot' => $data{'linkprot'}});
                 if ($env{'request.linkkey'}) {
                     &Apache::lonnet::delenv('request.linkkey');
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1479 loncom/lonnet/perl/lonnet.pm:1.1480
--- loncom/lonnet/perl/lonnet.pm:1.1479	Tue Feb  1 23:13:20 2022
+++ loncom/lonnet/perl/lonnet.pm	Sun Feb  6 21:37:07 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1479 2022/02/01 23:13:20 raeburn Exp $
+# $Id: lonnet.pm,v 1.1480 2022/02/06 21:37:07 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2699,7 +2699,10 @@
         }
         if ($domconfig{'coursedefaults'}{'texengine'}) {
             $domdefaults{'texengine'} = $domconfig{'coursedefaults'}{'texengine'};
-        } 
+        }
+        if (exists($domconfig{'coursedefaults'}{'ltiauth'})) {
+            $domdefaults{'crsltiauth'} = $domconfig{'coursedefaults'}{'ltiauth'};
+        }
     }
     if (ref($domconfig{'usersessions'}) eq 'HASH') {
         if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') {


More information about the LON-CAPA-cvs mailing list