[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lon-capa-cvs
Subject:    [LON-CAPA-cvs] cvs: loncom /interface domainprefs.pm
From:       raeburn <raeburn () source ! lon-capa ! org>
Date:       2019-04-22 1:55:17
Message-ID: cvsraeburn1555898117 () cvsserver
[Download RAW message or body]

This is a MIME encoded message


raeburn		Mon Apr 22 01:55:17 2019 EDT

  Modified files:              
    /loncom/interface	domainprefs.pm 
  Log:
  - Domain Configuration for Passwords for internally authenticated users.
    - Options for "Forgot Password" utility: link lifetime, case sensitivity,
      information required, e-mail types, custom text.
    - Options for encryption of Stored Passwords (moved from "Defaults" menu).
    - Rules for LON-CAPA Passwords (length, characters, expiration).
    - Option to allow Course Owner to change student's password (with conditions).   
  
  
["raeburn-20190422015517.txt" (text/plain)]

Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.353 loncom/interface/domainprefs.pm:1.354
--- loncom/interface/domainprefs.pm:1.353	Mon Apr 22 00:38:45 2019
+++ loncom/interface/domainprefs.pm	Mon Apr 22 01:55:17 2019
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.353 2019/04/22 00:38:45 raeburn Exp $
+# $Id: domainprefs.pm,v 1.354 2019/04/22 01:55:17 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -219,7 +219,7 @@
                 'serverstatuses','requestcourses','helpsettings',
                 'coursedefaults','usersessions','loadbalancing',
                 'requestauthor','selfenrollment','inststatus',
-                'ltitools','ssl','trust','lti'],$dom);
+                'ltitools','ssl','trust','lti','passwords'],$dom);
     my %encconfig =
         &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom);
     if (ref($domconfig{'ltitools'}) eq 'HASH') {
@@ -246,7 +246,7 @@
             }
         }
     }
-    my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll',
+    my @prefs_order = \
                ('rolecolors','login','defaults','passwords','quotas','autoenroll',
                        'autoupdate','autocreate','directorysrch','contacts',
                        'usercreation','selfcreation','usermodification','scantron',
                        'requestcourses','requestauthor','coursecategories',
@@ -291,13 +291,25 @@
                       help => 'Domain_Configuration_LangTZAuth',
                       header => [{col1 => 'Setting',
                                   col2 => 'Value'},
-                                 {col1 => 'Internal Authentication',
-                                  col2 => 'Value'},
                                  {col1 => 'Institutional user types',
                                   col2 => 'Name displayed'}],
                       print => \&print_defaults,
                       modify => \&modify_defaults,
                     },
+        'passwords' =>
+                    { text => 'Passwords (Internal authentication)',
+                      help => 'Domain_Configuration_Passwords',
+                      header => [{col1 => 'Resetting Forgotten Password',
+                                  col2 => 'Settings'},
+                                 {col1 => 'Encryption of Stored Passwords (Internal \
Auth)', +                                  col2 => 'Settings'},
+                                 {col1 => 'Rules for LON-CAPA Passwords',
+                                  col2 => 'Settings'},
+                                 {col1 => 'Course Owner Changing Student Passwords',
+                                  col2 => 'Settings'}],
+                      print => \&print_passwords,
+                      modify => \&modify_passwords,
+                    },
         'quotas' => 
                     { text => 'Blogs, personal web pages, webDAV/quotas, \
portfolios',  help => 'Domain_Configuration_Quotas',
@@ -759,6 +771,8 @@
         $output = &modify_trust($dom,$lastactref,%domconfig);
     } elsif ($action eq 'lti') {
         $output = &modify_lti($r,$dom,$action,$lastactref,%domconfig);
+    } elsif ($action eq 'passwords') {
+        $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig);
     }
     return $output;
 }
@@ -771,6 +785,8 @@
         $output = &coursecategories_javascript($settings);
     } elsif ($action eq 'defaults') {
         $output = &defaults_javascript($settings); 
+    } elsif ($action eq 'passwords') {
+        $output = &passwords_javascript();
     } elsif ($action eq 'helpsettings') {
         my (%privs,%levelscurrent);
         my %full=();
@@ -830,6 +846,8 @@
             ($action eq 'directorysrch') || ($action eq 'trust') || ($action eq \
'helpsettings') ||  ($action eq 'contacts')) {
             $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal);
+        } elsif ($action eq 'passwords') {
+            $output .= \
$item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal);  } elsif ($action eq \
                'coursecategories') {
             $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal);
         } elsif ($action eq 'scantron') {
@@ -861,12 +879,14 @@
         if (($action eq 'autoupdate') || ($action eq 'usercreation') ||
             ($action eq 'selfcreation') || ($action eq 'selfenrollment') ||
             ($action eq 'usersessions') || ($action eq 'coursecategories') || 
-            ($action eq 'trust') || ($action eq 'contacts') || ($action eq \
'defaults')) { +            ($action eq 'trust') || ($action eq 'contacts') || \
($action eq 'passwords')) {  if ($action eq 'coursecategories') {
                 $output .= \
&print_coursecategories('middle',$dom,$item,$settings,\$rowtotal);  $colspan = ' \
colspan="2"';  } elsif ($action eq 'trust') {
                 $output .= $item->{'print'}->('shared',$dom,$settings,\$rowtotal);
+            } elsif ($action eq 'passwords') {
+                $output .= \
$item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal);  } else {
                 $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal);
             }
@@ -912,8 +932,13 @@
              </tr>'."\n";
                 if ($action eq 'coursecategories') {
                     $output .= \
                &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal);
-                } elsif ($action eq 'contacts') {
-                    $output .= \
$item->{'print'}->('lower',$dom,$settings,\$rowtotal).' +                } elsif \
(($action eq 'contacts') || ($action eq 'passwords')) { +                    if \
($action eq 'passwords') { +                        $output .= \
$item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal); +                    \
} else { +                        $output .= \
$item->{'print'}->('lower',$dom,$settings,\$rowtotal); +                    }
+                    $output .= '
              </tr>
             </table>
            </td>
@@ -923,8 +948,13 @@
             <table class="LC_nested">
              <tr class="LC_info_row">
               <td class="LC_left_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col1'}).'</td>
                
-              <td class="LC_right_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col2'}).'</td></tr>'.
                
-                           $item->{'print'}->('bottom',$dom,$settings,\$rowtotal).'
+              <td class="LC_right_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col2'}).'</td></tr>'."\n";
 +                    if ($action eq 'passwords') {
+                        $output .= \
$item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal); +                   \
} else { +                        $output .= \
$item->{'print'}->('bottom',$dom,$settings,\$rowtotal); +                    }
+                    $output .= '
             </table>
           </td>
          </tr>
@@ -5770,6 +5800,395 @@
     return $datatable;
 }
 
