[LON-CAPA-cvs] cvs: loncom / Lond.pm /interface domainprefs.pm lonconfigsettings.pm /lonnet/perl lonnet.pm

raeburn raeburn at source.lon-capa.org
Sun Feb 13 21:48:53 EST 2022


raeburn		Mon Feb 14 02:48:53 2022 EDT

  Modified files:              
    /loncom/interface	domainprefs.pm lonconfigsettings.pm 
    /loncom	Lond.pm 
    /loncom/lonnet/perl	lonnet.pm 
  Log:
  - Bug 6907
    - Rename "LTI Provider" domain config item.
    - Add three additional sections: "Encryption of shared secrets",
      "Rules for shared secrets" and Link Protectors (domain). 
    - Keys used in domain for a particular library server may only be set in
      a session on that server (and use Lond.pm and not lonc/lond).
    - Min and max length and character requirements can be set for secrets used
      for LTI-based link protection for deep-links.
  
  
-------------- next part --------------
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.404 loncom/interface/domainprefs.pm:1.405
--- loncom/interface/domainprefs.pm:1.404	Sun Feb  6 21:36:59 2022
+++ loncom/interface/domainprefs.pm	Mon Feb 14 02:48:46 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.404 2022/02/06 21:36:59 raeburn Exp $
+# $Id: domainprefs.pm,v 1.405 2022/02/14 02:48:46 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -220,10 +220,10 @@
                 'serverstatuses','requestcourses','helpsettings',
                 'coursedefaults','usersessions','loadbalancing',
                 'requestauthor','selfenrollment','inststatus',
-                'ltitools','ssl','trust','lti','privacy','passwords',
+                'ltitools','ssl','trust','lti','ltisec','privacy','passwords',
                 'proctoring','wafproxy','ipaccess'],$dom);
     my %encconfig =
-        &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom,undef,1);
+        &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring','linkprot'],$dom,undef,1);
     if (ref($domconfig{'ltitools'}) eq 'HASH') {
         if (ref($encconfig{'ltitools'}) eq 'HASH') {
             foreach my $id (keys(%{$domconfig{'ltitools'}})) {
@@ -248,6 +248,20 @@
             }
         }
     }
+    if (ref($domconfig{'ltisec'}) eq 'HASH') {
+        if (ref($domconfig{'ltisec'}{'prot'}) eq 'HASH') {
+            if (ref($encconfig{'linkprot'}) eq 'HASH') {
+                foreach my $id (keys(%{$domconfig{'ltisec'}{'prot'}})) {
+                    if ((ref($domconfig{'ltisec'}{'prot'}{$id}) eq 'HASH') &&
+                        (ref($encconfig{'linkprot'}{$id}) eq 'HASH')) {
+                        foreach my $item ('key','secret') {
+                            $domconfig{'ltisec'}{'prot'}{$id}{$item} = $encconfig{'linkprot'}{$id}{$item};
+                        }
+                    }
+                }
+            }
+        }
+    }
     if (ref($domconfig{'proctoring'}) eq 'HASH') {
         if (ref($encconfig{'proctoring'}) eq 'HASH') {
             foreach my $provider (keys(%{$domconfig{'proctoring'}})) {
@@ -617,10 +631,16 @@
                   modify => \&modify_trust,
                  },
           'lti' =>
-                 {text => 'LTI Provider',
+                 {text => 'LTI Link Protection and LTI Consumers',
                   help => 'Domain_Configuration_LTI_Provider',
-                  header => [{col1 => 'Setting',
-                              col2 => 'Value',}],
+                  header => [{col1 => 'Encryption of shared secrets',
+                              col2 => 'Settings'},
+                             {col1 => 'Rules for shared secrets', 
+                              col2 => 'Settings'},
+                             {col1 => 'Link Protectors (domain)',
+                              col2 => 'Settings'},
+                             {col1 => 'Consumers',
+                              col2 => 'Settings'},],
                   print => \&print_lti,
                   modify => \&modify_lti,
                  },
@@ -639,7 +659,7 @@
                             header => [{col1 => 'Log-in Service',
                                         col2 => 'Server Setting',},
                                        {col1 => 'Log-in Page Items',
-                                        col2 => ''},
+                                        col2 => 'Settings'},
                                        {col1 => 'Log-in Help',
                                         col2 => 'Value'},
                                        {col1 => 'Custom HTML in document head',
@@ -850,7 +870,7 @@
     } elsif ($action eq 'defaults') {
         $output = &defaults_javascript($settings); 
     } elsif ($action eq 'passwords') {
-        $output = &passwords_javascript();
+        $output = &passwords_javascript($action);
     } elsif ($action eq 'helpsettings') {
         my (%privs,%levelscurrent);
         my %full=();
@@ -870,7 +890,8 @@
     } elsif ($action eq 'ltitools') {
         $output .= &ltitools_javascript($settings);
     } elsif ($action eq 'lti') {
-        $output .= &lti_javascript($settings);
+        $output .= &passwords_javascript('secrets')."\n".
+                   &lti_javascript($dom,$settings);
     } elsif ($action eq 'proctoring') {
         $output .= &proctoring_javascript($settings);
     } elsif ($action eq 'wafproxy') {
@@ -924,7 +945,7 @@
             ($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') ||
             ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'ssl') ||
             ($action eq 'directorysrch') || ($action eq 'trust') || ($action eq 'helpsettings') ||
-            ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) {
+            ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy') || ($action eq 'lti')) {
             $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal);
         } elsif ($action eq 'passwords') {
             $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal);
@@ -960,7 +981,7 @@
             ($action eq 'selfcreation') || ($action eq 'selfenrollment') ||
             ($action eq 'usersessions') || ($action eq 'coursecategories') || 
             ($action eq 'trust') || ($action eq 'contacts') ||
-            ($action eq 'privacy') || ($action eq 'passwords')) {
+            ($action eq 'privacy') || ($action eq 'passwords') || ($action eq 'lti')) {
             if ($action eq 'coursecategories') {
                 $output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal);
                 $colspan = ' colspan="2"';
@@ -1013,7 +1034,8 @@
              </tr>'."\n";
                 if ($action eq 'coursecategories') {
                     $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal);
-                } elsif (($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'passwords')) {
+                } elsif (($action eq 'contacts') || ($action eq 'privacy') || 
+                         ($action eq 'passwords') || ($action eq 'lti')) {
                     if ($action eq 'passwords') {
                         $output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal);
                     } else {
@@ -1250,8 +1272,8 @@
             $output .= &print_quotas($dom,$settings,\$rowtotal,$action);
         } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || 
                  ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || 
-                 ($action eq 'ltitools') || ($action eq 'lti') ||
-                 ($action eq 'proctoring') || ($action eq 'ipaccess')) {
+                 ($action eq 'ltitools') || ($action eq 'proctoring') ||
+                 ($action eq 'ipaccess')) {
             $output .= $item->{'print'}->($dom,$settings,\$rowtotal);
         }
     }
@@ -3386,8 +3408,8 @@
 
 
 sub lti_javascript {
-    my ($settings) = @_;
-    my $togglejs = &lti_toggle_js();
+    my ($dom,$settings) = @_;
+    my $togglejs = &lti_toggle_js($dom);
     unless (ref($settings) eq 'HASH') {
         return $togglejs;
     }
@@ -3460,12 +3482,17 @@
 }
 
 sub lti_toggle_js {
+    my ($dom) = @_;
     my %lcauthparmtext = &Apache::lonlocal::texthash (
                             localauth => 'Local auth argument',
                             krb       => 'Kerberos domain',
                          );
     my $crsincalert = &mt('"User\'s identity sent" needs to be set to "Yes" first,[_1] before setting "Course\'s identity sent" to "Yes"',"\n");
     &js_escape(\$crsincalert);
+    my %servers = &Apache::lonnet::get_servers($dom,'library');
+    my $primary = &Apache::lonnet::domain($dom,'primary');
+    my $course_servers = "'".join("','",keys(%servers))."'";
+
     return <<"ENDSCRIPT";
 <script type="text/javascript">
 // <![CDATA[
@@ -3668,6 +3695,125 @@
     }
     return;
 }
