[LON-CAPA-cvs] cvs: loncom / lontrans.pm /auth lonacc.pm lonauth.pm lonlogin.pm lonroles.pm lonshibauth.pm migrateuser.pm /interface lontiny.pm /lti ltiauth.pm

raeburn raeburn at source.lon-capa.org
Tue Nov 2 21:04:04 EDT 2021


raeburn		Wed Nov  3 01:04:04 2021 EDT

  Modified files:              
    /loncom/auth	lonacc.pm lonauth.pm lonlogin.pm lonroles.pm 
                	lonshibauth.pm migrateuser.pm 
    /loncom/interface	lontiny.pm 
    /loncom	lontrans.pm 
    /loncom/lti	ltiauth.pm 
  Log:
  - Bug 6907
    - Use of token to store linkprot or linkkey compatible with use of
      btoken and iptoken (for load balancing and IP change respectively).
    - Launching access from a deeplink, with its own ltoken and/or linkkey,
      for a user session originally launched from a different deeplink will
      update required session information.
  
  
-------------- next part --------------
Index: loncom/auth/lonacc.pm
diff -u loncom/auth/lonacc.pm:1.199 loncom/auth/lonacc.pm:1.200
--- loncom/auth/lonacc.pm:1.199	Tue Oct 26 15:52:54 2021
+++ loncom/auth/lonacc.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Cookie Based Access Handler
 #
