[LON-CAPA-cvs] cvs: loncom / lonsql /interface loncommon.pm lonpreferences.pm resetpw.pm /lonnet/perl lonnet.pm

raeburn raeburn at source.lon-capa.org
Tue Apr 23 21:44:46 EDT 2019


raeburn		Wed Apr 24 01:44:46 2019 EDT

  Modified files:              
    /loncom/interface	resetpw.pm lonpreferences.pm loncommon.pm 
    /loncom	lonsql 
    /loncom/lonnet/perl	lonnet.pm 
  Log:
  - Forgot Password utility
    - Support configurable: link lifetime, case sensitivity (for username and
      e-mail address), information required, e-mail types, and custom text.
    - Bug 6648: captcha can be enabled for public-facing web form.
  
  
-------------- next part --------------
Index: loncom/interface/resetpw.pm
diff -u loncom/interface/resetpw.pm:1.43 loncom/interface/resetpw.pm:1.44
--- loncom/interface/resetpw.pm:1.43	Fri Feb  8 19:57:29 2019
+++ loncom/interface/resetpw.pm	Wed Apr 24 01:44:30 2019
@@ -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.43 2019/02/08 19:57:29 raeburn Exp $
+# $Id: resetpw.pm,v 1.44 2019/04/24 01:44:30 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -55,6 +55,7 @@
 use Apache::lonacc;
 use Apache::lonnet;
 use Apache::loncommon;
+use Apache::lonpreferences;
 use Apache::lonlocal;
 use LONCAPA;
 use HTML::Entities;
@@ -92,22 +93,80 @@
     $uname =~ s/^\s+|\s+$//g;
     $uname = &LONCAPA::clean_username($uname);
     my $udom = &LONCAPA::clean_domain($env{'form.udom'});
-    my ($domdesc,$otherinst);
+    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 $server (keys(%servers)) {
-                if (grep(/^\Q$server\E$/, at ids)) {
+            foreach my $hostid (keys(%servers)) {
+                if (grep(/^\Q$hostid\E$/, at 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 $token = $env{'form.token'};
+    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},
@@ -157,12 +216,11 @@
             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.'
+            mail  => 'You must enter an e-mail address.',
+            eith  => 'Enter a username and/or an e-mail address.',
         );
         &js_escape(\%js_lt);
         $js = <<"END";
-<script type="text/javascript">
-// <![CDATA[
 function verifyDomain(caller,form) {
     var redirect = 1; 
     var instdoms = new Array($instdomstr);
@@ -174,14 +232,43 @@
             }
         }
     }
+    if (redirect == 0) {
+        if (caller.options[caller.selectedIndex].value != '$dom_in_effect') {
+            document.forgotpw.uname.value = '';
+            document.forgotpw.useremail.value = '';
+            form.submit();
+        }
+    }
     if (redirect == 1) {
         if (confirm('$js_lt{thdo}\\n$js_lt{yowi}')) {
             form.submit();
+        } else {
+            for (var i=0; i<caller.options.length; i++) { 
+                if (caller.option[i].value == '$dom_in_effect') {
+                    caller.selectedIndex = i;
+                    break;
+                }
+            }
         }
     }
     return;
 }
 