+
+function toggleLTIEncKey(form) {
+    var shownhosts = new Array();
+    var hiddenhosts = new Array();
+    var forcourse = new Array($course_servers);
+    var fromdomain = '$primary';
+    var crsradio = form.elements['ltisec_crslinkprot'];
+    if (crsradio.length) {
+        for (var i=0; i<crsradio.length; i++) {
+            if (crsradio[i].checked) {
+                if (crsradio[i].value == 1) {
+                    if (forcourse.length > 0) {
+                        for (var j=0; j<forcourse.length; j++) {
+                            if (!shownhosts.includes(forcourse[j])) {
+                                shownhosts.push(forcourse[j]);
+                            }
+                        }
+                    }
+                } else {
+                    if (forcourse.length > 0) {
+                        for (var j=0; j<forcourse.length; j++) {
+                            if (!hiddenhosts.includes(forcourse[j])) { 
+                                hiddenhosts.push(forcourse[j]);
+                            }
+                        }
+                    } 
+                }
+            }
+        }
+    }
+    var domradio = form.elements['ltisec_domlinkprot'];
+    if (domradio.length) {
+        for (var i=0; i<domradio.length; i++) {
+            if (domradio[i].checked) {
+                if (domradio[i].value == 1) {
+                    if (!shownhosts.includes(fromdomain)) { 
+                        shownhosts.push(fromdomain);
+                    }
+                } else {
+                    if (!hiddenhosts.includes(fromdomain)) {
+                        hiddenhosts.push(fromdomain);
+                    }
+                }
+            }
+        }
+    }
+    var consumersradio = form.elements['ltisec_consumers'];
+    if (consumersradio.length) {
+        for (var i=0; i<consumersradio.length; i++) {
+            if (consumersradio[i].checked) {
+                if (consumersradio[i].value == 1) {
+                    if (!shownhosts.includes(fromdomain)) {     
+                        shownhosts.push(fromdomain);
+                    }
+                } else {
+                    if (!hiddenhosts.includes(fromdomain)) {
+                        hiddenhosts.push(fromdomain);
+                    }
+                }
+            }
+        }
+    }
+    if (shownhosts.length > 0) {
+        for (var i=0; i<shownhosts.length; i++) {
+            if (document.getElementById('ltisec_info_'+shownhosts[i])) {
+                document.getElementById('ltisec_info_'+shownhosts[i]).style.display = 'block';                 
+            }
+        }
+        if (document.getElementById('ltisec_noprivkey')) {
+            document.getElementById('ltisec_noprivkey').style.display = 'none';
+        }
+    } else {
+        if (document.getElementById('ltisec_noprivkey')) {
+            document.getElementById('ltisec_noprivkey').style.display = 'inline-block';
+        }
+    }
+    if (hiddenhosts.length > 0) {
+        for (var i=0; i<hiddenhosts.length; i++) {
+            if (!shownhosts.includes(hiddenhosts[i])) {
+                if (document.getElementById('ltisec_info_'+hiddenhosts[i])) {
+                    document.getElementById('ltisec_info_'+hiddenhosts[i]).style.display = 'none';
+                }
+            }
+        }
+    }
+    return;
+}
+
+function togglePrivKey(form,hostid) {
+    var radioname = '';
+    var currdivid = '';
+    var newdivid = '';
+    if ((document.getElementById('ltisec_divcurrprivkey_'+hostid)) &&
+        (document.getElementById('ltisec_divchgprivkey_'+hostid))) {
+        currdivid = document.getElementById('ltisec_divcurrprivkey_'+hostid);
+        newdivid = document.getElementById('ltisec_divchgprivkey_'+hostid);
+        radioname = form.elements['ltisec_changeprivkey_'+hostid];
+        if (radioname) {
+            if (radioname.length > 0) {
+                var setvis;
+                for (var i=0; i<radioname.length; i++) {
+                    if (radioname[i].checked == true) {
+                        if (radioname[i].value == 1) {
+                            newdivid.style.display = 'inline-block';
+                            currdivid.style.display = 'none';
+                            setvis = 1;
+                        }
+                        break;
+                    }
+                }
+                if (!setvis) {
+                    newdivid.style.display = 'none';
+                    currdivid.style.display = 'inline-block';
+                }
+            }
+        }
+    }
+}
+
 // ]]>
 </script>
 
