--- loncom/interface/resetpw.pm 2009/09/14 14:48:20 1.11.2.1 +++ loncom/interface/resetpw.pm 2025/02/15 03:43:36 1.54 @@ -1,7 +1,7 @@ # The LearningOnline Network # Allow access to password changing via a token sent to user's e-mail. # -# $Id: resetpw.pm,v 1.11.2.1 2009/09/14 14:48:20 raeburn Exp $ +# $Id: resetpw.pm,v 1.54 2025/02/15 03:43:36 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,6 +26,28 @@ # http://www.lon-capa.org/ # # + +=pod + +=head1 NAME + +Apache::resetpw: reset user password. + +=head1 SYNOPSIS + +Handles resetting of forgotten passwords. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +=head1 OVERVIEW + +A user with an e-mail address associated with his/her LON-CAPA username +can reset a forgotten password, using a link sent to the e-mail address +if the authentication type for the account is "internal". + +=cut + package Apache::resetpw; use strict; @@ -33,8 +55,10 @@ use Apache::Constants qw(:common); use Apache::lonacc; use Apache::lonnet; use Apache::loncommon; +use Apache::lonpreferences; use Apache::lonlocal; use LONCAPA; +use HTML::Entities; sub handler { my $r = shift; @@ -44,119 +68,424 @@ sub handler { return OK; } my $contact_name = &mt('LON-CAPA helpdesk'); - my $contact_email = $r->dir_config('lonSupportEMail'); + my $origmail = $r->dir_config('lonSupportEMail'); my $server = $r->dir_config('lonHostID'); - my $defdom = $r->dir_config('lonDefDomain'); + my $defdom = &Apache::lonnet::default_login_domain(); + my $contacts = + &Apache::loncommon::build_recipient_list(undef,'helpdeskmail', + $defdom,$origmail); + my ($contact_email) = split(',',$contacts); + my $handle = &Apache::lonnet::check_for_valid_session($r); + my $lonidsdir=$r->dir_config('lonIDsDir'); + if ($handle ne '') { + if ($handle=~/^publicuser\_/) { + unlink($r->dir_config('lonIDsDir')."/$handle.id"); + } else { + &Apache::lonnet::transfer_profile_to_env($lonidsdir,$handle); + } + } &Apache::lonacc::get_posted_cgi($r); &Apache::lonlocal::get_language_handle($r); &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['token']); my @emailtypes = ('permanentemail','critnotification','notification'); - my $uname = &unescape($env{'form.uname'}); - my $udom = $env{'form.udom'}; + my $uname = $env{'form.uname'}; + $uname =~ s/^\s+|\s+$//g; + $uname = &LONCAPA::clean_username($uname); + my $udom = &LONCAPA::clean_domain($env{'form.udom'}); + my ($domdesc,$otherinst,$lookup); + if ($udom) { + $domdesc = &Apache::lonnet::domain($udom,'description'); + if ($domdesc) { + $otherinst = 1; + my @ids=&Apache::lonnet::current_machine_ids(); + my %servers = &Apache::lonnet::internet_dom_servers($udom); + foreach my $hostid (keys(%servers)) { + if (grep(/^\Q$hostid\E$/,@ids)) { + $otherinst = 0; + last; + } + } + } + } + my $dom_in_effect = $defdom; + if (($udom ne '') && ($domdesc ne '')) { + unless ($otherinst) { + $dom_in_effect = $udom; + } + } + my %passwdconf = &Apache::lonnet::get_passwdconf($dom_in_effect); + my $clientip = &Apache::lonnet::get_requestor_ip($r); my $token = $env{'form.token'}; - my $start_page = - &Apache::loncommon::start_page('Reset password','', - { - 'no_inline_link' => 1,}); - $r->print($start_page); - $r->print('

'.&mt('Reset forgotten LON-CAPA password').'