+END
+        if ($passwdconf{resetprelink} eq 'either') {
+            $js .= <<"END";
+function validInfo() {
+    if ((document.forgotpw.uname.value == '') &&
+        (document.forgotpw.useremail.value == '')) {
+        alert("$js_lt{'eith'}");
+        return false;
+    }
+    return true;
+}
+END
+        } else {
+            $js .= <<"END";
+
 function validInfo() {
     if (document.forgotpw.uname.value == '') {
         alert("$js_lt{'unam'}");
@@ -193,16 +280,19 @@
     }
     return true;
 }
-// ]]>
-</script>
 END
+        }
+    }
+    $js = &Apache::lonhtmlcommon::scripttag($js);
+    if (($passwdconf{'captcha'} eq 'recaptcha') && ($passwdconf{'recaptchaversion'} >=2)) {
+        $js.= "\n".'<script src="https://www.google.com/recaptcha/api.js"></script>'."\n";
     }
     my $header = &Apache::loncommon::start_page('Reset password',$js,$args).
                  '<h2>'.&mt('Reset forgotten LON-CAPA password').'</h2>';
     my $output;
     if ($token) {
         $r->print($header);
-        &reset_passwd($r,$token,$contact_name,$contact_email);
+        &reset_passwd($r,$token,$contact_name,$contact_email,\%passwdconf);
         $r->print(&Apache::loncommon::end_page());
         return OK;
     } elsif ($udom) {
@@ -211,66 +301,121 @@
                                      $contact_name,$contact_email); 
         } elsif ($otherinst) {
             ($header,$output) = &homeserver_redirect($uname,$udom,$domdesc,$brcrum);
-        } elsif ($uname) {
-            my $authtype = &Apache::lonnet::queryauthenticate($uname,$udom);
-            if ($authtype =~ /^internal/) {
-                my $useremail = $env{'form.useremail'};
-                my ($blocked,$blocktext) =
-                    &Apache::loncommon::blocking_status('passwd',$uname,$udom);
-                if ($blocked) {
-                    $output = '<p class="LC_warning">'.$blocktext.'</p>'
-                              .&display_actions($contact_email,$domdesc);
-                } elsif ($useremail !~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) {
-                    $output = &invalid_state('baduseremail',$domdesc,
+        } elsif (($uname) || ($useremail)) {
+            my $earlyout;
+            unless ($passwdconf{'captcha'} eq 'unused') {
+                my ($captcha_chk,$captcha_error) =
+                    &Apache::loncommon::captcha_response('passwords',$server);
+                if ($captcha_chk != 1) {
+                    my $error = 'captcha'; 
+                    if ($passwdconf{'captcha'} eq 'recaptcha') {
+                        $error = 'recaptcha';
+                    }
+                    $output = &invalid_state($error,$domdesc,
                                              $contact_name,$contact_email);
-                } else {
-                    my %userinfo = 
-	                &Apache::lonnet::get('environment',\@emailtypes,
-					     $udom,$uname);
-                    my @allemails;
-                    foreach my $type (@emailtypes) {
-                        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$/, at allemails)) { 
-                                    push(@allemails,$item);
+                    $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',$uname,$udom);
+                    if ($blocked) {
+                        $output = '<p class="LC_warning">'.$blocktext.'</p>'
+                                  .&display_actions($contact_email,$domdesc);
+                    } elsif (($passwdconf{'resetprelink'} ne 'either') && 
+                             ($useremail !~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/)) {
+                        $output = &invalid_state('baduseremail',$domdesc,
+                                                 $contact_name,$contact_email);
+                    } else {
+                        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, at allemails)) { 
+                                        push(@allemails,$item);
+                                    }
                                 }
                             }
                         }
-                    }
-                    if (@allemails > 0) {
-                        if (grep(/^\Q$useremail\E$/, at allemails)) {
-                            $output = &send_token($uname,$udom,$useremail,$server,
-                                                  $domdesc,$contact_name,
-                                                  $contact_email);
+                        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, at 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, at 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('mismatch',$domdesc,
-                                                     $contact_name,
-                                                     $contact_email);
+                            $output = &invalid_state('missing',$domdesc,
+                                                     $contact_name,$contact_email);
                         }
-                    } 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('invalid',$domdesc,
+                } elsif ($authtype =~ /^(krb|unix|local)/) {
+                    $output = &invalid_state('authentication',$domdesc,
                                          $contact_name,$contact_email);
+                } else {
+                    $output = &invalid_state('invalid',$domdesc,
+                                             $contact_name,$contact_email);
+                }
             }
         } else {
-            $output = &get_uname($defdom);
+            $output = &get_uname($server,$dom_in_effect,\%passwdconf);
         }
     } else {
-        $output = &get_uname($defdom);
+        $output = &get_uname($server,$defdom,\%passwdconf);
     }
     $r->print($header.$output);
     $r->print(&Apache::loncommon::end_page());