@@ -6308,144 +6454,286 @@
 }
 
 sub print_lti {
-    my ($dom,$settings,$rowtotal) = @_;
+    my ($position,$dom,$settings,$rowtotal) = @_;
     my $itemcount = 1;
-    my $maxnum = 0;
-    my $css_class;
-    my %ordered;
+    my ($datatable,$css_class);
+    my (%rules,%encrypt,%privkeys,%linkprot);
     if (ref($settings) eq 'HASH') {
-        foreach my $item (keys(%{$settings})) {
-            if (ref($settings->{$item}) eq 'HASH') {
-                my $num = $settings->{$item}{'order'};
-                if ($num eq '') {
-                    $num = scalar(keys(%{$settings}));
+        if ($position eq 'top') {
+            if (exists($settings->{'encrypt'})) {
+                if (ref($settings->{'encrypt'}) eq 'HASH') {
+                    foreach my $key (keys(%{$settings->{'encrypt'}})) {
+                        if ($key eq 'consumers') {
+                            $encrypt{'ltisec_'.$key} = $settings->{'encrypt'}{$key};
+                        } else {
+                            $encrypt{'ltisec_'.$key.'linkprot'} = $settings->{'encrypt'}{$key};
+                        }
+                    }
+                }
+            }
+            if (exists($settings->{'private'})) {
+                if (ref($settings->{'private'}) eq 'HASH') {
+                    if (ref($settings->{'private'}) eq 'HASH') {
+                        if (ref($settings->{'private'}{'keys'}) eq 'ARRAY') {
+                            map { $privkeys{$_} = 1; } (@{$settings->{'private'}{'keys'}});
+                        }
+                    }
+                }
+            }
+        } elsif ($position eq 'middle') {
+            if (exists($settings->{'rules'})) {
+                if (ref($settings->{'rules'}) eq 'HASH') {
+                    %rules = %{$settings->{'rules'}};
+                }
+            }
+        } elsif ($position eq 'lower') {
+            if (exists($settings->{'linkprot'})) {
+                if (ref($settings->{'linkprot'}) eq 'HASH') {
+                    %linkprot = %{$settings->{'linkprot'}};
+                }
+            }
+        } else {
+            foreach my $key ('encrypt','private','rules','linkprot') {
+                if (exists($settings->{$key})) {
+                    delete($settings->{$key});
                 }
-                $ordered{$num} = $item;
             }
         }
     }
-    my $maxnum = scalar(keys(%ordered));
-    my $datatable;
-    my %lt = &lti_names();
-    if (keys(%ordered)) {
-        my @items = sort { $a <=> $b } keys(%ordered);
-        for (my $i=0; $i<@items; $i++) {
-            $css_class = $itemcount%2?' class="LC_odd_row"':'';
-            my $item = $ordered{$items[$i]};
-            my ($key,$secret,$lifetime,$consumer,$requser,$crsinc,$current);
-            if (ref($settings->{$item}) eq 'HASH') {
-                $key = $settings->{$item}->{'key'};
-                $secret = $settings->{$item}->{'secret'};
-                $lifetime = $settings->{$item}->{'lifetime'};
-                $consumer = $settings->{$item}->{'consumer'};
-                $requser = $settings->{$item}->{'requser'};
-                $crsinc = $settings->{$item}->{'crsinc'};
-                $current = $settings->{$item};
-            }
-            my $onclickrequser = ' onclick="toggleLTI(this.form,'."'requser','$i'".');"';
-            my %checkedrequser = (
-                                   yes => ' checked="checked"',
-                                   no  => '',
-                                 );
-            if (!$requser) {
-                $checkedrequser{'no'} = $checkedrequser{'yes'};
-                $checkedrequser{'yes'} = '';
+    if ($position eq 'top') {
+        my @ids=&Apache::lonnet::current_machine_ids();
+        my %servers = &Apache::lonnet::get_servers($dom,'library');
+        my $primary = &Apache::lonnet::domain($dom,'primary');
+        my ($extra,$numshown);
+        foreach my $hostid (sort(keys(%servers))) {
+            my ($showextra,$divsty,$switch);
+            if ($hostid eq $primary) {
+                if (($encrypt{'ltisec_consumers'}) || ($encrypt{'ltisec_domlinkprot'})) {
+                    $showextra = 1;
+                }
+            }
+            if ($encrypt{'ltisec_crslinkprot'}) {
+                $showextra = 1;
+            }
+            unless (grep(/^\Q$hostid\E$/, at ids)) {
+                $switch = 1;
+            }
+            if ($showextra) {
+                $numshown ++;
+                $divsty = 'display:inline-block'; 
+            } else {
+                $divsty = 'display:none';
+            } 
+            $extra .= '<fieldset id="ltisec_info_'.$hostid.'" style="'.$divsty.'">'.
+                      '<legend>'.$hostid.'</legend>';
+            if ($switch) {
+                my $switchserver = '<a href="/adm/switchserver?otherserver='.$hostid.'&role='.
+                                   &HTML::Entities::encode($env{'request.role'},'\'<>"&').
+                                   '&destinationurl=/adm/domainprefs">'.&mt('Switch Server').'</a>';
+                if (exists($privkeys{$hostid})) {
+                    $extra .= '<div id="ltisec_divcurrprivkey_'.$hostid.'" style="display:inline-block" />'.
+                              '<span class="LC_nobreak">'.
+                              &mt('Encryption Key').': ['.&mt('not shown').'] '.(' 'x2).'</span></div>'.
+                              '<span class="LC_nobreak">'.&mt('Change?').
+                              '<label><input type="radio" value="0" name="ltisec_changeprivkey_'.$hostid.'" onclick="javascript:togglePrivKey(this.form,'."'$hostid'".');" checked="checked" />'.&mt('No').'</label>'.
+                              (' 'x2).
+                              '<label><input type="radio" value="1" name="ltisec_changeprivkey_'.$hostid.'" onclick="javascript:togglePrivKey(this.form,'."'$hostid'".');" />'.&mt('Yes').
+                              '</label>  </span><div id="ltisec_divchgprivkey_'.$hostid.'" style="display:none" />'.
+                              '<span class="LC_nobreak"> - '.&mt('submit from server ([_1]): [_2].',$hostid,$switchserver).
+                              '</span></div>';
+                } else {
+                    $extra .= '<span class="LC_nobreak">'.
+                              &mt('Key required').' - '.&mt('submit from server ([_1]): [_2].',$hostid,$switchserver).
+                              '</span>'."\n";
+                }
+            } elsif (exists($privkeys{$hostid})) {
+                $extra .= '<div id="ltisec_divcurrprivkey_'.$hostid.'" style="display:inline-block" /><span class="LC_nobreak">'.
+                          &mt('Encryption Key').': ['.&mt('not shown').'] '.(' 'x2).'</span></div>'.
+                          '<span class="LC_nobreak">'.&mt('Change?').
+                          '<label><input type="radio" value="0" name="ltisec_changeprivkey_'.$hostid.'" onclick="javascript:togglePrivKey(this.form,'."'$hostid'".');" checked="checked" />'.&mt('No').'</label>'.
+                          (' 'x2).
+                          '<label><input type="radio" value="1" name="ltisec_changeprivkey_'.$hostid.'" onclick="javascript:togglePrivKey(this.form,'."'$hostid'".');" />'.&mt('Yes').
+                          '</label>  </span><div id="ltisec_divchgprivkey_'.$hostid.'" style="display:none" />'.
+                          '<span class="LC_nobreak">'.&mt('New Key').':'.
+                          '<input type="password" size="20" name="ltisec_privkey_'.$hostid.'" value="" autocomplete="off" />'.
+                          '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.ltisec_privkey_'.$hostid.'.type='."'text'".' } else { this.form.ltisec_privkey_'.$hostid.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'.
+                          '</span></div>';
+            } else {
+                $extra .= '<span class="LC_nobreak">'.&mt('Encryption Key').':'.
+                          '<input type="password" size="20" name="ltisec_privkey_'.$hostid.'" value="" autocomplete="off" />'.
+                          '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.ltisec_privkey_'.$hostid.'.type='."'text'".' } else { this.form.ltisec_privkey_'.$hostid.'.type='."'password'".' }" />'.&mt('Visible input').'</label>';
+            }
+            $extra .= '</fieldset>';
+        }
+        my %choices = &Apache::lonlocal::texthash (
+                                                      ltisec_crslinkprot => 'Encrypt stored link protection secrets defined in courses',
+                                                      ltisec_domlinkprot => 'Encrypt stored link protection secrets defined in domain',
+                                                      ltisec_consumers   => 'Encrypt stored consumer secrets defined in domain',
+                                                  );
+        my @toggles = qw(ltisec_crslinkprot ltisec_domlinkprot ltisec_consumers);
+        my %defaultchecked = (
+                               'ltisec_crslinkprot' => 'off',
+                               'ltisec_domlinkprot' => 'off',
+                               'ltisec_consumers'   => 'off',
+                             );
+        my ($onclick,$itemcount);
+        $onclick = 'javascript:toggleLTIEncKey(this.form);';
+        ($datatable,$itemcount) = &radiobutton_prefs(\%encrypt,\@toggles,\%defaultchecked,
+                                                     \%choices,$itemcount,$onclick,'','left','no');
+
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        my $noprivkeysty = 'display:inline-block';
+        if ($numshown) {
+            $noprivkeysty = 'display:none'; 
+        }
+        $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'.&mt('Encryption Key(s)').'</td>'.
+                      '<td><div id="ltisec_noprivkey" style="'.$noprivkeysty.'" >'.
+                      '<span class="LC_nobreak">'.&mt('Not in use').'</span></div>'.
+                      $extra.
+                      '</td></tr>';
+        $itemcount ++;                
+        $$rowtotal += $itemcount;
+    } elsif ($position eq 'middle') {
+        $datatable = &password_rules('secrets',\$itemcount,\%rules);
+    } elsif ($position eq 'lower') {
+        $datatable .= '<tr><td>Not set yet</td><td>To be done</td></tr>';
+    } else {
+        my $maxnum = 0;
+        my %ordered;
+        if (ref($settings) eq 'HASH') {
+            foreach my $item (keys(%{$settings})) {
+                if (ref($settings->{$item}) eq 'HASH') {
+                    my $num = $settings->{$item}{'order'};
+                    if ($num eq '') {
+                        $num = scalar(keys(%{$settings}));
+                    }
+                    $ordered{$num} = $item;
+                }
             }
-            my $onclickcrsinc = ' onclick="toggleLTI(this.form,'."'crsinc','$i'".');"';
-            my %checkedcrsinc = (
+        }
+        $maxnum = scalar(keys(%ordered));
+        my %lt = &lti_names();
+        if (keys(%ordered)) {
+            my @items = sort { $a <=> $b } keys(%ordered);
+            for (my $i=0; $i<@items; $i++) {
+                $css_class = $itemcount%2?' class="LC_odd_row"':'';
+                my $item = $ordered{$items[$i]};
+                my ($key,$secret,$lifetime,$consumer,$requser,$crsinc,$current);
+                if (ref($settings->{$item}) eq 'HASH') {
+                    $key = $settings->{$item}->{'key'};
+                    $secret = $settings->{$item}->{'secret'};
+                    $lifetime = $settings->{$item}->{'lifetime'};
+                    $consumer = $settings->{$item}->{'consumer'};
+                    $requser = $settings->{$item}->{'requser'};
+                    $crsinc = $settings->{$item}->{'crsinc'};
+                    $current = $settings->{$item};
+                }
+                my $onclickrequser = ' onclick="toggleLTI(this.form,'."'requser','$i'".');"';
+                my %checkedrequser = (
+                                       yes => ' checked="checked"',
+                                       no  => '',
+                                     );
+                if (!$requser) {
+                    $checkedrequser{'no'} = $checkedrequser{'yes'};
+                    $checkedrequser{'yes'} = '';
+                }
+                my $onclickcrsinc = ' onclick="toggleLTI(this.form,'."'crsinc','$i'".');"';
+                my %checkedcrsinc = (
                                       yes => ' checked="checked"',
                                       no  => '',
-                                   );
-            if (!$crsinc) {
-                $checkedcrsinc{'no'} = $checkedcrsinc{'yes'};
-                $checkedcrsinc{'yes'} = '';
-            }
-            my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_".$item."'".');"';
-            $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'
-                         .'<select name="lti_pos_'.$item.'"'.$chgstr.'>';
-            for (my $k=0; $k<=$maxnum; $k++) {
-                my $vpos = $k+1;
-                my $selstr;
-                if ($k == $i) {
-                    $selstr = ' selected="selected" ';
+                                    );
+                if (!$crsinc) {
+                    $checkedcrsinc{'no'} = $checkedcrsinc{'yes'};
+                    $checkedcrsinc{'yes'} = '';
+                }
+                my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_".$item."'".');"';
+                $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'
+                             .'<select name="lti_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 .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
+                $datatable .= '</select>'.(' 'x2).
+                    '<label><input type="checkbox" name="lti_del" value="'.$item.'" />'.
+                    &mt('Delete?').'</label></span></td>'.
+                    '<td colspan="2">'.
+                    '<fieldset><legend>'.&mt('Required settings').'</legend>'.
+                    '<span class="LC_nobreak">'.$lt{'consumer'}.
+                    ':<input type="text" size="15" name="lti_consumer_'.$i.'" value="'.$consumer.'" /></span> '.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_'.$i.'">'.
+                    '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="lti_lifetime_'.$i.'"'.
+                    'value="'.$lifetime.'" size="3" /></span>'.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'requser'}.':'.
+                    '<label><input type="radio" name="lti_requser_'.$i.'" value="1"'.$onclickrequser.$checkedrequser{yes}.' />'.&mt('Yes').'</label> '."\n".
+                    '<label><input type="radio" name="lti_requser_'.$i.'" value="0"'.$onclickrequser.$checkedrequser{no}.' />'.&mt('No').'</label></span>'."\n".
+                    '<br /><br />'.
+                    '<span class="LC_nobreak">'.$lt{'crsinc'}.':'.
+                    '<label><input type="radio" name="lti_crsinc_'.$i.'" value="1"'.$onclickcrsinc.$checkedcrsinc{yes}.' />'.&mt('Yes').'</label> '."\n".
+                    '<label><input type="radio" name="lti_crsinc_'.$i.'" value="0"'.$onclickcrsinc.$checkedcrsinc{no}.' />'.&mt('No').'</label></span>'."\n".
+                    (' 'x4).
+                    '<span class="LC_nobreak">'.$lt{'key'}.
+                    ':<input type="text" size="25" name="lti_key_'.$i.'" value="'.$key.'" /></span> '.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'secret'}.':'.
+                    '<input type="password" size="20" name="lti_secret_'.$i.'" value="'.$secret.'" />'.
+                    '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_'.$i.'.type='."'text'".' } else { this.form.lti_secret_'.$i.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'.
+                    '<input type="hidden" name="lti_id_'.$i.'" value="'.$item.'" /></span>'.
+                    '</fieldset>'.&lti_options($i,$current,$itemcount,%lt).'</td></tr>';
+                $itemcount ++;
             }
-            $datatable .= '</select>'.(' 'x2).
-                '<label><input type="checkbox" name="lti_del" value="'.$item.'" />'.
-                &mt('Delete?').'</label></span></td>'.
-                '<td colspan="2">'.
-                '<fieldset><legend>'.&mt('Required settings').'</legend>'.
-                '<span class="LC_nobreak">'.$lt{'consumer'}.
-                ':<input type="text" size="15" name="lti_consumer_'.$i.'" value="'.$consumer.'" /></span> '.
-                (' 'x2).
-                '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_'.$i.'">'.
-                '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '.
-                (' 'x2).
-                '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="lti_lifetime_'.$i.'"'.
-                'value="'.$lifetime.'" size="3" /></span>'.
-                (' 'x2).
-                 '<span class="LC_nobreak">'.$lt{'requser'}.':'.
-                 '<label><input type="radio" name="lti_requser_'.$i.'" value="1"'.$onclickrequser.$checkedrequser{yes}.' />'.&mt('Yes').'</label> '."\n".
-                 '<label><input type="radio" name="lti_requser_'.$i.'" value="0"'.$onclickrequser.$checkedrequser{no}.' />'.&mt('No').'</label></span>'."\n".
-                '<br /><br />'.
-                '<span class="LC_nobreak">'.$lt{'crsinc'}.':'.
-                '<label><input type="radio" name="lti_crsinc_'.$i.'" value="1"'.$onclickcrsinc.$checkedcrsinc{yes}.' />'.&mt('Yes').'</label> '."\n".
-                '<label><input type="radio" name="lti_crsinc_'.$i.'" value="0"'.$onclickcrsinc.$checkedcrsinc{no}.' />'.&mt('No').'</label></span>'."\n".
-                (' 'x4).
-                '<span class="LC_nobreak">'.$lt{'key'}.
-                ':<input type="text" size="25" name="lti_key_'.$i.'" value="'.$key.'" /></span> '.
-                (' 'x2).
-                '<span class="LC_nobreak">'.$lt{'secret'}.':'.
-                '<input type="password" size="20" name="lti_secret_'.$i.'" value="'.$secret.'" />'.
-                '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_'.$i.'.type='."'text'".' } else { this.form.lti_secret_'.$i.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'.
-                '<input type="hidden" name="lti_id_'.$i.'" value="'.$item.'" /></span>'.
-                '</fieldset>'.&lti_options($i,$current,$itemcount,%lt).'</td></tr>';
-            $itemcount ++;
         }
-    }
-    $css_class = $itemcount%2?' class="LC_odd_row"':'';
-    my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_add'".');"';
-    $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n".
-                  '<input type="hidden" name="lti_maxnum" value="'.$maxnum.'" />'."\n".
-                  '<select name="lti_pos_add"'.$chgstr.'>';
-    for (my $k=0; $k<$maxnum+1; $k++) {
-        my $vpos = $k+1;
-        my $selstr;
-        if ($k == $maxnum) {
-            $selstr = ' selected="selected" ';
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_add'".');"';
+        $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n".
+                      '<input type="hidden" name="lti_maxnum" value="'.$maxnum.'" />'."\n".
+                      '<select name="lti_pos_add"'.$chgstr.'>';
+        for (my $k=0; $k<$maxnum+1; $k++) {
+            my $vpos = $k+1;
+            my $selstr;
+            if ($k == $maxnum) {
+                $selstr = ' selected="selected" ';
+            }
+            $datatable .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
         }
-        $datatable .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
+        $datatable .= '</select> '."\n".
+                      '<input type="checkbox" name="lti_add" value="1" />'.&mt('Add').'</span></td>'."\n".
+                      '<td colspan="2">'.
+                      '<fieldset><legend>'.&mt('Required settings').'</legend>'.
+                      '<span class="LC_nobreak">'.$lt{'consumer'}.
+                      ':<input type="text" size="15" name="lti_consumer_add" value="" /></span> '."\n".
+                      (' 'x2).
+                      '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_add">'.
+                      '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
+                      (' 'x2).
+                      '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="3" name="lti_lifetime_add" value="300" /></span> '."\n".
+                      (' 'x2).
+                      '<span class="LC_nobreak">'.$lt{'requser'}.':'.
+                      '<label><input type="radio" name="lti_requser_add" value="1" onclick="toggleLTI(this.form,'."'requser','add'".');" checked="checked" />'.&mt('Yes').'</label> '."\n".
+                      '<label><input type="radio" name="lti_requser_add" value="0" onclick="toggleLTI(this.form,'."'requser','add'".');" />'.&mt('No').'</label></span>'."\n".
+                      '<br /><br />'.
+                      '<span class="LC_nobreak">'.$lt{'crsinc'}.':'.
+                      '<label><input type="radio" name="lti_crsinc_add" value="1" onclick="toggleLTI(this.form,'."'crsinc','add'".');" checked="checked" />'.&mt('Yes').'</label> '."\n".
+                      '<label><input type="radio" name="lti_crsinc_add" value="0" onclick="toggleLTI(this.form,'."'crsinc','add'".');" />'.&mt('No').'</label></span>'."\n".
+                      (' 'x4).
+                      '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="lti_key_add" value="" /></span> '."\n".
+                      (' 'x2).
+                      '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="lti_secret_add" value="" />'.
+                      '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_add.type='."'text'".' } else { this.form.lti_secret_add.type='."'password'".' }" />'.&mt('Visible input').'</label></span> '."\n".
+                      '</fieldset>'.&lti_options('add',undef,$itemcount,%lt).
+                      '</td>'."\n".
+                      '</tr>'."\n";
+        $itemcount ++;
     }
-    $datatable .= '</select> '."\n".
-                  '<input type="checkbox" name="lti_add" value="1" />'.&mt('Add').'</span></td>'."\n".
-                  '<td colspan="2">'.
-                  '<fieldset><legend>'.&mt('Required settings').'</legend>'.
-                  '<span class="LC_nobreak">'.$lt{'consumer'}.
-                  ':<input type="text" size="15" name="lti_consumer_add" value="" /></span> '."\n".
-                  (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_add">'.
-                  '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
-                  (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="3" name="lti_lifetime_add" value="300" /></span> '."\n".
-                  (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'requser'}.':'.
-                  '<label><input type="radio" name="lti_requser_add" value="1" onclick="toggleLTI(this.form,'."'requser','add'".');" checked="checked" />'.&mt('Yes').'</label> '."\n".
-                  '<label><input type="radio" name="lti_requser_add" value="0" onclick="toggleLTI(this.form,'."'requser','add'".');" />'.&mt('No').'</label></span>'."\n".
-                  '<br /><br />'.
-                  '<span class="LC_nobreak">'.$lt{'crsinc'}.':'.
-                  '<label><input type="radio" name="lti_crsinc_add" value="1" onclick="toggleLTI(this.form,'."'crsinc','add'".');" checked="checked" />'.&mt('Yes').'</label> '."\n".
-                  '<label><input type="radio" name="lti_crsinc_add" value="0" onclick="toggleLTI(this.form,'."'crsinc','add'".');" />'.&mt('No').'</label></span>'."\n".
-                  (' 'x4).
-                  '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="lti_key_add" value="" /></span> '."\n".
-                  (' 'x2).
-                  '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="lti_secret_add" value="" />'.
-                  '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_add.type='."'text'".' } else { this.form.lti_secret_add.type='."'password'".' }" />'.&mt('Visible input').'</label></span> '."\n".
-                  '</fieldset>'.&lti_options('add',undef,$itemcount,%lt).
-                  '</td>'."\n".
-                  '</tr>'."\n";
-    $$rowtotal ++;
-    return $datatable;;
+    $$rowtotal += $itemcount;
+    return $datatable;
 }
 
 sub lti_names {
@@ -6847,6 +7135,22 @@
     );
 }
 
+sub check_switchserver {
+    my ($home) = @_;
+    my $switchserver;
+    if ($home ne '') {
+        my $allowed;
+        my @ids=&Apache::lonnet::current_machine_ids();
+        foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } }
+        if (!$allowed) {
+            $switchserver='<a href="/adm/switchserver?otherserver='.$home.'&role='.
+                          &HTML::Entities::encode($env{'request.role'},'\'<>"&').
+                          '&destinationurl=/adm/domainprefs">'.&mt('Switch Server').'</a>';
+        }
+    }
+    return $switchserver;
+}
+
 sub print_coursedefaults {
     my ($position,$dom,$settings,$rowtotal) = @_;
     my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked, at toggles);
@@ -6863,7 +7167,7 @@
         postsubmit           => 'Disable submit button/keypress following student submission',
         canclone             => "People who may clone a course (besides course's owner and coordinators)",
         mysqltables          => 'Lifetime (s) of "Temporary" MySQL tables (student performance data) on homeserver',
-        ltiauth              => 'Student username in LTI launch of deep-linked URL can be accepted without re-authentication', 
+        ltiauth              => 'Student username in LTI launch of deep-linked URL can be accepted without re-authentication',
     );
     my %staticdefaults = (
                            anonsurvey_threshold => 10,
@@ -6986,12 +7290,12 @@
         my ($currdefresponder,%defcredits,%curruploadquota,%deftimeout,%currmysql);
         my $currusecredits = 0;
         my $postsubmitclient = 1;
-        my $ltiauth = 0; 
+        my $ltiauth = 0;
         my @types = ('official','unofficial','community','textbook','placement');
         if (ref($settings) eq 'HASH') {
             if ($settings->{'ltiauth'}) {
                 $ltiauth = 1;
-            } 
+            }
             $currdefresponder = $settings->{'anonsurvey_threshold'};
             if (ref($settings->{'uploadquota'}) eq 'HASH') {
                 foreach my $type (keys(%{$settings->{'uploadquota'}})) {
@@ -7800,95 +8104,7 @@
             $itemcount ++;
         }
     } elsif ($position eq 'lower') {
-        my ($min,$max,%chars,$expire,$numsaved);
-        $min = $Apache::lonnet::passwdmin;
-        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};
-            }
-            if ($settings->{numsaved}) {
-                $numsaved = $settings->{numsaved};
-            }
-        }
-        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" '.
-                      'onblur="javascript:warnIntPass(this);" />'.
-                      '<span class="LC_fontsize_small"> '.&mt('(Enter an integer: 7 or larger)').'</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" '.
-                      'onblur="javascript:warnIntPass(this);" />'.
-                      '<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">'.
-                          ' </td>';
-        } elsif ($colsleft == 1) {
-            $datatable .= '<td class="LC_left_item"> </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" '.
-                      'onblur="javascript:warnIntPass(this);" />'.
-                      '<span class="LC_fontsize_small"> '.&mt('(Leave blank for no expiration)').'</span>'.
-                      '</span></td></tr>';
-        $itemcount ++;
-        $css_class = $itemcount%2?' class="LC_odd_row"':'';
-        $datatable .= '<tr'.$css_class.'><td>'.$titles{'numsaved'}.'</td>'.
-                      '<td class="LC_left_item"><span class="LC_nobreak">'.
-                      '<input type="text" name="passwords_numsaved" value="'.$numsaved.'" size="3" '.
-                      'onblur="javascript:warnIntPass(this);" />'. 
-                      '<span class="LC_fontsize_small"> '.&mt('(Leave blank to not save previous passwords)').'</span>'.
-                      '</span></td></tr>';
+        $datatable .= &password_rules('passwords',\$itemcount,$settings);
     } else {
         my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom);
         my %ownerchg = (
@@ -7948,6 +8164,129 @@
     return $datatable;
 }
 