+sub print_passwords {
+    my ($position,$dom,$confname,$settings,$rowtotal) = @_;
+    my ($datatable,$css_class);
+    my $itemcount = 0;
+    my %titles = &Apache::lonlocal::texthash (
+        captcha        => '"Forgot Password" CAPTCHA validation',
+        link           => 'Reset link expiration (hours)',
+        case           => 'Case-sensitive usernames/e-mail',
+        prelink        => 'Information required (form 1)',
+        postlink       => 'Information required (form 2)',
+        emailsrc       => 'LON-CAPA e-mail address type(s)',
+        customtext     => 'Domain specific text (HTML)',
+        intauth_cost   => 'Encryption cost for bcrypt (positive integer)',
+        intauth_check  => 'Check bcrypt cost if authenticated',
+        intauth_switch => 'Existing crypt-based switched to bcrypt on \
authentication', +        permanent      => 'Permanent e-mail address',
+        critical       => 'Critical notification address',
+        notify         => 'Notification address',
+        min            => 'Minimum password length',
+        max            => 'Maximum password length',
+        chars          => 'Required characters',
+        expire         => 'Password expiration (days)',
+    );
+    if ($position eq 'top') {
+        my ($othertitle,$usertypes,$types) = \
&Apache::loncommon::sorted_inst_types($dom); +        my $shownlinklife = 2;
+        my $prelink = 'both';
+        my (%casesens,%postlink,%emailsrc,$nostdtext,$customurl);
+        if (ref($settings) eq 'HASH') {
+            if ($settings->{resetlink} =~ /^\d+(|\.\d*)$/) {
+                $shownlinklife = $settings->{resetlink};
+            }
+            if (ref($settings->{resetcase}) eq 'ARRAY') {
+                map { $casesens{$_} = 1; } (@{$settings->{resetcase}});
+            }
+            if ($settings->{resetprelink} =~ /^(both|either)$/) {
+                $prelink = $settings->{resetprelink};
+            }
+            if (ref($settings->{resetpostlink}) eq 'HASH') {
+                %postlink = %{$settings->{resetpostlink}};
+            }
+            if (ref($settings->{resetemail}) eq 'ARRAY') {
+                map { $emailsrc{$_} = 1; } (@{$settings->{resetemail}});
+            }
+            if ($settings->{resetremove}) {
+                $nostdtext = 1;
+            }
+            if ($settings->{resetcustom}) {
+                $customurl = $settings->{resetcustom};
+            }
+        } else {
+            if (ref($types) eq 'ARRAY') {
+                foreach my $item (@{$types}) {
+                    $casesens{$item} = 1;
+                    $postlink{$item} = ['username','email'];
+                }
+            }
+            $casesens{'default'} = 1;
+            $postlink{'default'} = ['username','email'];
+            $prelink = 'both';
+            %emailsrc = (
+                          permanent => 1,
+                          critical  => 1,
+                          notify    => 1,
+            );
+        }
+        $datatable = &captcha_choice('passwords',$settings,$$rowtotal);
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'link'}.'</td>'.
+                      '<td class="LC_left_item">'.
+                      '<input type="textbox" value="'.$shownlinklife.'" '.
+                      'name="passwords_link" size="3" /></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'case'}.'</td>'.
+                      '<td class="LC_left_item">';
+        if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) {
+            foreach my $item (@{$types}) {
+                my $checkedcase;
+                if ($casesens{$item}) {
+                    $checkedcase = ' checked="checked"';
+                }
+                $datatable .= '<span class="LC_nobreak"><label>'.
+                              '<input type="checkbox" \
name="passwords_case_sensitive" value="'. +                              \
$item.'"'.$checkedcase.' />'.$usertypes->{$item}.'</label>'. +                        \
'<span>&nbsp;&nbsp; '; +            }
+        }
+        my $checkedcase;
+        if ($casesens{'default'}) {
+            $checkedcase = ' checked="checked"';
+        }
+        $datatable .= '<span class="LC_nobreak"><label><input type="checkbox" '.
+                      'name="passwords_case_sensitive" \
value="default"'.$checkedcase.' />'. +                      \
$othertitle.'</label></span></td>'; +        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        my %checkedpre = (
+                             both => ' checked="checked"',
+                             either => '',
+                         );
+        if ($prelink eq 'either') {
+            $checkedpre{either} = ' checked="checked"';
+            $checkedpre{both} = '';
+        }
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'prelink'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      '<label><input type="radio" name="passwords_prelink" \
value="both"'.$checkedpre{'both'}.' />'. +                      &mt('Both username \
and e-mail address').'</label></span>&nbsp;&nbsp; '. +                      '<span \
class="LC_nobreak"><label>'. +                      '<input type="radio" \
name="passwords_prelink" value="either"'.$checkedpre{'either'}.' />'. +               \
&mt('Either username or e-mail address').'</label></span></td></tr>'; +        \
$itemcount ++; +        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'postlink'}.'</td>'.
+                      '<td class="LC_left_item">';
+        my %postlinked;
+        if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) {
+            foreach my $item (@{$types}) {
+                undef(%postlinked);
+                $datatable .= '<fieldset style="display: inline-block;">'.
+                              '<legend>'.$usertypes->{$item}.'</legend>';
+                if (ref($postlink{$item}) eq 'ARRAY') {
+                    map { $postlinked{$_} = 1; } (@{$postlink{$item}});
+                }
+                foreach my $field ('email','username') {
+                    my $checked;
+                    if ($postlinked{$field}) {
+                        $checked = ' checked="checked"';
+                    }
+                    $datatable .= '<span class="LC_nobreak"><label>'.
+                                  '<input type="checkbox" \
name="passwords_postlink_'.$item.'" value="'. +                                  \
$field.'"'.$checked.' />'.$field.'</label>'. +                                  \
'<span>&nbsp;&nbsp; '; +                }
+                $datatable .= '</fieldset>';
+            }
+        }
+        if (ref($postlink{'default'}) eq 'ARRAY') {
+            map { $postlinked{$_} = 1; } (@{$postlink{'default'}});
+        }
+        $datatable .= '<fieldset style="display: inline-block;">'.
+                      '<legend>'.$othertitle.'</legend>';
+        foreach my $field ('email','username') {
+            my $checked;
+            if ($postlinked{$field}) {
+                $checked = ' checked="checked"';
+            }
+            $datatable .= '<span class="LC_nobreak"><label>'.
+                          '<input type="checkbox" name="passwords_postlink_default" \
value="'. +                          $field.'"'.$checked.' />'.$field.'</label>'.
+                          '<span>&nbsp;&nbsp; ';
+        }
+        $datatable .= '</fieldset></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'emailsrc'}.'</td>'.
+                      '<td class="LC_left_item">';
+        foreach my $type ('permanent','critical','notify') {
+            my $checkedemail;
+            if ($emailsrc{$type}) {
+                $checkedemail = ' checked="checked"';
+            }
+            $datatable .= '<span class="LC_nobreak"><label>'.
+                          '<input type="checkbox" name="passwords_emailsrc" \
value="'. +                          $type.'"'.$checkedemail.' \
/>'.$titles{$type}.'</label>'. +                          '<span>&nbsp;&nbsp; ';
+        }
+        $datatable .= '</td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        my $switchserver = &check_switchserver($dom,$confname);
+        my ($showstd,$noshowstd);
+        if ($nostdtext) {
+            $noshowstd = ' checked="checked"';
+        } else {
+            $showstd = ' checked="checked"';
+        }
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'customtext'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      &mt('Retain standard text:').
+                      '<label><input type="radio" name="passwords_stdtext" \
value="1"'.$showstd.' />'. +                      &mt('Yes').'</label>'.'&nbsp;'.
+                      '<label><input type="radio" name="passwords_stdtext" \
value="0"'.$noshowstd.' />'. +                      &mt('No').'</label></span><br \
/>'. +                      '<span class="LC_fontsize_small">'.
+                      &mt('(If you use the same account ...  reset a password from \
this page.)').'</span><br /><br />'. +                      &mt('Include custom \
text:'); +        if ($customurl) {
+            my $link =  &Apache::loncommon::modal_link($customurl,&mt('Custom text \
file'),600,500, +                                                       \
undef,undef,undef,undef,'background-color:#ffffff'); +            $datatable .= \
'<span class="LC_nobreak">&nbsp;'.$link. +                          '<label><input \
type="checkbox" name="passwords_custom_del"'. +                          ' value="1" \
/>'.&mt('Delete?').'</label></span>'. +                          ' <span \
class="LC_nobreak">&nbsp;'.&mt('Replace:').'</span>'; +        }
+        if ($switchserver) {
+            $datatable .= '<span class="LC_nobreak">&nbsp;'.&mt('Upload to library \
server: [_1]',$switchserver).'</span>'; +        } else {
+            $datatable .='<span class="LC_nobreak">&nbsp;'.
+                         '<input type="file" name="passwords_customfile" /></span>';
+        }
+        $datatable .= '</td></tr>';
+    } elsif ($position eq 'middle') {
+        my %domconf = &Apache::lonnet::get_dom('configuration',['defaults'],$dom);
+        my @items = ('intauth_cost','intauth_check','intauth_switch');
+        my %defaults;
+        if (ref($domconf{'defaults'}) eq 'HASH') {
+            %defaults = %{$domconf{'defaults'}};
+            if ($defaults{'intauth_cost'} !~ /^\d+$/) {
+                $defaults{'intauth_cost'} = 10;
+            }
+            if ($defaults{'intauth_check'} !~ /^(0|1|2)$/) {
+                $defaults{'intauth_check'} = 0;
+            }
+            if ($defaults{'intauth_switch'} !~ /^(0|1|2)$/) {
+                $defaults{'intauth_switch'} = 0;
+            }
+        } else {
+            %defaults = (
+                          'intauth_cost'   => 10,
+                          'intauth_check'  => 0,
+                          'intauth_switch' => 0,
+                        );
+        }
+        foreach my $item (@items) {
+            if ($itemcount%2) {
+                $css_class = '';
+            } else {
+                $css_class = ' class="LC_odd_row" ';
+            }
+            $datatable .= '<tr'.$css_class.'>'.
+                          '<td><span class="LC_nobreak">'.$titles{$item}.
+                          '</span></td><td class="LC_left_item" colspan="3">';
+            if ($item eq 'intauth_switch') {
+                my @options = (0,1,2);
+                my %optiondesc = &Apache::lonlocal::texthash (
+                                   0 => 'No',
+                                   1 => 'Yes',
+                                   2 => 'Yes, and copy existing passwd file to \
passwd.bak file', +                                 );
+                $datatable .= '<table width="100%">';
+                foreach my $option (@options) {
+                    my $checked = ' ';
+                    if ($defaults{$item} eq $option) {
+                        $checked = ' checked="checked"';
+                    }
+                    $datatable .= '<tr><td class="LC_left_item"><span \
class="LC_nobreak">'. +                                  '<label><input type="radio" \
name="'.$item. +                                  '" value="'.$option.'"'.$checked.' \
/>'. +                                  \
$optiondesc{$option}.'</label></span></td></tr>'; +                }
+                $datatable .= '</table>';
+            } elsif ($item eq 'intauth_check') {
+                my @options = (0,1,2);
+                my %optiondesc = &Apache::lonlocal::texthash (
+                                   0 => 'No',
+                                   1 => 'Yes, allow login then update passwd file \
using default cost (if higher)', +                                   2 => 'Yes, \
disallow login if stored cost is less than domain default', +                         \
); +                $datatable .= '<table width="100%">';
+                foreach my $option (@options) {
+                    my $checked = ' ';
+                    my $onclick;
+                    if ($defaults{$item} eq $option) {
+                        $checked = ' checked="checked"';
+                    }
+                    if ($option == 2) {
+                        $onclick = ' onclick="javascript:warnIntAuth(this);"';
+                    }
+                    $datatable .= '<tr><td class="LC_left_item"><span \
class="LC_nobreak">'. +                                  '<label><input type="radio" \
name="'.$item. +                                  '" \
value="'.$option.'"'.$checked.$onclick.' />'. +                                  \
$optiondesc{$option}.'</label></span></td></tr>'; +                }
+                $datatable .= '</table>';
+            } else {
+                $datatable .= '<input type="text" name="'.$item.'" value="'.
+                              $defaults{$item}.'" size="3" \
onblur="javascript:warnIntAuth(this);" />'; +            }
+            $datatable .= '</td></tr>';
+            $itemcount ++;
+        }
+    } elsif ($position eq 'lower') {
+        my ($min,$max,%chars,$expire);
+        if (ref($settings) eq 'HASH') {
+            if ($settings->{min}) {
+                $min = $settings->{min};
+            }
+            if ($settings->{max}) {
+                $max = $settings->{max};
+            }
+            if (ref($settings->{chars}) eq 'ARRAY') {
+                map { $chars{$_} = 1; } (@{$settings->{chars}});
+            }
+            if ($settings->{expire}) {
+                $expire = $settings->{expire};
+            }
+        } else {
+            $min = '7';
+        }
+        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', +                                                   );
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'min'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      '<input type="text" name="passwords_min" value="'.$min.'" \
size="3" />'. +                      '<span class="LC_fontsize_small"> '.&mt('(Leave \
blank for no minimum)').'</span>'. +                      '</span></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'max'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      '<input type="text" name="passwords_max" value="'.$max.'" \
size="3" />'. +                      '<span class="LC_fontsize_small"> '.&mt('(Leave \
blank for no maximum)').'</span>'. +                      '</span></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'chars'}.'<br />'.
+                      '<span class="LC_nobreak LC_fontsize_small">'.&mt('(Leave \
unchecked if not required)'). +                      '</span></td>';
+        my $numinrow = 2;
+        my @possrules = ('uc','lc','num','spec');
+        $datatable .= '<td class="LC_left_item"><table>';
+        for (my $i=0; $i<@possrules; $i++) {
+            my ($rem,$checked);
+            if ($chars{$possrules[$i]}) {
+                $checked = ' checked="checked"';
+            }
+            $rem = $i%($numinrow);
+            if ($rem == 0) {
+                if ($i > 0) {
+                    $datatable .= '</tr>';
+                }
+                $datatable .= '<tr>';
+            }
+            $datatable .= '<td><span class="LC_nobreak"><label>'.
+                          '<input type="checkbox" name="passwords_chars" \
value="'.$possrules[$i].'"'.$checked.' />'. +                          \
$rulenames{$possrules[$i]}.'</label></span></td>'; +        }
+        my $rem = @possrules%($numinrow);
+        my $colsleft = $numinrow - $rem;
+        if ($colsleft > 1 ) {
+            $datatable .= '<td colspan="'.$colsleft.'" class="LC_left_item">'.
+                          '&nbsp;</td>';
+        } elsif ($colsleft == 1) {
+            $datatable .= '<td class="LC_left_item">&nbsp;</td>';
+        }
+        $datatable .='</table></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'expire'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      '<input type="text" name="passwords_expire" \
value="'.$expire.'" size="4" />'. +                      '<span \
class="LC_fontsize_small"> '.&mt('(Leave blank for no expiration)').'</span>'. +      \
'</span></td></tr>'; +    } else {
+        my $checkedon;
+        my $checkedoff = ' checked="checked"';
+        if (ref($settings) eq 'HASH') {
+            if ($settings->{crsownerchg}) {
+                $checkedon = $checkedoff;
+                $checkedoff = '';
+            }
+        }
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr '.$css_class.'>'.
+                      '<td>'.
+                      &mt('Requirements').'<ul>'.
+                      '<li>'.&mt("Course 'type' is not a Community").'</li>'.
+                      '<li>'.&mt('User is Course Coordinator and also course \
owner').'</li>'. +                      '<li>'.&mt("Student's only active roles are \
student role(s) in course(s) owned by this user").'</li>'. +                      \
'</ul>'. +                      '</td>'.
+                      '<td class="LC_left_item" colspan="2"><span \
class="LC_nobreak">'. +                      '<label><input type="radio" \
name="passwords_crsowner" value="1"'.$checkedon.' />'.&mt('Yes').'</label></span> \
&nbsp;&nbsp;'. +                      '<span class="LC_nobreak"><label><input \
type="radio" name="passwords_crsowner" value="0"'.$checkedoff.' />'. +                \
&mt('No').'</label></span>'. +                      '</td></tr>';
+
+    }
+    return $datatable;
+}
+
 sub print_usersessions {
     my ($position,$dom,$settings,$rowtotal) = @_;
     my ($css_class,$datatable,$itemcount,%checked,%choices);
@@ -7528,10 +7947,14 @@
         $vertext,$currver);
     my %lt = &captcha_phrases();
     $keyentry = 'hidden';