-# $Id: lonacc.pm,v 1.199 2021/10/26 15:52:54 raeburn Exp $
+# $Id: lonacc.pm,v 1.200 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -305,7 +305,8 @@
     my $query = $r->args;
     my %form;
     if ($query) {
-        my @items = ('role','symb','iptoken','origurl','ltoken','linkkey','logtoken');
+        my @items = ('role','symb','iptoken','origurl','ttoken',
+                     'ltoken','linkkey','logtoken','sso');
         &Apache::loncommon::get_unprocessed_cgi($query,\@items);
         foreach my $item (@items) {
             if (defined($env{'form.'.$item})) {
@@ -324,26 +325,97 @@
     }
 
     my ($linkprot,$linkkey);
-    if ($form{'logtoken'}) {
+
+#
+# If Shibboleth auth is in use, and a dual SSO and non-SSO login page
+# is in use, then the query string will contain the logtoken item with
+# a value set to the name of a .tmp file in /home/httpd/perl/tmp
+# containing the url to display after authentication, and also,
+# optionally, role and symb, or linkprot or linkkey (deep-link access).
+#
+# If Shibboleth auth is in use, but a dual log-in page is not in use,
+# and the originally requested URL was /tiny/$domain/$id (i.e.,
+# for deeplinking), then the query string will contain the sso item
+# with a value set to the name of a .tmp file in /home/httpd/perl/tmp
+# containing the url to display after authentication, and also,
+# optionally, linkprot or linkkey (deep-link access).
+#
+# Otherwise the query string may contain role and symb, or if the
+# originally requested URL was /tiny/$domain/$id (i.e. for deeplinking)
+# then the query string may contain a ttoken item with a value set
+# to the name of a .tmp file in /home/httpd/perl/tmp containing either
+# linkprot or linkkey (deep-link access).
+#
+# If deep-linked, i.e., the originally requested URL was /tiny/$domain/$id
+# the linkkey may have originally been sent in POSTed data, which will
+# have been processed in lontrans.pm
+#
+
+    if ($form{'ttoken'}) {
+        my %info = &Apache::lonnet::tmpget($form{'ttoken'});
+        &Apache::lonnet::tmpdel($form{'ttoken'});
+        if ($info{'origurl'}) {
+            $form{'origurl'} = $info{'origurl'};
+        }
+        if ($info{'linkprot'}) {
+            $linkprot = $info{'linkprot'};
+        } elsif ($info{'linkkey'} ne '') {
+            $linkkey = $info{'linkkey'};
+        }
+    } elsif ($form{'logtoken'}) {
         my ($firsturl, at rest);
         my $lonhost = $r->dir_config('lonHostID');
         my $tmpinfo = &Apache::lonnet::reply('tmpget:'.$form{'logtoken'},$lonhost);
         my $delete = &Apache::lonnet::tmpdel($form{'logtoken'});
-        (undef,$firsturl, at rest) = split(/&/,$tmpinfo);
-        foreach my $item (@rest) {
-            my ($key,$value) = split(/=/,$item);
-            $form{$key} = &unescape($value);
+        unless (($tmpinfo=~/^error/) || ($tmpinfo eq 'con_lost') ||
+                ($tmpinfo eq 'no_such_host')) {
+            (undef,$firsturl, at rest) = split(/&/,$tmpinfo);
+            if ($firsturl ne '') {
+                $firsturl = &unescape($firsturl);
+            }
+            foreach my $item (@rest) {
+                my ($key,$value) = split(/=/,$item);
+                $form{$key} = &unescape($value);
+            }
+            if ($firsturl =~ m{^/tiny/$match_domain/\w+$}) {
+                $form{'origurl'} = $firsturl;
+            }
+            if ($form{'linkprot'}) {
+                $linkprot = $form{'linkprot'};
+            } elsif ($form{'linkkey'} ne '') {
+                $linkkey = $form{'linkkey'};
+            }
+            if ($form{'iptoken'}) {
+                %sessiondata = &Apache::lonnet::tmpget($form{'iptoken'});
+                my $delete = &Apache::lonnet::tmpdel($form{'iptoken'});
+            }
         }
-        if ($firsturl =~ m{^/tiny/$match_domain/\w+$}) {
-            $form{'origurl'} = $firsturl;
+    } elsif ($form{'sso'}) {
+        my $lonhost = $r->dir_config('lonHostID');
+        my $info = &Apache::lonnet::reply('tmpget:'.$form{'sso'},$lonhost);
+        &Apache::lonnet::tmpdel($form{'sso'});
+        unless (($info=~/^error/) || ($info eq 'con_lost') ||
+                ($info eq 'no_such_host')) {
+            my ($firsturl, at rest)=split(/\&/,$info);
+            if ($firsturl ne '') {
+                $form{'origurl'} = &unescape($firsturl);
+            }
+            foreach my $item (@rest) {
+                my ($key,$value) = split(/=/,$item);
+                $form{$key} = &unescape($value);
+            }
+            if ($form{'linkprot'}) {
+                $linkprot = $form{'linkprot'};
+            } elsif ($form{'linkkey'} ne '') {
+                $linkkey = $form{'linkkey'};
+            }
         }
-    }
-    if ($form{'ltoken'}) {
+    } elsif ($form{'ltoken'}) {
         my %link_info = &Apache::lonnet::tmpget($form{'ltoken'});
         $linkprot = $link_info{'linkprot'};
         my $delete = &Apache::lonnet::tmpdel($form{'ltoken'});
-    }
-    if ($form{'linkkey'} ne '') {
+        delete($form{'ltoken'});
+    } elsif ($form{'linkkey'} ne '') {
         $linkkey = $form{'linkkey'};
     }
 
@@ -432,6 +504,8 @@
             foreach my $item ('role','symb','iptoken','origurl') {
                 if (exists($form{$item})) {
                     $info{$item} = $form{$item};
+                } elsif ($sessiondata{$item} ne '') {
+                    $info{$item} = $sessiondata{$item};
                 }
             }
             unless (($info{'symb'}) || ($info{'origurl'})) {
@@ -488,6 +562,8 @@
             $r->subprocess_env->set('SSOUserUnknown' => $user);
             $r->subprocess_env->set('SSOUserDomain' => $domain);
             if (grep(/^sso$/, at cancreate)) {
+#FIXME - need to preserve origurl, role and symb, or linkprot or linkkey for use after account
+# creation
                 $r->set_handlers('PerlHandler'=> [\&Apache::createaccount::handler]);
                 $r->handler('perl-script');
             } else {
Index: loncom/auth/lonauth.pm
diff -u loncom/auth/lonauth.pm:1.168 loncom/auth/lonauth.pm:1.169
--- loncom/auth/lonauth.pm:1.168	Tue Oct 26 15:52:54 2021
+++ loncom/auth/lonauth.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # User Authentication Module
 #
-# $Id: lonauth.pm,v 1.168 2021/10/26 15:52:54 raeburn Exp $
+# $Id: lonauth.pm,v 1.169 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -178,6 +178,22 @@
         $destination .= 'source=login';
     }
 
+    if (($env{'request.deeplink.login'} eq $lowerurl) &&
+        (($env{'request.linkprot'}) || ($env{'request.linkkey'} ne ''))) {
+        my %info;
+        if ($env{'request.linkprot'}) {
+            $info{'linkprot'} = $env{'request.linkprot'};
+        } elsif ($env{'request.linkkey'} ne '') {
+            $info{'linkkey'} = $env{'request.linkkey'};
+        }
+        $info{'origurl'} = $lowerurl;
+        my $token = &Apache::lonnet::tmpput(\%info,$r->dir_config('lonHostID'),'link');
+        unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+            $destination .= (($destination =~ /\?/) ? '&' : '?') . 'ttoken='.$token;
+        }
+    }
+
     my $windowname = 'loncapaclient';
     if ($env{'request.lti.login'}) {
         $windowname .= 'lti';
@@ -329,7 +345,7 @@
         }
         if (exists($form->{linkprot})) {
             my $ltoken = &Apache::lonnet::tmpput({linkprot => $form->{'linkprot'}},
-                                                  $r->dir_config('lonHostID'));
+                                                 $r->dir_config('lonHostID'),'retry');
             if ($ltoken) {
                 $retry .= (($retry =~ /\?/) ? '&' : '?').'ltoken='.$ltoken;
             }
@@ -398,44 +414,71 @@
 	    my $end_page = 
 	        &Apache::loncommon::end_page();
             my $dest = '/adm/roles';
-            if ($env{'form.firsturl'} ne '') {
-                $dest = $env{'form.firsturl'};
-                if (($dest =~ m{^/tiny/$match_domain/\w+$}) && ($env{'request.course.id'})) {
-                    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-                    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
-                    my $symb = &Apache::loncommon::symb_from_tinyurl($dest,$cnum,$cdom);
-                    if ($symb) {
-                        my $buffer;
-                        if ($r->header_in('Content-length') > 0) {
-                            $r->read($buffer,$r->header_in('Content-length'),0);
-                        }
-                        my %form;
-                        foreach my $pair (split(/&/,$buffer)) {
-                            my ($name,$value) = split(/=/,$pair);
-                            $value =~ tr/+/ /;
-                            $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
-                            $form{$name}=$value;
+            my %form = &get_form_items($r);
+            if ($form{'logtoken'}) {
+                my $tmpinfo = &Apache::lonnet::reply('tmpget:'.$form{'logtoken'},
+                                                     $form{'serverid'});
+                unless (($tmpinfo=~/^error/) || ($tmpinfo eq 'con_lost') ||
+                        ($tmpinfo eq 'no_such_host')) {
+                    my ($des_key,$firsturl, at rest)=split(/&/,$tmpinfo);
+                    $firsturl = &unescape($firsturl);
+                    my %info;
+                    foreach my $item (@rest) {
+                        my ($key,$value) = split(/=/,$item);
+                        $info{$key} = &unescape($value);
+                    }
+                    if ($firsturl ne '') {
+                        $info{'firsturl'} = $firsturl;
+                        $dest = $firsturl;
+                        my $relogin;
+                        if ($dest =~ m{^/tiny/$match_domain/\w+$}) {
+                            if ($env{'request.course.id'}) {
+                                my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+                                my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+                                my $symb = &Apache::loncommon::symb_from_tinyurl($dest,$cnum,$cdom);
+                                if ($symb) {
+                                    unless (&set_deeplink_login(%info) eq 'ok') {
+                                        $relogin = 1;
+                                    }
+                                }
+                            }
+                            if ($relogin) {
+                                $r->print(
+                                      $start_page
+                                     .'<p class="LC_warning">'.&mt('You are already logged in!').'</p>'
+                                     .'<p>'.&mt('Please [_1]log out[_2] first, and then try your access again',
+                                                '<a href="/adm/logout">','</a>')
+                                     .'</p>'
+                                     .$end_page);
+                            } else {
+                                if (($info{'linkprot'}) || ($info{'linkkey'} ne '')) {
+                                    my $token = &Apache::lonnet::tmpput(\%info,$r->dir_config('lonHostID'),'link');
+                                    unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                                            ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+                                        $dest .= (($dest =~ /\?/) ? '&' : '?') . 'ttoken='.$token;
+                                    }
+                                }
+                                $r->print(
+                                      $start_page
+                                     .'<p class="LC_warning">'.&mt('You are already logged in!').'</p>'
+                                     .'<p>'.&mt('Please either [_1]continue the current session[_2] or [_3]log out[_4] first, and then try your access again',
+                                                '<a href="'.$dest.'">','</a>',
+                                                '<a href="/adm/logout">','</a>')
+                                     .'</p>'
+                                     .$end_page);
+                            }
+                            return OK;
                         }
-                        &set_deeplink_login(%form);
-                    } else {
-                        $r->print(
-                                  $start_page
-                                 .'<p class="LC_warning">'.&mt('You are already logged in!').'</p>'
-                                 .'<p>'.&mt('Please [_1]log out[_2] first, and then try your access again',
-                                            '<a href="/adm/logout">','</a>')
-                                 .'</p>'
-                                 .$end_page);
-                        return OK;
                     }
                 }
             }
             $r->print(
-               $start_page
-              .'<p class="LC_warning">'.&mt('You are already logged in!').'</p>'
-              .'<p>'.&mt('Please either [_1]continue the current session[_2] or [_3]log out[_4].'
-                    ,'<a href="'.$dest.'">','</a>','<a href="/adm/logout">','</a>')
-              .'</p>'
-              .$end_page
+                  $start_page
+                 .'<p class="LC_warning">'.&mt('You are already logged in!').'</p>'
+                 .'<p>'.&mt('Please either [_1]continue the current session[_2] or [_3]log out[_4].'
+                          ,'<a href="'.$dest.'">','</a>','<a href="/adm/logout">','</a>')
+                 .'</p>'
+                 .$end_page
             );
             return OK;
         }
@@ -443,19 +486,7 @@
 
 # ---------------------------------------------------- No valid token, continue
 
-
-    my $buffer;
-    if ($r->header_in('Content-length') > 0) {
-	$r->read($buffer,$r->header_in('Content-length'),0);
-    }
-    my %form;
-    foreach my $pair (split(/&/,$buffer)) {
-       my ($name,$value) = split(/=/,$pair);
-       $value =~ tr/+/ /;
-       $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
-       $form{$name}=$value;
-    }
-
+    my %form = &get_form_items($r);
     if ((!$form{'uname'}) || (!$form{'upass0'}) || (!$form{'udom'})) {
 	&failed($r,'Username, password and domain need to be specified.',
 		\%form);
@@ -499,7 +530,8 @@
         return OK;
     }
 
-    my ($key,$firsturl, at rest)=split(/&/,$tmpinfo);
+    my ($des_key,$firsturl, at rest)=split(/&/,$tmpinfo);
+    $firsturl = &unescape($firsturl);
     foreach my $item (@rest) {
         my ($key,$value) = split(/=/,$item);
         $form{$key} = &unescape($value);
@@ -508,7 +540,7 @@
         $form{'firsturl'} = $firsturl;
     }
     my $upass = $ENV{HTTPS} ? $form{'upass0'} 
-        : &Apache::loncommon::des_decrypt($key,$form{'upass0'});
+        : &Apache::loncommon::des_decrypt($des_key,$form{'upass0'});
 
 # ---------------------------------------------------------------- Authenticate
 
@@ -570,9 +602,9 @@
 	$firsturl='/adm/roles';
     }
 
-    my $hosthere;
+    my ($hosthere,%sessiondata);
     if ($form{'iptoken'}) {
-        my %sessiondata = &Apache::lonnet::tmpget($form{'iptoken'});
+        %sessiondata = &Apache::lonnet::tmpget($form{'iptoken'});
         my $delete = &Apache::lonnet::tmpdel($form{'iptoken'});
         if (($sessiondata{'domain'} eq $form{'udom'}) &&
             ($sessiondata{'username'} eq $form{'uname'})) {
@@ -616,7 +648,7 @@
                         unless ($suprim eq $uprim) {
                             unless ($suintdom eq $uintdom) {
                                 &Apache::lonnet::logthis('Attempted switch user '
-                                   .'to user with different "internet domain".');                        
+                                   .'to user with different "internet domain".');
                                 $noprivswitch = 1;
                             }
                         }
@@ -778,6 +810,18 @@
             $form{'noloadbalance'} = $hosthere;
         }
         my $extra_env;
+        if (($hosthere) && ($sessiondata{'sessionserver'} ne '')) {
+            if ($sessiondata{'origurl'} ne '') {
+                $firsturl = $sessiondata{'origurl'};
+                $form{'firsturl'} = $sessiondata{'origurl'};
+                my @names = ('role','symb','linkprot','linkkey');
+                foreach my $item (@names) {
+                    if ($sessiondata{$item} ne '') {
+                        $form{$item} = $sessiondata{$item};
+                    }
+                }
+            }
+        }
         if ($form{'linkprot'}) {
             my ($linkprotector,$uri) = split(/:/,$form{'linkprot'},2);
             if ($linkprotector) {
@@ -817,14 +861,31 @@
     }
 }
 
+sub get_form_items {
+    my ($r) = @_;
+    my $buffer;
+    if ($r->header_in('Content-length') > 0) {
+        $r->read($buffer,$r->header_in('Content-length'),0);
+    }
+    my %form;
+    foreach my $pair (split(/&/,$buffer)) {
+       my ($name,$value) = split(/=/,$pair);
+       $value =~ tr/+/ /;
+       $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
+       $form{$name}=$value;
+    }
+    return %form;
+}
+
 sub set_deeplink_login {
     my (%form) = @_;
+    my $disallow;
     if ($form{'firsturl'} =~ m{^/tiny/($match_domain)/\w+$}) {
         my $cdom = $1;
         my ($cnum,$symb) = &Apache::loncommon::symb_from_tinyurl($form{'firsturl'},'',$cdom);
         if ($symb) {
             if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
-                my ($disallow,$deeplink);
+                my $deeplink;
                 if ($symb =~ /\.(page|sequence)$/) {
                     my $mapname = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($symb))[2]);
                     my $navmap = Apache::lonnavmaps::navmap->new();
@@ -857,14 +918,17 @@
             }
         }
     }
-    return;
+    if ($disallow) {
+        return;
+    }
+    return 'ok';
 }
 
 sub set_retry_token {
     my ($form,$lonhost,$querystr) = @_;
     if (ref($form) eq 'HASH') {
         my ($firsturl,$token,$extras, at names);
-        @names = ('role','symb','linkprot','linkkey');
+        @names = ('role','symb','linkprot','linkkey','iptoken');
         foreach my $name (@names) {
             if ($form->{$name} ne '') {
                 $extras .= '&'.$name.'='.&escape($form->{$name});
Index: loncom/auth/lonlogin.pm
diff -u loncom/auth/lonlogin.pm:1.192 loncom/auth/lonlogin.pm:1.193
--- loncom/auth/lonlogin.pm:1.192	Tue Oct 26 15:52:54 2021
+++ loncom/auth/lonlogin.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Login Screen
 #
-# $Id: lonlogin.pm,v 1.192 2021/10/26 15:52:54 raeburn Exp $
+# $Id: lonlogin.pm,v 1.193 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -49,11 +49,29 @@
 	(join('&',$ENV{'QUERY_STRING'},$env{'request.querystring'},
 	      $ENV{'REDIRECT_QUERY_STRING'}),
 	 ['interface','username','domain','firsturl','localpath','localres',
-	  'token','role','symb','iptoken','btoken','ltoken','linkkey','saml',
-          'sso','retry']);
+	  'token','role','symb','iptoken','btoken','ltoken','ttoken','linkkey',
+          'saml','sso','retry']); 
+
+# -- check if they are a migrating user
+    if (defined($env{'form.token'})) {
+        return &Apache::migrateuser::handler($r);
+    }
+
     my $lonhost = $r->dir_config('lonHostID');
-    my $linkkey;
-    if (($env{'form.sso'}) || ($env{'form.retry'})) {
+    if ($env{'form.ttoken'}) {
+        my %info = &Apache::lonnet::tmpget($env{'form.ttoken'});
+        &Apache::lonnet::tmpdel($env{'form.ttoken'});
+        if ($info{'origurl'}) {
+            $env{'form.firsturl'} = $info{'origurl'};
+        }
+        if ($info{'ltoken'}) {
+            $env{'form.ltoken'} = $info{'ltoken'};
+        } elsif ($info{'linkprot'}) {
+            $env{'form.linkprot'} = $info{'linkprot'};
+        } elsif ($info{'linkkey'} ne '') {
+            $env{'form.linkkey'} = $info{'linkkey'};
+        }
+    } elsif (($env{'form.sso'}) || ($env{'form.retry'})) {
         my $infotoken;
         if ($env{'form.sso'}) {
             $infotoken = $env{'form.sso'};
@@ -70,9 +88,6 @@
             &Apache::lonnet::tmpdel($infotoken);
         }
     } else {
-        if ($env{'form.linkkey'}) {
-            $linkkey = $env{'form.linkkey'};
-        }        
         if (!defined($env{'form.firsturl'})) {
             &Apache::lonacc::get_posted_cgi($r,['firsturl']);
         }
@@ -82,7 +97,7 @@
             }
         }
         if (($env{'form.firsturl'} =~ m{^/+tiny/+$LONCAPA::match_domain/+\w+$}) &&
-            (!$env{'form.ltoken'}) && (!$env{'form.linkkey'})) {
+            (!$env{'form.ltoken'}) && (!$env{'form.linkprot'}) && (!$env{'form.linkkey'})) {
             &Apache::lonacc::get_posted_cgi($r,['linkkey']);
         }
         if ($env{'form.firsturl'} eq '/adm/logout') {
@@ -90,11 +105,6 @@
         }
     }
 
-# -- check if they are a migrating user
-    if (defined($env{'form.token'})) {
-	return &Apache::migrateuser::handler($r);
-    }
-
 # For "public user" - remove any exising "public" cookie, as user really wants to log-in
     my ($handle,$lonidsdir,$expirepub,$userdom);
     $lonidsdir=$r->dir_config('lonIDsDir');
@@ -149,31 +159,40 @@
             $protocol = 'http' if ($protocol ne 'https');
             my $dest = '/adm/roles';
             if ($env{'form.firsturl'} ne '') {
-                if ($env{'form.firsturl'} =~ /[^\x00-\xFF]/) {
-                    $dest = &uri_escape_utf8($env{'form.firsturl'});
-                } else {
-                    $dest = &uri_escape($env{'form.firsturl'});
-                }
-                $dest = &HTML::Entities::encode($dest,"'");
+                $dest = &HTML::Entities::encode($env{'form.firsturl'},'\'"<>&');
             }
             my %info = (
                          balcookie => $lonhost.':'.$balancer_cookie,
                        );
-            if ($env{'form.ltoken'}) {
-                my %link_info = &Apache::lonnet::tmpget($env{'form.ltoken'});
-                if ($link_info{'linkprot'}) {
-                    $info{'linkprot'} = $link_info{'linkprot'};
-                }
-                &Apache::lonnet::tmpdel($env{'form.ltoken'});
-                delete($env{'form.ltoken'});
-            } elsif ($env{'form.linkkey'}) {
-                $info{'linkkey'} = $env{'form.linkkey'};
-                delete($env{'form.linkkey'});
+            if ($env{'form.role'}) {
+                $info{'role'} = $env{'form.role'};
+            }
+            if ($env{'form.symb'}) {
+                $info{'symb'} = $env{'form.symb'};
             }
             my $balancer_token = &Apache::lonnet::tmpput(\%info,$found_server);
-            if ($balancer_token) {
+            unless (($balancer_token eq 'con_lost') || ($balancer_token eq 'refused') ||
+                    ($balancer_token eq 'unknown_cmd') || ($balancer_token eq 'no_such_host')) {
                 $dest .=  (($dest=~/\?/)?'&':'?') . 'btoken='.$balancer_token;
             }
+            if ($env{'form.firsturl'} =~ m{^/tiny/$match_domain/\w+$}) {
+                my %link_info;
+                if ($env{'form.ltoken'}) {
+                    $link_info{'ltoken'} = $env{'form.ltoken'};
+                } elsif ($env{'form.linkprot'}) {
+                    $link_info{'linkprot'} = $env{'form.linkprot'};
+                } elsif ($env{'form.linkkey'} ne '') {
+                    $link_info{'linkkey'} = $env{'form.linkkey'};
+                }
+                if (keys(%link_info)) {
+                    $link_info{'origurl'} = $env{'form.firsturl'};
+                    my $token = &Apache::lonnet::tmpput(\%link_info,$found_server,'link');
+                    unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                            ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+                        $dest .=  (($dest=~/\?/)?'&':'?') . 'ttoken='.$token;
+                    }
+                }
+            }
             unless ($found_server eq $lonhost) {
                 my $alias = &Apache::lonnet::use_proxy_alias($r,$found_server);
                 $hostname = $alias if ($alias ne '');
@@ -193,17 +212,10 @@
 # it a balancer cookie for an active session on this server.
 #
 
-    my ($balcookie,$linkprot,$linkkey);
+    my $balcookie;
     if ($env{'form.btoken'}) {
         my %info = &Apache::lonnet::tmpget($env{'form.btoken'});
         $balcookie = $info{'balcookie'};
-        if ($balcookie) {
-            if ($info{'linkprot'}) {
-                $linkprot = $info{'linkprot'};
-            } elsif ($info{'linkkey'}) {
-                $linkkey = $info{'linkkey'};
-            }
-        }    
         &Apache::lonnet::tmpdel($env{'form.btoken'});
         delete($env{'form.btoken'});
     }
@@ -234,19 +246,16 @@
 	    &Apache::loncommon::end_page();
         my $dest = '/adm/roles';
         if ($env{'form.firsturl'} ne '') {
-            if ($env{'form.firsturl'} =~ /[^\x00-\xFF]/) {
-                $dest = &uri_escape_utf8($env{'form.firsturl'});
-            } else {
-                $dest = &uri_escape($env{'form.firsturl'});
-            }
-            $dest = &HTML::Entities::encode($dest,"'");
+            $dest = &HTML::Entities::encode($env{'form.firsturl'},'\'"<>&');
         }
-        if (($env{'form.ltoken'}) || ($linkprot)) {
-            unless ($linkprot) {
+        if (($env{'form.ltoken'}) || ($env{'form.linkprot'})) {
+            my $linkprot;
+            if ($env{'form.ltoken'}) {
                 my %info = &Apache::lonnet::tmpget($env{'form.ltoken'});
                 $linkprot = $info{'linkprot'};
                 my $delete = &Apache::lonnet::tmpdel($env{'form.ltoken'});
-                delete($env{'form.ltoken'});
+            } else {
+                $linkprot = $env{'form.linkprot'};
             }
             if ($linkprot) {
                 my ($linkprotector,$deeplink) = split(/:/,$linkprot,2);
@@ -271,16 +280,14 @@
                     &Apache::lonnet::appenv({'user.linkproturi' => $deeplink});
                 }
             }
-        } elsif (($env{'form.linkkey'}) || ($linkkey)) {
+        } elsif ($env{'form.linkkey'} ne '') {
             if ($env{'form.firsturl'} =~ m{^/tiny/$match_domain/\w+$}) {
-                if ($linkkey eq '') {
-                    $linkkey = $env{'form.linkkey'};
-                }
+                my $linkkey = $env{'form.linkkey'};
                 if ($env{'user.deeplinkkey'}) {
                     my @linkkeys = split(/,/,$env{'user.deeplinkkey'});
                     unless (grep(/^\Q$linkkey\E$/, at linkkeys)) {
                         push(@linkkeys,$linkkey);
-                        &Apache::lonnet::appenv({'user.deeplinkkey' => join(',',sort(@linkkeys))});  
+                        &Apache::lonnet::appenv({'user.deeplinkkey' => join(',',sort(@linkkeys))});
                     }
                 } else {
                     &Apache::lonnet::appenv({'user.deeplinkkey' => $linkkey});
@@ -337,8 +344,7 @@
     my $defdom = $domain;
     if ($lonhost ne '') {
         unless ($sessiondata{'sessionserver'}) {
-            my $redirect = &check_loginvia($domain,$lonhost,$lonidsdir,$balcookie,
-                                           $linkprot,$linkkey);
+            my $redirect = &check_loginvia($domain,$lonhost,$lonidsdir,$balcookie);
             if ($redirect) {
                 $r->print($redirect);
                 return OK;
@@ -423,11 +429,11 @@
 
 # -------------------------------------------------------- Store away log token
     my ($tokenextras,$tokentype);
-    my @names = ('role','symb','iptoken','ltoken','linkkey');
+    my @names = ('role','symb','iptoken','ltoken','linkprot','linkkey');
     foreach my $name (@names) {
         if ($env{'form.'.$name} ne '') {
             if ($name eq 'ltoken') {
-                my %info = &Apache::lonnet::tmpget($env{'form.ltoken'});
+                my %info = &Apache::lonnet::tmpget($env{'form.'.$name});
                 if ($info{'linkprot'}) {
                     $tokenextras .= '&linkprot='.&escape($info{'linkprot'});
                     $tokentype = 'link';
@@ -435,7 +441,7 @@
                 }
             } else {
                 $tokenextras .= '&'.$name.'='.&escape($env{'form.'.$name});
-                if ($name eq 'linkkey') {
+                if (($name eq 'linkkey') || ($name eq 'linkprot')) {
                     $tokentype = 'link';
                 }
             }
@@ -445,7 +451,7 @@
         $tokenextras .= ":$tokentype";
     }
     my $logtoken=Apache::lonnet::reply(
-       'tmpput:'.$ukey.$lkey.'&'.$firsturl.$tokenextras,
+       'tmpput:'.$ukey.$lkey.'&'.&escape($firsturl).$tokenextras,
        $lonhost);
 
 # -- If we cannot talk to ourselves, or hostID does not map to a hostname
@@ -1000,7 +1006,7 @@
 }
 
 sub check_loginvia {
-    my ($domain,$lonhost,$lonidsdir,$balcookie,$linkprot,$linkkey) = @_;
+    my ($domain,$lonhost,$lonidsdir,$balcookie) = @_;
     if ($domain eq '' || $lonhost eq '' || $lonidsdir eq '') {
         return;
     }
@@ -1060,7 +1066,7 @@
                             }
                         }
                     }
-                    $output = &redirect_page($newhost,$path,$linkprot,$linkkey);
+                    $output = &redirect_page($newhost,$path);
                 }
             }
         }
@@ -1069,7 +1075,7 @@
 }
 
 sub redirect_page {
-    my ($desthost,$path,$linkprot,$linkkey) = @_;
+    my ($desthost,$path) = @_;
     my $hostname = &Apache::lonnet::hostname($desthost);
     my $protocol = $Apache::lonnet::protocol{$desthost};
     $protocol = 'http' if ($protocol ne 'https');
@@ -1087,13 +1093,18 @@
         $querystring = &HTML::Entities::encode($querystring,"'");
         $url .='?firsturl='.$querystring;
     }
-    if ($linkprot) {
-        my $ltoken = &Apache::lonnet::tmpput({linkprot => $linkprot},$desthost);
-        if ($ltoken) {
-            $url .= (($url =~ /\?/) ? '&' : '?').'ltoken='.$ltoken;
+    if (($env{'form.ltoken'}) || ($env{'form.linkkey'} ne '')) {
+        my %link_info;
+        if ($env{'form.ltoken'}) {
+            $link_info{'ltoken'} = $env{'form.ltoken'};
+        } elsif ($env{'form.linkkey'} ne '') {
+            $link_info{'linkkey'} = $env{'form.linkkey'};
+        }
+        my $token = &Apache::lonnet::tmpput(\%link_info,$desthost,'link');
+        unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+            $url .= (($url=~/\?/)?'&':'?') . 'ttoken='.$token;
         }
-    } elsif ($linkkey) {
-        $url .= (($url =~ /\?/) ? '&' : '?').'linkkey='.&uri_escape($linkkey);        
     }
     my $start_page = &Apache::loncommon::start_page('Switching Server ...',undef,
                                                     {'redirect' => [0,$url],});
Index: loncom/auth/lonroles.pm
diff -u loncom/auth/lonroles.pm:1.352 loncom/auth/lonroles.pm:1.353
--- loncom/auth/lonroles.pm:1.352	Tue Oct 26 14:25:09 2021
+++ loncom/auth/lonroles.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # User Roles Screen
 #
-# $Id: lonroles.pm,v 1.352 2021/10/26 14:25:09 raeburn Exp $
+# $Id: lonroles.pm,v 1.353 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -912,6 +912,9 @@
                                                 $dest .= (($dest =~/\?/)? '&':'?').'symb='.$esc_symb;
                                             }
                                         }
+                                        if ($env{'form.ttoken'}) {
+                                            $dest .= (($dest =~/\?/)? '&':'?').'ttoken='.$env{'form.ttoken'};
+                                        }
                                         unless ($env{'request.lti.login'}) {
                                             $msg = '<p>'.&mt('Entering [_1] ...',
                                                              $env{'course.'.$cdom.'_'.$cnum.'.description'}).
Index: loncom/auth/lonshibauth.pm
diff -u loncom/auth/lonshibauth.pm:1.11 loncom/auth/lonshibauth.pm:1.12
--- loncom/auth/lonshibauth.pm:1.11	Tue Oct 26 15:52:54 2021
+++ loncom/auth/lonshibauth.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Redirect Shibboleth authentication to designated URL (/adm/sso).
 #
-# $Id: lonshibauth.pm,v 1.11 2021/10/26 15:52:54 raeburn Exp $
+# $Id: lonshibauth.pm,v 1.12 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -98,19 +98,18 @@
              }
         } else {
             my $uri = $r->uri;
-            if ($r->args ne '') {
-                $dest .= (($dest=~/\?/)?'&':'?').$r->args;
-            }
-            unless (($uri eq '/adm/roles') || ($uri eq '/adm/logout')) {
-                unless ($r->args =~ /origurl=/) {
-                    $dest.=(($dest=~/\?/)?'&':'?').'origurl='.$uri;
-                }
-            }
             if ($uri =~ m{^/tiny/$match_domain/\w+$}) {
-                unless (($r->args =~ /ltoken=/) || ($r->args =~ /linkkey=/)) {
-                    &Apache::lonacc::get_posted_cgi($r,['linkkey']);
-                    if ($env{'form.linkkey'} ne '') {
-                        $dest.=(($dest=~/\?/)?'&':'?').'linkkey='.$env{'form.linkkey'};
+                my $querystring = &set_token($r,$lonhost);
+                if ($querystring ne '') {
+                    $dest .= '?'.$querystring;
+                }
+            } else {
+                if ($r->args ne '') {
+                    $dest .= (($dest=~/\?/)?'&':'?').$r->args;
+                }
+                unless (($uri eq '/adm/roles') || ($uri eq '/adm/logout')) {
+                    unless ($r->args =~ /origurl=/) {
+                        $dest.=(($dest=~/\?/)?'&':'?').'origurl='.$uri;
                     }
                 }
             }
@@ -134,8 +133,18 @@
         &Apache::loncommon::get_unprocessed_cgi($r->args);
     }
     if ($r->uri =~ m{^/tiny/$match_domain/\w+$}) {
-        unless (($env{'form.ltoken'}) || ($env{'form.linkkey'})) {
-            &Apache::lonacc::get_posted_cgi($r,['linkkey']);
+        if ($env{'form.ttoken'}) {
+            my %info = &Apache::lonnet::tmpget($env{'form.ttoken'});
+            &Apache::lonnet::tmpdel($env{'form.ttoken'});
+            if ($info{'ltoken'}) {
+                $env{'form.ltoken'} = $info{'ltoken'};
+            } elsif ($info{'linkkey'} ne '') {
+                $env{'form.linkkey'} = $info{'linkkey'};
+            }
+        } else {
+            unless (($env{'form.ltoken'}) || ($env{'form.linkkey'})) {
+                &Apache::lonacc::get_posted_cgi($r,['linkkey']);
+            }
         }
     }
     my $extras;
@@ -143,6 +152,7 @@
         if ($env{'form.'.$name} ne '') {
             if ($name eq 'ltoken') {
                 my %info = &Apache::lonnet::tmpget($env{'form.ltoken'});
+                &Apache::lonnet::tmpdel($env{'form.ltoken'});
                 if ($info{'linkprot'}) {
                     $extras .= '&linkprot='.&escape($info{'linkprot'});
                     last;
@@ -162,7 +172,7 @@
         foreach my $key (sort(keys(%env))) {
             if ($key =~ /^form\.(.+)$/) {
                 my $name = $1;
-                next if ($token{$name});
+                next if (($token{$name}) || ($name eq 'ttoken'));
                 $querystring .= '&'.$name.'='.$env{$key};
             }
         }
Index: loncom/auth/migrateuser.pm
diff -u loncom/auth/migrateuser.pm:1.53 loncom/auth/migrateuser.pm:1.54
--- loncom/auth/migrateuser.pm:1.53	Tue Oct 26 14:20:40 2021
+++ loncom/auth/migrateuser.pm	Wed Nov  3 01:04:02 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Starts a user off based of an existing token.
 #
-# $Id: migrateuser.pm,v 1.53 2021/10/26 14:20:40 raeburn Exp $
+# $Id: migrateuser.pm,v 1.54 2021/11/03 01:04:02 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -283,19 +283,17 @@
         my %info= (
                     'domain'          => $udom,
                     'username'        => $dataref->{'username'},
-                    'role'            => $dataref->{'role'},
                     'sessionserver'   => $lonhost,
                   );
-        if ($dataref->{'origurl'}) {
-            $info{'origurl'} = $dataref->{'origurl'};
-        }
-        if ($dataref->{'symb'}) {
-            $info{'symb'} = $dataref->{'symb'};
+        my @names = ('origurl','symb','role','linkprot','linkkey');
+        foreach my $name (@names) {
+            if ($dataref->{$name} ne '') {
+                $info{$name} = $dataref->{$name};
+            }
         }
-        my $iptoken = &Apache::lonnet::tmpput(\%info,$switchto);
+        my $iptoken = &Apache::lonnet::tmpput(\%info,$switchto,'link');
         unless ($iptoken eq 'conlost') {
-            $url .= ($url =~ /\?/) ? '&' : '?';
-            $url .= 'iptoken='.$iptoken;
+            $url .= (($url =~ /\?/) ? '&' : '?') . 'iptoken='.$iptoken;
         }
         $r->print(&Apache::loncommon::start_page($title,undef,
                                                  {'redirect' =>
@@ -709,8 +707,29 @@
 	if ($handle) {
 	    &Apache::lonnet::transfer_profile_to_env($r->dir_config('lonIDsDir'),
 						     $handle);
+            my $checklaunch;
+            if ($data{'origurl'} =~ m{^/tiny/$match_domain/\w+$}) {
+                if ($env{'request.linkprot'} ne '') {
+                     unless ($env{'request.linkprot'} eq $data{'linkprot'}) {
+                         $checklaunch = 1;
+                     }
+                }
+                if ($env{'request.linkkey'} ne '') {
+                    unless ($env{'request.linkkey'} eq $data{'linkkey'}) {
+                        $checklaunch = 1;
+                    }
+                }
+                if ($env{'request.deeplink.login'}) {
+                    unless ($env{'request.deeplink.login'} eq $data{'deeplink.login'}) {
+                        $checklaunch = 1;
+                    }
+                }
+            }
             if ($data{'linkprot'} ne '') {
                 &Apache::lonnet::appenv({'request.linkprot' => $data{'linkprot'}});
+                if ($env{'request.linkkey'}) {
+                    &Apache::lonnet::delenv('request.linkkey');
+                }
                 my ($linkprotector,$deeplink) = split(/:/,$data{'linkprot'},2);
                 if ($env{'user.linkprotector'}) {
                     my @protectors = split(/,/,$env{'user.linkprotector'});
@@ -734,6 +753,9 @@
                 }
             } elsif ($data{'linkkey'} ne '') {
                 &Apache::lonnet::appenv({'request.linkkey' => $data{'linkkey'}});
+                if ($env{'request.linkprot'}) {
+                    &Apache::lonnet::delenv('request.linkprot');
+                }
                 my $deeplink = $data{'deeplink.login'};
                 my $linkkey = $data{'linkkey'};
                 if ($env{'user.deeplinkkey'} ne '') {
@@ -837,7 +859,26 @@
                     }
                 }
             } elsif ($data{'origurl'} ne '') {
-                $r->internal_redirect($data{'origurl'});
+                my $dest = $data{'origurl'};
+                if (($env{'request.deeplink.login'} eq $data{'origurl'}) &&
+                    (($env{'request.linkprot'}) || ($env{'request.linkkey'} ne ''))) {
+                    my %info;
+                    if ($env{'request.linkprot'}) {
+                        $info{'linkprot'} = $env{'request.linkprot'};
+                    } elsif ($env{'request.linkkey'} ne '') {
+                        $info{'linkkey'} = $env{'request.linkkey'};
+                    }
+                    $info{'origurl'} = $data{'origurl'};
+                    if ($checklaunch) {
+                        $info{'checklaunch'} = 1;
+                    }
+                    my $token = &Apache::lonnet::tmpput(\%info,$r->dir_config('lonHostID'),'link');
+                    unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                            ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+                        $dest .= (($dest =~ /\?/) ? '&' : '?') . 'ttoken='.$token;
+                    }
+                }
+                $r->internal_redirect($dest);
             } elsif ($env{'request.course.id'}) {
                 $r->internal_redirect('/adm/navmaps');
 	    } else {
@@ -889,7 +930,7 @@
                 }
                 if ($data{'deeplink.login'}) {
                    if (ref($extra_env) eq 'HASH') {
-                        $extra_env->{'request.deeplink.login' => $data{'deeplink.login'}};
+                        $extra_env->{'request.deeplink.login'} = $data{'deeplink.login'};
                     } else {
                         $extra_env = {'request.deeplink.login' => $data{'deeplink.login'}};
                     }
@@ -939,15 +980,15 @@
     } elsif ($data{'origurl'} =~ m{^/tiny/$match_domain/\w+$}) {
         $next_url=$data{'origurl'};
     } else {
-        $next_url='/adm/roles?selectrole=1&'.&escape($data{'role'}).'=1';
+        $next_url='/adm/roles?selectrole=1&'.&escape($data{'role'}).'=1';
         if ($data{'origurl'} ne '') {
-            $next_url .= '&orgurl='.&escape($data{'origurl'});
+            $next_url .= '&orgurl='.&escape($data{'origurl'});
         }
     }
     if ($data{'lti.login'}) {
         if (($data{'origurl'} =~ m{/default_\d+\.sequence$}) ||
             ($data{'origurl'} =~ m{^/res/.+\.sequence$})) {
-            $next_url .= '&navmap=1';
+            $next_url .= '&navmap=1';
         }
     }
     if ($reuse_session) {
Index: loncom/interface/lontiny.pm
diff -u loncom/interface/lontiny.pm:1.7 loncom/interface/lontiny.pm:1.8
--- loncom/interface/lontiny.pm:1.7	Tue Aug 10 15:28:14 2021
+++ loncom/interface/lontiny.pm	Wed Nov  3 01:04:03 2021
@@ -2,7 +2,7 @@
 # Extract domain, courseID, and symb from a shortened URL,
 # and switch role to a role in designated course.
 #
-# $Id: lontiny.pm,v 1.7 2021/08/10 15:28:14 raeburn Exp $
+# $Id: lontiny.pm,v 1.8 2021/11/03 01:04:03 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -67,9 +67,10 @@
                     if ($cnum =~ /^$match_courseid$/) {
                         my $chome = &Apache::lonnet::homeserver($cnum,$cdom);
                         if ($chome ne 'no_host') {
-                            # Check for ltoken or linkkey
-                            my $newlauncher = &launch_check($r->uri,$symb,$cnum,$cdom);
+                            &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['ttoken']);
                             if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                                # Check for ttoken
+                                my $newlauncher = &launch_check($r->uri,$symb);
                                 my ($map,$resid,$url) = &Apache::lonnet::decode_symb($symb);
                                 if (&Apache::lonnet::is_on_map($url)) {
                                     my $realuri;
@@ -207,6 +208,9 @@
                                     }
                                     my $destination .= '/adm/roles?selectrole=1&'.$newrole.'=1'.
                                                        '&destinationurl='.&HTML::Entities::encode($r->uri);
+                                    if ($env{'form.ttoken'}) {
+                                        $destination .= '&ttoken='.$env{'form.ttoken'};
+                                    }
                                     &do_redirect($r,$destination);
                                 } elsif (keys(%possroles) > 1) {
                                     if (grep(/^(cc|co)$/, at allposs)) {
@@ -219,6 +223,9 @@
                                         $newrole .= "./$cdom/$cnum";
                                         my $destination .= '/adm/roles?selectrole=1&'.$newrole.'=1'.
                                                            '&destinationurl='.&HTML::Entities::encode($r->uri);
+                                        if ($env{'form.ttoken'}) {
+                                            $destination .= '&ttoken='.$env{'form.ttoken'};
+                                        }
                                         &do_redirect($r,$destination);
                                     } else {
                                         my $hascustom;
@@ -243,13 +250,12 @@
 }
 
 sub launch_check {
-    my ($linkuri,$symb,$cnum,$cdom) = @_;
-    &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['ltoken','linkkey']);
+    my ($linkuri,$symb) = @_;
     my ($linkprotector,$linkproturi,$linkkey,$newlauncher);
-    if ($env{'form.ltoken'}) {
-        my %link_info = &Apache::lonnet::tmpget($env{'form.ltoken'});
-        &Apache::lonnet::tmpdel($env{'form.ltoken'});
-        delete($env{'form.ltoken'});
+    if ($env{'form.ttoken'}) {
+        my %link_info = &Apache::lonnet::tmpget($env{'form.ttoken'});
+        &Apache::lonnet::tmpdel($env{'form.ttoken'});
+        delete($env{'form.ttoken'});
         if ($link_info{'linkprot'}) {
             ($linkprotector,$linkproturi) = split(/:/,$link_info{'linkprot'},2);
             if ($env{'user.linkprotector'}) {
@@ -272,84 +278,101 @@
             } else {
                 &Apache::lonnet::appenv({'user.linkproturi' => $linkproturi});
             }
-        }
-    } elsif ($env{'form.linkkey'}) {
-        $linkkey = $env{'form.linkkey'};
-        my $keyedlinkuri = $linkuri;
-        if ($env{'user.deeplinkkey'}) {
-            my @linkkeys = split(/,/,$env{'user.deeplinkkey'});
-            unless (grep(/^\Q$linkkey\E$/, at linkkeys)) {
-                push(@linkkeys,$linkkey);
-                &Apache::lonnet::appenv({'user.deeplinkkey' => join(',',sort(@linkkeys))});
-            }
-        } else {
-            &Apache::lonnet::appenv({'user.deeplinkkey' => $linkkey});
-        }
-        if ($env{'user.keyedlinkuri'}) {
-            my @keyeduris = split(/,/,$env{'user.keyedlinkuri'});
-            unless (grep(/^\Q$keyedlinkuri\E$/, at keyeduris)) {
-                push(@keyeduris,$keyedlinkuri);
-                &Apache::lonnet::appenv({'user.keyedlinkuri' => join(',',sort(@keyeduris))});
+        } elsif ($link_info{'linkkey'} ne '') {
+            $linkkey = $link_info{'linkkey'};
+            my $keyedlinkuri = $linkuri;
+            if ($env{'user.deeplinkkey'} ne '') {
+                my @linkkeys = split(/,/,$env{'user.deeplinkkey'});
+                unless (grep(/^\Q$linkkey\E$/, at linkkeys)) {
+                    push(@linkkeys,$linkkey);
+                    &Apache::lonnet::appenv({'user.deeplinkkey' => join(',',sort(@linkkeys))});
+                }
+            } else {
+                &Apache::lonnet::appenv({'user.deeplinkkey' => $linkkey});
             }
-        } else {
-            &Apache::lonnet::appenv({'user.keyedlinkuri' => $keyedlinkuri});
-        }
-        delete($env{'form.linkkey'});
-    }
-    if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
-        my $currdeeplinklogin = $env{'request.deeplink.login'};
-        if ($linkprotector || $linkkey) {
-            my $deeplink;
-            if ($symb =~ /\.(page|sequence)$/) {
-                my $mapname = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($symb))[2]);
-                my $navmap = Apache::lonnavmaps::navmap->new();
-                if (ref($navmap)) {
-                    $deeplink = $navmap->get_mapparam(undef,$mapname,'0.deeplink');
+            if ($env{'user.keyedlinkuri'}) {
+                my @keyeduris = split(/,/,$env{'user.keyedlinkuri'});
+                unless (grep(/^\Q$keyedlinkuri\E$/, at keyeduris)) {
+                    push(@keyeduris,$keyedlinkuri);
+                    &Apache::lonnet::appenv({'user.keyedlinkuri' => join(',',sort(@keyeduris))});
                 }
             } else {
-                $deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb);
+                &Apache::lonnet::appenv({'user.keyedlinkuri' => $keyedlinkuri});
             }
-            if ($deeplink ne '') {
-                my $disallow;
-                my ($state,$others,$listed,$scope,$protect) = split(/,/,$deeplink);
-                if (($protect ne 'none') && ($protect ne '')) {
-                    my ($acctype,$item) = split(/:/,$protect);
-                    if ($acctype =~ /lti(c|d)$/) {
-                        unless ($linkprotector.':'.$linkproturi eq $item.$1.':'.$linkuri) {
-                            $disallow = 1;
-                        }
-                    } elsif ($acctype eq 'key') {
-                        unless ($linkkey eq $item) {
-                            $disallow = 1;
-                        }
+        }
+        if ($link_info{'checklaunch'}) {
+            $newlauncher = 1;
+        }
+    }
+    my $currdeeplinklogin = $env{'request.deeplink.login'};
+    my $deeplink;
+    if ($symb =~ /\.(page|sequence)$/) {
+        my $mapname = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($symb))[2]);
+        my $navmap = Apache::lonnavmaps::navmap->new();
+        if (ref($navmap)) {
+            $deeplink = $navmap->get_mapparam(undef,$mapname,'0.deeplink');
+        }
+    } else {
+        $deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb);
+    }
+    if ($deeplink ne '') {
+        my $disallow;
+        my ($state,$others,$listed,$scope,$protect) = split(/,/,$deeplink);
+        if (($protect ne 'none') && ($protect ne '')) {
+            my ($acctype,$item) = split(/:/,$protect);
+            if ($acctype =~ /lti(c|d)$/) {
+                my $ltitype = $1;
+                if ($linkprotector) {
+                    unless ($linkprotector.':'.$linkproturi eq $item.$ltitype.':'.$linkuri) {
+                        $disallow = 1;
                     }
+                } else {
+                    $disallow = 1;
                 }
-                if ($disallow) {
-                    if ($currdeeplinklogin eq $linkuri) {
-                        &Apache::lonnet::delenv('request.deeplink.login');
+            } elsif ($acctype eq 'key') {
+                if ($linkkey ne '') {
+                    unless ($linkkey eq $item) {
+                        $disallow = 1;
                     }
                 } else {
-                    unless ($currdeeplinklogin eq $linkuri) {
-                        if ($linkprotector) {
-                            &Apache::lonnet::appenv({'request.linkprot' => $linkprotector.':'.$linkproturi});
-                        } elsif ($linkkey) {
-                            &Apache::lonnet::appenv({'request.linkkey' => $linkkey});
-                        }
-                        $newlauncher = 1;
-                    }
-                    &Apache::lonnet::appenv({'request.deeplink.login' => $linkuri});
+                    $disallow = 1;
                 }
             }
+        }
+        if ($disallow) {
+            if ($currdeeplinklogin eq $linkuri) {
+                &Apache::lonnet::delenv('request.deeplink.login');
+            }
         } else {
+            unless ($currdeeplinklogin eq $linkuri) {
+                if (($linkprotector) || ($linkkey ne '')) {
+                    if ($linkprotector) {
+                        &Apache::lonnet::appenv({'request.linkprot' => $linkprotector.':'.$linkproturi});
+                    } elsif ($env{'request.linkprot'}) {
+                        &Apache::lonnet::delenv({'request.linkprot'});
+                    }
+                    if ($linkkey ne '') {
+                        &Apache::lonnet::appenv({'request.linkkey' => $linkkey});
+                    } elsif ($env{'request.linkkey'} ne '') {
+                        &Apache::lonnet::delenv({'request.linkkey'});
+                    }
+                    $newlauncher = 1;
+                }
+            }
             &Apache::lonnet::appenv({'request.deeplink.login' => $linkuri});
         }
     } else {
-        &Apache::lonnet::appenv({'request.deeplink.login' => $linkuri});
         if ($linkprotector) {
             &Apache::lonnet::appenv({'request.linkprot' => $linkprotector.':'.$linkproturi});
-        } elsif ($linkkey) {
+        } elsif ($env{'request.linkprot'}) {
+            &Apache::lonnet::delenv({'request.linkprot'});
+        }
+        if ($linkkey ne '') {
             &Apache::lonnet::appenv({'request.linkkey' => $linkkey});
+        } else {
+            &Apache::lonnet::delenv({'request.linkkey'});
         }
+        &Apache::lonnet::appenv({'request.deeplink.login' => $linkuri});
     }
     return $newlauncher;
 }
@@ -402,12 +425,15 @@
                 $preamble = &mt('You have the following active roles in this course:');
             }
             $datatable = '<form name="" action="/adm/roles">'.
-                         '<input type="hidden" name="newrole" value="" />'.
-                         '<input type="hidden" name="selectrole" value="1" />'.
-                         '<input type="hidden" name="destinationurl" value="'.$r->uri.'" />'.
-                         &Apache::loncommon::start_data_table().
-                         &Apache::loncommon::start_data_table_header_row().
-                         '<th></th><th>'.&mt('User role').'</th>';
+                         '<input type="hidden" name="newrole" value="" />'."\n".
+                         '<input type="hidden" name="selectrole" value="1" />'."\n".
+                         '<input type="hidden" name="destinationurl" value="'.$r->uri.'" />'."\n";
+            if ($env{'form.ttoken'}) {
+                $datatable .= '<input type="hidden" name="ttoken" value="'.$env{'form.ttoken'}.'" />'."\n";
+            }
+            $datatable .= &Apache::loncommon::start_data_table().
+                          &Apache::loncommon::start_data_table_header_row().
+                          '<th></th><th>'.&mt('User role').'</th>';
             if ($hassection) {
                 $datatable .= '<th>'.&mt('Section').'</th>';
             }
Index: loncom/lontrans.pm
diff -u loncom/lontrans.pm:1.36 loncom/lontrans.pm:1.37
--- loncom/lontrans.pm:1.36	Fri Oct  8 14:36:51 2021
+++ loncom/lontrans.pm	Wed Nov  3 01:04:03 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # URL translation for User Files
 #
-# $Id: lontrans.pm,v 1.36 2021/10/08 14:36:51 raeburn Exp $
+# $Id: lontrans.pm,v 1.37 2021/11/03 01:04:03 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -105,14 +105,15 @@
                 if (($uri eq '/adm/switchserver') || ($uri =~ m{^/Shibboleth.sso/})) {
                     return DECLINED;
                 }
-                unless ($uri eq '/adm/migrateuser') {
+                unless (($uri eq '/adm/migrateuser') || ($uri eq '/adm/sso')) {
                     my %user;
                     my $handle = &Apache::lonnet::check_for_valid_session($r,undef,\%user);
                     if (($handle) && ($user{'name'} ne '') && ($user{'domain'} ne '')) {
                         unless (($user{'name'} eq 'public') && ($user{'domain'} eq 'public')) {
-                            my $token = &get_token($r,$remote_ip,\%user);
+                            my $dest = '/adm/migrateuser';
+                            my $token = &set_token($r,$dest,$remote_ip,\%user);
                             unless ($token eq '') {
-                                $r->internal_redirect("/adm/migrateuser?token=$token");
+                                $r->internal_redirect("$dest?token=$token");
                                 $r->set_handlers('PerlHandler'=> undef);
                                 return DECLINED;
                             }
@@ -124,19 +125,29 @@
                 if ($port eq '443') {
                     $protocol = 'https';
                 }
-                my $querystring = $r->args;
                 if ($uri =~ m{^(/adm/css/)(.+)(.css)$}) {
                     $uri = $1.&escape($2).$3;
                 }
                 my $location = $protocol.'://'.$redirect.$uri;
-                if ($querystring) {
-                    $location .= "?$querystring";
+                if ($r->uri =~ m{^/tiny/$match_domain/\w+$}) {
+                    my $token = &set_token($r,$r->uri);
+                    unless ($token eq '') {
+                        $location .= '?ttoken='.$token;
+                    }
+                } elsif ($r->args) {
+                    $location .= '?'.$r->args;
                 }
                 $r->header_out(Location => $location);
                 return REDIRECT;
             }
         }
     }
+    if ($r->uri =~ m{^/tiny/$match_domain/\w+$}) {
+        my $token = &set_token($r,$r->uri);
+        unless ($token eq '') {
+            $r->args('ttoken='.$token);
+        }
+    }
     if ($r->uri=~m|^(/raw)?/uploaded/|) {
         my $fn = $r->uri();
         $fn=~s/^\/raw//;
@@ -208,40 +219,83 @@
     return $redirect;
 }
 
-sub get_token {
-    my ($r,$remote_ip,$userref) = @_;
-    return unless (ref($userref) eq 'HASH');
-    my %user = %{$userref};
-    my %info = ('ip' => $remote_ip,
-                'domain'    => $user{'domain'},
-                'username'  => $user{'name'},
-                'server'    => $r->dir_config('lonHostID'),
-               );
-    my $query = $r->args;
-    if ($query) {
-        foreach my $pair (split(/&/,$query)) {
+sub set_token {
+    my ($r,$dest,$remote_ip,$userref) = @_;
+    my (%info,%user);
+    if ($dest eq '/adm/migrateuser') {
+        return unless (ref($userref) eq 'HASH');
+        %user = %{$userref};
+        %info = ('ip' => $remote_ip,
+                 'domain'    => $user{'domain'},
+                 'username'  => $user{'name'},
+                 'server'    => $r->dir_config('lonHostID'),
+                );
+    }
+    if ($r->args) {
+        foreach my $pair (split(/&/,$r->args)) {
             my ($name,$value) = split(/=/,$pair);
             $name = &LONCAPA::unescape($name);
-            next unless (($name eq 'role') || ($name eq 'symb'));
+            next unless (($name eq 'role') || ($name eq 'symb') ||
+                         ($name eq 'ltoken') || ($name eq 'linkkey') ||
+                         ($name eq 'ttoken'));
             $value =~ tr/+/ /;
             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
             $info{$name} = $value;
         }
+        if ($info{'ttoken'}) {
+            my %current = &Apache::lonnet::tmpget($info{'ttoken'});
+            delete($info{'ttoken'});
+            if ($current{'origurl'} eq $r->uri) {
+                return if (keys(%current));
+            }
+        }
     }
-    unless ($info{'role'}) {
-        if ($user{'role'} ne '') {
-            $info{'role'} = $user{'role'};
+#
+# To be able to support deep-linking for a shortened URL, i.e.,
+# /tiny/$dom/$id with (a) linkkey included in POSTed data and
+# data and (b) non-Shibboleth SSO in use, ltoken (or linkprot
+# if sending to /adm/migrateuser), or linkkey are written to a
+# .tmp file in /home/httpd/perl/tmp and the query string is
+# replaced with ttoken=tokenid.  This does mean other POSTed
+# data will be discarded.
+#
+    if ($r->uri =~ m{^/tiny/$match_domain/\w+$}) {
+        unless (($info{'ltoken'}) || ($info{'linkkey'} ne '')) {
+            &Apache::lonacc::get_posted_cgi($r,['linkkey']);
+            if ($env{'form.linkkey'} ne '') {
+                $info{'linkkey'} = $env{'form.linkkey'};
+            }
         }
     }
-    unless ($info{'symb'}) {
-        unless ($r->uri eq '/adm/roles') {
-             $info{'origurl'} = $r->uri;
+    if ($dest eq '/adm/migrateuser') {
+        if ($info{'ltoken'}) {
+            my %link_info = &Apache::lonnet::tmpget($info{'ltoken'});
+            if ($link_info{'linkprot'}) {
+                $info{'linkprot'} = $link_info{'linkprot'};
+            }
+            &Apache::lonnet::tmpdel($info{'ltoken'});;
+            delete($info{'ltoken'});
+        }
+        unless ($info{'role'}) {
+            if ($user{'role'} ne '') {
+                $info{'role'} = $user{'role'};
+            }
+        }
+        unless ($info{'symb'}) {
+            unless ($r->uri eq '/adm/roles') {
+                $info{'origurl'} = $r->uri;
+            }
         }
     }
-    my $token = &Apache::lonnet::tmpput(\%info,$r->dir_config('lonHostID'));
-    unless (($token eq 'con_lost') || ($token eq 'refused') ||
-            ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
-        return $token;
+    if (($dest eq '/adm/migrateuser') || (keys(%info) > 0)) {
+        unless ($dest eq '/adm/migrateuser') {
+            $info{'origurl'} = $r->uri;
+        }
+        my $token = &Apache::lonnet::tmpput(\%info,$r->dir_config('lonHostID'),'link');
+        unless (($token eq 'con_lost') || ($token eq 'refused') ||
+                ($token eq 'unknown_cmd') || ($token eq 'no_such_host')) {
+            return $token;
+        }
     }
     return;
 }
Index: loncom/lti/ltiauth.pm
diff -u loncom/lti/ltiauth.pm:1.23 loncom/lti/ltiauth.pm:1.24
--- loncom/lti/ltiauth.pm:1.23	Thu Aug 12 00:05:27 2021
+++ loncom/lti/ltiauth.pm	Wed Nov  3 01:04:04 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Basic LTI Authentication Module
 #
-# $Id: ltiauth.pm,v 1.23 2021/08/12 00:05:27 raeburn Exp $
+# $Id: ltiauth.pm,v 1.24 2021/11/03 01:04:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -197,7 +197,7 @@
                                 delete($env{'form.'.$key});
                             }
                             my $ltoken = &Apache::lonnet::tmpput({'linkprot' => $itemid.$ltitype.':'.$tail},
-                                                                 $lonhost);
+                                                                 $lonhost,'link');
                             if ($ltoken) {
                                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                                 $r->set_handlers('PerlHandler'=> undef);


More information about the LON-CAPA-cvs mailing list