+sub password_rules {
+    my ($prefix,$itemcountref,$settings) = @_;
+    my ($min,$max,%chars,$expire,$numsaved,$numinrow);
+    my %titles;
+    if ($prefix eq 'passwords') {
+        %titles = &Apache::lonlocal::texthash (
+            min            => 'Minimum password length',
+            max            => 'Maximum password length',
+            chars          => 'Required characters',
+        );
+    } elsif ($prefix eq 'secrets') {
+        %titles = &Apache::lonlocal::texthash (
+            min            => 'Minimum secret length',
+            max            => 'Maximum secret length',
+            chars          => 'Required characters',
+        );
+    }
+    $min = $Apache::lonnet::passwdmin;
+    my $datatable;
+    my $itemcount;
+    if (ref($itemcountref)) {
+        $itemcount = $$itemcountref;
+    }
+    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 ($prefix eq 'passwords') { 
+            if ($settings->{expire}) {
+                $expire = $settings->{expire};
+            }
+            if ($settings->{numsaved}) {
+                $numsaved = $settings->{numsaved};
+            }
+        }
+    }
+    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',
+                                               );
+    my $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="'.$prefix.'_min" value="'.$min.'" size="3" '.
+                  'onblur="javascript:warnInt'.$prefix.'(this);" />'.
+                  '<span class="LC_fontsize_small"> '.&mt('(Enter an integer: 7 or larger)').'</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="'.$prefix.'_max" value="'.$max.'" size="3" '.
+                  'onblur="javascript:warnInt'.$prefix.'(this);" />'.
+                  '<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="'.$prefix.'_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">'.
+                      ' </td>';
+    } elsif ($colsleft == 1) {
+        $datatable .= '<td class="LC_left_item"> </td>';
+    }
+    $datatable .='</table></td></tr>';
+    $itemcount ++;
+    if ($prefix eq 'passwords') {
+        $titles{'expire'} = &mt('Password expiration (days)');
+        $titles{'numsaved'} = &mt('Number of previous passwords to save and disallow reuse');
+        $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="'.$prefix.'_expire" value="'.$expire.'" size="4" '.
+                      'onblur="javascript:warnInt'.$prefix.'(this);" />'.
+                      '<span class="LC_fontsize_small"> '.&mt('(Leave blank for no expiration)').'</span>'.
+                      '</span></td></tr>';
+        $itemcount ++;
+        $css_class = $itemcount%2?' class="LC_odd_row"':'';
+        $datatable .= '<tr'.$css_class.'><td>'.$titles{'numsaved'}.'</td>'.
+                      '<td class="LC_left_item"><span class="LC_nobreak">'.
+                      '<input type="text" name="'.$prefix.'_numsaved" value="'.$numsaved.'" size="3" '.
+                      'onblur="javascript:warnInt'.$prefix.'(this);" />'.
+                      '<span class="LC_fontsize_small"> '.&mt('(Leave blank to not save previous passwords)').'</span>'.
+                      '</span></td></tr>';
+        $itemcount ++;
+    }
+    if (ref($itemcountref)) {
+        $$itemcountref += $itemcount;
+    }
+    return $datatable;
+}
+
 sub print_wafproxy {
     my ($position,$dom,$settings,$rowtotal) = @_;
     my $css_class;
@@ -11168,17 +11507,27 @@
 }
 
 sub passwords_javascript {
-    my %intalert = &Apache::lonlocal::texthash (
-        authcheck => '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.',
-        authcost => 'Warning: bcrypt encryption cost for internal authentication must be an integer.',
-        passmin => 'Warning: minimum password length must be a positive integer greater than 6.',
-        passmax => 'Warning: maximum password length must be a positive integer (or blank).',
-        passexp => 'Warning: days before password expiration must be a positive integer (or blank).',
-        passnum => 'Warning: number of previous passwords to save must be a positive integer (or blank).',
-    );
+    my ($prefix) = @_;
+    my %intalert;
+    if ($prefix eq 'passwords') {
+        %intalert = &Apache::lonlocal::texthash (
+            authcheck => '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.',
+            authcost => 'Warning: bcrypt encryption cost for internal authentication must be an integer.',
+            passmin => 'Warning: minimum password length must be a positive integer greater than 6.',
+            passmax => 'Warning: maximum password length must be a positive integer (or blank).',
+            passexp => 'Warning: days before password expiration must be a positive integer (or blank).',
+            passnum => 'Warning: number of previous passwords to save must be a positive integer (or blank).',
+        );
+    } elsif ($prefix eq 'secrets') {
+        %intalert = &Apache::lonlocal::texthash (
+            passmin => 'Warning: minimum secret length must be a positive integer greater than 6.',
+            passmax => 'Warning: maximum secret length must be a positive integer (or blank).',
+        );
+    }
     &js_escape(\%intalert);
     my $defmin = $Apache::lonnet::passwdmin;
-    my $intauthjs = <<"ENDSCRIPT";
+    my $intauthjs; 
+    if ($prefix eq 'passwords') { $intauthjs = <<"ENDSCRIPT";
 
 function warnIntAuth(field) {
     if (field.name == 'intauth_check') {
@@ -11198,11 +11547,17 @@
     return;
 }
 
-function warnIntPass(field) {
+ENDSCRIPT
+
+     }
+
+     $intauthjs .= <<"ENDSCRIPT";
+
+function warnInt$prefix(field) {
     field.value.replace(/^\s+/,'');
     field.value.replace(/\s+\$/,'');
     var regexdigit=/^\\d+\$/;
-    if (field.name == 'passwords_min') {
+    if (field.name == '$prefix\_min') {
         if (field.value == '') {
             alert('$intalert{passmin}');
             field.value = '$defmin';
@@ -11222,7 +11577,7 @@
             field.value = '';
         }
         if (field.value != '') {
-            if (field.name == 'passwords_expire') {
+            if (field.name == '$prefix\_expire') {
                 var regexpposnum=/^\\d+(|\\.\\d*)\$/; 
                 if (!regexpposnum.test(field.value)) {
                     alert('$intalert{passexp}');
@@ -11236,10 +11591,10 @@
                 }
             } else {
                 if (!regexdigit.test(field.value)) {
-                    if (field.name == 'passwords_max') {
+                    if (field.name == '$prefix\_max') {
                         alert('$intalert{passmax}');
                     } else {
-                        if (field.name == 'passwords_numsaved') {
+                        if (field.name == '$prefix\_numsaved') {
                             alert('$intalert{passnum}');
                         }
                     }
@@ -15473,6 +15828,73 @@
 
     my %menutitles = &ltimenu_titles();
 
+    my (%currltisec,%secchanges,%newltisec,%keyset,%newkeyset);
+    $newltisec{'private'}{'keys'} = [];
+    $newltisec{'encrypt'} = {};
+    $newltisec{'rules'} = {};
+    if (ref($domconfig{'ltisec'}) eq 'HASH') {
+        %currltisec = %{$domconfig{'ltisec'}};
+        if (ref($currltisec{'private'}) eq 'HASH') {
+            if (ref($currltisec{'private'}{'keys'}) eq 'ARRAY') {
+                $newltisec{'private'}{'keys'} = $currltisec{'private'}{'keys'};
+                map { $keyset{$_} = 1; } @{$currltisec{'private'}{'keys'}};
+            }
+        }
+    }
+    foreach my $item ('crs','dom','consumers') {
+        my $formelement;
+        if ($item eq 'consumers') { 
+            $formelement = 'form.ltisec_'.$item;
+        } else {
+            $formelement = 'form.ltisec_'.$item.'linkprot';
+        }
+        if ($env{$formelement}) {
+            $newltisec{'encrypt'}{$item} = 1;
+            if (ref($currltisec{'encrypt'}) eq 'HASH') {
+                unless ($currltisec{'encrypt'}{$item}) {
+                    $secchanges{'encrypt'} = 1;
+                }
+            } else {
+                $secchanges{'encrypt'} = 1;
+            }
+        } elsif (ref($currltisec{'encrypt'}) eq 'HASH') {
+            if ($currltisec{'encrypt'}{$item}) {
+                $secchanges{'encrypt'} = 1;
+            }
+        }
+    }
+    unless (exists($currltisec{'rules'})) {
+        $currltisec{'rules'} = {};
+    }
+    &password_rule_changes('secrets',$newltisec{'rules'},$currltisec{'rules'},\%secchanges);
+
+    my @ids=&Apache::lonnet::current_machine_ids();
+    my %servers = &Apache::lonnet::get_servers($dom,'library');
+   
+    foreach my $hostid (keys(%servers)) {
+        if (($hostid ne '') && (grep(/^\Q$hostid\E$/, at ids))) {
+            my $newkey;
+            my $keyitem = 'form.ltisec_privkey_'.$hostid;
+            if (exists($env{$keyitem})) {
+                $env{$keyitem} =~ s/(`)/'/g;
+                if ($keyset{$hostid}) {
+                    if ($env{'form.ltisec_changeprivkey_'.$hostid}) {
+                        if ($env{$keyitem} ne '') {
+                            $secchanges{'private'} = 1;
+                            $newkeyset{$hostid} = $env{$keyitem};
+                        }
+                    }
+                } elsif ($env{$keyitem} ne '') {
+                    unless (grep(/^\Q$hostid\E$/,@{$newltisec{'private'}{'keys'}})) { 
+                        push(@{$newltisec{'private'}{'keys'}},$hostid);
+                    }
+                    $secchanges{'private'} = 1;
+                    $newkeyset{$hostid} = $env{$keyitem};
+                }
+            }
+        }
+    }
+
     my (@items,%deletions,%itemids);
     if ($env{'form.lti_add'}) {
         my $consumer = $env{'form.lti_consumer_add'};
@@ -15538,14 +15960,14 @@
         }
         if ($confhash{$itemid}{'requser'}) {
             if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') {
-                $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; 
+                $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid';
             } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') {
                 $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary';
             } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') {
                 my $mapuser = $env{'form.lti_customuser_'.$idx};
                 $mapuser =~ s/(`)/'/g;
-                $mapuser =~ s/^\s+|\s+$//g; 
-                $confhash{$itemid}{'mapuser'} = $mapuser; 
+                $mapuser =~ s/^\s+|\s+$//g;
+                $confhash{$itemid}{'mapuser'} = $mapuser;
             }
             my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx);
             my @makeuser;
@@ -15776,15 +16198,109 @@
         }
     }
     my %ltihash = (
-                          $action => { %confhash }
-                       );
-    my $putresult = &Apache::lonnet::put_dom('configuration',\%ltihash,
-                                             $dom);
+                      $action => { %confhash }
+                  );
+    if (keys(%secchanges)) {
+        $ltihash{'ltisec'} = \%newltisec;
+    }
+    my $putresult = &Apache::lonnet::put_dom('configuration',\%ltihash,$dom);
     if ($putresult eq 'ok') {
+        my %keystore;
+        if (keys(%secchanges)) {
+            if ($secchanges{'private'}) {
+                my $who = &escape($env{'user.name'}.':'.$env{'user.domain'});
+                foreach my $hostid (keys(%newkeyset)) {
+                    my $storehash = {
+                                       key => $newkeyset{$hostid},
+                                       who => $env{'user.name'}.':'.$env{'user.domain'},
+                                    };
+                    $keystore{$hostid} = &Apache::lonnet::store_dom($storehash,'lti','private',
+                                                                    $dom,$hostid);
+                }
+            }
+        }
         my %ltienchash = (
                              $action => { %encconfig }
                          );
         &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom,undef,1);
+        if ((keys(%changes) == 0) && (keys(%secchanges) == 0)) {
+            return &mt('No changes made.');
+        }
+        $resulttext = &mt('Changes made:').'<ul>';
+        if (keys(%secchanges) > 0) {
+            foreach my $item (keys(%secchanges)) {
+                if ($item eq 'encrypt') {
+                    my %encrypted = (
+                              crs  => {
+                                        on => &mt('Encryption of stored link protection secrets defined in courses enabled'),
+                                        off => &mt('Encryption of stored link protection secrets defined in courses disabled'),
+                                      },
+                              dom => {
+                                       on => &mt('Encryption of stored link protection secrets defined in domain enabled'),
+                                       off => &mt('Encryption of stored link protection secrets defined in domain disabled'),
+                                     },
+                              consumers => {
+                                             on => &mt('Encryption of stored consumer secrets defined in domain enabled'),
+                                             off => &mt('Encryption of stored consumer secrets defined in domain disabled'),
+                                           },
+                            );
+                    foreach my $type ('crs','dom','consumers') {
+                        my $shown = $encrypted{$type}{'off'};
+                        if (ref($newltisec{$item}) eq 'HASH') {
+                            if ($newltisec{$item}{$type}) {
+                                $shown = $encrypted{$type}{'on'}; 
+                            }
+                        }
+                        $resulttext .= '<li>'.$shown.'</li>';
+                    } 
+                } elsif ($item eq 'rules') {
+                     my %titles = &Apache::lonlocal::texthash(
+                                      min   => 'Minimum password length',
+                                      max   => 'Maximum password length',
+                                      chars => 'Required characters',
+                     );
+                     foreach my $rule ('min','max') {
+                         if ($newltisec{rules}{$rule} eq '') {
+                             if ($rule eq 'min') {
+                                 $resulttext .= '<li>'.&mt('[_1] not set.',$titles{$rule});
+                                                ' '.&mt('Default of [_1] will be used',
+                                                            $Apache::lonnet::passwdmin).'</li>';
+                             } else {
+                                 $resulttext .= '<li>'.&mt('[_1] set to none',$titles{$rule}).'</li>';
+                             }
+                         } else {
+                             $resulttext .= '<li>'.&mt('[_1] set to [_2]',$titles{$rule},$newltisec{rules}{$rule}).'</li>';
+                         }
+                     }
+                     if (ref($newltisec{'rules'}{'chars'}) eq 'ARRAY') {
+                         if (@{$newltisec{'rules'}{'chars'}} > 0) {
+                             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',
+                                             );
+                             my $needed = '<ul><li>'.
+                                          join('</li><li>',map {$rulenames{$_} } @{$newltisec{'rules'}{'chars'}}).
+                                          '</li></ul>';
+                             $resulttext .= '<li>'.&mt('[_1] set to: [_2]',$titles{'chars'},$needed).'</li>';
+                         } else {
+                             $resulttext .= '<li>'.&mt('[_1] set to none',$titles{'chars'}).'</li>';
+                         }
+                     } else {
+                         $resulttext .= '<li>'.&mt('[_1] set to none',$titles{'chars'}).'</li>';
+                     }
+                } elsif ($item eq 'private') {
+                    if (keys(%newkeyset)) {
+                        foreach my $hostid (sort(keys(%newkeyset))) {
+                            if ($keystore{$hostid} eq 'ok') {
+                                $resulttext .= '<li>'.&mt('Encryption key for storage of shared secrets saved for [_1]',$hostid).'</li>';
+                            }
+                        }
+                    }
+                }
+            }
+        }
         if (keys(%changes) > 0) {
             my $cachetime = 24*60*60;
             my %ltiall = %confhash;
@@ -15799,7 +16315,6 @@
             if (ref($lastactref) eq 'HASH') {
                 $lastactref->{'lti'} = 1;
             }
-            $resulttext = &mt('Changes made:').'<ul>';
             my %bynum;
             foreach my $itemid (sort(keys(%changes))) {
                 my $position = $confhash{$itemid}{'order'};
@@ -15976,10 +16491,8 @@
                     $resulttext .= '</ul></li>';
                 }
             }
-            $resulttext .= '</ul>';
-        } else {
-            $resulttext = &mt('No changes made.');
         }
+        $resulttext .= '</ul>';
     } else {
         $errors .= '<li><span class="LC_error">'.&mt('Failed to save changes').'</span></li>';
     }
@@ -17705,61 +18218,7 @@
             $updatedefaults = 1;
         }
     }
-    foreach my $rule ('min','max','expire','numsaved') {
-        $env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g;
-        my $ruleok;
-        if ($rule eq 'expire') {
-            if (($env{'form.passwords_'.$rule} =~ /^\d+(|\.\d*)$/) &&
-                ($env{'form.passwords_'.$rule} ne '0')) {
-                $ruleok = 1;
-            }
-        } elsif ($rule eq 'min') {
-            if ($env{'form.passwords_'.$rule} =~ /^\d+$/) {
-                if ($env{'form.passwords_'.$rule} >= $Apache::lonnet::passwdmin) {
-                    $ruleok = 1;
-                }
-            }
-        } elsif (($env{'form.passwords_'.$rule} =~ /^\d+$/) &&
-                 ($env{'form.passwords_'.$rule} ne '0')) {
-            $ruleok = 1;
-        }
-        if ($ruleok) {
-            $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;
-                }
-            } else {
-                $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;
-            }
-        }
-    }
+    &password_rule_changes('passwords',\%newvalues,\%current,\%changes);
     my %crsownerchg = (
                         by => [],
                         for => [],
@@ -18019,6 +18478,76 @@
     return $resulttext;
 }
 
+sub password_rule_changes {
+    my ($prefix,$newvalues,$current,$changes) = @_;
+    return unless ((ref($newvalues) eq 'HASH') &&
+                   (ref($current) eq 'HASH') &&
+                   (ref($changes) eq 'HASH'));
+    my (@rules,%staticdefaults);
+    if ($prefix eq 'passwords') {
+        @rules = ('min','max','expire','numsaved');
+    } elsif ($prefix eq 'secrets') {
+        @rules = ('min','max');
+    }
+    $staticdefaults{'min'} = $Apache::lonnet::passwdmin;
+    foreach my $rule (@rules) {
+        $env{'form.'.$prefix.'_'.$rule} =~ s/^\s+|\s+$//g;
+        my $ruleok;
+        if ($rule eq 'expire') {
+            if (($env{'form.'.$prefix.'_'.$rule} =~ /^\d+(|\.\d*)$/) &&
+                ($env{'form.'.$prefix.'_'.$rule} ne '0')) {
+                $ruleok = 1;
+            }
+        } elsif ($rule eq 'min') {
+            if ($env{'form.'.$prefix.'_'.$rule} =~ /^\d+$/) {
+                if ($env{'form.'.$prefix.'_'.$rule} >= $staticdefaults{$rule}) {
+                    $ruleok = 1;
+                }
+            }
+        } elsif (($env{'form.'.$prefix.'_'.$rule} =~ /^\d+$/) &&
+                 ($env{'form.'.$prefix.'_'.$rule} ne '0')) {
+            $ruleok = 1;
+        }
+        if ($ruleok) {
+            $newvalues->{$rule} = $env{'form.'.$prefix.'_'.$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;
+                }
+            } else {
+                $changes->{'rules'} = 1;
+            }
+        } elsif (exists($current->{$rule})) {
+            $changes->{'rules'} = 1;
+        }
+    }
+    my @posschars = &Apache::loncommon::get_env_multiple('form.'.$prefix.'_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;
+            }
+        }
+    }
+    return;
+}
+
 sub modify_usercreation {
     my ($dom,%domconfig) = @_;
     my ($resulttext,%curr_usercreation,%changes,%authallowed,%cancreate,%save_usercreate);
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.57 loncom/interface/lonconfigsettings.pm:1.58
--- loncom/interface/lonconfigsettings.pm:1.57	Sun Feb  6 21:36:59 2022
+++ loncom/interface/lonconfigsettings.pm	Mon Feb 14 02:48:46 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.57 2022/02/06 21:36:59 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.58 2022/02/14 02:48:46 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -237,6 +237,11 @@
                         }
                     }
                 }