+    my $colspan=2;
     if ($context eq 'cancreate') {
         $rowname = &mt('CAPTCHA validation');
     } elsif ($context eq 'login') {
         $rowname =  &mt('"Contact helpdesk" CAPTCHA validation');
+    } elsif ($context eq 'passwords') {
+        $rowname = &mt('"Forgot Password" CAPTCHA validation');
+        $colspan=1;
     }
     if (ref($settings) eq 'HASH') {
         if ($settings->{'captcha'}) {
@@ -7571,7 +7994,7 @@
         $css_class .= ' style="'.$rowstyle.'"';
     }
     my $output = '<tr'.$css_class.'>'.
-                 '<td class="LC_left_item">'.$rowname.'</td><td class="LC_left_item" \
colspan="2">'."\n". +                 '<td class="LC_left_item">'.$rowname.'</td><td \
class="LC_left_item" colspan="'.$colspan.'">'."\n".  '<table><tr><td>'."\n";
     foreach my $option ('original','recaptcha','notused') {
         $output .= '<span class="LC_nobreak"><label><input type="radio" \
name="'.$context.'_captcha" value="'. @@ -7810,133 +8233,54 @@
             $datatable .= '</td></tr>';
             $rownum ++;
         }
-    } elsif ($position eq 'middle') {
-        my @items = ('intauth_cost','intauth_check','intauth_switch');
+    } else {
         my %defaults;
         if (ref($settings) eq 'HASH') {
-            %defaults = %{$settings};
-            if ($defaults{'intauth_cost'} !~ /^\d+$/) {
-                $defaults{'intauth_cost'} = 10;
-            }
-            if ($defaults{'intauth_check'} !~ /^(0|1|2)$/) {
-                $defaults{'intauth_check'} = 0;
-            }
-            if ($defaults{'intauth_switch'} !~ /^(0|1|2)$/) {
-                $defaults{'intauth_switch'} = 0;
-            }
-        } else {
-            %defaults = (
-                          'intauth_cost'   => 10,
-                          'intauth_check'  => 0,
-                          'intauth_switch' => 0,
-                        );
-        }
-        foreach my $item (@items) {
-            if ($rownum%2) {
-                $css_class = '';
-            } else {
-                $css_class = ' class="LC_odd_row" ';
-            }
-            $datatable .= '<tr'.$css_class.'>'.
-                          '<td><span class="LC_nobreak">'.$titles->{$item}.
-                          '</span></td><td class="LC_left_item" colspan="3">';
-            if ($item eq 'intauth_switch') {
-                my @options = (0,1,2);
-                my %optiondesc = &Apache::lonlocal::texthash (
-                                   0 => 'No',
-                                   1 => 'Yes',
-                                   2 => 'Yes, and copy existing passwd file to \
                passwd.bak file',
-                                 );
-                $datatable .= '<table width="100%">';
-                foreach my $option (@options) {
-                    my $checked = ' ';
-                    if ($defaults{$item} eq $option) {
-                        $checked = ' checked="checked"';
-                    }
-                    $datatable .= '<tr><td class="LC_left_item"><span \
                class="LC_nobreak">'.
-                                  '<label><input type="radio" name="'.$item.
-                                  '" value="'.$option.'"'.$checked.' />'.
-                                  $optiondesc{$option}.'</label></span></td></tr>';
-                }
-                $datatable .= '</table>';
-            } elsif ($item eq 'intauth_check') {
-                my @options = (0,1,2);
-                my %optiondesc = &Apache::lonlocal::texthash (
-                                   0 => 'No',
-                                   1 => 'Yes, allow login then update passwd file \
                using default cost (if higher)',
-                                   2 => 'Yes, disallow login if stored cost is less \
                than domain default',
-                                 );
-                $datatable .= '<table width="100%">';
-                foreach my $option (@options) {
-                    my $checked = ' ';
-                    my $onclick;
-                    if ($defaults{$item} eq $option) {
-                        $checked = ' checked="checked"';
-                    }
-                    if ($option == 2) {
-                        $onclick = ' onclick="javascript:warnIntAuth(this);"';
-                    }
-                    $datatable .= '<tr><td class="LC_left_item"><span \
                class="LC_nobreak">'.
-                                  '<label><input type="radio" name="'.$item.
-                                  '" value="'.$option.'"'.$checked.$onclick.' />'.
-                                  $optiondesc{$option}.'</label></span></td></tr>';
-                }
-                $datatable .= '</table>';
-            } else {
-                $datatable .= '<input type="text" name="'.$item.'" value="'.
-                              $defaults{$item}.'" size="3" \
                onblur="javascript:warnIntAuth(this);" />'; 
-            }
-            $datatable .= '</td></tr>';
-            $rownum ++;
-        }
-    } else {
-        my %defaults;
-        if (ref($settings) eq 'HASH') {
-            if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && \
                (ref($settings->{'inststatustypes'}) eq 'HASH')) {
-                my $maxnum = @{$settings->{'inststatusorder'}};
-                for (my $i=0; $i<$maxnum; $i++) {
-                    $css_class = $rownum%2?' class="LC_odd_row"':'';
-                    my $item = $settings->{'inststatusorder'}->[$i];
-                    my $title = $settings->{'inststatustypes'}->{$item};
-                    my $chgstr = ' \
                onchange="javascript:reorderTypes(this.form,'."'$item'".');"';
-                    $datatable .= '<tr'.$css_class.'>'.
-                                  '<td><span class="LC_nobreak">'.
-                                  '<select \
                name="inststatus_pos_'.$item.'"'.$chgstr.'>';
-                    for (my $k=0; $k<=$maxnum; $k++) {
-                        my $vpos = $k+1;
-                        my $selstr;
-                        if ($k == $i) {
-                            $selstr = ' selected="selected" ';
-                        }
-                        $datatable .= '<option \
                value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
-                    }
-                    $datatable .= '</select>&nbsp;'.&mt('Internal \
                ID:').'&nbsp;<b>'.$item.'</b>&nbsp;'.
-                                  '<input type="checkbox" name="inststatus_delete" \
                value="'.$item.'" />'.
-                                  &mt('delete').'</span></td>'.
-                                  '<td class="LC_left_item" colspan="2"><span \
                class="LC_nobreak">'.&mt('Name displayed:').
-                                  '<input type="text" size="20" \
                name="inststatus_title_'.$item.'" value="'.$title.'" />'.
-                                  '</span></td></tr>';
-                }
-                $css_class = $rownum%2?' class="LC_odd_row"':'';
-                my $chgstr = ' \
                onchange="javascript:reorderTypes(this.form,'."'addinststatus_pos'".');"';
                
-                $datatable .= '<tr '.$css_class.'>'.
-                              '<td><span class="LC_nobreak"><select \
                name="addinststatus_pos"'.$chgstr.'>';
-                for (my $k=0; $k<=$maxnum; $k++) {
-                    my $vpos = $k+1;
-                    my $selstr;
-                    if ($k == $maxnum) {
-                        $selstr = ' selected="selected" ';
-                    }
-                    $datatable .= '<option \
                value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
-                }
-                $datatable .= '</select>&nbsp;'.&mt('Internal ID:').
-                              '<input type="text" size="10" name="addinststatus" \
                value="" />'.
-                              '&nbsp;'.&mt('(new)').
-                              '</span></td><td class="LC_left_item" \
                colspan="2"><span class="LC_nobreak">'.
-                              &mt('Name displayed:').
-                              '<input type="text" size="20" \
                name="addinststatus_title" value="" /></span></td>'.
-                              '</tr>'."\n";
-                $rownum ++;
+            if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && \
(ref($settings->{'inststatustypes'}) eq 'HASH')) { +                my $maxnum = \
@{$settings->{'inststatusorder'}}; +                for (my $i=0; $i<$maxnum; $i++) {
+                    $css_class = $rownum%2?' class="LC_odd_row"':'';
+                    my $item = $settings->{'inststatusorder'}->[$i];
+                    my $title = $settings->{'inststatustypes'}->{$item};
+                    my $chgstr = ' \
onchange="javascript:reorderTypes(this.form,'."'$item'".');"'; +                    \
$datatable .= '<tr'.$css_class.'>'. +                                  '<td><span \
class="LC_nobreak">'. +                                  '<select \
name="inststatus_pos_'.$item.'"'.$chgstr.'>'; +                    for (my $k=0; \
$k<=$maxnum; $k++) { +                        my $vpos = $k+1;
+                        my $selstr;
+                        if ($k == $i) {
+                            $selstr = ' selected="selected" ';
+                        }
+                        $datatable .= '<option \
value="'.$k.'"'.$selstr.'>'.$vpos.'</option>'; +                    }
+                    $datatable .= '</select>&nbsp;'.&mt('Internal \
ID:').'&nbsp;<b>'.$item.'</b>&nbsp;'. +                                  '<input \
type="checkbox" name="inststatus_delete" value="'.$item.'" />'. +                     \
&mt('delete').'</span></td>'. +                                  '<td \
class="LC_left_item" colspan="2"><span class="LC_nobreak">'.&mt('Name displayed:'). + \
'<input type="text" size="20" name="inststatus_title_'.$item.'" value="'.$title.'" \
/>'. +                                  '</span></td></tr>';
+                }
+                $css_class = $rownum%2?' class="LC_odd_row"':'';
+                my $chgstr = ' \
onchange="javascript:reorderTypes(this.form,'."'addinststatus_pos'".');"'; +          \
$datatable .= '<tr '.$css_class.'>'. +                              '<td><span \
class="LC_nobreak"><select name="addinststatus_pos"'.$chgstr.'>'; +                \
for (my $k=0; $k<=$maxnum; $k++) { +                    my $vpos = $k+1;
+                    my $selstr;
+                    if ($k == $maxnum) {
+                        $selstr = ' selected="selected" ';
+                    }
+                    $datatable .= '<option \
value="'.$k.'"'.$selstr.'>'.$vpos.'</option>'; +                }
+                $datatable .= '</select>&nbsp;'.&mt('Internal ID:').
+                              '<input type="text" size="10" name="addinststatus" \
value="" />'. +                              '&nbsp;'.&mt('(new)').
+                              '</span></td><td class="LC_left_item" \
colspan="2"><span class="LC_nobreak">'. +                              &mt('Name \
displayed:'). +                              '<input type="text" size="20" \
name="addinststatus_title" value="" /></span></td>'. +                              \
'</tr>'."\n"; +                $rownum ++;
             }
         }
     }