'); + my $useremail = $env{'form.useremail'}; + if (($udom ne '') && (!$otherinst) && (!$token)) { + if ($uname ne '') { + my $uhome = &Apache::lonnet::homeserver($uname,$udom); + if ($uhome eq 'no_host') { + my %srch = (srchby => 'uname_ci', + srchdomain => $udom, + srchterm => $uname, + srchtype => 'exact'); + my %srch_results = &Apache::lonnet::usersearch(\%srch); + if (keys(%srch_results) > 1) { + $lookup = 'nonunique'; + if ($useremail =~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) { + foreach my $key (keys(%srch_results)) { + if (ref($srch_results{$key}) eq 'HASH') { + if ($srch_results{$key}{permanentemail} =~ /^\Q$useremail\E$/i) { + ($uname) = split(/:/,$key); + undef($lookup); + last; + } + } + } + } + } elsif (keys(%srch_results) == 1) { + my $match = (keys(%srch_results))[0]; + ($uname) = split(/:/,$match); + } else { + $lookup = 'nomatch'; + } + } + } + if (($lookup eq 'nomatch') || ($uname eq '')) { + if (($useremail =~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) && + ($passwdconf{'resetprelink'} eq 'either')) { + my %srch = (srchby => 'email', + srchdomain => $udom, + srchterm => $useremail, + srchtype => 'exact'); + my %srch_results = &Apache::lonnet::usersearch(\%srch); + if (keys(%srch_results) > 1) { + $lookup = 'nonunique'; + } elsif (keys(%srch_results) == 1) { + my $match = (keys(%srch_results))[0]; + ($uname) = split(/:/,$match); + undef($lookup); + } else { + $lookup = 'nomatch'; + } + } + } + } + my $brcrum = []; + if ($token) { + push (@{$brcrum}, + {href => '/adm/resetpw', + text => 'Update Password'}); + } else { + push (@{$brcrum}, + {href => '/adm/resetpw', + text => 'Account Information'}); + if ($uname && $udom) { + push (@{$brcrum}, + {href => '/adm/resetpw', + text => 'Result'}); + } + } + my $args = {bread_crumbs => $brcrum}; + my $js; + unless ($token || $otherinst || ($uname && $udom)) { + my (@intdoms,@instdoms); + my $internet_names = &Apache::lonnet::get_internet_names($server); + if (ref($internet_names) eq 'ARRAY') { + @intdoms = @{$internet_names}; + } + if (@intdoms) { + my %iphost = &Apache::lonnet::get_iphost(); + foreach my $ip (keys(%iphost)) { + if (ref($iphost{$ip}) eq 'ARRAY') { + foreach my $id (@{$iphost{$ip}}) { + my $location = &Apache::lonnet::internet_dom($id); + if ($location) { + if (grep(/^\Q$location\E$/,@intdoms)) { + my $dom = &Apache::lonnet::host_domain($id); + unless (grep(/^\Q$dom\E/,@instdoms)) { + push(@instdoms,$dom); + } + } + } + } + } + } + } + my $instdomstr; + if (@instdoms > 0) { + $instdomstr = "'".join("','",@instdoms)."'"; + } + my %js_lt = &Apache::lonlocal::texthash( + thdo => 'The domain you have selected is for another institution.', + yowi => 'You will be switched to the Forgot Password utility at that institution.', + unam => 'You must enter a username.', + mail => 'You must enter an e-mail address.', + eith => 'Enter a username and/or an e-mail address.', + ); + &js_escape(\%js_lt); + $js = <<"END"; +function verifyDomain(caller,form) { + var redirect = 1; + var instdoms = new Array($instdomstr); + if (instdoms.length > 0) { + for (var i=0; i=2)) { + $js.= "\n".''."\n"; + } + my $header = &Apache::loncommon::start_page('Reset password',$js,$args). + ''; my $output; if ($token) { - $output = &reset_passwd($r,$token,$contact_name,$contact_email); - } elsif ($uname && $udom) { - my $domdesc = &Apache::lonnet::domain($udom,'description'); - my $authtype = &Apache::lonnet::queryauthenticate($uname,$udom); - if ($authtype =~ /^internal/) { - my $useremail = $env{'form.useremail'}; - if ($useremail !~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) { - $output = &invalid_state('baduseremail',$domdesc, - $contact_name,$contact_email); - } else { - my %userinfo = - &Apache::lonnet::get('environment',\@emailtypes, - $udom,$uname); - my $email = ''; - my $emailtarget; - foreach my $type (@emailtypes) { - $email = $userinfo{$type}; - if ($email =~ /[^\@]+\@[^\@]+/) { - $emailtarget = $type; - last; - } - } - if ($email =~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) { - if ($useremail eq $email) { - $output = &send_token($uname,$udom,$email,$server, - $domdesc,$contact_name, - $contact_email); + $r->print($header.'
'); + &reset_passwd($r,$token,$contact_name,$contact_email,$clientip,\%passwdconf); + $r->print('
'.&Apache::loncommon::end_page()); + return OK; + } elsif ($udom) { + if (!$domdesc) { + $output = &invalid_state('baddomain',$domdesc, + $contact_name,$contact_email); + } elsif ($otherinst) { + ($header,$output) = &homeserver_redirect($r,$uname,$udom,$domdesc,$brcrum); + } elsif (($uname) || ($useremail)) { + my $earlyout; + unless ($passwdconf{'captcha'} eq 'unused') { + my ($captcha_chk,$captcha_error) = + &Apache::loncommon::captcha_response('passwords',$server,$dom_in_effect); + if ($captcha_chk != 1) { + my $error = 'captcha'; + if ($passwdconf{'captcha'} eq 'recaptcha') { + $error = 'recaptcha'; + } + $output = &invalid_state($error,$domdesc, + $contact_name,$contact_email); + $earlyout = 1; + } + } + unless ($earlyout) { + if ($lookup) { + $output = &invalid_state($lookup,$domdesc, + $contact_name,$contact_email); + $earlyout = 1; + } + } + unless ($earlyout) { + my $authtype = &Apache::lonnet::queryauthenticate($uname,$udom); + if ($authtype =~ /^internal/) { + my ($blocked,$blocktext) = + &Apache::loncommon::blocking_status('passwd',$clientip,$uname,$udom); + if ($blocked) { + $output = '

'.$blocktext.'

' + .&display_actions($contact_email,$domdesc); + } elsif (($passwdconf{'resetprelink'} ne 'either') && + ($useremail !~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/)) { + $output = &invalid_state('baduseremail',$domdesc, + $contact_name,$contact_email); } else { - $output = &invalid_state('mismatch',$domdesc, - $contact_name, - $contact_email); + my %userinfo = + &Apache::lonnet::get('environment',\@emailtypes, + $udom,$uname); + my @allemails; + foreach my $type (@emailtypes) { + if (ref($passwdconf{resetemail}) eq 'ARRAY') { + if ($type eq 'permanentemail') { + next unless (grep(/^permanent$/,@{$passwdconf{resetemail}})); + } elsif ($type eq 'critnotification') { + next unless (grep(/^critical$/,@{$passwdconf{resetemail}})); + } elsif ($type eq 'notification') { + next unless (grep(/^notify$/,@{$passwdconf{resetemail}})); + } + } + my $email = $userinfo{$type}; + my @items; + if ($email =~ /,/) { + @items = split(',',$userinfo{$type}); + } else { + @items = ($email); + } + foreach my $item (@items) { + if ($item =~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) { + unless (grep(/^\Q$item\E$/i,@allemails)) { + push(@allemails,$item); + } + } + } + } + if (@allemails > 0) { + my ($sendto,$warning,$timelimit); + my $timelimit = 2; + if ($passwdconf{'resetlink'} =~ /^\d+(|\.\d*)$/) { + $timelimit = $passwdconf{'resetlink'}; + } + if ($passwdconf{'resetprelink'} eq 'either') { + if ($useremail ne '') { + if (grep(/^\Q$useremail\E$/i,@allemails)) { + $sendto = $useremail; + } else { + $warning = &mt('The e-mail address you entered did not match the expected e-mail address.'); + } + } elsif (@allemails > 1) { + $warning = &mt('More than one e-mail address is associated with your username, and one has been selected to receive the message sent by LON-CAPA.'); + } + unless ($sendto) { + $sendto = $allemails[0]; + } + } else { + if (grep(/^\Q$useremail\E$/i,@allemails)) { + $sendto = $useremail; + } else { + $output = &invalid_state('mismatch',$domdesc, + $contact_name, + $contact_email); + } + } + if ($sendto) { + $output = &send_token($uname,$udom,$sendto,$server, + $domdesc,$contact_name, + $contact_email,$timelimit,$warning); + } + } else { + $output = &invalid_state('missing',$domdesc, + $contact_name,$contact_email); + } } + } elsif ($authtype =~ /^(krb|unix|local)/) { + $output = &invalid_state('authentication',$domdesc, + $contact_name,$contact_email); } else { - $output = &invalid_state('missing',$domdesc, + $output = &invalid_state('invalid',$domdesc, $contact_name,$contact_email); } } - } elsif ($authtype =~ /^(krb|unix|local)/) { - $output = &invalid_state('authentication',$domdesc, - $contact_name,$contact_email); } else { - $output = &invalid_state('invalid',$domdesc, - $contact_name,$contact_email); + $output = &get_uname($server,$dom_in_effect,\%passwdconf); } } else { - $output = &get_uname($defdom); + $output = &get_uname($server,$defdom,\%passwdconf); } - $r->print($output); + $r->print($header.'
'.$output.'
'); $r->print(&Apache::loncommon::end_page()); return OK; } sub get_uname { - my ($defdom) = @_; + my ($server,$defdom,$passwdconf) = @_; + return unless (ref($passwdconf) eq 'HASH'); my %lt = &Apache::lonlocal::texthash( - unam => 'username', - udom => 'domain', - uemail => 'E-mail address in LON-CAPA', - proc => 'Proceed'); - - my $msg = &mt('If you use the same account for other campus services besides LON-CAPA, (e.g., e-mail, course registration, etc.), a separate centrally managed mechanism likely exists to reset a password. However, if your account is used for just LON-CAPA access you will probably be able to reset a password from this page.'); - $msg .= &mt('Three conditions must be met:') - .'