[LON-CAPA-cvs] cvs: loncom /auth lonlogin.pm lonshibauth.pm /interface domainprefs.pm loncommon.pm lonconfigsettings.pm lonmenu.pm /lonnet/perl lonnet.pm

raeburn raeburn at source.lon-capa.org
Tue Sep 21 18:54:27 EDT 2021


raeburn		Tue Sep 21 22:54:27 2021 EDT

  Modified files:              
    /loncom/interface	loncommon.pm lonconfigsettings.pm lonmenu.pm 
                     	domainprefs.pm 
    /loncom/auth	lonlogin.pm lonshibauth.pm 
    /loncom/lonnet/perl	lonnet.pm 
  Log:
  - For SAML authentication (Shibboleth) config for each of domain's nodes
    to set /adm/log-in as landing page for user toggle between SSO or non-SSO.    
  
  
-------------- next part --------------
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1365 loncom/interface/loncommon.pm:1.1366
--- loncom/interface/loncommon.pm:1.1365	Sun Sep  5 05:55:50 2021
+++ loncom/interface/loncommon.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1365 2021/09/05 05:55:50 raeburn Exp $
+# $Id: loncommon.pm,v 1.1366 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -5745,6 +5745,17 @@
                                     }
                                 }
                             }
+                        } elsif ($key eq 'saml') {
+                            if (ref($domconfig{'login'}{$key}) eq 'HASH') {
+                                foreach my $host (keys(%{$domconfig{'login'}{$key}})) {
+                                    if (ref($domconfig{'login'}{$key}{$host}) eq 'HASH') {
+                                        $designhash{$udom.'.login.'.$key.'_'.$host} = 1;
+                                        foreach my $item ('text','img','alt','url','title','notsso') {
+                                            $designhash{$udom.'.login.'.$key.'_'.$item.'_'.$host} = $domconfig{'login'}{$key}{$host}{$item};
+                                        }
+                                    }
+                                }
+                            }
                         } else {
                             foreach my $img (keys(%{$domconfig{'login'}{$key}})) {
                                 $designhash{$udom.'.login.'.$key.'_'.$img} = 
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.52 loncom/interface/lonconfigsettings.pm:1.53
--- loncom/interface/lonconfigsettings.pm:1.52	Wed Sep  1 00:21:52 2021
+++ loncom/interface/lonconfigsettings.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.52 2021/09/01 00:21:52 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.53 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -266,6 +266,12 @@
             if (grep(/^autoupdate$/, at actions)) {
                 $onload .= "toggleLastActiveDays('document.display');";
             }
+            if (grep(/^login$/, at actions)) {
+                my %domservers = &Apache::lonnet::get_servers($dom);
+                foreach my $server (sort(keys(%domservers))) {
+                    $onload .= "toggleSamlOptions(document.display,'$server');";
+                }
+            }
             if ($onload) {
                 my %loaditems = (
                                   'onload' => $onload,
Index: loncom/interface/lonmenu.pm
diff -u loncom/interface/lonmenu.pm:1.509 loncom/interface/lonmenu.pm:1.510
--- loncom/interface/lonmenu.pm:1.509	Fri Jul 16 01:14:14 2021
+++ loncom/interface/lonmenu.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Routines to control the menu
 #
-# $Id: lonmenu.pm,v 1.509 2021/07/16 01:14:14 raeburn Exp $
+# $Id: lonmenu.pm,v 1.510 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -387,6 +387,13 @@
             } else {
                 $menu{$position} .= '<li>'.&Apache::loncommon::top_nav_help('Help').'</li>';
             }
+        } elsif ($$menuitem[3] eq 'Log In') {
+            if ($public) {
+                if (&Apache::lonnet::get_saml_landing()) {
+                    $$menuitem[0] = '/adm/login';
+                }
+            }
+            $menu{$position} .= prep_menuitem($menuitem,$ltitarget);
         } else {
             $menu{$position} .= prep_menuitem($menuitem,$ltitarget);
         }
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.385 loncom/interface/domainprefs.pm:1.386
--- loncom/interface/domainprefs.pm:1.385	Wed Sep  1 00:21:52 2021
+++ loncom/interface/domainprefs.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.385 2021/09/01 00:21:52 raeburn Exp $
+# $Id: domainprefs.pm,v 1.386 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -297,7 +297,10 @@
                                  {col1 => 'Log-in Help',
                                   col2 => 'Value'},
                                  {col1 => 'Custom HTML in document head',
-                                  col2 => 'Value'}],
+                                  col2 => 'Value'},
+                                 {col1 => 'SSO',
+                                  col2 => 'Dual login: SSO and non-SSO options'},
+                                ],
                       print => \&print_login,
                       modify => \&modify_login,
                     },
@@ -632,7 +635,10 @@
                                        {col1 => 'Log-in Help',
                                         col2 => 'Value'},
                                        {col1 => 'Custom HTML in document head',
-                                        col2 => 'Value'}],
+                                        col2 => 'Value'},
+                                       {col1 => 'SSO',
+                                        col2 => 'Dual login: SSO and non-SSO options'},
+                                      ],
                             print => \&print_login,
                             modify => \&modify_login,
                            };
@@ -859,6 +865,8 @@
         $output .= &wafproxy_javascript($dom);
     } elsif ($action eq 'autoupdate') {
         $output .= &autoupdate_javascript();
+    } elsif ($action eq 'login') {
+        $output .= &saml_javascript();
     }
     $output .=
          '<table class="LC_nested_outer">
@@ -878,7 +886,7 @@
         my $leftnobr = '';
         if (($action eq 'rolecolors') || ($action eq 'defaults') ||
             ($action eq 'directorysrch') ||
-            (($action eq 'login') && ($numheaders < 4))) {
+            (($action eq 'login') && ($numheaders < 5))) {
             $colspan = ' colspan="2"';
         }
         if ($action eq 'usersessions') {
@@ -909,7 +917,7 @@
         } elsif ($action eq 'scantron') {
             $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal);
         } elsif ($action eq 'login') {
-            if ($numheaders == 4) {
+            if ($numheaders == 5) {
                 $colspan = ' colspan="2"';
                 $output .= &print_login('service',$dom,$confname,$phase,$settings,\$rowtotal);
             } else {
@@ -1050,7 +1058,7 @@
               <td class="LC_right_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col2'}).'</td></tr>'.
                            $item->{'print'}->('bottom',$dom,$settings,\$rowtotal);
         } elsif ($action eq 'login') {
-            if ($numheaders == 4) {
+            if ($numheaders == 5) {
                 $output .= &print_login('page',$dom,$confname,$phase,$settings,\$rowtotal).'
            </table>
           </td>
@@ -1074,7 +1082,7 @@
            <td>
             <table class="LC_nested">
              <tr class="LC_info_row">';
-            if ($numheaders == 4) {
+            if ($numheaders == 5) {
                 $output .= '
               <td class="LC_left_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col1'}).'</td>
               <td class="LC_right_item"'.$colspan.'>'.&mt($item->{'header'}->[3]->{'col2'}).'</td>
@@ -1086,7 +1094,27 @@
              </tr>';
             }
             $rowtotal ++;
-            $output .= &print_login('headtag',$dom,$confname,$phase,$settings,\$rowtotal);
+            $output .= &print_login('headtag',$dom,$confname,$phase,$settings,\$rowtotal).'
+           </table>
+          </td>
+         </tr>
+         <tr>
+           <td>
+            <table class="LC_nested">
+             <tr class="LC_info_row">';
+            if ($numheaders == 5) {
+                $output .= '
+              <td class="LC_left_item"'.$colspan.'>'.&mt($item->{'header'}->[4]->{'col1'}).'</td>
+              <td class="LC_right_item"'.$colspan.'>'.&mt($item->{'header'}->[4]->{'col2'}).'</td>
+             </tr>';
+            } else {
+                $output .= '
+              <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>';
+            }
+            $rowtotal ++;
+            $output .= &print_login('saml',$dom,$confname,$phase,$settings,\$rowtotal);
         } elsif ($action eq 'requestcourses') {
             $output .= &print_requestmail($dom,$action,$settings,\$rowtotal);
             $rowtotal ++;
@@ -1221,9 +1249,12 @@
 
 sub print_login {
     my ($caller,$dom,$confname,$phase,$settings,$rowtotal) = @_;
-    my ($css_class,$datatable);
+    my ($css_class,$datatable,$switchserver,%lt);
     my %choices = &login_choices();
-
+    if (($caller eq 'help') || ($caller eq 'headtag') || ($caller eq 'saml')) {
+        %lt = &login_file_options();
+        $switchserver = &check_switchserver($dom,$confname);
+    }
     if ($caller eq 'service') {
         my %servers = &Apache::lonnet::internet_dom_servers($dom);
         my $choice = $choices{'disallowlogin'};
@@ -1417,18 +1448,10 @@
         $datatable .= &display_color_options($dom,$confname,$phase,'login',$itemcount,\%choices,\%is_custom,\%defaults,\%designs,\@images,\@bgs,\@links,\%alt_text,$rowtotal,\@logintext);
         $datatable .= '</tr></table></td></tr>';
     } elsif ($caller eq 'help') {
-        my ($defaulturl,$defaulttype,%url,%type,%lt,%langchoices);
-        my $switchserver = &check_switchserver($dom,$confname);
+        my ($defaulturl,$defaulttype,%url,%type,%langchoices);
         my $itemcount = 1;
         $defaulturl = '/adm/loginproblems.html';
         $defaulttype = 'default';
-        %lt = &Apache::lonlocal::texthash (
-                     del     => 'Delete?',
-                     rep     => 'Replace:',
-                     upl     => 'Upload:',
-                     default => 'Default',
-                     custom  => 'Custom',
-                                             );
         %langchoices = &Apache::lonlocal::texthash(&get_languages_hash());
         my @currlangs;
         if (ref($settings) eq 'HASH') {
@@ -1525,14 +1548,6 @@
                 }
             }
         }
-        my %lt = &Apache::lonlocal::texthash(
-                                               del  => 'Delete?',
-                                               rep  => 'Replace:',
-                                               upl  => 'Upload:',
-                                               curr => 'View contents',
-                                               none => 'None',
-        );
-        my $switchserver = &check_switchserver($dom,$confname);
         foreach my $lonhost (sort(keys(%domservers))) {
             my $exempt = &check_exempt_addresses($currexempt{$lonhost});
             $datatable .= '<tr><td>'.$domservers{$lonhost}.'</td>';
@@ -1556,6 +1571,88 @@
             $datatable .= '</td><td><input type="text" name="loginheadtagexempt_'.$lonhost.'" value="'.$exempt.'" /></td></tr>';
         }
         $datatable .= '</table></td></tr>';
+    } elsif ($caller eq 'saml') {
+        my %domservers = &Apache::lonnet::get_servers($dom);
+        $datatable .= '<tr><td colspan="3" style="text-align: left">'.
+                      '<table><tr><th>'.$choices{'hostid'}.'</th>'.
+                      '<th>'.$choices{'samllanding'}.'</th>'.
+                      '<th>'.$choices{'samloptions'}.'</th></tr>'."\n";
+        my (%saml,%samltext,%samlimg,%samlalt,%samlurl,%samltitle,%samlnotsso,%styleon,%styleoff);
+        foreach my $lonhost (keys(%domservers)) {
+            $samlurl{$lonhost} = '/adm/sso';
+            $styleon{$lonhost} = 'display:none';
+            $styleoff{$lonhost} = '';
+        }
+        if (ref($settings->{'saml'}) eq 'HASH') {
+            foreach my $lonhost (keys(%{$settings->{'saml'}})) {
+                if (ref($settings->{'saml'}{$lonhost}) eq 'HASH') {
+                    $saml{$lonhost} = 1;
+                    $samltext{$lonhost} = $settings->{'saml'}{$lonhost}{'text'};
+                    $samlimg{$lonhost} = $settings->{'saml'}{$lonhost}{'img'};
+                    $samlalt{$lonhost} = $settings->{'saml'}{$lonhost}{'alt'};
+                    $samlurl{$lonhost} = $settings->{'saml'}{$lonhost}{'url'};
+                    $samltitle{$lonhost} = $settings->{'saml'}{$lonhost}{'title'};
+                    $samlnotsso{$lonhost} = $settings->{'saml'}{$lonhost}{'notsso'};
+                    $styleon{$lonhost} = '';
+                    $styleoff{$lonhost} = 'display:none';
+                } else {
+                    $styleon{$lonhost} = 'display:none';
+                    $styleoff{$lonhost} = '';
+                }
+            }
+        }
+        my $itemcount = 1;
+        foreach my $lonhost (sort(keys(%domservers))) {
+            my $samlon = ' ';
+            my $samloff = ' checked="checked" ';
+            if ($saml{$lonhost}) {
+                $samlon = $samloff;
+                $samloff = ' ';
+            }
+            my $css_class = $itemcount%2?' class="LC_odd_row"':'';
+            $datatable .= '<tr'.$css_class.'><td><span class="LC_nobreak">'.$domservers{$lonhost}.'</span></td>'.
+                          '<td><span class="LC_nobreak"><label><input type="radio" name="saml_'.$lonhost.'"'.$samloff.
+                          'onclick="toggleSamlOptions(this.form,'."'$lonhost'".');" value="0" />'.
+                          &mt('No').'</label>'.(' 'x2).
+                          '<label><input type="radio" name="saml_'.$lonhost.'"'.$samlon.
+                          'onclick="toggleSamlOptions(this.form,'."'$lonhost'".');" value="1" />'.
+                          &mt('Yes').'</label></span></td>'.
+                          '<td id="samloptionson_'.$lonhost.'" style="'.$styleon{$lonhost}.'" width="100%">'.
+                          '<table><tr><th colspan="5" align="center">'.&mt('SSO').'</th><th align="center">'.
+                          '<span class="LC_nobreak">'.&mt('Non-SSO').'</span></th></tr>'.
+                          '<tr><th>'.&mt('Text').'</th><th>'.&mt('Image').'</th>'.
+                          '<th>'.&mt('Alt Text').'</th><th>'.&mt('URL').'</th>'.
+                          '<th>'.&mt('Tool Tip').'</th><th>'.&mt('Text').'</th></tr>'.
+                          '<tr'.$css_class.'><td><input type="text" name="saml_text_'.$lonhost.'" size="8" value="'.
+                          $samltext{$lonhost}.'" /></td><td>';
+            if ($samlimg{$lonhost}) {
+                $datatable .= '<img src="'.$samlimg{$lonhost}.'" /><br />'.
+                              '<span class="LC_nobreak"><label>'.
+                              '<input type="checkbox" name="saml_img_del" value="'.$lonhost.'" />'.
+                              $lt{'del'}.'</label> '.$lt{'rep'}.'</span>';
+            } else {
+                $datatable .= $lt{'upl'};
+            }
+            $datatable .='<br />';
+            if ($switchserver) {
+                $datatable .= &mt('Upload to library server: [_1]',$switchserver);
+            } else {
+                $datatable .= '<input type="file" name="saml_img_'.$lonhost.'" />';
+            }
+            $datatable .= '</td>'.
+                          '<td><input type="text" name="saml_alt_'.$lonhost.'" size="20" '.
+                          'value="'.$samlalt{$lonhost}.'" /></td>'.
+                          '<td><input type="text" name="saml_url_'.$lonhost.'" size="8" '.
+                          'value="'.$samlurl{$lonhost}.'" /></td>'.
+                          '<td><textarea name="saml_title_'.$lonhost.'" rows="3" cols="15">'.
+                          $samltitle{$lonhost}.'</textarea></td>'.
+                          '<td><input type="text" name="saml_notsso_'.$lonhost.'" size="8" '.
+                          'value="'.$samlnotsso{$lonhost}.'" /></td></tr>'.
+                          '</table></td>'.
+                          '<td id="samloptionsoff_'.$lonhost.'" style="'.$styleoff{$lonhost}.'" width="100%"> </td></tr>';
+           $itemcount ++;
+        }
+        $datatable .= '</table></td></tr>';
     }
     return $datatable;
 }
@@ -1592,10 +1689,24 @@
             headtag       => "Custom markup",
             action        => "Action",
             current       => "Current",
+            samllanding   => "Dual login?",
+            samloptions   => "Options",
         );
     return %choices;
 }
 
+sub login_file_options {
+      return &Apache::lonlocal::texthash(
+                                           del     => 'Delete?',
+                                           rep     => 'Replace:',
+                                           upl     => 'Upload:',
+                                           curr    => 'View contents',
+                                           default => 'Default',
+                                           custom  => 'Custom',
+                                           none    => 'None',
+      );
+}
+
 sub print_rolecolors {
     my ($phase,$role,$dom,$confname,$settings,$rowtotal) = @_;
     my %choices = &color_font_choices();
@@ -3348,6 +3459,48 @@
 ENDSCRIPT
 }
 
+sub saml_javascript {
+    return <<"ENDSCRIPT";
+<script type="text/javascript">
+// <![CDATA[
+function toggleSamlOptions(form,hostid) { 
+    var radioname = 'saml_'+hostid;
+    var tablecellon = 'samloptionson_'+hostid;
+    var tablecelloff = 'samloptionsoff_'+hostid;
+    var num = form.elements[radioname].length;
+    if (num) {
+        var setvis = ''; 
+        for (var i=0; i<num; i++) {
+            if (form.elements[radioname][i].checked) {
+                if (form.elements[radioname][i].value == '1') { 
+                    if (document.getElementById(tablecellon)) {
+                        document.getElementById(tablecellon).style.display='';
+                    }
+                    if (document.getElementById(tablecelloff)) {
+                        document.getElementById(tablecelloff).style.display='none';
+                    }
+                    setvis = 1;
+                }
+                break;
+            }
+        }
+        if (!setvis) {
+            if (document.getElementById(tablecellon)) {
+                document.getElementById(tablecellon).style.display='none';
+            }
+            if (document.getElementById(tablecelloff)) {
+                document.getElementById(tablecelloff).style.display='';
+            }
+        }
+    }
+    return;
+}
+// ]]>
+</script>
+
+ENDSCRIPT
+}
+
 sub print_autoenroll {
     my ($dom,$settings,$rowtotal) = @_;
     my $autorun = &Apache::lonnet::auto_run(undef,$dom),
@@ -11251,12 +11404,14 @@
 sub modify_login {
     my ($r,$dom,$confname,$lastactref,%domconfig) = @_;
     my ($resulttext,$errors,$colchgtext,%changes,%colchanges,%newfile,%newurl,
-        %curr_loginvia,%loginhash, at currlangs, at newlangs,$addedfile,%title, at offon);
+        %curr_loginvia,%loginhash, at currlangs, at newlangs,$addedfile,%title, at offon,
+        %currsaml,%saml,%samltext,%samlimg,%samlalt,%samlurl,%samltitle,%samlnotsso);
     %title = ( coursecatalog => 'Display course catalog',
                adminmail => 'Display administrator E-mail address',
                helpdesk  => 'Display "Contact Helpdesk" link',
                newuser => 'Link for visitors to create a user account',
-               loginheader => 'Log-in box header');
+               loginheader => 'Log-in box header',
+               saml => 'Dual SSO and non-SSO login');
     @offon = ('off','on');
     if (ref($domconfig{login}) eq 'HASH') {
         if (ref($domconfig{login}{loginvia}) eq 'HASH') {
@@ -11264,6 +11419,20 @@
                 $curr_loginvia{$lonhost} = $domconfig{login}{loginvia}{$lonhost};
             }
         }
+        if (ref($domconfig{login}{'saml'}) eq 'HASH') {
+            foreach my $lonhost (keys(%{$domconfig{login}{'saml'}})) {
+                if (ref($domconfig{login}{'saml'}{$lonhost}) eq 'HASH') {
+                    $currsaml{$lonhost} = $domconfig{login}{'saml'}{$lonhost};
+                    $saml{$lonhost} = 1;
+                    $samltext{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'text'};
+                    $samlurl{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'url'};
+                    $samlalt{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'alt'};
+                    $samlimg{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'img'};
+                    $samltitle{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'title'};
+                    $samlnotsso{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'notsso'};
+                }
+            }
+        }
     }
     ($errors,%colchanges) = &modify_colors($r,$dom,$confname,['login'],
                                            \%domconfig,\%loginhash);
@@ -11510,6 +11679,86 @@
             $errors .= '<li><span class="LC_error">'.$error.'</span></li>';
         }
     }
+    my @delsamlimg = &Apache::loncommon::get_env_multiple('form.saml_img_del');
+    my @newsamlimgs;
+    foreach my $lonhost (keys(%domservers)) {
+        if ($env{'form.saml_'.$lonhost}) {
+            if ($env{'form.saml_img_'.$lonhost.'.filename'}) {
+                push(@newsamlimgs,$lonhost);
+            }
+            foreach my $item ('text','alt','url','title','notsso') {
+                $env{'form.saml_'.$item.'_'.$lonhost} =~ s/^\s+|\s+$//g;
+            }
+            if ($saml{$lonhost}) {
+                if (grep(/^\Q$lonhost\E$/, at delsamlimg)) {
+#FIXME Need to obsolete published image
+                    delete($currsaml{$lonhost}{'img'});
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+                if ($env{'form.saml_alt_'.$lonhost} ne $samlalt{$lonhost}) {
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+                if ($env{'form.saml_text_'.$lonhost} ne $samltext{$lonhost}) {
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+                if ($env{'form.saml_url_'.$lonhost} ne $samlurl{$lonhost}) {
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+                if ($env{'form.saml_title_'.$lonhost} ne $samltitle{$lonhost}) {
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+                if ($env{'form.saml_notsso_'.$lonhost} ne $samlnotsso{$lonhost}) {
+                    $changes{'saml'}{$lonhost} = 1;
+                }
+            } else {
+                $changes{'saml'}{$lonhost} = 1;
+            }
+            foreach my $item ('text','alt','url','title','notsso') {
+                $currsaml{$lonhost}{$item} = $env{'form.saml_'.$item.'_'.$lonhost};
+            }
+        } else {
+            delete($currsaml{$lonhost});
+        }
+    }
+    foreach my $posshost (keys(%currsaml)) {
+        unless (exists($domservers{$posshost})) { 
+            delete($currsaml{$posshost}); 
+        }
+    }
+    %{$loginhash{'login'}{'saml'}} = %currsaml;
+    if (@newsamlimgs) {
+        my $error;
+        my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm);
+        if ($configuserok eq 'ok') {
+            if ($switchserver) {
+                $error = &mt("Upload of SSO Button Image is not permitted to this server: [_1].",$switchserver);
+            } elsif ($author_ok eq 'ok') {
+                foreach my $lonhost (@newsamlimgs) {
+                    my $formelem = 'saml_img_'.$lonhost;
+                    my ($result,$imgurl) = &publishlogo($r,'upload',$formelem,$dom,$confname,
+                                                        "login/saml/$lonhost",'','',
+                                                        $env{'form.saml_img_'.$lonhost.'.filename'});
+                    if ($result eq 'ok') {
+                        $currsaml{$lonhost}{'img'} = $imgurl;
+                        $loginhash{'login'}{'saml'}{$lonhost}{'img'} = $imgurl;
+                        $changes{'saml'}{$lonhost} = 1;
+                    } else {
+                        my $puberror = &mt("Upload of SSO button image failed for [_1] because an error occurred publishing the file in RES space. Error was: [_2].",
+                                           $lonhost,$result);
+                        $errors .= '<li><span class="LC_error">'.$puberror.'</span></li>';
+                    }
+                }
+            } else {
+                $error = &mt("Upload of SSO button image file(s) failed because an author role could not be assigned to a Domain Configuration user ([_1]) in domain: [_2].  Error was: [_3].",$confname,$dom,$author_ok);
+            }
+        } else {
+            $error = &mt("Upload of SSO button image file(s) failed because a Domain Configuration user ([_1]) could not be created in domain: [_2].  Error was: [_3].",$confname,$dom,$configuserok);
+        }
+        if ($error) {
+            &Apache::lonnet::logthis($error);
+            $errors .= '<li><span class="LC_error">'.$error.'</span></li>';
+        }
+    }
     &process_captcha('login',\%changes,$loginhash{'login'},$domconfig{'login'});
 
     my $defaulthelpfile = '/adm/loginproblems.html';
@@ -11550,6 +11799,31 @@
         }
         if (keys(%changes) > 0 || $colchgtext) {
             &Apache::loncommon::devalidate_domconfig_cache($dom);
+            if (exists($changes{'saml'})) {
+                my $hostid_in_use;
+                my @hosts = &Apache::lonnet::current_machine_ids();
+                if (@hosts > 1) {
+                    foreach my $hostid (@hosts) {
+                        if (&Apache::lonnet::host_domain($hostid) eq $dom) {
+                            $hostid_in_use = $hostid;
+                            last;
+                        }
+                    }
+                } else {
+                    $hostid_in_use = $r->dir_config('lonHostID');
+                }
+                if (($hostid_in_use) &&
+                    (&Apache::lonnet::host_domain($hostid_in_use) eq $dom)) {
+                    &devalidate_cache_new('samllanding',$hostid_in_use);
+                }
+                if (ref($lastactref) eq 'HASH') {
+                    if (ref($changes{'saml'}) eq 'HASH') {
+                        my %updates;
+                        map { $updates{$_} = 1; } keys(%{$changes{'saml'}});
+                        $lastactref->{'samllanding'} = \%updates;
+                    }
+                }
+            }
             if (ref($lastactref) eq 'HASH') {
                 $lastactref->{'domainconfig'} = 1;
             }
@@ -11629,6 +11903,38 @@
                             }
                         }
                     }
+                } elsif ($item eq 'saml') {
+                    if (ref($changes{$item}) eq 'HASH') {
+                        my %notlt = (
+                                       text   => 'Text for log-in by SSO',
+                                       img    => 'SSO button image',
+                                       alt    => 'Alt text for button image',
+                                       url    => 'SSO URL',
+                                       title  => 'Tooltip for SSO link',
+                                       notsso => 'Text for non-SSO log-in',
+                                    );
+                        foreach my $lonhost (sort(keys(%{$changes{$item}}))) {
+                            if (ref($currsaml{$lonhost}) eq 'HASH') {
+                                $resulttext .= '<li>'.&mt("$title{$item} in use for [_1]","<b>$lonhost</b>").
+                                               '<ul>';
+                                foreach my $key ('text','img','alt','url','title','notsso') {
+                                    if ($currsaml{$lonhost}{$key} eq '') {
+                                        $resulttext .= '<li>'.&mt("$notlt{$key} not in use").'</li>';
+                                    } else {
+                                        my $value = "'$currsaml{$lonhost}{$key}'";
+                                        if ($key eq 'img') {
+                                            $value = '<img src="'.$currsaml{$lonhost}{$key}.'" />';
+                                        }
+                                        $resulttext .= '<li>'.&mt("$notlt{$key} set to: [_1]",
+                                                                  $value).'</li>';
+                                    }
+                                }
+                                $resulttext .= '</ul></li>';
+                            } else {
+                                $resulttext .= '<li>'.&mt("$title{$item} not in use for [_1]",$lonhost).'</li>';
+                            }
+                        }
+                    }
                 } elsif ($item eq 'captcha') {
                     if (ref($loginhash{'login'}) eq 'HASH') {
                         my $chgtxt;
@@ -21913,6 +22219,25 @@
     map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids();
     my @posscached = ('domainconfig','domdefaults','ltitools','usersessions',
                       'directorysrch','passwdconf','cats','proxyalias');
+    my %cache_by_lonhost;
+    if (exists($cachekeys->{'samllanding'})) {
+        if (ref($cachekeys->{'samllanding'}) eq 'HASH') {
+            my %landing = %{$cachekeys->{'samllanding'}};
+            my %domservers = &Apache::lonnet::get_servers($dom);
+            if (keys(%domservers)) {
+                foreach my $server (keys(%domservers)) {
+                    my @cached;
+                    next if ($thismachine{$server});
+                    if ($landing{$server}) {
+                        push(@cached,&escape('samllanding').':'.&escape($server));
+                    }
+                    if (@cached) {
+                        $cache_by_lonhost{$server} = \@cached;
+                    }
+                }
+            }
+        }
+    }
     if (keys(%servers)) {
         foreach my $server (keys(%servers)) {
             next if ($thismachine{$server});
@@ -21920,7 +22245,7 @@
             foreach my $name (@posscached) {
                 if ($cachekeys->{$name}) {
                     if ($name eq 'proxyalias') {
-                        if (ref($cachekeys->{$name}) eq 'HASH') {  
+                        if (ref($cachekeys->{$name}) eq 'HASH') {
                             foreach my $key (keys(%{$cachekeys->{$name}})) {
                                 push(@cached,&escape($name).':'.&escape($key));
                             }
@@ -21930,6 +22255,10 @@
                     }
                 }
             }
+            if ((exists($cache_by_lonhost{$server})) &&
+                (ref($cache_by_lonhost{$server}) eq 'ARRAY')) {
+                push(@cached,@{$cache_by_lonhost{$server}});
+            }
             if (@cached) {
                 &Apache::lonnet::remote_devalidate_cache($server,\@cached);
             }
Index: loncom/auth/lonlogin.pm
diff -u loncom/auth/lonlogin.pm:1.183 loncom/auth/lonlogin.pm:1.184
--- loncom/auth/lonlogin.pm:1.183	Mon May  3 15:27:44 2021
+++ loncom/auth/lonlogin.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Login Screen
 #
-# $Id: lonlogin.pm,v 1.183 2021/05/03 15:27:44 raeburn Exp $
+# $Id: lonlogin.pm,v 1.184 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -47,7 +47,7 @@
 	(join('&',$ENV{'QUERY_STRING'},$env{'request.querystring'},
 	      $ENV{'REDIRECT_QUERY_STRING'}),
 	 ['interface','username','domain','firsturl','localpath','localres',
-	  'token','role','symb','iptoken','btoken','ltoken','linkkey']);
+	  'token','role','symb','iptoken','btoken','ltoken','linkkey','saml']);
     if (!defined($env{'form.firsturl'})) {
         &Apache::lonacc::get_posted_cgi($r,['firsturl']);
     }
@@ -581,17 +581,9 @@
 
 ENDSCRIPT
 
-# --------------------------------------------------- Print login screen header
-
-    my %add_entries = (
-	       bgcolor      => "$mainbg",
-	       text         => "$font",
-	       link         => "$link",
-	       vlink        => "$vlink",
-	       alink        => "$alink",
-               onload       => 'javascript:enableInput();',);
-
-    my ($lonhost_in_use,$headextra,$headextra_exempt, at hosts,%defaultdomconf);
+    my ($lonhost_in_use, at hosts,%defaultdomconf,$saml_prefix,$saml_landing,
+        $samlssotext,$samlnonsso,$samlssoimg,$samlssoalt,$samlssourl,$samltooltip);
+    %defaultdomconf = &Apache::loncommon::get_domainconf($defdom);
     @hosts = &Apache::lonnet::current_machine_ids();
     $lonhost_in_use = $lonhost;
     if (@hosts > 1) {
@@ -602,7 +594,67 @@
             }
         }
     }
-    %defaultdomconf = &Apache::loncommon::get_domainconf($defdom);
+    $saml_prefix = $defdom.'.login.saml_';
+    if ($defaultdomconf{$saml_prefix.$lonhost_in_use}) {
+        $saml_landing = 1;
+        $samlssotext = $defaultdomconf{$saml_prefix.'text_'.$lonhost_in_use};
+        $samlnonsso = $defaultdomconf{$saml_prefix.'notsso_'.$lonhost_in_use};
+        $samlssoimg = $defaultdomconf{$saml_prefix.'img_'.$lonhost_in_use};
+        $samlssoalt = $defaultdomconf{$saml_prefix.'alt_'.$lonhost_in_use};
+        $samlssourl = $defaultdomconf{$saml_prefix.'url_'.$lonhost_in_use};
+        $samltooltip = $defaultdomconf{$saml_prefix.'title_'.$lonhost_in_use};
+    }
+    if ($saml_landing) {
+       if ($samlssotext eq '') {
+           $samlssotext = 'SSO Login';
+       }
+       if ($samlnonsso eq '') {
+           $samlnonsso = 'Non-SSO Login';
+       }
+       $js .= <<"ENDSAMLJS";
+
+<script type="text/javascript">
+// <![CDATA[
+function toggleLClogin() {
+    if (document.getElementById('LC_standard_login')) {
+        if (document.getElementById('LC_standard_login').style.display == 'none') {
+            document.getElementById('LC_standard_login').style.display = 'inline-block';
+            if (document.getElementById('LC_login_text')) {
+                document.getElementById('LC_login_text').innerHTML = '$samlnonsso';
+            }
+            if (document.getElementById('LC_SSO_login')) {
+                document.getElementById('LC_SSO_login').style.display = 'none';
+            }
+        } else {
+            document.getElementById('LC_standard_login').style.display = 'none';
+            if (document.getElementById('LC_login_text')) {
+                document.getElementById('LC_login_text').innerHTML = '$samlssotext';
+            }
+            if (document.getElementById('LC_SSO_login')) {
+                document.getElementById('LC_SSO_login').style.display = 'inline-block';
+            }
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+
+ENDSAMLJS
+    }
+
+# --------------------------------------------------- Print login screen header
+
+    my %add_entries = (
+	       bgcolor      => "$mainbg",
+	       text         => "$font",
+	       link         => "$link",
+	       vlink        => "$vlink",
+	       alink        => "$alink",
+               onload       => 'javascript:enableInput();',);
+
+    my ($headextra,$headextra_exempt, at hosts,%defaultdomconf);
     $headextra = $defaultdomconf{$defdom.'.login.headtag_'.$lonhost_in_use};
     $headextra_exempt = $defaultdomconf{$domain.'.login.headtag_exempt_'.$lonhost_in_use};
     if ($headextra) {
@@ -647,6 +699,7 @@
           'helpdesk' => 'Contact Helpdesk',
           'forgotpw' => 'Forgot password?',
           'newuser'  => 'New User?',
+          'change'   => 'Change?',
        );
 # -------------------------------------------------- Change password field name
 
@@ -708,7 +761,7 @@
         $mobileargs = 'autocapitalize="off" autocorrect="off"'; 
     }
     my $loginform=(<<LFORM);
-<form name="client" action="" onsubmit="return(send())">
+<form name="client" action="" onsubmit="return(send())" id="lclogin">
   <input type="hidden" name="lextkey" value="$lextkey" />
   <input type="hidden" name="uextkey" value="$uextkey" />
   <b><label for="uname">$lt{'un'}</label>:</b><br />
@@ -729,8 +782,56 @@
 </div>
 HEADER
     }
-    $r->print(<<ENDTOP);
-<div style="float:left;margin-top:0;">
+
+    my $stdauthformstyle = 'inline-block';
+    my $ssoauthstyle = 'none';
+    my $logintype;
+    $r->print('<div style="float:left;margin-top:0;">');
+    if ($saml_landing) {
+        $ssoauthstyle = 'inline-block';
+        $stdauthformstyle = 'none';
+        $logintype = $samlssotext;
+        my $ssologin = '/adm/sso';
+        if ($samlssourl  ne '') {
+            $ssologin = $samlssourl;
+        }
+        my $ssohref;
+        if ($samlssoimg ne '') {
+            $ssohref = '<a href="'.$ssologin.'" title="'.$samltooltip.'"><img src="'.$samlssoimg.'" alt="'.$samlssoalt.'" /></a>';
+        } else {
+            $ssohref = '<a href="'.$ssologin.'">'.$samlssotext.'</a>';
+        }
+        if ($env{'form.firsturl'}) {
+            $ssologin .= '?origurl='.&HTML::Entities::encode($env{'form.firsturl'},'<>&"');
+        }
+        if (($env{'form.saml'} eq 'no') ||
+            (($env{'form.username'} ne '') && ($env{'form.domain'} ne ''))) {
+            $ssoauthstyle = 'none';
+            $stdauthformstyle = 'inline-block';
+            $logintype = $samlnonsso;
+        }
+        $r->print(<<ENDSAML);
+<p>
+Log-in type:
+<span style="font-weight:bold" id="LC_login_text">$logintype</span><br />
+<span><a href="javascript:toggleLClogin();" style="color:#000000">$lt{'change'}</a></span>
+</p>
+<div style="display:$ssoauthstyle" id="LC_SSO_login">
+<div class="LC_Box" style="padding-top: 10px;">
+$ssohref
+$noscript_warning
+</div>
+<div class="LC_Box" style="padding-top: 10px;">
+$loginhelp
+$contactblock
+$coursecatalog
+</div>
+</div>
+ENDSAML
+    }
+
+    $r->print(<<ENDLOGIN);
+<div style="display:$stdauthformstyle;" id="LC_standard_login">
 <div class="LC_Box" style="background:$loginbox_bg;">
   $logintitle
   $loginform
@@ -746,8 +847,8 @@
 </div>
 </div>
 
-<div>
-ENDTOP
+ENDLOGIN
+    $r->print('</div><div>'."\n");
     if ($showmainlogo) {
         $r->print(' <img src="'.$logo.'" alt="" class="LC_maxwidth" />'."\n");
     }
Index: loncom/auth/lonshibauth.pm
diff -u loncom/auth/lonshibauth.pm:1.5 loncom/auth/lonshibauth.pm:1.6
--- loncom/auth/lonshibauth.pm:1.5	Tue Jun 22 16:56:35 2021
+++ loncom/auth/lonshibauth.pm	Tue Sep 21 22:54:26 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Redirect Shibboleth authentication to designated URL (/adm/sso).
 #
-# $Id: lonshibauth.pm,v 1.5 2021/06/22 16:56:35 raeburn Exp $
+# $Id: lonshibauth.pm,v 1.6 2021/09/21 22:54:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -74,8 +74,11 @@
 sub handler {
     my $r = shift;
     my $target = '/adm/sso';
+    if (&Apache::lonnet::get_saml_landing()) {
+        $target = '/adm/login';
+    }
     my $uri = $r->uri;
-    if (($r->user eq '') && ($uri ne $target)) {
+    if (($r->user eq '') && ($uri ne $target) && ($uri ne '/adm/sso')) {
         my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
         my $hostname = &Apache::lonnet::hostname($lonhost);
         if (!$hostname) { $hostname = $r->hostname(); }
@@ -88,10 +91,8 @@
         if ($ENV{'QUERY_STRING'} ne '') {
             $dest .= '?'.$ENV{'QUERY_STRING'};
         }
-        if ($uri ne '/adm/roles/') {
-            unless ($ENV{'QUERY_STRING'} =~ /origurl=/) {
-                $dest.=(($dest=~/\?/)?'&':'?').'origurl='.$uri;
-            }
+        unless (($uri eq '/adm/roles') || ($ENV{'QUERY_STRING'} =~ /origurl=/)) {
+            $dest.=(($dest=~/\?/)?'&':'?').'origurl='.$uri;
         }
         $r->header_out(Location => $dest);
         return REDIRECT;
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1464 loncom/lonnet/perl/lonnet.pm:1.1465
--- loncom/lonnet/perl/lonnet.pm:1.1464	Fri Aug 13 20:32:23 2021
+++ loncom/lonnet/perl/lonnet.pm	Tue Sep 21 22:54:27 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1464 2021/08/13 20:32:23 raeburn Exp $
+# $Id: lonnet.pm,v 1.1465 2021/09/21 22:54:27 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -14619,6 +14619,52 @@
     return;
 }
 
+sub get_saml_landing {
+    my ($lonid) = @_;
+    if ($lonid eq '') {
+        my $defdom = &default_login_domain();
+        my @hosts = &current_machine_ids();
+        if (@hosts > 1) {
+            foreach my $hostid (@hosts) {
+                if (&host_domain($hostid) eq $defdom) {
+                    $lonid = $hostid;
+                    last;
+                }
+            }
+        } else {
+            $lonid = $perlvar{'lonHostID'};
+        }
+        if ($lonid) {
+            unless (&Apache::lonnet::host_domain($lonid) eq $defdom) {
+                return;
+            }
+        } else {
+            return;
+        }
+    } elsif (!defined(&hostname($lonid))) {
+        return;
+    }
+    my ($landing,$cached) = &is_cached_new('samllanding',$lonid);
+    if ($cached) {
+        return $landing;
+    }
+    my $dom = &Apache::lonnet::host_domain($lonid);
+    if ($dom ne '') {
+        my $cachetime = 60*60*24;
+        my %domconfig =
+            &Apache::lonnet::get_dom('configuration',['login'],$dom);
+        if (ref($domconfig{'login'}) eq 'HASH') {
+            if (ref($domconfig{'login'}{'saml'}) eq 'HASH') {
+                if (ref($domconfig{'login'}{'saml'}{$lonid}) eq 'HASH') {
+                    $landing = 1;
+                }
+            }
+        }
+        return &do_cache_new('samllanding',$lonid,$landing,$cachetime);
+    }
+    return;
+}
+
 # ------------------------------------------------------------- Declutters URLs
 
 sub declutter {


More information about the LON-CAPA-cvs mailing list