@@ -8677,35 +9021,7 @@
 
 sub defaults_javascript {
     my ($settings) = @_;
-    my $intauthcheck = &mt('Warning: disallowing login for an authenticated user if \
the stored cost is less than the default will require a password reset by/for the \
                user.');
-    my $intauthcost = &mt('Warning: bcrypt encryption cost for internal \
                authentication must be an integer.');
-    &js_escape(\$intauthcheck);
-    &js_escape(\$intauthcost);
-    my $intauthjs = <<"ENDSCRIPT";
-
-function warnIntAuth(field) {
-    if (field.name == 'intauth_check') {
-        if (field.value == '2') {
-            alert('$intauthcheck');
-        }
-    }
-    if (field.name == 'intauth_cost') {
-        field.value.replace(/\s/g,'');
-        if (field.value != '') {
-            var regexdigit=/^\\d+\$/;
-            if (!regexdigit.test(field.value)) {
-                alert('$intauthcost');
-            }
-        }
-    }
-    return;
-}
-
-ENDSCRIPT
-
-    if (ref($settings) ne 'HASH') {
-        return &Apache::lonhtmlcommon::scripttag($intauthjs);
-    }
+    return unless (ref($settings) eq 'HASH');
     if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && \
(ref($settings->{'inststatustypes'}) eq 'HASH')) {  my $maxnum = \
scalar(@{$settings->{'inststatusorder'}});  if ($maxnum eq '') {
@@ -8759,15 +9075,41 @@
     return;
 }
 
-$intauthjs
-
 // ]]>
 </script>
 
 ENDSCRIPT
-    } else {
-        return &Apache::lonhtmlcommon::scripttag($intauthjs);
     }
