--- loncom/auth/lonauth.pm 2018/04/21 21:29:31 1.151 +++ loncom/auth/lonauth.pm 2021/08/10 15:28:13 1.166 @@ -1,7 +1,7 @@ # The LearningOnline Network # User Authentication Module # -# $Id: lonauth.pm,v 1.151 2018/04/21 21:29:31 raeburn Exp $ +# $Id: lonauth.pm,v 1.166 2021/08/10 15:28:13 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -46,7 +46,7 @@ use Digest::MD5; # ------------------------------------------------------------ Successful login sub success { my ($r, $username, $domain, $authhost, $lowerurl, $extra_env, - $form,$skipcritical) = @_; + $form,$skipcritical,$cid) = @_; # ------------------------------------------------------------ Get cookie ready my $cookie = @@ -60,8 +60,9 @@ sub success { # -------------------------------------------------------------------- Log this + my $ip = &Apache::lonnet::get_requestor_ip(); &Apache::lonnet::log($domain,$username,$authhost, - "Login $ENV{'REMOTE_ADDR'}"); + "Login $ip"); # ------------------------------------------------- Check for critical messages @@ -76,8 +77,9 @@ sub success { # ----------------------------------------------------------- Get cookies ready my ($securecookie,$defaultcookie); - if ($ENV{'SERVER_PORT'} == 443) { - $securecookie="lonID=$cookie; path=/; HttpOnly; secure"; + my $ssl = $r->subprocess_env('https'); + if ($ssl) { + $securecookie="lonSID=$cookie; path=/; HttpOnly; secure"; my $lonidsdir=$r->dir_config('lonIDsDir'); if (($lonidsdir) && (-e "$lonidsdir/$cookie.id")) { my $linkname=substr(Digest::MD5::md5_hex(Digest::MD5::md5_hex(time(). {}. rand(). $$)), 0, 32).'_linked'; @@ -88,7 +90,7 @@ sub success { "$lonidsdir/$linkname.id"); 1 }; if ($made_symlink) { $defaultcookie = "lonLinkID=$linkname; path=/; HttpOnly;"; - &Apache::lonnet::appenv({'user.linkedenv' => "$lonidsdir/$linkname.id"}); + &Apache::lonnet::appenv({'user.linkedenv' => $linkname}); } } } else { @@ -106,6 +108,12 @@ sub success { $r->headers_out->add('Set-cookie' => $defaultcookie); } $r->send_http_header; + if (ref($form) eq 'HASH') { + $form->{'lti.login'} = $env{'request.lti.login'}; + $form->{'lti.reqcrs'} = $env{'request.lti.reqcrs'}; + $form->{'lti.reqrole'} = $env{'request.lti.reqrole'}; + $form->{'lti.sourcecrs'} = $env{'request.lti.sourcecrs'}; + } &Apache::ltiauth::lti_reqcrs($r,$domain,$form,$username,$domain); return; } @@ -141,16 +149,26 @@ sub success { } if (defined($form->{symb})) { my $destsymb = $form->{symb}; + my $encrypted; + if ($destsymb =~ m{^/enc/}) { + $encrypted = 1; + if ($cid) { + $destsymb = &Apache::lonenc::unencrypted($destsymb,$cid); + } + } $destination .= ($destination =~ /\?/) ? '&' : '?'; if ($destsymb =~ /___/) { - # FIXME Need to deal with encrypted symbs and urls as needed. my ($map,$resid,$desturl)=split(/___/,$destsymb); $desturl = &Apache::lonnet::clutter($desturl); + if ($encrypted) { + $desturl = &Apache::lonenc::encrypted($desturl,1,$cid); + $destsymb = $form->{symb}; + } $desturl = &HTML::Entities::encode($desturl,'"<>&'); $destsymb = &HTML::Entities::encode($destsymb,'"<>&'); $destination .= 'destinationurl='.$desturl. '&destsymb='.$destsymb; - } else { + } elsif (!$encrypted) { $destsymb = &HTML::Entities::encode($destsymb,'"<>&'); $destination .= 'destinationurl='.$destsymb; } @@ -165,7 +183,6 @@ sub success { $windowname .= 'lti'; } my $windowinfo = Apache::lonhtmlcommon::scripttag('self.name="'.$windowname.'";'); - my $header = ''; my $brcrum = [{'href' => '', 'text' => 'Successful Login'},]; my $args = {'bread_crumbs' => $brcrum,}; @@ -202,20 +219,40 @@ sub success { } $r->send_http_header; - my $start_page; + my ($start_page,$js,$pagebody,$end_page); if ($env{'request.lti.login'}) { - $args = {'only_body' => 1,}; - $start_page=&Apache::loncommon::start_page('',$header,$args); - } else { - $start_page=&Apache::loncommon::start_page('Successful Login', - $header,$args); + $args = {'only_body' => 1}; + if ($env{'request.lti.target'} eq '') { + my $ltitarget = (($destination =~ /\?/) ? '&' : '?'). + 'ltitarget=iframe'; + $js = <<"ENDJS"; + + - my $continuelink=''.&mt('Continue').''; +ENDJS + $args->{'add_entries'} = {'onload' => "javascript:setLTItarget();"}; + $pagebody = ''; + } else { + $args->{'redirect'} = [0,$destination,1]; + } + $start_page=&Apache::loncommon::start_page('',$js,$args); + } else { + $args->{'redirect'} = [0,$destination]; + $start_page=&Apache::loncommon::start_page('Successful Login', + $js,$args); - my $pagebody; - unless ($env{'request.lti.login'}) { my %lt=&Apache::lonlocal::texthash( 'wel' => 'Welcome', 'pro' => 'Login problems?', @@ -227,11 +264,11 @@ sub success { $pagebody .= '