+                my %servers = &Apache::lonnet::get_servers($dom,'library');
+                foreach my $server (keys(%servers)) {
+                    $onload .= "togglePrivKey(document.display,'$server');";
+                }
+                $onload .= "toggleLTIEncKey(document.display);";
             }
             if (grep(/^ltitools$/, at actions)) {
                 $onload .= "toggleLTITools(document.display,'passback','add');".
@@ -308,7 +313,7 @@
                                 if ($values->{'linkprotection'}->{$i}->{'usable'}) {
                                     $onload .= "toggleLTI(document.display,'$num','secret');";
                                 }
-                            } 
+                            }
                             if ($ltiauth) {
                                 $onload .= "toggleLTIReqUser(document.display,'requser','optional','1','block','$num');".
                                            "toggleLTIReqUser(document.display,'mapuser','userfield','other','inline-block','$num');";
@@ -523,6 +528,16 @@
                                     $settings = $inststatus;
                                 }
                             }
+                        } elsif ($item eq 'lti') {
+                            if (ref($values->{'ltisec'}) eq 'HASH') {
+                                if (ref($values->{'lti'}) eq 'HASH') {
+                                    $settings = {%{$values->{'lti'}},%{$values->{'ltisec'}}};
+                                } else {
+                                    $settings = $values->{'ltisec'};
+                                }
+                            } elsif (ref($values->{'lti'}) eq 'HASH') {
+                                $settings = $values->{'lti'};   
+                            }
                         }
                         ($output{$item},$rowtotal{$item}) =
                             &Apache::domainprefs::print_config_box($r,$dom,$confname,
Index: loncom/Lond.pm
diff -u loncom/Lond.pm:1.18 loncom/Lond.pm:1.19
--- loncom/Lond.pm:1.18	Tue Feb  1 23:13:21 2022
+++ loncom/Lond.pm	Mon Feb 14 02:48:49 2022
@@ -1,6 +1,6 @@
 # The LearningOnline Network
 #
-# $Id: Lond.pm,v 1.18 2022/02/01 23:13:21 raeburn Exp $
+# $Id: Lond.pm,v 1.19 2022/02/14 02:48:49 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1044,6 +1044,56 @@
     return $qresult;
 }
 
+sub store_dom {
+    my ($userinput) = @_;
+    my ($cmd,$dom,$namespace,$rid,$what) =split(/:/,$userinput);
+    my $hashref  = &tie_domain_hash($dom,$namespace,&GDBM_WRCREAT(),"S","$rid:$what") or
+        return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd";
+    $hashref->{"version:$rid"}++;
+    my $version=$hashref->{"version:$rid"};
+    my $allkeys='';
+    my @pairs=split(/\&/,$what);
+    foreach my $pair (@pairs) {
+        my ($key,$value)=split(/=/,$pair);
+        $allkeys.=$key.':';
+        $hashref->{"$version:$rid:$key"}=$value;
+    }
+    my $now = time;
+    $hashref->{"$version:$rid:timestamp"}=$now;
+    $allkeys.='timestamp';
+    $hashref->{"$version:keys:$rid"}=$allkeys;
+    &untie_user_hash($hashref) or
+        return "error: ".($!+0)." untie(GDBM) Failed while attempting $cmd";    
+    return 'ok';
+}
+
+sub restore_dom {
+    my ($userinput) = @_;
+    my ($cmd,$dom,$namespace,$rid) = split(/:/,$userinput);
+    my $hashref = &tie_domain_hash($dom,$namespace,&GDBM_READER()) or
+        return "error: ".($!+0)." tie(GDBM) Failed while attempting $cmd";
+    my $qresult='';
+    if (ref($hashref)) {
+        chomp($rid);
+        my $version=$hashref->{"version:$rid"};
+        $qresult.="version=$version&";
+        my $scope;
+        for ($scope=1;$scope<=$version;$scope++) {
+            my $vkeys=$hashref->{"$scope:keys:$rid"};
+            my @keys=split(/:/,$vkeys);
+            my $key;
+            $qresult.="$scope:keys=$vkeys&";
+            foreach $key (@keys) {
+                $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&";
+            }
+        }
+        $qresult=~s/\&$//;
+    }
+    &untie_user_hash($hashref) or
+        return "error: ".($!+0)." untie(GDBM) Failed while attempting $cmd";
+    return $qresult;
+}
+
 sub crslti_itemid {
     my ($cdom,$cnum,$url,$method,$params,$loncaparev) = @_;
     unless (ref($params) eq 'HASH') {
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1480 loncom/lonnet/perl/lonnet.pm:1.1481
--- loncom/lonnet/perl/lonnet.pm:1.1480	Sun Feb  6 21:37:07 2022
+++ loncom/lonnet/perl/lonnet.pm	Mon Feb 14 02:48:53 2022
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1480 2022/02/06 21:37:07 raeburn Exp $
+# $Id: lonnet.pm,v 1.1481 2022/02/14 02:48:53 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2262,6 +2262,26 @@
     }
 }
 
+sub store_dom {
+    my ($storehash,$id,$namespace,$dom,$home) = @_;
+    $$storehash{'ip'}=&get_requestor_ip();
+    $$storehash{'host'}=$perlvar{'lonHostID'};
+    my $namevalue='';
+    foreach my $key (keys(%{$storehash})) {
+        $namevalue.=&escape($key).'='.&freeze_escape($$storehash{$key}).'&';
+    }
+    $namevalue=~s/\&$//;
+    if (grep { $_ eq $home } current_machine_ids()) {
+        return LONCAPA::Lond::store_dom("storedom:$dom:$namespace:$id:$namevalue");
+    } else {
+        if ($namespace eq 'private') {
+            return 'refused';
+        } else {
+            return reply("storedom:$dom:$namespace:$id:$namevalue","$home");
+        }
+    }
+}
+
 # ----------------------------------construct domainconfig user for a domain 
 sub get_domainconfiguser {
     my ($udom) = @_;


More information about the LON-CAPA-cvs mailing list