+    return;
+}
+
+sub passwords_javascript {
+    my $intauthcheck = &mt('Warning: disallowing login for an authenticated user if \
the stored cost is less than the default will require a password reset by/for the \
user.'); +    my $intauthcost = &mt('Warning: bcrypt encryption cost for internal \
authentication must be an integer.'); +    &js_escape(\$intauthcheck);
+    &js_escape(\$intauthcost);
+    my $intauthjs = <<"ENDSCRIPT";
+
+function warnIntAuth(field) {
+    if (field.name == 'intauth_check') {
+        if (field.value == '2') {
+            alert('$intauthcheck');
+        }
+    }
+    if (field.name == 'intauth_cost') {
+        field.value.replace(/\s/g,'');
+        if (field.value != '') {
+            var regexdigit=/^\\d+\$/;
+            if (!regexdigit.test(field.value)) {
+                alert('$intauthcost');
+            }
+        }
+    }
+    return;
+}
+
+ENDSCRIPT
+    return &Apache::lonhtmlcommon::scripttag($intauthjs);
 }
 
 sub coursecategories_javascript {
@@ -9122,7 +9464,7 @@
         my $check = ' ';
         unless ($role eq 'emailusername') {
             if (exists($checks{$fields[$i]})) {
-                $check = $checks{$fields[$i]}
+                $check = $checks{$fields[$i]};
             } elsif ($context ne 'lti') {
                 if ($role eq 'st') {
                     if (ref($settings) ne 'HASH') {
@@ -13616,6 +13958,460 @@
     return $resulttext;
 }
 
+sub modify_passwords {
+    my ($r,$dom,$confname,$lastactref,%domconfig) = @_;
+    my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors,$updatedefaults);
+    my $customfn = 'resetpw.html';
+    if (ref($domconfig{'passwords'}) eq 'HASH') {
+        %current = %{$domconfig{'passwords'}};
+    }
+    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1);
+    my ($othertitle,$usertypes,$types) = \
&Apache::loncommon::sorted_inst_types($dom); +    if (ref($types) eq 'ARRAY') {
+        @oktypes = @{$types};
+    }
+    push(@oktypes,'default');
+
+    my %titles = &Apache::lonlocal::texthash (
+        intauth_cost   => 'Encryption cost for bcrypt (positive integer)',
+        intauth_check  => 'Check bcrypt cost if authenticated',
+        intauth_switch => 'Existing crypt-based switched to bcrypt on \
authentication', +        permanent      => 'Permanent e-mail address',
+        critical       => 'Critical notification address',
+        notify         => 'Notification address',
+        min            => 'Minimum password length',
+        max            => 'Maximum password length',
+        chars          => 'Required characters',
+        expire         => 'Password expiration (days)',
+        reset          => 'Resetting Forgotten Password',
+        intauth        => 'Encryption of Stored Passwords (Internal Auth)',
+        rules          => 'Rules for LON-CAPA Passwords',
+        crsownerchg    => 'Course Owner Changing Student Passwords',
+        username       => 'Username',
+        email          => 'E-mail address',
+    );
+
+#
+# Retrieve current domain configuration for internal authentication from \
$domconfig{'defaults'}. +#
+    my (%curr_defaults,%save_defaults);
+    if (ref($domconfig{'defaults'}) eq 'HASH') {
+        foreach my $key (keys(%{$domconfig{'defaults'}})) {
+            if ($key =~ /^intauth_(cost|check|switch)$/) {
+                $curr_defaults{$key} = $domconfig{'defaults'}{$key};
+            } else {
+                $save_defaults{$key} = $domconfig{'defaults'}{$key};
+            }
+        }
+    }
+    my %staticdefaults = (
+        'resetlink'      => 2,
+        'resetcase'      => \@oktypes,
+        'resetprelink'   => 'both',
+        'resetemail'     => ['critical','notify','permanent'],
+        'intauth_cost'   => 10,
+        'intauth_check'  => 0,
+        'intauth_switch' => 0,
+        'min'            => 7,
+    );
+    foreach my $type (@oktypes) {
+        $staticdefaults{'resetpostlink'}{$type} = ['email','username'];
+    }
+    my $linklife = $env{'form.passwords_link'};
+    $linklife =~ s/^\s+|\s+$//g;
+    if (($linklife =~ /^\d+(|\.\d*)$/) && ($linklife > 0)) {
+        $newvalues{'resetlink'} = $linklife;
+        if ($current{'resetlink'}) {
+            if ($current{'resetlink'} ne $linklife) {
+                $changes{'reset'} = 1;
+            }
+        } elsif (!exists($domconfig{passwords})) {
+            if ($staticdefaults{'resetlink'} ne $linklife) {
+                $changes{'reset'} = 1;
+            }
+        }
+    } elsif ($current{'resetlink'}) {
+        $changes{'reset'} = 1;
+    }
+    my @casesens;
+    my @posscase = &Apache::loncommon::get_env_multiple('form.passwords_case_sensitive');
 +    foreach my $case (sort(@posscase)) {
+        if (grep(/^\Q$case\E$/,@oktypes)) {
+            push(@casesens,$case);
+        }
+    }
+    $newvalues{'resetcase'} = \@casesens;
+    if (ref($current{'resetcase'}) eq 'ARRAY') {
+        my @diffs = \
&Apache::loncommon::compare_arrays($current{'resetcase'},\@casesens); +        if \
(@diffs > 0) { +            $changes{'reset'} = 1;
+        }
+    } elsif (!exists($domconfig{passwords})) {
+        my @diffs = \
&Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); +        \
if (@diffs > 0) { +            $changes{'reset'} = 1;
+        }
+    }
+    if ($env{'form.passwords_prelink'} =~ /^(both|either)$/) {
+        $newvalues{'resetprelink'} = $env{'form.passwords_prelink'};
+        if (exists($current{'resetprelink'})) {
+            if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) {
+                $changes{'reset'} = 1;
+            }
+        } elsif (!exists($domconfig{passwords})) {
+            if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) {
+                $changes{'reset'} = 1;
+            }
+        }
+    } elsif ($current{'resetprelink'}) {
+        $changes{'reset'} = 1;
+    }
+    foreach my $type (@oktypes) {
+        my @possplink = \
&Apache::loncommon::get_env_multiple('form.passwords_postlink_'.$type); +        my \
@postlink; +        foreach my $item (sort(@possplink)) {
+            if ($item =~ /^(email|username)$/) {
+                push(@postlink,$item);
+            }
+        }
+        $newvalues{'resetpostlink'}{$type} = \@postlink;
+        unless ($changes{'reset'}) {
+            if (ref($current{'resetpostlink'}) eq 'HASH') {
+                if (ref($current{'resetpostlink'}{$type}) eq 'ARRAY') {
+                    my @diffs = \
&Apache::loncommon::compare_arrays($current{'resetpostlink'}{$type},\@postlink); +    \
if (@diffs > 0) { +                        $changes{'reset'} = 1;
+                    }
+                } else {
+                    $changes{'reset'} = 1;
+                }
+            } elsif (!exists($domconfig{passwords})) {
+                my @diffs = \
&Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink);
 +                if (@diffs > 0) {
+                    $changes{'reset'} = 1;
+                }
+            }
+        }
+    }
+    my @possemailsrc = \
&Apache::loncommon::get_env_multiple('form.passwords_emailsrc'); +    my @resetemail;
+    foreach my $item (sort(@possemailsrc)) {
+        if ($item =~ /^(permanent|critical|notify)$/) {
+            push(@resetemail,$item);
+        }
+    }
+    $newvalues{'resetemail'} = \@resetemail;
+    unless ($changes{'reset'}) {
+        if (ref($current{'resetemail'}) eq 'ARRAY') {
+            my @diffs = \
&Apache::loncommon::compare_arrays($current{'resetemail'},\@resetemail); +            \
if (@diffs > 0) { +                $changes{'reset'} = 1;
+            }
+        } elsif (!exists($domconfig{passwords})) {
+            my @diffs = \
&Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); +     \
if (@diffs > 0) { +                $changes{'reset'} = 1;
+            }
+        }
+    }
+    if ($env{'form.passwords_stdtext'} == 0) {
+        $newvalues{'resetremove'} = 1;
+        unless ($current{'resetremove'}) {
+            $changes{'reset'} = 1;
+        }
+    } elsif ($current{'resetremove'}) {
+        $changes{'reset'} = 1;
+    }
+    if ($env{'form.passwords_customfile.filename'} ne '') {
+        my $servadm = $r->dir_config('lonAdmEMail');
+        my ($configuserok,$author_ok,$switchserver) =
+            &config_check($dom,$confname,$servadm);
+        my $error;
+        if ($configuserok eq 'ok') {
+            if ($switchserver) {
+                $error = &mt("Upload of file containing domain-specific text is not \
permitted to this server: [_1]",$switchserver); +            } else {
+                if ($author_ok eq 'ok') {
+                    my ($result,$customurl) =
+                        &publishlogo($r,'upload','passwords_customfile',$dom,
+                                     \
$confname,'customtext/resetpw','','',$customfn); +                    if ($result eq \
'ok') { +                        $newvalues{'resetcustom'} = $customurl;
+                        $changes{'reset'} = 1;
+                    } else {
+                        $error = &mt("Upload of [_1] failed because an error \
occurred publishing the file in RES space. Error was: [_2].",$customfn,$result); +    \
} +                } else {
+                    $error = &mt("Upload of [_1] failed because an author role could \
not be assigned to a Domain Configuration user ([_2]) in domain: [_3].  Error was: \
[_4].",$customfn,$confname,$dom,$author_ok); +                }
+            }
+        } else {
+            $error = &mt("Upload of [_1] failed because a Domain Configuration user \
([_2]) could not be created in domain: [_3].  Error was: \
[_4].",$customfn,$confname,$dom,$configuserok); +        }
+        if ($error) {
+            &Apache::lonnet::logthis($error);
+            $errors .= '<li><span class="LC_error">'.$error.'</span></li>';
+        }
+    } elsif ($current{'resetcustom'}) {
+        if ($env{'form.passwords_custom_del'}) {
+            $changes{'reset'} = 1;
+        } else {
+            $newvalues{'resetcustom'} = $current{'resetcustom'};
+        }
+    }
+    $env{'form.intauth_cost'} =~ s/^\s+|\s+$//g;
+    if (($env{'form.intauth_cost'} ne '') && ($env{'form.intauth_cost'} =~ /^\d+$/)) \
{ +        $save_defaults{'intauth_cost'} = $env{'form.intauth_cost'};
+        if ($save_defaults{'intauth_cost'} ne $curr_defaults{'intauth_cost'}) {
+            $changes{'intauth'} = 1;
+        }
+    } else {
+        $save_defaults{'intauth_cost'} = $curr_defaults{'intauth_cost'};
+    }
+    if ($env{'form.intauth_check'} =~ /^(0|1|2)$/) {
+        $save_defaults{'intauth_check'} = $env{'form.intauth_check'};
+        if ($save_defaults{'intauth_check'} ne $curr_defaults{'intauth_check'}) {
+            $changes{'intauth'} = 1;
+        }
+    } else {
+        $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'};
+    }
+    if ($env{'form.intauth_switch'} =~ /^(0|1|2)$/) {
+        $save_defaults{'intauth_switch'} = $env{'form.intauth_switch'};
+        if ($save_defaults{'intauth_switch'} ne $curr_defaults{'intauth_switch'}) {
+            $changes{'intauth'} = 1;
+        }
+    } else {
+        $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'};
+    }
+    foreach my $item ('cost','check','switch') {
+        if ($save_defaults{'intauth_'.$item} ne $domdefaults{'intauth_'.$item}) {
+            $domdefaults{'intauth_'.$item} = $save_defaults{'intauth_'.$item};
+            $updatedefaults = 1;
+        }
+    }
+    foreach my $rule ('min','max','expire') {
+        $env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g;
+        if ($env{'form.passwords_'.$rule} =~ /^(|\d+(|\.\d*))$/) {
+            $newvalues{$rule} = $env{'form.passwords_'.$rule};
+            if (exists($current{$rule})) {
+                if ($newvalues{$rule} ne $current{$rule}) {
+                    $changes{'rules'} = 1;
+                }
+            } elsif ($rule eq 'min') {
+                if ($staticdefaults{$rule} ne $newvalues{$rule}) {
+                    $changes{'rules'} = 1;
+                }
+            }
+        } elsif (exists($current{$rule})) {
+            $changes{'rules'} = 1;
+        }
+    }
+    my @posschars = &Apache::loncommon::get_env_multiple('form.passwords_chars');
+    my @chars;
+    foreach my $item (sort(@posschars)) {
+        if ($item =~ /^(uc|lc|num|spec)$/) {
+            push(@chars,$item);
+        }
+    }
+    $newvalues{'chars'} = \@chars;
+    unless ($changes{'rules'}) {
+        if (ref($current{'chars'}) eq 'ARRAY') {
+            my @diffs = \
&Apache::loncommon::compare_arrays($current{'chars'},\@chars); +            if \
(@diffs > 0) { +                $changes{'rules'} = 1;
+            }
+        } else {
+            if (@chars > 0) {
+                $changes{'rules'} = 1;
+            }
+        }
+    }
+    if ($env{'form.passwords_crsowner'}) {
+        $newvalues{'crsownerchg'} = 1;
+        unless ($current{'crsownerchg'}) {
+            $changes{'crsownerchg'} = 1;
+        }
+    } elsif ($current{'crsownerchg'}) {
+        $changes{'crsownerchg'} = 1;
+    }
+
+    my %confighash = (
+                        defaults  => \%save_defaults,
+                        passwords => \%newvalues,
+                     );
+    &process_captcha('passwords',\%changes,$confighash{'passwords'},$domconfig{'passwords'});
 +