'.$lt{'pro'}.'

'; } } + $end_page = &Apache::loncommon::end_page(); $r->print(<{firsturl})) { + my $firsturl = &Apache::loncommon::cleanup_html($form->{firsturl}); + if ($firsturl ne '') { + $retry .= (($retry=~/\?/)?'&':'?').'firsturl='.$firsturl; + if ($firsturl =~ m{^/tiny/$match_domain/\w+$}) { + unless (exists($form->{linkprot})) { + if (exists($form->{linkkey})) { + $retry .= 'linkkey='.$form->{linkkey}; + } + } + } + } + } + if (exists($form->{linkprot})) { + my $ltoken = &Apache::lonnet::tmpput({linkprot => $form->{'linkprot'}}, + $r->dir_config('lonHostID')); + if ($ltoken) { + $retry .= (($retry =~ /\?/) ? '&' : '?').'ltoken='.$ltoken; + } + } my $end_page = &Apache::loncommon::end_page(); &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; @@ -329,13 +386,41 @@ sub handler { &Apache::lonnet::transfer_profile_to_env($lonidsdir,$handle); &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; - my $start_page = + my $start_page = &Apache::loncommon::start_page('Already logged in'); 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; + } + &set_deeplink_login(%form); + } else { + $r->print( + $start_page + .'

'.&mt('You are already logged in!').'

' + .'

'.&mt('Please [_1]log out[_2] first, and then try your access again', + '','') + .'

' + .$end_page); + return OK; + } + } } $r->print( $start_page @@ -407,7 +492,7 @@ sub handler { return OK; } - my ($key,$firsturl,$rolestr,$symbstr,$iptokenstr)=split(/&/,$tmpinfo); + my ($key,$firsturl,$rolestr,$symbstr,$iptokenstr,$linkstr)=split(/&/,$tmpinfo); if ($rolestr) { $rolestr = &unescape($rolestr); } @@ -417,6 +502,12 @@ sub handler { if ($iptokenstr) { $iptokenstr = &unescape($iptokenstr); } + if ($linkstr) { + $linkstr = &unescape($linkstr); + } + if ($firsturl =~ m{^/tiny/$match_domain/\w+$}) { + $form{'firsturl'} = $firsturl; + } if ($rolestr =~ /^role=/) { (undef,$form{'role'}) = split('=',$rolestr); } @@ -426,6 +517,11 @@ sub handler { if ($iptokenstr =~ /^iptoken=/) { (undef,$form{'iptoken'}) = split('=',$iptokenstr); } + if ($linkstr =~ /^linkprot=/) { + (undef,$form{'linkprot'}) = split('=',$linkstr); + } elsif ($linkstr =~ /^linkkey=/) { + (undef,$form{'linkkey'}) = split('=',$linkstr); + } my $upass = $ENV{HTTPS} ? $form{'upass0'} : &Apache::loncommon::des_decrypt($key,$form{'upass0'}); @@ -576,11 +672,16 @@ sub handler { ($is_balancer,$otherserver) = &Apache::lonnet::check_loadbalancing($form{'uname'},$form{'udom'},'login'); if ($is_balancer) { + # Check if browser sent a LON-CAPA load balancer cookie (and this is a balancer) + my ($found_server,$balancer_cookie) = &Apache::lonnet::check_for_balancer_cookie($r); + if (($found_server) && ($balancer_cookie =~ /^\Q$env{'user.domain'}\E_\Q$env{'user.name'}\E_/)) { + $otherserver = $found_server; + } if ($otherserver eq '') { my $lowest_load; ($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($form{'udom'}); if ($lowest_load > 100) { - $otherserver = &Apache::lonnet::spareserver($lowest_load,$lowest_load,1,$form{'udom'}); + $otherserver = &Apache::lonnet::spareserver($r,$lowest_load,$lowest_load,1,$form{'udom'}); } } if ($otherserver ne '') { @@ -598,7 +699,7 @@ sub handler { \%form); my $switchto = '/adm/switchserver?otherserver='.$otherserver; if (($firsturl) && ($firsturl ne '/adm/switchserver') && ($firsturl ne '/adm/roles')) { - $switchto .= '&origurl='.$firsturl; + $switchto .= '&origurl='.$firsturl; #should escape } if ($form{'role'}) { $switchto .= '&role='.$form{'role'}; @@ -606,8 +707,18 @@ sub handler { if ($form{'symb'}) { $switchto .= '&symb='.$form{'symb'}; } + if ($form{'linkprot'}) { + $env{'request.linkprot'} = $form{'linkprot'}; + } elsif ($form{'linkkey'}) { + $env{'request.linkkey'} = $form{'linkkey'}; + } + if ($form{'firsturl'} =~ m{^/tiny/$match_domain/\w+$}) { + &set_deeplink_login(%form); + } $r->internal_redirect($switchto); } else { + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; $r->print(&noswitch()); } return OK; @@ -619,7 +730,7 @@ sub handler { \%form); my $switchto = '/adm/switchserver?otherserver='.$otherserver; if (($firsturl) && ($firsturl ne '/adm/switchserver') && ($firsturl ne '/adm/roles')) { - $switchto .= '&origurl='.$firsturl; + $switchto .= '&origurl='.$firsturl; #should escape } if ($form{'role'}) { $switchto .= '&role='.$form{'role'}; @@ -627,8 +738,18 @@ sub handler { if ($form{'symb'}) { $switchto .= '&symb='.$form{'symb'}; } + if ($form{'linkprot'}) { + $env{'request.linkprot'} = $form{'linkprot'}; + } elsif ($form{'linkkey'}) { + $env{'request.linkkey'} = $form{'linkkey'}; + } + if ($form{'firsturl'} =~ m{^/tiny/$match_domain/\w+$}) { + &set_deeplink_login(%form); + } $r->internal_redirect($switchto); } else { + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; $r->print(&noswitch()); } return OK; @@ -649,13 +770,21 @@ sub handler { # ---------------------------------------------------------- Are we overloaded? if ((($userloadpercent>100.0)||($loadpercent>100.0))) { - my $unloaded=Apache::lonnet::spareserver($loadpercent,$userloadpercent,1,$form{'udom'}); + my $unloaded=Apache::lonnet::spareserver($r,$loadpercent,$userloadpercent,1,$form{'udom'}); if (!$unloaded) { ($unloaded) = &Apache::lonnet::choose_server($form{'udom'}); } if ($unloaded) { &success($r,$form{'uname'},$form{'udom'},$authhost,'noredirect', undef,\%form); + if ($form{'linkprot'}) { + $env{'request.linkprot'} = $form{'linkprot'}; + } elsif ($form{'linkkey'}) { + $env{'request.linkkey'} = $form{'linkkey'}; + } + if ($form{'firsturl'} =~ m{^/tiny/$match_domain/\w+$}) { + &set_deeplink_login(%form); + } $r->internal_redirect('/adm/switchserver?otherserver='.$unloaded.'&origurl='.$firsturl); return OK; } @@ -663,12 +792,89 @@ sub handler { if (($is_balancer) && ($hosthere)) { $form{'noloadbalance'} = $hosthere; } - &success($r,$form{'uname'},$form{'udom'},$authhost,$firsturl,undef, + my $extra_env; + if ($form{'linkprot'}) { + my ($linkprotector,$uri) = split(/:/,$form{'linkprot'},2); + if ($linkprotector) { + $extra_env = {'user.linkprotector' => $linkprotector, + 'user.linkproturi' => $uri}; + } + } elsif ($form{'linkkey'}) { + $extra_env = {'user.deeplinkkey' => $form{'linkkey'}, + 'user.keyedlinkuri' => $form{'firsturl'}}; + } + if ($form{'firsturl'} =~ m{^/tiny/$match_domain/\w+$}) { + &set_deeplink_login(%form); + if ($form{'linkprot'}) { + if (ref($extra_env) eq 'HASH') { + %{$extra_env} = ( %{$extra_env}, 'request.linkprot' => $form{'linkprot'} ); + } else { + $extra_env = {'request.linkprot' => $form{'linkprot'}}; + } + } elsif ($form{'linkkey'}) { + if (ref($extra_env) eq 'HASH') { + %{$extra_env} = ( %{$extra_env}, 'request.linkkey' => $form{'linkkey'} ); + } else { + $extra_env = {'request.linkkey' => $form{'linkkey'}}; + } + } + if ($env{'request.deeplink.login'}) { + if (ref($extra_env) eq 'HASH') { + %{$extra_env} = ( %{$extra_env}, 'request.deeplink.login' => $form{'firsturl'} ); + } else { + $extra_env = {'request.deeplink.login' => $form{'firsturl'}}; + } + } + } + &success($r,$form{'uname'},$form{'udom'},$authhost,$firsturl,$extra_env, \%form); return OK; } } +sub set_deeplink_login { + my (%form) = @_; + 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); + 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 ($state,$others,$listed,$scope,$protect) = split(/,/,$deeplink); + if (($protect ne 'none') && ($protect ne '')) { + my ($acctype,$item) = split(/:/,$protect); + if ($acctype =~ /lti(c|d)$/) { + unless ($form{'linkprot'} eq $item.$1.':'.$env{'request.deeplink.login'}) { + $disallow = 1; + } + } elsif ($acctype eq 'key') { + unless ($form{'linkkey'} eq $item) { + $disallow = 1; + } + } + } + } + unless ($disallow) { + $env{'request.deeplink.login'} = $form{'firsturl'}; + } + } else { + $env{'request.deeplink.login'} = $form{'firsturl'}; + } + } + } + return; +} + sub check_can_host { my ($r,$form,$authhost,$domdesc) = @_; return unless (ref($form) eq 'HASH'); @@ -709,7 +915,10 @@ sub check_can_host { if ($login_host ne '') { my $protocol = $Apache::lonnet::protocol{$login_host}; $protocol = 'http' if ($protocol ne 'https'); + my $alias = &Apache::lonnet::use_proxy_alias($r,$login_host); + $hostname = $alias if ($alias ne ''); my $newurl = $protocol.'://'.$hostname.'/adm/createaccount'; +#FIXME Should preserve where user was going and linkprot by setting ltoken at $login_host $r->print(&Apache::loncommon::start_page('Create a user account in LON-CAPA'). '

'.&mt('Account creation').'

'. &mt('You do not currently have a LON-CAPA account at this institution.').'
'. @@ -726,6 +935,14 @@ sub check_can_host { } else { &success($r,$form->{'uname'},$udom,$authhost,'noredirect',undef, $form); + if ($form->{'linkprot'}) { + $env{'request.linkprot'} = $form->{'linkprot'}; + } elsif ($form->{'firsturl'} =~ m{^/tiny/$match_domain/\w+$}) { + if ($form->{'linkkey'}) { + $env{'request.linkkey'} = $form->{'linkkey'}; + } + $env{'request.deeplink.login'} = $form->{'firsturl'}; + } my ($otherserver) = &Apache::lonnet::choose_server($udom); $r->internal_redirect('/adm/switchserver?otherserver='.$otherserver); }