@@ -278,44 +423,58 @@
 }
 
 sub get_uname {
-    my ($defdom) = @_;
+    my ($server,$defdom,$passwdconf) = @_;
+    return unless (ref($passwdconf) eq 'HASH');
     my %lt = &Apache::lonlocal::texthash(
-                                         unam => 'LON-CAPA username',
-                                         udom => 'LON-CAPA 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 .= '<br /><br />'.&mt('Three conditions must be met:')
+            unam => 'LON-CAPA username',
+            udom => 'LON-CAPA domain',
+            uemail => 'E-mail address in LON-CAPA',
+            vali   => 'Validation',
+            proc => 'Proceed');
+    my $msg;
+    unless ($passwdconf->{'resetremove'}) {
+        $msg = '<p>'.&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.').'</p>';
+    }
+    if ($passwdconf->{'resetcustom'} eq "/res/$defdom/$defdom-domainconfig/customtext/resetpw/resetpw.html") {
+        my $contents = &Apache::lonnet::getfile(&Apache::lonnet::filelocation('',$passwdconf->{'resetcustom'}));
+        unless ($contents eq '-1') {
+            $msg .= $contents;
+        }
+    }
+    $msg .= '<p>'.&mt('Three conditions must be met:')
            .'<ul><li>'.&mt('An e-mail address must have previously been associated with your LON-CAPA username.').'</li>'
            .'<li>'.&mt('You must be able to access e-mail sent to that address.').'</li>'
            .'<li>'.&mt('Your LON-CAPA account must be of a type for which LON-CAPA can reset a password.')
-           .'</ul>';
-    my $mobileargs;
-    (undef,undef,undef,undef,undef,undef,my $clientmobile) =
-        &Apache::loncommon::decode_user_agent();
-    if ($clientmobile) {
-        $mobileargs = 'autocapitalize="off" autocorrect="off" ';
-    }
+           .'</ul></p>';
     my $onchange = 'javascript:verifyDomain(this,this.form);';
     $msg .= '<form name="forgotpw" method="post" action="/adm/resetpw" onsubmit="return validInfo();">'.
-            &Apache::lonhtmlcommon::start_pick_box(). 
-            &Apache::lonhtmlcommon::row_title($lt{'unam'}).
-            '<input type="text" name="uname" size="20" '.$mobileargs.'/>'.
-            &Apache::lonhtmlcommon::row_closure(1).
+            &Apache::lonhtmlcommon::start_pick_box().
             &Apache::lonhtmlcommon::row_title($lt{'udom'}).
             &Apache::loncommon::select_dom_form($defdom,'udom',undef,undef,$onchange).
             &Apache::lonhtmlcommon::row_closure(1).
+            &Apache::lonhtmlcommon::row_title($lt{'unam'}).
+            '<input type="text" name="uname" size="20" autocapitalize="off" autocorrect="off" />'.
+            &Apache::lonhtmlcommon::row_closure(1).
             &Apache::lonhtmlcommon::row_title($lt{'uemail'}).
-            '<input type="text" name="useremail" size="30" '.$mobileargs.'/>'.
-            &Apache::lonhtmlcommon::end_pick_box().
+            '<input type="text" name="useremail" size="30" autocapitalize="off" autocorrect="off" />'.
+            &Apache::lonhtmlcommon::row_closure(1);
+    unless ($passwdconf->{'captcha'} eq 'notused') {
+        my ($captcha_form,$captcha_error,$captcha,$recaptcha_version) =
+            &Apache::loncommon::captcha_display('passwords',$server,$defdom);
+        if ($captcha_form) {
+            $msg .= &Apache::lonhtmlcommon::row_title($lt{'vali'}).
+                    $captcha_form."\n".
+                    &Apache::lonhtmlcommon::row_closure(1);
+        }
+    }
+    $msg .= &Apache::lonhtmlcommon::end_pick_box().
             '<br /><br /><input type="submit" name="resetter" value="'.$lt{'proc'}.'" /></form>';
     return $msg;
 }
 
 sub send_token {
     my ($uname,$udom,$email,$server,$domdesc,$contact_name,
-        $contact_email) = @_;
+        $contact_email,$timelimit,$warning) = @_;
     my $msg =
         '<p class="LC_info">'
        .&mt('Thank you for your request to reset the password for your LON-CAPA account.')
@@ -342,7 +501,7 @@
             $msg .=
                 &mt('An e-mail sent to the e-mail address associated with your LON-CAPA account includes the web address for the link you should use to complete the reset process.')
                .'<br /><br />'
-               .&mt('The link included in the message will be valid for the next [_1]two[_2] hours.','<b>','</b>');
+               .&mt('The link included in the message will be valid for the next [_1][quant,_2,hour][_3].','<b>',$timelimit,'</b>');
         } else {
             $msg .=
                 '<p class="LC_error">'
@@ -393,7 +552,15 @@
               .'</p>';
         $msg .= &display_actions($contact_email,$domdesc);
     } else {
-        if ($error eq 'baduseremail') {
+        if ($error eq 'captcha') {
+            $msg = &mt('Validation of the code you entered failed');
+        } elsif ($error eq 'recaptcha') {
+            $msg = &mt('Validation of human, not robot, failed'); 
+        } elsif ($error eq 'nonunique') {
+            $msg = &mt('More than one username was identified from the information you provided; try providing both a username and e-mail address');
+        } elsif ($error eq 'nomatch') {
+            $msg = &mt('A valid user could not be identified from the username and/or e-mail address you provided');
+        } elsif ($error eq 'baduseremail') {
             $msg = &mt('The e-mail address you provided does not appear to be a valid address.');
         } elsif ($error eq 'mismatch') {
             $msg = &mt('The e-mail address you provided does not match the address recorded in the LON-CAPA system for the username and domain you provided.');  
@@ -434,7 +601,8 @@
 }
 
 sub reset_passwd {
-    my ($r,$token,$contact_name,$contact_email) = @_;
+    my ($r,$token,$contact_name,$contact_email,$passwdconf) = @_;
+    return unless (ref($passwdconf) eq 'HASH');
     my %data = &Apache::lonnet::tmpget($token);
     my $now = time;
     if (keys(%data) == 0) {
@@ -448,23 +616,59 @@
         ($data{'domain'} ne '') && 
         ($data{'email'}  =~ /^[^\@]+\@[^\@]+\.[^\@\.]+$/) && 
         ($data{'temppasswd'} =~/^\w+$/)) {
+        my $timelimit = 7200;
+        if ($passwdconf->{resetlink} =~ /^\d+(|\.\d*)$/) {
+            $timelimit = 3600 * $passwdconf->{resetlink};
+        }
         my $reqtime = &Apache::lonlocal::locallocaltime($data{'time'});
         my ($blocked,$blocktext) =
             &Apache::loncommon::blocking_status('passwd',$data{'username'},$data{'domain'});
         if ($blocked) {
             $r->print('<p class="LC_warning">'.$blocktext.'</p>');
             return;
-        } elsif ($now - $data{'time'} < 7200) {
+        } elsif ($now - $data{'time'} < $timelimit) {
+            my ($needscase,%formfields) = &reset_requires($data{'username'},$data{'domain'},
+                                                          $passwdconf);
             if ($env{'form.action'} eq 'verify_and_change_pass') {
-                $env{'form.uname'} =~ s/^\s+|\s+$//g;
-                $env{'form.udom'} =~ s/^\s+|\s+$//g;
-                $env{'form.email'} =~ s/^\s+|\s+$//g;
-                unless (($env{'form.uname'} eq $data{'username'}) && ($env{'form.udom'} eq $data{'domain'}) && ($env{'form.email'} eq $data{'email'})) {
-                    &Apache::lonnet::logthis("Forgot Password -- token data: ||$data{'username'}|| ||$data{'domain'}|| ||$data{'email'}|| differs from form: ||$env{'form.uname'}|| ||$env{'form.udom'}|| ||$env{'form.email'}||");
+                my $invalidinfo;
+                if ($formfields{'username'}) {
+                    $env{'form.uname'} =~ s/^\s+|\s+$//g;
+                    $env{'form.udom'} =~ s/^\s+|\s+$//g;
+                    if ($needscase) {
+                        unless (($env{'form.uname'} eq $data{'username'}) && ($env{'form.udom'} eq $data{'domain'})) {
+                            $invalidinfo = "||$env{'form.uname'}|| ||$env{'form.udom'}|| ";
+                        }
+                    } else {
+                        unless ((lc($env{'form.uname'}) eq lc($data{'username'})) && (lc($env{'form.udom'}) eq lc($data{'domain'}))) {
+                            $invalidinfo = "||$env{'form.uname'}|| ||$env{'form.udom'}|| ";
+                        }
+                    }
+                } else {
+                    $env{'form.uname'} = $data{'username'};
+                    $env{'form.udom'} = $data{'domain'};
+                }
+                if ($formfields{'email'}) {
+                    $env{'form.email'} =~ s/^\s+|\s+$//g;
+                    if ($needscase) {
+                        unless ($env{'form.email'} eq $data{'email'}) {
+                            $invalidinfo .= "||$env{'form.email'}||";
+                        }
+                    } else {
+                        unless (lc($env{'form.email'}) eq lc($data{'email'})) {
+                            $invalidinfo = "||$env{'form.email'}||";
+                        }
+                    }
+                }
+                if ($invalidinfo) {
+                    &Apache::lonnet::logthis("Forgot Password -- token data: ||$data{'username'}|| ||$data{'domain'}|| ||$data{'email'}|| differs from form: $invalidinfo");
                     $r->print(&generic_failure_msg($contact_name,$contact_email));
+                    unless ($formfields{'username'}) {
+                        delete($env{'form.uname'});
+                        delete($env{'form.udom'});
+                    }
                     return;
                 }
-                my $change_failed = 
+                my $change_failed =
 		    &Apache::lonpreferences::verify_and_change_password($r,'reset_by_email',$token);
                 if (!$change_failed) {
                     my $delete = &Apache::lonnet::tmpdel($token);
@@ -519,10 +723,46 @@
                 } else {
                     $r->print(&generic_failure_msg($contact_name,$contact_email));
                 }
+                unless ($formfields{'username'}) {
+                    delete($env{'form.uname'});
+                    delete($env{'form.udom'});
+                }
             } else {
                 $r->print(&mt('The token included in an e-mail sent to you [_1] has been verified, so you may now proceed to reset the password for your LON-CAPA account.',$reqtime).'<br /><br />');
-                $r->print(&mt('Please enter the username and domain of the LON-CAPA account, and the associated e-mail address, for which you are setting a password. The new password must contain at least 7 characters.').' '.&mt('Your new password will be sent to the LON-CAPA server in an encrypted form.').'<br />');
-                &Apache::lonpreferences::passwordchanger($r,'','reset_by_email',$token);
+                if (keys(%formfields)) {
+                    if (($formfields{'username'}) && ($formfields{'email'})) {
+                        $r->print(&mt('Please enter the username and domain of the LON-CAPA account, and the associated e-mail address, for which you are setting a password.'));
+                    } elsif ($formfields{'username'}) {
+                        $r->print(&mt('Please enter the username and domain of the LON-CAPA account for which you are setting a password.'));
+                    } elsif ($formfields{'email'}) {
+                        $r->print(&mt('Please enter the e-mail address associated with the LON-CAPA account for which you are setting a password.'));
+                    }
+                    if ($needscase) {
+                        $r->print(' '.&mt('User data entered must match LON-CAPA account information (including case).'));
+                    }
+                    $r->print(' ');
+                }
+                if (ref($passwdconf->{chars}) eq 'ARRAY') {
+                    my %rules;
+                    map { $rules{$_} = 1; } @{$passwdconf->{chars}};
+                    my %rulenames = &Apache::lonlocal::texthash(
+                                                     uc => 'At least one upper case letter',
+                                                     lc => 'At least one lower case letter',
+                                                     num => 'At least one number',
+                                                     spec => 'At least one non-alphanumeric',
+                                                   );
+                    $r->print(&mt('The new password must satisfy the following:').'<ul>');
+                    foreach my $poss ('uc','lc','num','spec') {
+                        if ($rules{$poss}) {
+                            $r->print('<li>'.$rulenames{$poss}.'</li>');
+                        }
+                    }
+                    $r->print('</ul>');
+                } else {
+                    $r->print(&mt('The new password must contain at least 7 characters.').' ');
+                }
+                $r->print(&mt('Your new password will be sent to the LON-CAPA server in an encrypted form.').'<br />');
+                &Apache::lonpreferences::passwordchanger($r,'','reset_by_email',$token,$timelimit,\%formfields);
             }
         } else {
             $r->print(
@@ -593,9 +833,41 @@
                 '<i>'.$domdesc.'</i>')
            .'</p>';
     }
-
     return &Apache::lonhtmlcommon::actionbox(\@msg).$msg2;
+}
 
+sub reset_requires {
+    my ($username,$domain,$passwdconf) = @_;
+    my (%fields,$needscase);
+    return ($needscase,%fields) unless (ref($passwdconf) eq 'HASH');
+    my (%postlink,%resetcase);
+    if (ref($passwdconf->{resetpostlink}) eq 'HASH') {
+        %postlink = %{$passwdconf->{resetpostlink}};
+    }
+    if (ref($passwdconf->{resetcase}) eq 'ARRAY') {
+        map { $resetcase{$_} = 1; } (@{$passwdconf->{resetcase}});
+    } else {
+        $needscase = 1;
+    }
+    my %userenv =
+        &Apache::lonnet::get('environment',['inststatus'],$domain,$username);
+    my @inststatuses;
+    if ($userenv{'inststatus'} ne '') {
+        @inststatuses = split(/:/,$userenv{'inststatus'});
+    } else {
+        @inststatuses = ('default');
+    }
+    foreach my $status (@inststatuses) {
+        if (ref($postlink{$status}) eq 'ARRAY') {
+            map { $fields{$_} = 1; } (@{$postlink{$status}});
+        } else {
+            map { $fields{$_} = 1; } ('username','email');
+        }
+        if ($resetcase{$status}) {
+            $needscase = 1;
+        }
+    }
+    return ($needscase,%fields);
 }
 
 1;
Index: loncom/interface/lonpreferences.pm
diff -u loncom/interface/lonpreferences.pm:1.227 loncom/interface/lonpreferences.pm:1.228
--- loncom/interface/lonpreferences.pm:1.227	Fri Apr 27 22:42:20 2018
+++ loncom/interface/lonpreferences.pm	Wed Apr 24 01:44:30 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Preferences
 #
-# $Id: lonpreferences.pm,v 1.227 2018/04/27 22:42:20 raeburn Exp $
+# $Id: lonpreferences.pm,v 1.228 2019/04/24 01:44:30 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1263,7 +1263,7 @@
 #            password handler subroutines            #
 ######################################################
 sub passwordchanger {
-    my ($r,$errormessage,$caller,$mailtoken) = @_;
+    my ($r,$errormessage,$caller,$mailtoken,$timelimit,$extrafields) = @_;
     # This function is a bit of a mess....
     # Passwords are encrypted using londes.js (DES encryption)
     $errormessage = ($errormessage || '');
@@ -1299,7 +1299,7 @@
                 return;
             }
             if (defined($data{time})) {
-                if (time - $data{'time'} < 7200) {
+                if (time - $data{'time'} < $timelimit) {
                     $user = $data{'username'};
                     $domain = $data{'domain'};
                     $currentpass = $data{'temppasswd'};
@@ -1362,7 +1362,7 @@
 	my $jsh=Apache::File->new($include."/londes.js");
 	$r->print(<$jsh>);
     }
-    $r->print(&jscript_send($caller));
+    $r->print(&jscript_send($caller,$extrafields));
     $r->print(<<ENDFORM);
 $errormessage
 
@@ -1371,15 +1371,15 @@
      ensure that unencrypted passwords will not be sent out by a
      crappy browser -->
 ENDFORM
-    $r->print(&server_form($logtoken,$caller,$mailtoken));
-    $r->print(&client_form($caller,\%hexkey,$currentpass,$domain));
+    $r->print(&server_form($logtoken,$caller,$mailtoken,$extrafields));
+    $r->print(&client_form($caller,\%hexkey,$currentpass,$domain,$extrafields));
 
     #
     return;
 }
 
 sub jscript_send {
-    my ($caller) = @_;
+    my ($caller,$extrafields) = @_;
     my $output = qq|
 <script type="text/javascript" language="JavaScript">
 
@@ -1401,14 +1401,20 @@
             =getCrypted(this.document.client.elements.newpass_2.value);
 |;
     if ($caller eq 'reset_by_email') {
-        $output .= qq|
+        if ((ref($extrafields) eq 'HASH') && ($extrafields->{'username'})) {
+            $output .= qq|
         this.document.pserver.elements.uname.value =
                    this.document.client.elements.uname.value;
         this.document.pserver.elements.udom.value =
                    this.document.client.elements.udom.options[this.document.client.elements.udom.selectedIndex].value;
+|;
+        }
+        if ((ref($extrafields) eq 'HASH') && ($extrafields->{'email'})) {
+            $output .= qq| 
         this.document.pserver.elements.email.value =
                    this.document.client.elements.email.value;
 |;
+        }
     }
     $ output .= qq|
         this.document.pserver.submit();
@@ -1419,7 +1425,7 @@
 }
 
 sub client_form {
-    my ($caller,$hexkey,$currentpass,$defdom) = @_;
+    my ($caller,$hexkey,$currentpass,$defdom,$extrafields) = @_;
     my %lt=&Apache::lonlocal::texthash(
                 'email' => 'E-mail Address',
                 'username' => 'Username',
@@ -1433,25 +1439,22 @@
     my $output = '<form name="client" action="">'
                 .&Apache::lonhtmlcommon::start_pick_box();
     if ($caller eq 'reset_by_email') {
-        my $mobileargs;
-        (undef,undef,undef,undef,undef,undef,my $clientmobile) =
-            &Apache::loncommon::decode_user_agent();
-        if ($clientmobile) {
-            $mobileargs = 'autocapitalize="off" autocorrect="off" ';
-        }
-        $output .= &Apache::lonhtmlcommon::row_title(
+        if ((ref($extrafields) eq 'HASH') && ($extrafields->{'email'})) {
+            $output .= &Apache::lonhtmlcommon::row_title(
                        '<label for="email">'.$lt{'email'}.'</label>')
-                  .'<input type="text" name="email" size="30" '.$mobileargs.'/>'
-                  .&Apache::lonhtmlcommon::row_closure()
-                  .&Apache::lonhtmlcommon::row_title(
+                      .'<input type="text" name="email" size="30" autocapitalize="off" autocorrect="off" />'
+                      .&Apache::lonhtmlcommon::row_closure();
+        }
+        if ((ref($extrafields) eq 'HASH') && ($extrafields->{'username'})) {
+            $output .= &Apache::lonhtmlcommon::row_title(  
                        '<label for="uname">'.$lt{'username'}.'</label>')
-                  .'<input type="text" name="uname" size="20" '.$mobileargs.'/>'
-                  .'<input type="hidden" name="currentpass" value="'.$currentpass.'" />'
-                  .&Apache::lonhtmlcommon::row_closure()
-                  .&Apache::lonhtmlcommon::row_title(
+                      .'<input type="text" name="uname" size="20" autocapitalize="off" autocorrect="off" />'
+                      .&Apache::lonhtmlcommon::row_closure()
+                      .&Apache::lonhtmlcommon::row_title(
                        '<label for="udom">'.$lt{'domain'}.'</label>')
-                  .&Apache::loncommon::select_dom_form($defdom,'udom')
-                  .&Apache::lonhtmlcommon::row_closure();
+                      .&Apache::loncommon::select_dom_form($defdom,'udom')
+                      .&Apache::lonhtmlcommon::row_closure();
+        }
     } else {
         $output .= &Apache::lonhtmlcommon::row_title(
                        '<label for="currentpass">'.$lt{'currentpass'}.'</label>')
@@ -1467,6 +1470,9 @@
               .'<input type="password" name="newpass_2" size="20" />'
               .&Apache::lonhtmlcommon::row_closure(1)
               .&Apache::lonhtmlcommon::end_pick_box();
+    if ($caller eq 'reset_by_email') {
+        $output .= '<input type="hidden" name="currentpass" value="'.$currentpass.'" />';
+    }
     $output .= '<p><input type="button" value="'.$lt{'changepass'}.'" onclick="send();" /></p>'
               .qq|
 <input type="hidden" name="ukey_cpass"  value="$hexkey->{'ukey_cpass'}" />
@@ -1482,7 +1488,7 @@
 }
 
 sub server_form {
-    my ($logtoken,$caller,$mailtoken) = @_;
+    my ($logtoken,$caller,$mailtoken,$extrafields) = @_;
     my $action = '/adm/preferences';
     if ($caller eq 'reset_by_email') {
         $action = '/adm/resetpw';
@@ -1493,15 +1499,22 @@
 <input type="hidden" name="currentpass" value="" />
 <input type="hidden" name="newpass_1"   value="" />
 <input type="hidden" name="newpass_2"   value="" />
-    |;
+|;
     if ($caller eq 'reset_by_email') {
         $output .=  qq|
 <input type="hidden" name="token"   value="$mailtoken" />
+|;
+       if ((ref($extrafields) eq 'HASH') && ($extrafields->{'username'})) {
+           $output .=  qq|
 <input type="hidden" name="uname"   value="" />
 <input type="hidden" name="udom"   value="" />
+|;
+       }
+       if ((ref($extrafields) eq 'HASH') && ($extrafields->{'email'})) {
+           $output .=  qq|
 <input type="hidden" name="email"   value="" />
-
 |;
+       }
     }
     $output .= qq|
 <input type="hidden" name="action" value="verify_and_change_pass" />
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1326 loncom/interface/loncommon.pm:1.1327
--- loncom/interface/loncommon.pm:1.1326	Thu Apr 11 14:47:14 2019
+++ loncom/interface/loncommon.pm	Wed Apr 24 01:44:30 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1326 2019/04/11 14:47:14 raeburn Exp $
+# $Id: loncommon.pm,v 1.1327 2019/04/24 01:44:30 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -17710,10 +17710,10 @@
 }
 
 sub captcha_display {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$defdom) = @_;
     my ($output,$error);
     my ($captcha,$pubkey,$privkey,$version) = 
-        &get_captcha_config($context,$lonhost);
+        &get_captcha_config($context,$lonhost,$defdom);
     if ($captcha eq 'original') {
         $output = &create_captcha();
         unless ($output) {
@@ -17729,9 +17729,9 @@
 }
 
 sub captcha_response {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$defdom) = @_;
     my ($captcha_chk,$captcha_error);
-    my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost);
+    my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost,$defdom);
     if ($captcha eq 'original') {
         ($captcha_chk,$captcha_error) = &check_captcha();
     } elsif ($captcha eq 'recaptcha') {
@@ -17743,7 +17743,7 @@
 }
 
 sub get_captcha_config {
-    my ($context,$lonhost) = @_;
+    my ($context,$lonhost,$dom_in_effect) = @_;
     my ($captcha,$pubkey,$privkey,$version,$hashtocheck);
     my $hostname = &Apache::lonnet::hostname($lonhost);
     my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
@@ -17791,7 +17791,28 @@
         } elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
             $captcha = 'original';
         }
-    }
+    } elsif ($context eq 'passwords') {
+        if ($dom_in_effect) {
+            my %passwdconf = &Apache::lonnet::get_passwdconf($dom_in_effect);
+            if ($passwdconf{'captcha'} eq 'recaptcha') {
+                if (ref($passwdconf{'recaptchakeys'}) eq 'HASH') {
+                    $pubkey = $passwdconf{'recaptchakeys'}{'public'};
+                    $privkey = $passwdconf{'recaptchakeys'}{'private'};
+                }
+                if ($privkey && $pubkey) {
+                    $captcha = 'recaptcha';
+                    $version = $passwdconf{'recaptchaversion'};
+                    if ($version ne '2') {
+                        $version = 1;
+                    }
+                } else {
+                    $captcha = 'original';
+                }
+            } elsif ($passwdconf{'captcha'} ne 'notused') {
+                $captcha = 'original';
+            }
+        }
+    } 
     return ($captcha,$pubkey,$privkey,$version);
 }
 
Index: loncom/lonsql
diff -u loncom/lonsql:1.97 loncom/lonsql:1.98
--- loncom/lonsql:1.97	Mon Oct 29 02:57:30 2018
+++ loncom/lonsql	Wed Apr 24 01:44:38 2019
@@ -3,7 +3,7 @@
 # The LearningOnline Network
 # lonsql - LON TCP-MySQL-Server Daemon for handling database requests.
 #
-# $Id: lonsql,v 1.97 2018/10/29 02:57:30 raeburn Exp $
+# $Id: lonsql,v 1.98 2019/04/24 01:44:38 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -540,6 +540,7 @@
         }
     } else {
         my %srchfield = (
+                          uname_ci => 'username collate latin1_general_ci',
                           uname    => 'username',
                           lastname => 'lastname',
                           email    => 'permanentemail',
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1406 loncom/lonnet/perl/lonnet.pm:1.1407
--- loncom/lonnet/perl/lonnet.pm:1.1406	Tue Feb 26 14:42:27 2019
+++ loncom/lonnet/perl/lonnet.pm	Wed Apr 24 01:44:46 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1406 2019/02/26 14:42:27 raeburn Exp $
+# $Id: lonnet.pm,v 1.1407 2019/04/24 01:44:46 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2721,6 +2721,29 @@
     return $firsturl;
 }
 
+# --------------------------------------------- Get domain config for passwords
+
+sub get_passwdconf {
+    my ($dom) = @_;
+    my (%passwdconf,$gotconf,$lookup);
+    my ($result,$cached)=&is_cached_new('passwdconf',$dom);
+    if (defined($cached)) {
+        if (ref($result) eq 'HASH') {
+            %passwdconf = %{$result};
+            $gotconf = 1;
+        }
+    }
+    unless ($gotconf) {
+        my %domconfig = &get_dom('configuration',['passwords'],$dom);
+        if (ref($domconfig{'passwords'}) eq 'HASH') {
+            %passwdconf = %{$domconfig{'passwords'}};
+        }
+        my $cachetime = 24*60*60;
+        &do_cache_new('passwdconf',$dom,\%passwdconf,$cachetime);
+    }
+    return %passwdconf;
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {


More information about the LON-CAPA-cvs mailing list