+    my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom);
+    if ($putresult eq 'ok') {
+        if (keys(%changes) > 0) {
+            $resulttext = &mt('Changes made: ').'<ul>';
+            foreach my $key ('reset','intauth','rules','crsownerchg') {
+                if ($changes{$key}) {
+                    $resulttext .= '<li>'.$titles{$key}.':<ul>';
+                    if ($key eq 'reset') {
+                        if ($confighash{'passwords'}{'captcha'} eq 'original') {
+                            $resulttext .= '<li>'.&mt('CAPTCHA validation set to \
use: original CAPTCHA').'</li>'; +                        } elsif \
($confighash{'passwords'}{'captcha'} eq 'recaptcha') { +                            \
$resulttext .= '<li>'.&mt('CAPTCHA validation set to use: reCAPTCHA').' '. +          \
&mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).'<br />'. +         \
&mt('Public key: [_1]',$confighash{'passwords'}{'recaptchapub'}).'</br>'. +           \
&mt('Private key: [_1]',$confighash{'passwords'}{'recaptchapriv'}).'</li>'; +         \
} else { +                            $resulttext .= '<li>'.&mt('No CAPTCHA \
validation').'</li>'; +                        }
+                        if ($confighash{'passwords'}{'resetlink'}) {
+                            $resulttext .= '<li>'.&mt('Reset link expiration set to \
[quant,_1,hour]',$confighash{'passwords'}{'resetlink'}).'</li>'; +                    \
} else { +                            $resulttext .= '<li>'.&mt('No reset link \
expiration set.').' '. +                                                  &mt('Will \
default to 2 hours').'</li>'; +                        }
+                        if (ref($confighash{'passwords'}{'resetcase'}) eq 'ARRAY') {
+                            if (@{$confighash{'passwords'}{'resetcase'}} == 0) {
+                                $resulttext .= '<li>'.&mt('User input for username \
and/or e-mail address not case sensitive for "Forgot Password" web form').'</li>'; +  \
} else { +                                my $casesens;
+                                foreach my $type \
(@{$confighash{'passwords'}{'resetcase'}}) { +                                    if \
($type eq 'default') { +                                        $casesens .= \
$othertitle.', '; +                                    } elsif ($usertypes->{$type} \
ne '') { +                                        $casesens .= $usertypes->{$type}.', \
'; +                                    }
+                                }
+                                $casesens =~ s/\Q, \E$//;
+                                $resulttext .= '<li>'.&mt('"Forgot Password" web \
form input for username and/or e-mail address is case-sensitive for: \
[_1]',$casesens).'</li>'; +                            }
+                        } else {
+                            $resulttext .= '<li>'.&mt('Case-sensitivity not set for \
"Forgot Password" web form').' '.&mt('Will default to case-sensitive for username \
and/or e-mail address for all').'</li>'; +                        }
+                        if ($confighash{'passwords'}{'resetprelink'} eq 'either') {
+                            $resulttext .= '<li>'.&mt('Users can enter either a \
username or an e-mail address in "Forgot Password" web form').'</li>'; +              \
} else { +                            $resulttext .= '<li>'.&mt('Users can enter both \
a username and an e-mail address in "Forgot Password" web form').'</li>'; +           \
} +                        if (ref($confighash{'passwords'}{'resetpostlink'}) eq \
'HASH') { +                            my $output;
+                            if (ref($types) eq 'ARRAY') {
+                                foreach my $type (@{$types}) {
+                                    if \
(ref($confighash{'passwords'}{'resetpostlink'}{$type}) eq 'ARRAY') { +                \
if (@{$confighash{'passwords'}{'resetpostlink'}{$type}} == 0) { +                     \
$output .= $usertypes->{$type}.' -- '.&mt('none'); +                                  \
} else { +                                            $output .= \
$usertypes->{$type}.' -- '. +                                                       \
join(', ',map { $titles{$_}; } \
(@{$confighash{'passwords'}{'resetpostlink'}{$type}})).'; '; +                        \
} +                                    }
+                                }
+                            }
+                            if \
(ref($confighash{'passwords'}{'resetpostlink'}{'default'}) eq 'ARRAY') { +            \
if (@{$confighash{'passwords'}{'resetpostlink'}{'default'}} == 0) { +                 \
$output .= $othertitle.' -- '.&mt('none'); +                                } else {
+                                    $output .= $othertitle.' -- '.
+                                               join(', ',map { $titles{$_}; } \
(@{$confighash{'passwords'}{'resetpostlink'}{'default'}})); +                         \
} +                            }
+                            if ($output) {
+                                $resulttext .= '<li>'.&mt('Information required for \
new password form (by user type) set to: [_1]',$output).'</li>'; +                    \
} else { +                                $resulttext .= '<li>'.&mt('Information \
required for new password form not set.').' '.&mt('Will default to requiring both the \
username and an e-mail address').'</li>'; +                            }
+                        } else {
+                            $resulttext .= '<li>'.&mt('Information required for new \
password form not set.').' '.&mt('Will default to requiring both the username and an \
e-mail address').'</li>'; +                        }
+                        if (ref($confighash{'passwords'}{'resetemail'}) eq 'ARRAY') \
{ +                            if (@{$confighash{'passwords'}{'resetemail'}} > 0) {
+                                $resulttext .= '<li>'.&mt('E-mail address(es) in \
LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } \
@{$confighash{'passwords'}{'resetemail'}})).'</li>'; +                            } \
else { +                                $resulttext .= '<li>'.&mt('E-mail address(es) \
in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } \
@{$staticdefaults{'resetemail'}})).'</li>'; +                            }
+                        } else {
+                            $resulttext .= '<li>'.&mt('E-mail address(es) in \
LON-CAPA usedfor verification will include: [_1]',join(', ',map { $titles{$_}; } \
@{$staticdefaults{'resetemail'}})).'</li>'; +                        }
+                        if ($confighash{'passwords'}{'resetremove'}) {
+                            $resulttext .= '<li>'.&mt('Preamble to "Forgot Password" \
web form not shown').'</li>'; +                        } else {
+                            $resulttext .= '<li>'.&mt('Preamble to "Forgot Password" \
web form is shown').'</li>'; +                        }
+                        if ($confighash{'passwords'}{'resetcustom'}) {
+                            my $customlink = \
&Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'}, +             \
$titles{custom},600,500); +                            $resulttext .= \
'<li>'.&mt('Preamble to "Forgot Password" form includes [_1]',$customlink).'</li>'; + \
} else { +                            $resulttext .= '<li>'.&mt('No custom text \
included in preamble to "Forgot Password" form').'</li>'; +                        }
+                    } elsif ($key eq 'intauth') {
+                        foreach my $item ('cost','switch','check') {
+                            my $value = $save_defaults{$key.'_'.$item};
+                            if ($item eq 'switch') {
+                                my %optiondesc = &Apache::lonlocal::texthash (
+                                                     0 => 'No',
+                                                     1 => 'Yes',
+                                                     2 => 'Yes, and copy existing \
passwd file to passwd.bak file', +                                                 );
+                                if ($value =~ /^(0|1|2)$/) {
+                                    $value = $optiondesc{$value};
+                                } else {
+                                    $value = &mt('none -- defaults to No');
+                                }
+                            } elsif ($item eq 'check') {
+                                my %optiondesc = &Apache::lonlocal::texthash (
+                                                     0 => 'No',
+                                                     1 => 'Yes, allow login then \
update passwd file using default cost (if higher)', +                                 \
2 => 'Yes, disallow login if stored cost is less than domain default', +              \
); +                                if ($value =~ /^(0|1|2)$/) {
+                                    $value = $optiondesc{$value};
+                                } else {
+                                    $value = &mt('none -- defaults to No');
+                                }
+                            }
+                            $resulttext .= '<li>'.&mt('[_1] set to \
"[_2]"',$titles{$key.'_'.$item},$value).'</li>'; +                        }
+                    } elsif ($key eq 'rules') {
+                        foreach my $rule ('min','max','expire') {
+                            if ($confighash{'passwords'}{$rule} eq '') {
+                                $resulttext .= '<li>'.&mt('[_1] not \
set.',$titles{$rule}); +                                if ($rule eq 'min') {
+                                   $resulttext .= ' '.&mt('Default of 7 will be \
used'); +                                }
+                                $resulttext .= '</li>';
+                            } else {
+                                $resulttext .= '<li>'.&mt('[_1] set to \
[_2]',$titles{$rule},$confighash{'passwords'}{$rule}).'</li>'; +                      \
} +                        }
+                    } elsif ($key eq 'crsownerchg') {
+                        if ($confighash{'passwords'}{'crsownerchg'}) {
+                            $resulttext .= '<li>'.&mt('Course owner may change \
student passwords.').'</li>'; +                        } else {
+                            $resulttext .= '<li>'.&mt('Course owner may not change \
student passwords.'); +                        }
+                    }
+                    $resulttext .= '</ul></li>';
+                }
+            }
+            $resulttext .= '</ul>';
+        } else {
+            $resulttext = &mt('No changes made to password settings');
+        }
+        if ($updatedefaults) {
+            my $cachetime = 24*60*60;
+            &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime);
 +            if (ref($lastactref) eq 'HASH') {
+                $lastactref->{'domdefaults'} = 1;
+            }
+        }
+    } else {
+        $resulttext = '<span class="LC_error">'.
+            &mt('An error occurred: [_1]',$putresult).'</span>';
+    }
+    if ($errors) {
+        $resulttext .= '<p>'.&mt('The following errors occurred: ').'<ul>'.
+                       $errors.'</ul></p>';
+    }
+    return $resulttext;
+}
+
 sub modify_usercreation {
     my ($dom,%domconfig) = @_;
     my ($resulttext,%curr_usercreation,%changes,%authallowed,%cancreate,%save_usercreate);
 @@ -14881,7 +15677,7 @@
     my ($resulttext,$mailmsgtxt,%newvalues,%changes,@errors);
     my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1);
     my @items = ('auth_def','auth_arg_def','lang_def','timezone_def','datelocale_def',
                
-                 'portal_def','intauth_cost','intauth_check','intauth_switch');
+                 'portal_def');
     my @authtypes = ('internal','krb4','krb5','localauth','lti');
     foreach my $item (@items) {
         $newvalues{$item} = $env{'form.'.$item};
@@ -14923,24 +15719,6 @@
                     push(@errors,$item);
                 }
             }
-        } elsif ($item eq 'intauth_cost') {
-            if ($newvalues{$item} ne '') {
-                if ($newvalues{$item} =~ /\D/) {
-                    push(@errors,$item);
-                }
-            }
-        } elsif ($item eq 'intauth_check') {
-            if ($newvalues{$item} ne '') {
-                unless ($newvalues{$item} =~ /^(0|1|2)$/) {
-                    push(@errors,$item);
-                }
-            }
-        } elsif ($item eq 'intauth_switch') {
-            if ($newvalues{$item} ne '') {
-                unless ($newvalues{$item} =~ /^(0|1|2)$/) {
-                    push(@errors,$item);
-                }
-            }
         }
         if (grep(/^\Q$item\E$/,@errors)) {
             $newvalues{$item} = $domdefaults{$item};
@@ -14949,6 +15727,18 @@
         }
         $domdefaults{$item} = $newvalues{$item};
     }
+    my %staticdefaults = (
+                           'intauth_cost'   => 10,
+                           'intauth_check'  => 0,
+                           'intauth_switch' => 0,
+                         );
+    foreach my $item ('intauth_cost','intauth_check','intauth_switch') {
+        if (exists($domdefaults{$item})) {
+            $newvalues{$item} = $domdefaults{$item};
+        } else {
+            $newvalues{$item} = $staticdefaults{$item};
+        }
+    }
     my %defaults_hash = (
                          defaults => \%newvalues,
                         );
@@ -15077,28 +15867,6 @@
                                           lti        => 'lti',
                         );
                         $value = $authnames{$shortauth{$value}};
-                    } elsif ($item eq 'intauth_switch') {
-                        my %optiondesc = &Apache::lonlocal::texthash (
-                                            0 => 'No',
-                                            1 => 'Yes',
-                                            2 => 'Yes, and copy existing passwd file \
                to passwd.bak file',
-                                         );
-                        if ($value =~ /^(0|1|2)$/) {
-                            $value = $optiondesc{$value};
-                        } else {
-                            $value = &mt('none -- defaults to No');
-                        }
-                    } elsif ($item eq 'intauth_check') {
-                        my %optiondesc = &Apache::lonlocal::texthash (
-                                             0 => 'No',
-                                             1 => 'Yes, allow login then update \
                passwd file using default cost (if higher)',
-                                             2 => 'Yes, disallow login if stored \
                cost is less than domain default',
-                                         );
-                        if ($value =~ /^(0|1|2)$/) {
-                            $value = $optiondesc{$value};
-                        } else {
-                            $value = &mt('none -- defaults to No');
-                        }
                     }
                     $resulttext .= '<li>'.&mt('[_1] set to \
"[_2]"',$title->{$item},$value).'</li>';  $mailmsgtext .= "$title->{$item} set to \
$value\n";  



_______________________________________________
LON-CAPA-cvs mailing list
LON-CAPA-cvs@mail.lon-capa.org
http://mail.lon-capa.org/mailman/listinfo/lon-capa-cvs


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic