--- loncom/interface/domainprefs.pm 2019/08/25 14:15:20 1.367 +++ loncom/interface/domainprefs.pm 2021/03/06 13:39:54 1.380 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.367 2019/08/25 14:15:20 raeburn Exp $ +# $Id: domainprefs.pm,v 1.380 2021/03/06 13:39:54 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -176,6 +176,7 @@ use Locale::Language; use DateTime::TimeZone; use DateTime::Locale; use Time::HiRes qw( sleep ); +use Net::CIDR; my $registered_cleanup; my $modified_urls; @@ -219,9 +220,10 @@ sub handler { 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', 'requestauthor','selfenrollment','inststatus', - 'ltitools','ssl','trust','lti','privacy','passwords'],$dom); + 'ltitools','ssl','trust','lti','privacy','passwords', + 'proctoring','wafproxy'],$dom); my %encconfig = - &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom); + &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom); if (ref($domconfig{'ltitools'}) eq 'HASH') { if (ref($encconfig{'ltitools'}) eq 'HASH') { foreach my $id (keys(%{$domconfig{'ltitools'}})) { @@ -246,12 +248,25 @@ sub handler { } } } - my @prefs_order = ('rolecolors','login','defaults','passwords','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts','privacy', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', - 'serverstatuses','helpsettings','coursedefaults', - 'ltitools','selfenrollment','usersessions','ssl','trust','lti'); + if (ref($domconfig{'proctoring'}) eq 'HASH') { + if (ref($encconfig{'proctoring'}) eq 'HASH') { + foreach my $provider (keys(%{$domconfig{'proctoring'}})) { + if ((ref($domconfig{'proctoring'}{$provider}) eq 'HASH') && + (ref($encconfig{'proctoring'}{$provider}) eq 'HASH')) { + foreach my $item ('key','secret') { + $domconfig{'proctoring'}{$provider}{$item} = $encconfig{'proctoring'}{$provider}{$item}; + } + } + } + } + } + my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas', + 'autoenroll','autoupdate','autocreate','directorysrch', + 'contacts','privacy','usercreation','selfcreation', + 'usermodification','scantron','requestcourses','requestauthor', + 'coursecategories','serverstatuses','helpsettings','coursedefaults', + 'ltitools','proctoring','selfenrollment','usersessions','ssl', + 'trust','lti'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -296,6 +311,17 @@ sub handler { print => \&print_defaults, modify => \&modify_defaults, }, + 'wafproxy' => + { text => 'Web Application Firewall/Reverse Proxy', + help => 'Domain_Configuration_WAF_Proxy', + header => [{col1 => 'Domain server', + col2 => 'Alias for WAF/Reverse Proxy', + }, + {col1 => 'Setting', + col2 => 'Value',}], + print => \&print_wafproxy, + modify => \&modify_wafproxy, + }, 'passwords' => { text => 'Passwords (Internal authentication)', help => 'Domain_Configuration_Passwords', @@ -541,6 +567,14 @@ sub handler { print => \&print_ltitools, modify => \&modify_ltitools, }, + 'proctoring' => + {text => 'Remote Proctoring Integration', + help => 'Domain_Configuration_Proctoring', + header => [{col1 => 'Name', + col2 => 'Configuration'}], + print => \&print_proctoring, + modify => \&modify_proctoring, + }, 'ssl' => {text => 'LON-CAPA Network (SSL)', help => 'Domain_Configuration_Network_SSL', @@ -771,6 +805,8 @@ sub process_changes { $output = &modify_loadbalancing($dom,%domconfig); } elsif ($action eq 'ltitools') { $output = &modify_ltitools($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'proctoring') { + $output = &modify_proctoring($r,$dom,$action,$lastactref,%domconfig); } elsif ($action eq 'ssl') { $output = &modify_ssl($dom,$lastactref,%domconfig); } elsif ($action eq 'trust') { @@ -781,6 +817,8 @@ sub process_changes { $output = &modify_privacy($dom,%domconfig); } elsif ($action eq 'passwords') { $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig); + } elsif ($action eq 'wafproxy') { + $output = &modify_wafproxy($dom,$action,$lastactref,%domconfig); } return $output; } @@ -815,6 +853,8 @@ sub print_config_box { $output .= <itools_javascript($settings); } elsif ($action eq 'lti') { $output .= <i_javascript($settings); + } elsif ($action eq 'proctoring') { + $output .= &proctoring_javascript($settings); } $output .= ' @@ -831,6 +871,7 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || ($action eq 'directorysrch') || (($action eq 'login') && ($numheaders < 4))) { @@ -839,12 +880,15 @@ sub print_config_box { if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= '
- + '; $rowtotal ++; @@ -852,7 +896,7 @@ sub print_config_box { ($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 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal); } elsif ($action eq 'passwords') { $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal); @@ -956,7 +1000,7 @@ sub print_config_box { '; $rownum ++; - $css_class = $rownum%2?' class="LC_odd_row"':''; - $datatable .= ''. - ''; - $rownum ++; } elsif ($position eq 'bottom') { my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); my (@posstypes,%usertypeshash); @@ -4616,7 +4751,7 @@ sub print_ltitools { } $datatable .= ''.(' ' x2)."\n"; + $lt{'crs'.$item}.'  '."\n"; } $datatable .= ''. '
'.&mt('Custom items sent on launch').''. @@ -4818,6 +4953,640 @@ sub ltitools_names { return %lt; } +sub print_proctoring { + my ($dom,$settings,$rowtotal) = @_; + my $itemcount = 1; + my (%ordered,%providernames,%current,%currentdef); + my $confname = $dom.'-domainconfig'; + my $switchserver = &check_switchserver($dom,$confname); + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + } else { + %ordered = ( + 1 => 'proctorio', + 2 => 'examity', + ); + } + %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%ordered)); + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + my $datatable; + my $css_class; + 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 $provider = $ordered{$items[$i]}; + my $optionsty = 'none'; + my ($available,$version,$lifetime,$imgsrc,$userincdom,$showroles, + %checkedfields,%rolemaps,%inuse,%crsconfig,%current); + if (ref($settings) eq 'HASH') { + if (ref($settings->{$provider}) eq 'HASH') { + %current = %{$settings->{$provider}}; + if ($current{'available'}) { + $optionsty = 'block'; + $available = 1; + } + if ($current{'lifetime'} =~ /^\d+$/) { + $lifetime = $current{'lifetime'}; + } + if ($current{'version'} =~ /^\d+\.\d+$/) { + $version = $current{'version'}; + } + if ($current{'image'} ne '') { + $imgsrc = ''.&mt('Proctoring service icon').''; + } + if (ref($current{'fields'}) eq 'ARRAY') { + map { $checkedfields{$_} = 1; } @{$current{'fields'}}; + } + $userincdom = $current{'incdom'}; + if (ref($current{'roles'}) eq 'HASH') { + %rolemaps = %{$current{'roles'}}; + $checkedfields{'roles'} = 1; + } + if (ref($current{'defaults'}) eq 'ARRAY') { + foreach my $val (@{$current{'defaults'}}) { + if (grep(/^\Q$val\E$/,@{$defaults{$provider}})) { + $inuse{$val} = 1; + } else { + foreach my $poss (keys(%{$extended{$provider}})) { + if (ref($extended{$provider}{$poss}) eq 'ARRAY') { + if (grep(/^\Q$val\E$/,@{$extended{$provider}{$poss}})) { + $inuse{$poss} = $val; + last; + } + } + } + } + } + } elsif (ref($current{'defaults'}) eq 'HASH') { + foreach my $key (keys(%{$current{'defaults'}})) { + my $currval = $current{'defaults'}{$key}; + if (grep(/^\Q$key\E$/,@{$defaults{$provider}})) { + $inuse{$key} = 1; + } else { + my $match; + foreach my $poss (keys(%{$extended{$provider}})) { + if (ref($extended{$provider}{$poss}) eq 'ARRAY') { + if (grep(/^\Q$key\E$/,@{$extended{$provider}{$poss}})) { + $inuse{$poss} = $key; + last; + } + } elsif (ref($extended{$provider}{$poss}) eq 'HASH') { + foreach my $inner (sort(keys(%{$extended{$provider}{$poss}}))) { + if (ref($extended{$provider}{$poss}{$inner}) eq 'ARRAY') { + if (grep(/^\Q$currval\E$/,@{$extended{$provider}{$poss}{$inner}})) { + $currentdef{$inner} = $currval; + $match = 1; + last; + } + } elsif ($inner eq $key) { + $currentdef{$key} = $currval; + $match = 1; + last; + } + } + } + last if ($match); + } + } + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + map { $crsconfig{$_} = 1; } @{$current{'crsconf'}}; + } + } + } + my %lt = &proctoring_titles($provider); + my %fieldtitles = &proctoring_fieldtitles($provider); + my $onclickavailable = ' onclick="toggleProctoring(this.form,'."'$provider'".');"'; + my %checkedavailable = ( + yes => '', + no => ' checked="checked"', + ); + if ($available) { + $checkedavailable{'yes'} = $checkedavailable{'no'}; + $checkedavailable{'no'} = ''; + } + my $chgstr = ' onchange="javascript:reorderProctoring(this.form,'."'proctoring_pos_".$provider."'".');"'; + $datatable .= '
'. + ''."\n"; + } + $datatable .= ''; + } + $itemcount ++; + } + } + return $datatable; +} + +sub proctoring_data { + my $requserfields = { + proctorio => ['user'], + examity => ['roles','user'], + }; + my $optuserfields = { + proctorio => ['fullname'], + examity => ['fullname','firstname','lastname','email'], + }; + my $defaults = { + proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic', + 'recordroomstart','verifyvideo','verifyaudio','verifydesktop', + 'verifyid','verifysignature','fullscreen','clipboard','tabslinks', + 'closetabs','onescreen','print','downloads','cache','rightclick', + 'reentry','calculator','whiteboard'], + examity => ['display'], + }; + my $extended = { + proctorio => { + verifyid => ['verifyidauto','verifyidlive'], + fullscreen => ['fullscreenlenient','fullscreenmoderate','fullscreensever'], + tabslinks => ['notabs','linksonly'], + reentry => ['noreentry','agentreentry'], + calculator => ['calculatorbasic','calculatorsci'], + }, + examity => { + display => { + target => ['iframe','tab','window'], + width => '', + height => '', + linktext => '', + explanation => '', + }, + }, + }; + my $crsconf = { + proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic', + 'recordroomstart','verifyvideo','verifyaudio','verifydesktop', + 'verifyid','verifysignature','fullscreen','clipboard','tabslinks', + 'closetabs','onescreen','print','downloads','cache','rightclick', + 'reentry','calculator','whiteboard'], + examity => ['label','title','target','linktext','explanation','append'], + }; + my $courseroles = ['cc','in','ta','ep','st']; + my $ltiroles = ['Instructor','ContentDeveloper','TeachingAssistant','Learner']; + return ($requserfields,$optuserfields,$defaults,$extended,$crsconf,$courseroles,$ltiroles); +} + +sub proctoring_titles { + my ($item) = @_; + my (%common_lt,%custom_lt); + %common_lt = &Apache::lonlocal::texthash ( + 'avai' => 'Available?', + 'base' => 'Basic Settings', + 'requ' => 'User data required to be sent on launch', + 'optu' => 'User data optionally sent on launch', + 'udsl' => 'User data sent on launch', + 'defa' => 'Defaults for items configurable in course', + 'sigmethod' => 'Signature Method', + 'key' => 'Key', + 'lifetime' => 'Nonce lifetime (s)', + 'secret' => 'Secret', + 'icon' => 'Icon', + 'fullname' => 'Full Name', + 'visible' => 'Visible input', + 'username' => 'username', + 'user' => 'User', + ); + if ($item eq 'proctorio') { + %custom_lt = &Apache::lonlocal::texthash ( + 'version' => 'OAuth version', + 'url' => 'API URL', + 'uname:dom' => 'username-domain', + ); + } elsif ($item eq 'examity') { + %custom_lt = &Apache::lonlocal::texthash ( + 'version' => 'LTI Version', + 'url' => 'URL', + 'uname:dom' => 'username:domain', + 'msgtype' => 'Message Type', + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'email' => 'E-mail', + 'roles' => 'Role', + 'crstarget' => 'Display target', + 'crslabel' => 'Course label', + 'crstitle' => 'Course title', + 'crslinktext' => 'Link Text', + 'crsexplanation' => 'Explanation', + 'crsappend' => 'Provider URL', + ); + } + my %lt = (%common_lt,%custom_lt); + return %lt; +} + +sub proctoring_fieldtitles { + my ($item) = @_; + if ($item eq 'proctorio') { + return &Apache::lonlocal::texthash ( + 'recordvideo' => 'Record video', + 'recordaudio' => 'Record audio', + 'recordscreen' => 'Record screen', + 'recordwebtraffic' => 'Record web traffic', + 'recordroomstart' => 'Record room scan', + 'verifyvideo' => 'Verify webcam', + 'verifyaudio' => 'Verify microphone', + 'verifydesktop' => 'Verify desktop recording', + 'verifyid' => 'Photo ID verification', + 'verifysignature' => 'Require signature', + 'fullscreen' => 'Fullscreen', + 'clipboard' => 'Disable copy/paste', + 'tabslinks' => 'New tabs/windows', + 'closetabs' => 'Close other tabs', + 'onescreen' => 'Limit to single screen', + 'print' => 'Disable Printing', + 'downloads' => 'Disable Downloads', + 'cache' => 'Empty cache after exam', + 'rightclick' => 'Disable right click', + 'reentry' => 'Re-entry to exam', + 'calculator' => 'Onscreen calculator', + 'whiteboard' => 'Onscreen whiteboard', + 'verifyidauto' => 'Automated verification', + 'verifyidlive' => 'Live agent verification', + 'fullscreenlenient' => 'Forced, but can navigate away for up to 30s', + 'fullscreenmoderate' => 'Forced, but can navigate away for up to 15s', + 'fullscreensever' => 'Forced, navigation away ends exam', + 'notabs' => 'Disaallowed', + 'linksonly' => 'Allowed from links in exam', + 'noreentry' => 'Disallowed', + 'agentreentry' => 'Agent required for re-entry', + 'calculatorbasic' => 'Basic', + 'calculatorsci' => 'Scientific', + ); + } elsif ($item eq 'examity') { + return &Apache::lonlocal::texthash ( + 'target' => 'Display target', + 'window' => 'Window', + 'tab' => 'Tab', + 'iframe' => 'iFrame', + 'height' => 'Height (pixels)', + 'width' => 'Width (pixels)', + 'linktext' => 'Default Link Text', + 'explanation' => 'Default Explanation', + 'append' => 'Provider URL', + ); + } +} + +sub proctoring_providernames { + return ( + proctorio => 'Proctorio', + examity => 'Examity', + ); +} + sub print_lti { my ($dom,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -5133,7 +5902,7 @@ sub lti_options { $output .= ''. '
'. '
'. + 'value="'.$userfield.'" />'. '
'.&mt('Mapping course roles').'
'.&mt($item->{'header'}->[0]->{'col1'}).''.&mt($item->{'header'}->[0]->{'col1'}).' '.&mt($item->{'header'}->[0]->{'col2'}).'
- + '."\n"; if ($action eq 'passwords') { $output .= $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal); @@ -975,7 +1019,7 @@ sub print_config_box { $rowtotal ++; } elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') || ($action eq 'defaults') || ($action eq 'directorysrch') || - ($action eq 'helpsettings')) { + ($action eq 'helpsettings') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } elsif ($action eq 'scantron') { $output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal); @@ -1158,7 +1202,8 @@ sub print_config_box { $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 'ltitools') || ($action eq 'lti') || + ($action eq 'proctoring')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); } } @@ -2799,6 +2844,102 @@ function toggleLTITools(form,setting,ite ENDSCRIPT } +sub proctoring_javascript { + my ($settings) = @_; + my (%ordered,$total,%jstext); + $total = 0; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + $total = scalar(keys(%{$settings})); + } else { + %ordered = ( + 0 => 'proctorio', + 1 => 'examity', + ); + $total = 2; + } + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var proctors = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + + sub lti_javascript { my ($settings) = @_; my $togglejs = <i_toggle_js(); @@ -3640,18 +3781,17 @@ sub print_contacts { \%choices,$rownum); $datatable .= $reports; } elsif ($position eq 'lower') { - $css_class = $rownum%2?' class="LC_odd_row"':''; - my ($threshold,$sysmail,%excluded,%weights); + my (%current,%excluded,%weights); my ($defaults,$names) = &Apache::loncommon::lon_status_items(); if ($lonstatus{'threshold'} =~ /^\d+$/) { - $threshold = $lonstatus{'threshold'}; + $current{'errorthreshold'} = $lonstatus{'threshold'}; } else { - $threshold = $defaults->{'threshold'}; + $current{'errorthreshold'} = $defaults->{'threshold'}; } if ($lonstatus{'sysmail'} =~ /^\d+$/) { - $sysmail = $lonstatus{'sysmail'}; + $current{'errorsysmail'} = $lonstatus{'sysmail'}; } else { - $sysmail = $defaults->{'sysmail'}; + $current{'errorsysmail'} = $defaults->{'sysmail'}; } if (ref($lonstatus{'weights'}) eq 'HASH') { foreach my $type ('E','W','N','U') { @@ -3671,13 +3811,16 @@ sub print_contacts { map {$excluded{$_} = 1; } @{$lonstatus{'excluded'}}; } } - $datatable .= ''. - ''; - $rownum ++; + foreach my $item ('errorthreshold','errorsysmail') { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $rownum ++; + } $css_class = $rownum%2?' class="LC_odd_row"':''; $datatable .= ''. '
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col1'}).' '.&mt($item->{'header'}->[3]->{'col2'}).'
'. - $titles->{'errorthreshold'}. - ''. - '
'. + $titles->{$item}. + ''. + '
'. @@ -3723,14 +3866,6 @@ sub print_contacts { } $datatable .= '
'. - $titles->{'errorsysmail'}. - ''. - '
' + .''.(' 'x2).''.$providernames{$provider}.'
'. + ''.$lt{'avai'}.' '. + ' '."\n". + ''."\n". + '
'. + '
'.$lt{'base'}.''. + ''.$lt{'version'}.': '."\n". + (' 'x2). + ''.$lt{'sigmethod'}.':'. + (' 'x2). + ''.$lt{'lifetime'}.': '."\n". + '
'. + ''.$lt{'url'}.': '."\n". + '
'. + ''.$lt{'key'}.': '."\n". + (' 'x2). + ''.$lt{'secret'}.':'. + '
'."\n"; + $datatable .= ''.$lt{'icon'}.': '; + if ($imgsrc) { + $datatable .= $imgsrc. + ' '. + ' '.&mt('Replace:'); + } + $datatable .= ' '; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + unless ($imgsrc) { + $datatable .= '
('.&mt('if larger than 21x21 pixels, image will be scaled').')'; + } + $datatable .= '
'."\n"; + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + $datatable .= '
'.$lt{'requ'}.''; + foreach my $field (@{$requserfields{$provider}}) { + $datatable .= ''. + ''; + if ($field eq 'user') { + my $seluserdom = ''; + my $unseluserdom = ' selected="selected"'; + if ($userincdom) { + $seluserdom = $unseluserdom; + $unseluserdom = ''; + } + $datatable .= ': '. + ' '; + } else { + $datatable .= ' '; + if ($field eq 'roles') { + $showroles = 1; + } + } + $datatable .= ' '; + } + } + $datatable .= '
'."\n"; + } + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + $datatable .= '
'.$lt{'optu'}.''; + foreach my $field (@{$optuserfields{$provider}}) { + my $checked; + if ($checkedfields{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '  '; + } + $datatable .= '
'."\n"; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}}) { + my (%options,@selectboxes); + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + $datatable .= '
'.$lt{'defa'}.''; + my ($rem,$numinrow,$dropdowns); + if ($provider eq 'proctorio') { + $datatable .= ''; + $numinrow = 4; + } + my $i = 0; + foreach my $field (@{$defaults{$provider}}) { + my $checked; + if ($inuse{$field}) { + $checked = ' checked="checked"'; + } + if ($provider eq 'examity') { + if ($field eq 'display') { + $datatable .= ''.&mt('Display target:'); + foreach my $option ('iframe','tab','window') { + my $checkdisp; + if ($currentdef{'target'} eq $option) { + $checkdisp = ' checked="checked"'; + } + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
'. + '
'.$fieldtitles{'linktext'}.'
'. + '
'. + '
'.$fieldtitles{'explanation'}.'
'. + '

'; + } + } else { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my ($output,$selnone); + unless ($checked) { + $selnone = ' selected="selected"'; + } + $output .= ''.$fieldtitles{$field}.': '. + ''; + push(@selectboxes,$output); + } else { + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + $i++; + } + } + } + if ($provider eq 'proctorio') { + if ($numinrow) { + $rem = $i%$numinrow; + } + my $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= '
'. + ''. + ''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + if (@selectboxes) { + $datatable .= '
'; + $numinrow = 2; + for (my $i=0; $i<@selectboxes; $i++) { + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + } + if ($numinrow) { + $rem = $i%$numinrow; + } + $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= '
'. + $selectboxes[$i].''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + } + } + $datatable .= '
'; + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + $datatable .= '
'. + ''.&mt('Configurable in course').''; + my ($rem,$numinrow); + if ($provider eq 'proctorio') { + $datatable .= ''; + $numinrow = 4; + } + my $i = 0; + foreach my $item (@{$crsconf{$provider}}) { + my $name; + if ($provider eq 'examity') { + $name = $lt{'crs'.$item}; + } elsif ($provider eq 'proctorio') { + $name = $fieldtitles{$item}; + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= '
'. + $name.''; + if ($provider eq 'examity') { + $datatable .= '  '; + } + $datatable .= "\n"; + $i++; + } + if ($provider eq 'proctorio') { + if ($numinrow) { + $rem = $i%$numinrow; + } + my $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= ''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + } + $datatable .= '
'; + } + if ($showroles) { + $datatable .= '
'. + ''.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($selected,$selectnone); + if (!$rolemaps{$role}) { + $selectnone = ' selected="selected"'; + } + $datatable .= ''; + } + $datatable .= '
'. + &Apache::lonnet::plaintext($role,'Course').'
'. + '
'. + '
'. + ''.&mt('Custom items sent on launch').''. + ''. + ''. + ''; + if ((ref($settings) eq 'HASH') && (ref($settings->{$provider}) eq 'HASH') && + (ref($settings->{$provider}->{'custom'}) eq 'HASH')) { + my %custom = %{$settings->{$provider}->{'custom'}}; + if (keys(%custom) > 0) { + foreach my $key (sort(keys(%custom))) { + next if ($key eq 'lms'); + $datatable .= ''. + ''; + } + } + } + $datatable .= ''. + '
'.&mt('Action').''.&mt('Name').''.&mt('Value').'
lms
'. + ''.$key.'
'. + ''. + '
'; foreach my $ltirole (@lticourseroles) { my ($selected,$selectnone); @@ -5386,7 +6155,7 @@ sub print_coursedefaults { my $currcanclone = 'none'; my $onclick; my @cloneoptions = ('none','domain'); - my %clonetitles = ( + my %clonetitles = &Apache::lonlocal::texthash ( none => 'No additional course requesters', domain => "Any course requester in course's domain", instcode => 'Course requests for official courses ...', @@ -6056,7 +6825,7 @@ sub print_passwords { $datatable .= ''. - '   '; + '   '; } } my $checkedcase; @@ -6160,7 +6929,7 @@ sub print_passwords { &mt('(If you use the same account ... reset a password from this page.)').'

'. &mt('Include custom text:'); if ($customurl) { - my $link = &Apache::loncommon::modal_link($customurl,&mt('Custom text file'),600,500, + my $link = &Apache::loncommon::modal_link($customurl,&mt('custom text'),600,500, undef,undef,undef,undef,'background-color:#ffffff'); $datatable .= ' '.$link. ''. - '   '; + '   '; } } my $checked; @@ -6403,6 +7172,143 @@ sub print_passwords { return $datatable; } +sub print_wafproxy { + my ($position,$dom,$settings,$rowtotal) = @_; + my $css_class; + my $itemcount = 0; + my $datatable; + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my (%othercontrol,%otherdoms,%aliases,%values,$setdom); + my %lt = &wafproxy_titles(); + foreach my $server (sort(keys(%servers))) { + my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server}); + my $serverdom; + if ($serverhome ne $server) { + $serverdom = &Apache::lonnet::host_domain($serverhome); + $othercontrol{$server} = $serverdom; + } else { + $serverdom = &Apache::lonnet::host_domain($server); + if ($serverdom ne $dom) { + $othercontrol{$server} = $serverdom; + } else { + $setdom = 1; + if (ref($settings) eq 'HASH') { + %{$values{$dom}} = (); + if (ref($settings->{'alias'}) eq 'HASH') { + $aliases{$dom} = $settings->{'alias'}; + } + foreach my $item ('ipheader','trusted','vpnint','vpnext') { + $values{$dom}{$item} = $settings->{$item}; + } + } + } + } + } + if (keys(%othercontrol)) { + %otherdoms = reverse(%othercontrol); + foreach my $domain (keys(%otherdoms)) { + %{$values{$domain}} = (); + my %config = &Apache::lonnet::get_dom('configuration',['wafproxy'],$domain); + if (ref($config{$domain}) eq 'HASH') { + if (ref($config{$domain}{'wafproxy'}) eq 'HASH') { + $aliases{$domain} = $config{$domain}{'wafproxy'}{'alias'}; + foreach my $item ('ipheader','trusted','vpnint','vpnext') { + $values{$domain}{$item} = $config{$domain}{'wafproxy'}{$item}; + } + } + } + } + } + if ($position eq 'top') { + my %servers = &Apache::lonnet::internet_dom_servers($dom); + foreach my $server (sort(keys(%servers))) { + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + ''. + ''; + } + } else { + if ($setdom) { + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + ''. + ''; + } + if (keys(%otherdoms)) { + foreach my $domain (sort(keys(%otherdoms))) { + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + ''. + ''; + } + } + } + $$rowtotal += $itemcount; + return $datatable; +} + +sub wafproxy_titles { + return &Apache::lonlocal::texthash( + vpnint => 'Internal IP Range(s) for VPN sessions', + vpnext => 'IP Range for backend WAF connections', + trusted => 'Trusted IP range(s)', + ipheader => 'Custom request header', + ); +} + sub print_usersessions { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,$itemcount,%checked,%choices); @@ -6416,13 +7322,18 @@ sub print_usersessions { if ($position eq 'top') { if (keys(%serverhomes) > 1) { my %spareid = ¤t_offloads_to($dom,$settings,\%servers); - my $curroffloadnow; + my ($curroffloadnow,$curroffloadoth); if (ref($settings) eq 'HASH') { if (ref($settings->{'offloadnow'}) eq 'HASH') { $curroffloadnow = $settings->{'offloadnow'}; } + if (ref($settings->{'offloadoth'}) eq 'HASH') { + $curroffloadoth = $settings->{'offloadoth'}; + } } - $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids,$curroffloadnow,$rowtotal); + my $other_insts = scalar(keys(%by_location)); + $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids, + $other_insts,$curroffloadnow,$curroffloadoth,$rowtotal); } else { $datatable .= ''. - ''; } @@ -8491,7 +9415,7 @@ sub print_defaults { ''. ' '.&mt('(new)'). ''. ''."\n"; $rownum ++; @@ -9373,8 +10297,8 @@ function warnIntPass(field) { alert('$intalert{passnum}'); } } + field.value = ''; } - field.value = ''; } } } @@ -9500,7 +10424,7 @@ ENDSCRIPT sub initialize_categories { my ($itemcount) = @_; my ($datatable,$css_class,$chgstr); - my %default_names = ( + my %default_names = &Apache::lonlocal::texthash ( instcode => 'Official courses (with institutional codes)', communities => 'Communities', placement => 'Placement Tests', @@ -12405,7 +13329,7 @@ sub modify_ltitools { } } if (!$numconfig) { - $resulttext .= &mt('None'); + $resulttext .= ' '.&mt('None'); } $resulttext .= ''; foreach my $item ('passback','roster') { @@ -12584,6 +13508,536 @@ sub get_ltitools_id { return ($id,$error); } +sub modify_proctoring { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my (@allpos,%changes,%confhash,%encconfhash,$errors,$resulttext,%imgdeletions); + my $confname = $dom.'-domainconfig'; + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + my %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%providernames)); + + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + + if (ref($domconfig{$action}) eq 'HASH') { + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.proctoring_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.proctoring_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + foreach my $provider (sort(keys(%providernames))) { + $confhash{$provider} = {}; + my $pos = $env{'form.proctoring_pos_'.$provider}; + $pos =~ s/\D+//g; + $allpos[$pos] = $provider; + my (%current,%currentenc); + my $showroles = 0; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + %current = %{$domconfig{$action}{$provider}}; + foreach my $item ('key','secret') { + $currentenc{$item} = $current{$item}; + delete($current{$item}); + } + } + } + if ($env{'form.proctoring_available_'.$provider}) { + $confhash{$provider}{'available'} = 1; + unless ($current{'available'}) { + $changes{$provider} = 1; + } + } else { + %{$confhash{$provider}} = %current; + %{$encconfhash{$provider}} = %currentenc; + $confhash{$provider}{'available'} = 0; + if ($current{'available'}) { + $changes{$provider} = 1; + } + } + if ($confhash{$provider}{'available'}) { + foreach my $field ('lifetime','version','sigmethod','url','key','secret') { + my $possval = $env{'form.proctoring_'.$provider.'_'.$field}; + if ($field eq 'lifetime') { + if ($possval =~ /^\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'version') { + if ($possval =~ /^\d+\.\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'sigmethod') { + if ($possval =~ /^\QHMAC-SHA\E(1|256)$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'url') { + $confhash{$provider}{$field} = $possval; + } elsif (($field eq 'key') || ($field eq 'secret')) { + $encconfhash{$provider}{$field} = $possval; + unless ($currentenc{$field} eq $possval) { + $changes{$provider} = 1; + } + } + unless (($field eq 'key') || ($field eq 'secret')) { + unless ($current{$field} eq $confhash{$provider}{$field}) { + $changes{$provider} = 1; + } + } + } + if ($imgdeletions{$provider}) { + $changes{$provider} = 1; + } elsif ($env{'form.proctoring_image_'.$provider.'.filename'} ne '') { + my ($imageurl,$error) = + &process_proctoring_image($r,$dom,$confname,'proctoring_image_'.$provider,$provider, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$provider}{'image'} = $imageurl; + $changes{$provider} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif (exists($current{'image'})) { + $confhash{$provider}{'image'} = $current{'image'}; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($env{'form.proctoring_userincdom_'.$provider}) { + $confhash{$provider}{'incdom'} = 1; + } + unless ($current{'incdom'} eq $confhash{$provider}{'incdom'}) { + $changes{$provider} = 1; + } + } + if (grep(/^roles$/,@{$requserfields{$provider}})) { + $showroles = 1; + } + } + } + $confhash{$provider}{'fields'} = []; + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + my @optfields = &Apache::loncommon::get_env_multiple('form.proctoring_optional_'.$provider); + foreach my $field (@{$optuserfields{$provider}}) { + if (grep(/^\Q$field\E$/,@optfields)) { + push(@{$confhash{$provider}{'fields'}},$field); + } + } + } + if (ref($current{'fields'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'fields'}}); + my @old = sort(@{$current{'fields'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'fields'}}) { + $changes{$provider} = 1; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}} > 0) { + my %options; + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_defaults_'.$provider); + foreach my $field (@{$defaults{$provider}}) { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my $poss = $env{'form.proctoring_defaults_'.$field.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}})) { + push(@{$confhash{$provider}{'defaults'}},$poss); + } + } elsif ((exists($options{$field})) && (ref($options{$field}) eq 'HASH')) { + foreach my $inner (keys(%{$options{$field}})) { + if (ref($options{$field}{$inner}) eq 'ARRAY') { + my $poss = $env{'form.proctoring_'.$inner.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}{$inner}})) { + $confhash{$provider}{'defaults'}{$inner} = $poss; + } + } else { + $confhash{$provider}{'defaults'}{$inner} = $env{'form.proctoring_'.$inner.'_'.$provider}; + } + } + } else { + if (grep(/^\Q$field\E$/,@checked)) { + push(@{$confhash{$provider}{'defaults'}},$field); + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (ref($current{'defaults'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'defaults'}}); + my @old = sort(@{$current{'defaults'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (ref($current{'defaults'}) eq 'ARRAY') { + if (@{$current{'defaults'}}) { + $changes{$provider} = 1; + } + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (ref($current{'defaults'}) eq 'HASH') { + unless ($changes{$provider}) { + foreach my $key (keys(%{$confhash{$provider}{'defaults'}})) { + unless ($confhash{$provider}{'defaults'}{$key} eq $current{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + unless ($changes{$provider}) { + foreach my $key (keys(%{$current{'defaults'}})) { + unless ($current{'defaults'}{$key} eq $confhash{$provider}{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'defaults'}})) { + $changes{$provider} = 1; + } + } + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $confhash{$provider}{'crsconf'} = []; + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_crsconf_'.$provider); + foreach my $crsfield (@{$crsconf{$provider}}) { + if (grep(/^\Q$crsfield\E$/,@checked)) { + push(@{$confhash{$provider}{'crsconf'}},$crsfield); + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'crsconf'}}); + my @old = sort(@{$current{'crsconf'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'crsconf'}}) { + $changes{$provider} = 1; + } + } + } + if ($showroles) { + $confhash{$provider}{'roles'} = {}; + foreach my $role (@courseroles) { + my $poss = $env{'form.proctoring_roles_'.$role.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@ltiroles)) { + $confhash{$provider}{'roles'}{$role} = $poss; + } + } + unless ($changes{$provider}) { + if (ref($current{'roles'}) eq 'HASH') { + foreach my $role (keys(%{$current{'roles'}})) { + unless ($current{'roles'}{$role} eq $confhash{$provider}{'roles'}{$role}) { + $changes{$provider} = 1; + last + } + } + unless ($changes{$provider}) { + foreach my $role (keys(%{$confhash{$provider}{'roles'}})) { + unless ($confhash{$provider}{'roles'}{$role} eq $current{'roles'}{$role}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'roles'}})) { + $changes{$provider} = 1; + } + } + } + if (ref($current{'custom'}) eq 'HASH') { + my @customdels = &Apache::loncommon::get_env_multiple('form.proctoring_customdel_'.$provider); + foreach my $key (keys(%{$current{'custom'}})) { + if (grep(/^\Q$key\E$/,@customdels)) { + $changes{$provider} = 1; + } else { + $confhash{$provider}{'custom'}{$key} = $env{'form.proctoring_customval_'.$key.'_'.$provider}; + if ($confhash{$provider}{'custom'}{$key} ne $current{'custom'}{$key}) { + $changes{$provider} = 1; + } + } + } + } + if ($customadds{$provider}) { + my $name = $env{'form.proctoring_custom_name_'.$provider}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.proctoring_custom_value_'.$provider}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $confhash{$provider}{'custom'}{$name} = $value; + $changes{$provider} = 1; + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $provider (@allpos) { + if ($provider ne '') { + $confhash{$provider}{'order'} = $idx; + unless ($changes{$provider}) { + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + if ($domconfig{$action}{$provider}{'order'} ne $idx) { + $changes{$provider} = 1; + } + } + } + } + $idx ++; + } + } + } + my %proc_hash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%proc_hash, + $dom); + if ($putresult eq 'ok') { + my %proc_enchash = ( + $action => { %encconfhash } + ); + &Apache::lonnet::put_dom('encconfig',\%proc_enchash,$dom); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %procall = %confhash; + foreach my $provider (keys(%procall)) { + if (ref($encconfhash{$provider}) eq 'HASH') { + foreach my $key ('key','secret') { + $procall{$provider}{$key} = $encconfhash{$provider}{$key}; + } + } + } + &Apache::lonnet::do_cache_new('proctoring',$dom,\%procall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proctoring'} = 1; + } + $resulttext = &mt('Configuration for Provider(s) with changes:').'
      '; + my %bynum; + foreach my $provider (sort(keys(%changes))) { + my $position = $confhash{$provider}{'order'}; + $bynum{$position} = $provider; + } + foreach my $pos (sort { $a <=> $b } keys(%bynum)) { + my $provider = $bynum{$pos}; + my %lt = &proctoring_titles($provider); + my %fieldtitles = &proctoring_fieldtitles($provider); + if (!$confhash{$provider}{'available'}) { + $resulttext .= '
    • '.&mt('Proctoring integration unavailable for: [_1]',''.$providernames{$provider}.'').'
    • '; + } else { + $resulttext .= '
    • '.&mt('Proctoring integration available for: [_1]',''.$providernames{$provider}.''); + if ($confhash{$provider}{'image'}) { + $resulttext .= ' '. + ''.&mt('Proctoring icon').''; + } + $resulttext .= '
        '; + my $position = $pos + 1; + $resulttext .= '
      • '.&mt('Order: [_1]',$position).'
      • '; + foreach my $key ('version','sigmethod','url','lifetime') { + if ($confhash{$provider}{$key} ne '') { + $resulttext .= '
      • '.$lt{$key}.': '.$confhash{$provider}{$key}.'
      • '; + } + } + if ($encconfhash{$provider}{'key'} ne '') { + $resulttext .= '
      • '.$lt{'key'}.': '.$encconfhash{$provider}{'key'}.'
      • '; + } + if ($encconfhash{$provider}{'secret'} ne '') { + $resulttext .= '
      • '.$lt{'secret'}.': '; + my $num = length($encconfhash{$provider}{'secret'}); + $resulttext .= ('*'x$num).'
      • '; + } + my (@fields,$showroles); + if (ref($requserfields{$provider}) eq 'ARRAY') { + push(@fields,@{$requserfields{$provider}}); + } + if (ref($confhash{$provider}{'fields'}) eq 'ARRAY') { + push(@fields,@{$confhash{$provider}{'fields'}}); + } elsif (ref($confhash{$provider}{'fields'}) eq 'HASH') { + push(@fields,(keys(%{$confhash{$provider}{'fields'}}))); + } + if (@fields) { + if (grep(/^roles$/,@fields)) { + $showroles = 1; + } + $resulttext .= '
      • '.$lt{'udsl'}.': "'. + join('", "', map { $lt{$_}; } @fields).'"
      • '; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($confhash{$provider}{'incdom'}) { + $resulttext .= '
      • '.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'uname:dom'}).'
      • '; + } else { + $resulttext .= '
      • '.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'username'}).'
      • '; + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (@{$confhash{$provider}{'defaults'}} > 0) { + $resulttext .= '
      • '.$lt{'defa'}; + foreach my $field (@{$confhash{$provider}{'defaults'}}) { + $resulttext .= ' "'.$fieldtitles{$field}.'",'; + } + $resulttext =~ s/,$//; + $resulttext .= '
      • '; + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (keys(%{$confhash{$provider}{'defaults'}})) { + $resulttext .= '
      • '.$lt{'defa'}.': 
          '; + foreach my $key (sort(keys(%{$confhash{$provider}{'defaults'}}))) { + if ($confhash{$provider}{'defaults'}{$key} ne '') { + $resulttext .= '
        • '.$fieldtitles{$key}.' = '.$confhash{$provider}{'defaults'}{$key}.'
        • '; + } + } + $resulttext .= '
      • '; + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $resulttext .= '
      • '.&mt('Configurable in course:'); + my $numconfig = 0; + if (ref($confhash{$provider}{'crsconf'}) eq 'ARRAY') { + if (@{$confhash{$provider}{'crsconf'}} > 0) { + foreach my $field (@{$confhash{$provider}{'crsconf'}}) { + $numconfig ++; + if ($provider eq 'examity') { + $resulttext .= ' "'.$lt{'crs'.$field}.'",'; + } else { + $resulttext .= ' "'.$fieldtitles{$field}.'",'; + } + } + $resulttext =~ s/,$//; + } + } + if (!$numconfig) { + $resulttext .= ' '.&mt('None'); + } + $resulttext .= '
      • '; + } + } + if ($showroles) { + if (ref($confhash{$provider}{'roles'}) eq 'HASH') { + my $rolemaps; + foreach my $role (@courseroles) { + if ($confhash{$provider}{'roles'}{$role}) { + $rolemaps .= (' 'x2).&Apache::lonnet::plaintext($role,'Course').'='. + $confhash{$provider}{'roles'}{$role}.','; + } + } + if ($rolemaps) { + $rolemaps =~ s/,$//; + $resulttext .= '
      • '.&mt('Role mapping:').$rolemaps.'
      • '; + } + } + } + if (ref($confhash{$provider}{'custom'}) eq 'HASH') { + my $customlist; + if (keys(%{$confhash{$provider}{'custom'}})) { + foreach my $key (sort(keys(%{$confhash{$provider}{'custom'}}))) { + $customlist .= $key.'='.$confhash{$provider}{'custom'}{$key}.', '; + } + $customlist =~ s/,$//; + } + if ($customlist) { + $resulttext .= '
      • '.&mt('Custom items').': '.$customlist.'
      • '; + } + } + $resulttext .= '
    • '; + } + } + $resulttext .= '
    '; + } else { + $resulttext = &mt('No changes made.'); + } + } else { + $errors .= '
  • '.&mt('Failed to save changes').'
  • '; + } + if ($errors) { + $resulttext .= &mt('The following errors occurred: ').'
      '. + $errors.'
    '; + } + return $resulttext; +} + +sub process_proctoring_image { + my ($r,$dom,$confname,$caller,$provider,$configuserok,$switchserver,$author_ok) = @_; + my $filename = $env{'form.'.$caller.'.filename'}; + my ($error,$url); + my ($width,$height) = (21,21); + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt('Upload of Remote Proctoring Provider icon is not permitted to this server: [_1]', + $switchserver); + } elsif ($author_ok eq 'ok') { + my ($result,$imageurl,$madethumb) = + &publishlogo($r,'upload',$caller,$dom,$confname, + "proctoring/$provider/icon",$width,$height); + if ($result eq 'ok') { + if ($madethumb) { + my ($path,$imagefile) = ($imageurl =~ m{^(.+)/([^/]+)$}); + my $imagethumb = "$path/tn-".$imagefile; + $url = $imagethumb; + } else { + $url = $imageurl; + } + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$filename,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$author_ok); + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$configuserok); + } + return ($url,$error); +} + sub modify_lti { my ($r,$dom,$action,$lastactref,%domconfig) = @_; my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); @@ -13745,7 +15199,7 @@ sub modify_contacts { $contacts_hash{'contacts'}{'lonstatus'}{$item} = \@excluded; } } elsif ($item eq 'weights') { - foreach my $type ('E','W','N') { + foreach my $type ('E','W','N','U') { $env{'form.error'.$item.'_'.$type} =~ s/^\s+|\s+$//g; if ($env{'form.error'.$item.'_'.$type} =~ /^\d+$/) { unless ($env{'form.error'.$item.'_'.$type} == $lonstatus_defs->{$type}) { @@ -14565,7 +16019,7 @@ sub modify_passwords { if ($current{'resetlink'} ne $linklife) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { if ($staticdefaults{'resetlink'} ne $linklife) { $changes{'reset'} = 1; } @@ -14586,7 +16040,7 @@ sub modify_passwords { if (@diffs > 0) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); if (@diffs > 0) { $changes{'reset'} = 1; @@ -14598,7 +16052,7 @@ sub modify_passwords { if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) { $changes{'reset'} = 1; } @@ -14625,7 +16079,7 @@ sub modify_passwords { } else { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink); if (@diffs > 0) { $changes{'reset'} = 1; @@ -14647,7 +16101,7 @@ sub modify_passwords { if (@diffs > 0) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); if (@diffs > 0) { $changes{'reset'} = 1; @@ -14758,6 +16212,8 @@ sub modify_passwords { if ($staticdefaults{$rule} ne $newvalues{$rule}) { $changes{'rules'} = 1; } + } else { + $changes{'rules'} = 1; } } elsif (exists($current{$rule})) { $changes{'rules'} = 1; @@ -14806,7 +16262,7 @@ sub modify_passwords { } } } - } elsif (!exists($domconfig{passwords})) { + } elsif (!(ref($domconfig{passwords}) eq 'HASH')) { foreach my $item ('by','for') { if (@{$crsownerchg{$item}} > 0) { $changes{'crsownerchg'} = 1; @@ -14836,9 +16292,11 @@ sub modify_passwords { $resulttext .= '
  • '.&mt('CAPTCHA validation set to use: original CAPTCHA').'
  • '; } elsif ($confighash{'passwords'}{'captcha'} eq 'recaptcha') { $resulttext .= '
  • '.&mt('CAPTCHA validation set to use: reCAPTCHA').' '. - &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).'
    '. - &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchapub'}).'
    '. - &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchapriv'}).'
  • '; + &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).'
    '; + if (ref($confighash{'passwords'}{'recaptchakeys'}) eq 'HASH') { + $resulttext .= &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'public'}).'
    '. + &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'private'}).''; + } } else { $resulttext .= '
  • '.&mt('No CAPTCHA validation').'
  • '; } @@ -14908,7 +16366,7 @@ sub modify_passwords { $resulttext .= '
  • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
  • '; } } else { - $resulttext .= '
  • '.&mt('E-mail address(es) in LON-CAPA usedfor verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
  • '; + $resulttext .= '
  • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
  • '; } if ($confighash{'passwords'}{'resetremove'}) { $resulttext .= '
  • '.&mt('Preamble to "Forgot Password" web form not shown').'
  • '; @@ -14917,8 +16375,9 @@ sub modify_passwords { } if ($confighash{'passwords'}{'resetcustom'}) { my $customlink = &Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'}, - $titles{custom},600,500); - $resulttext .= '
  • '.&mt('Preamble to "Forgot Password" form includes [_1]',$customlink).'
  • '; + &mt('custom text'),600,500,undef,undef, + undef,undef,'background-color:#ffffff'); + $resulttext .= '
  • '.&mt('Preamble to "Forgot Password" form includes: [_1]',$customlink).'
  • '; } else { $resulttext .= '
  • '.&mt('No custom text included in preamble to "Forgot Password" form').'
  • '; } @@ -14964,6 +16423,24 @@ sub modify_passwords { $resulttext .= '
  • '.&mt('[_1] set to [_2]',$titles{$rule},$confighash{'passwords'}{$rule}).'
  • '; } } + if (ref($confighash{'passwords'}{'chars'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'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 = '
    • '. + join('
    • ',map {$rulenames{$_} } @{$confighash{'passwords'}{'chars'}}). + '
    '; + $resulttext .= '
  • '.&mt('[_1] set to: [_2]',$titles{'chars'},$needed).'
  • '; + } else { + $resulttext .= '
  • '.&mt('[_1] set to none',$titles{'chars'}).'
  • '; + } + } else { + $resulttext .= '
  • '.&mt('[_1] set to none',$titles{'chars'}).'
  • '; + } } elsif ($key eq 'crsownerchg') { if (ref($confighash{'passwords'}{'crsownerchg'}) eq 'HASH') { if ((@{$confighash{'passwords'}{'crsownerchg'}{'by'}} == 0) || @@ -16103,19 +17580,25 @@ sub modify_selfcreation { } sub process_captcha { - my ($container,$changes,$newsettings,$current) = @_; - return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH') || (ref($current) eq 'HASH')); + my ($container,$changes,$newsettings,$currsettings) = @_; + return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH')); $newsettings->{'captcha'} = $env{'form.'.$container.'_captcha'}; unless ($newsettings->{'captcha'} eq 'recaptcha' || $newsettings->{'captcha'} eq 'notused') { $newsettings->{'captcha'} = 'original'; } - if ($current->{'captcha'} ne $newsettings->{'captcha'}) { + my %current; + if (ref($currsettings) eq 'HASH') { + %current = %{$currsettings}; + } + if ($current{'captcha'} ne $newsettings->{'captcha'}) { if ($container eq 'cancreate') { if (ref($changes->{'cancreate'}) eq 'ARRAY') { push(@{$changes->{'cancreate'}},'captcha'); } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['captcha']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'captcha'} = 1; } @@ -16137,9 +17620,9 @@ sub process_captcha { } $newsettings->{'recaptchaversion'} = $newversion; } - if (ref($current->{'recaptchakeys'}) eq 'HASH') { - $currpub = $current->{'recaptchakeys'}{'public'}; - $currpriv = $current->{'recaptchakeys'}{'private'}; + if (ref($current{'recaptchakeys'}) eq 'HASH') { + $currpub = $current{'recaptchakeys'}{'public'}; + $currpriv = $current{'recaptchakeys'}{'private'}; unless ($newsettings->{'captcha'} eq 'recaptcha') { $newsettings->{'recaptchakeys'} = { public => '', @@ -16147,8 +17630,8 @@ sub process_captcha { } } } - if ($current->{'captcha'} eq 'recaptcha') { - $currversion = $current->{'recaptchaversion'}; + if ($current{'captcha'} eq 'recaptcha') { + $currversion = $current{'recaptchaversion'}; if ($currversion ne '2') { $currversion = 1; } @@ -16160,6 +17643,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchaversion']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchaversion'} = 1; } @@ -16171,6 +17656,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchakeys']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchakeys'} = 1; } @@ -18084,6 +19571,215 @@ sub modify_selfenrollment { return $resulttext; } +sub modify_wafproxy { + my ($dom,$action,$lastactref,%domconfig) = @_; + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my (%othercontrol,%canset,%values,%curralias,%currvalue,@warnings,%wafproxy, + %changes,%expirecache); + foreach my $server (sort(keys(%servers))) { + my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server}); + if ($serverhome eq $server) { + my $serverdom = &Apache::lonnet::host_domain($server); + if ($serverdom eq $dom) { + $canset{$server} = 1; + if (ref($domconfig{'wafproxy'}) eq 'HASH') { + %{$values{$dom}} = (); + if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') { + %curralias = %{$domconfig{'wafproxy'}{'alias'}}; + } + foreach my $item ('ipheader','trusted','exempt') { + $currvalue{$item} = $domconfig{'wafproxy'}{$item}; + } + } + } + } + } + my $output; + if (keys(%canset)) { + %{$wafproxy{'alias'}} = (); + foreach my $key (sort(keys(%canset))) { + $wafproxy{'alias'}{$key} = $env{'form.wafproxy_alias_'.$key}; + $wafproxy{'alias'}{$key} =~ s/^\s+|\s+$//g; + if ($wafproxy{'alias'}{$key} ne $curralias{$key}) { + $changes{'alias'} = 1; + } + if ($wafproxy{'alias'}{$key} eq '') { + if ($curralias{$key}) { + $expirecache{$key} = 1; + } + delete($wafproxy{'alias'}{$key}); + } + } + unless (keys(%{$wafproxy{'alias'}})) { + delete($wafproxy{'alias'}); + } + # Localization for values in %warn occus in &mt() calls separately. + my %warn = ( + trusted => 'trusted IP range(s)', + exempt => 'exempt IP range(s)', + ); + foreach my $item ('ipheader','trusted','exempt') { + my $possible = $env{'form.wafproxy_'.$item}; + $possible =~ s/^\s+|\s+$//g; + if ($possible ne '') { + if ($item eq 'ipheader') { + $wafproxy{$item} = $possible; + } else { + my (@ok,$count); + $possible =~ s/[\r\n]+/\s/g; + $possible =~ s/\s*-\s*/-/g; + $possible =~ s/\s+/,/g; + $count = 0; + if ($possible) { + foreach my $poss (split(/\,/,$possible)) { + $count ++; + if (&validate_ip_pattern($poss)) { + push(@ok,$poss); + } + } + if (@ok) { + $wafproxy{$item} = join(',',@ok); + } + my $diff = $count - scalar(@ok); + if ($diff) { + push(@warnings,'
  • '. + &mt('[quant,_1,IP] invalid and excluded from saved value for [_2]', + $diff,$warn{$item}). + '
  • '); + } + if ($wafproxy{$item} ne $currvalue{$item}) { + $changes{$item} = 1; + } + } + } + } else { + if ($currvalue{$item}) { + $changes{$item} = 1; + } + } + } + } + if (keys(%changes)) { + my %defaultshash = ( + wafproxy => \%wafproxy, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, + $dom); + if ($putresult eq 'ok') { + my $cachetime = 24*60*60; + my (%domdefaults,$updatedomdefs); + foreach my $item ('ipheader','trusted','exempt') { + if ($changes{$item}) { + unless ($updatedomdefs) { + %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + $updatedomdefs = 1; + } + if ($wafproxy{$item}) { + $domdefaults{'waf_'.$item} = $wafproxy{$item}; + } elsif (exists($domdefaults{'waf_'.$item})) { + delete($domdefaults{'waf_'.$item}); + } + } + } + if ($updatedomdefs) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ((exists($wafproxy{'alias'})) || (keys(%expirecache))) { + my %updates = %expirecache; + foreach my $key (keys(%expirecache)) { + &Apache::lonnet::devalidate_cache_new('proxyalias',$key); + } + if (ref($wafproxy{'alias'}) eq 'HASH') { + my $cachetime = 24*60*60; + foreach my $key (keys(%{$wafproxy{'alias'}})) { + $updates{$key} = 1; + &Apache::lonnet::do_cache_new('proxyalias',$key,$wafproxy{'alias'}{$key}, + $cachetime); + } + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proxyalias'} = \%updates; + } + } + $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'
      '; + foreach my $item ('alias','ipheader','trusted','exempt') { + if ($changes{$item}) { + if ($item eq 'alias') { + my $numaliased = 0; + if (ref($wafproxy{'alias'}) eq 'HASH') { + my $shown; + if (keys(%{$wafproxy{'alias'}})) { + foreach my $server (sort(keys(%{$wafproxy{'alias'}}))) { + $shown .= '
    • '.&mt('[_1] aliased by [_2]', + &Apache::lonnet::hostname($server), + $wafproxy{'alias'}{$server}).'
    • '; + $numaliased ++; + } + if ($numaliased) { + $output .= '
    • '.&mt('Aliases for hostnames set to: [_1]', + '
        '.$shown.'
      ').'
    • '; + } + } + } + unless ($numaliased) { + $output .= '
    • '.&mt('Aliases deleted for hostnames').'
    • '; + } + } else { + if ($item eq 'ipheader') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Custom request header set to [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Custom request header deleted').'
    • '; + } + } elsif ($item eq 'trusted') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Trusted IP range(s) set to [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Trusted IP range(s) deleted').'
    • '; + } + } elsif ($item eq 'exempt') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Exempt IP range(s) set to [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Exempt IP range(s) deleted').'
    • '; + } + } + } + } + } + } else { + $output = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + } elsif (keys(%canset)) { + $output = &mt('No changes made to Web Application Firewall/Reverse Proxy settings'); + } + if (@warnings) { + $output .= '
      '.&mt('Warnings:').'
        '. + join("\n",@warnings).'
      '; + } + return $output; +} + +sub validate_ip_pattern { + my ($pattern) = @_; + if ($pattern =~ /^([^-]+)\-([^-]+)$/) { + my ($start,$end) = ($1,$2); + if ((&Net::CIDR::cidrvalidate($start)) && (&Net::CIDR::cidrvalidate($end))) { + return 1; + } + } elsif (&Net::CIDR::cidrvalidate($pattern)) { + return 1; + } + return +} + sub modify_usersessions { my ($dom,$lastactref,%domconfig) = @_; my @hostingtypes = ('version','excludedomain','includedomain'); @@ -18251,6 +19947,7 @@ sub modify_usersessions { } } $defaultshash{'usersessions'}{'offloadnow'} = {}; + $defaultshash{'usersessions'}{'offloadoth'} = {}; my @offloadnow = &Apache::loncommon::get_env_multiple('form.offloadnow'); my @okoffload; if (@offloadnow) { @@ -18267,6 +19964,22 @@ sub modify_usersessions { } } } + my @offloadoth = &Apache::loncommon::get_env_multiple('form.offloadoth'); + my @okoffloadoth; + if (@offloadoth) { + foreach my $server (@offloadoth) { + if (&Apache::lonnet::hostname($server) ne '') { + unless (grep(/^\Q$server\E$/,@okoffloadoth)) { + push(@okoffloadoth,$server); + } + } + } + if (@okoffloadoth) { + foreach my $lonhost (@okoffloadoth) { + $defaultshash{'usersessions'}{'offloadoth'}{$lonhost} = 1; + } + } + } if (ref($domconfig{'usersessions'}) eq 'HASH') { if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') { if (ref($changes{'spares'}) eq 'HASH') { @@ -18277,26 +19990,38 @@ sub modify_usersessions { } else { $savespares = 1; } - if (ref($domconfig{'usersessions'}{'offloadnow'}) eq 'HASH') { - foreach my $lonhost (keys(%{$domconfig{'usersessions'}{'offloadnow'}})) { - unless ($defaultshash{'usersessions'}{'offloadnow'}{$lonhost}) { - $changes{'offloadnow'} = 1; - last; - } - } - unless ($changes{'offloadnow'}) { - foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{'offloadnow'}})) { - unless ($domconfig{'usersessions'}{'offloadnow'}{$lonhost}) { - $changes{'offloadnow'} = 1; + foreach my $offload ('offloadnow','offloadoth') { + if (ref($domconfig{'usersessions'}{$offload}) eq 'HASH') { + foreach my $lonhost (keys(%{$domconfig{'usersessions'}{$offload}})) { + unless ($defaultshash{'usersessions'}{$offload}{$lonhost}) { + $changes{$offload} = 1; last; } } - } - } elsif (@okoffload) { + unless ($changes{$offload}) { + foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{$offload}})) { + unless ($domconfig{'usersessions'}{$offload}{$lonhost}) { + $changes{$offload} = 1; + last; + } + } + } + } else { + if (($offload eq 'offloadnow') && (@okoffload)) { + $changes{'offloadnow'} = 1; + } + if (($offload eq 'offloadoth') && (@okoffloadoth)) { + $changes{'offloadoth'} = 1; + } + } + } + } else { + if (@okoffload) { $changes{'offloadnow'} = 1; } - } elsif (@okoffload) { - $changes{'offloadnow'} = 1; + if (@okoffloadoth) { + $changes{'offloadoth'} = 1; + } } my $nochgmsg = &mt('No changes made to settings for user session hosting/offloading.'); if ((keys(%changes) > 0) || ($savespares)) { @@ -18313,6 +20038,9 @@ sub modify_usersessions { if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') { $domdefaults{'offloadnow'} = $defaultshash{'usersessions'}{'offloadnow'}; } + if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') { + $domdefaults{'offloadoth'} = $defaultshash{'usersessions'}{'offloadoth'}; + } } my $cachetime = 24*60*60; &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); @@ -18392,16 +20120,31 @@ sub modify_usersessions { if ($changes{'offloadnow'}) { if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') { if (keys(%{$defaultshash{'usersessions'}{'offloadnow'}}) > 0) { - $resulttext .= '
    • '.&mt('Switch active users on next access, for server(s):').'
        '; + $resulttext .= '
      • '.&mt('Switch any active user on next access, for server(s):').'
          '; foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadnow'}}))) { $resulttext .= '
        • '.$lonhost.'
        • '; } $resulttext .= '
        '; } else { - $resulttext .= '
      • '.&mt('No servers now set to switch active users on next access.'); + $resulttext .= '
      • '.&mt('No servers now set to switch any active user on next access.'); } } else { - $resulttext .= '
      • '.&mt('No servers now set to switch active users on next access.').'
      • '; + $resulttext .= '
      • '.&mt('No servers now set to switch any active user on next access.').'
      • '; + } + } + if ($changes{'offloadoth'}) { + if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') { + if (keys(%{$defaultshash{'usersessions'}{'offloadoth'}}) > 0) { + $resulttext .= '
      • '.&mt('Switch other institutions on next access, for server(s):').'
          '; + foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadoth'}}))) { + $resulttext .= '
        • '.$lonhost.'
        • '; + } + $resulttext .= '
        '; + } else { + $resulttext .= '
      • '.&mt('No servers now set to switch other institutions on next access.'); + } + } else { + $resulttext .= '
      • '.&mt('No servers now set to switch other institutions on next access.').'
      • '; } } $resulttext .= '
      '; @@ -18973,24 +20716,24 @@ sub modify_loadbalancing { $resulttext .= '
    • '.&mt('Load Balancer: [_1] -- cookie use enabled', $balancer).'
    • '; } - if (keys(%toupdate)) { - my %thismachine; - my $updatedhere; - my $cachetime = 60*60*24; - map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - foreach my $lonhost (keys(%toupdate)) { - if ($thismachine{$lonhost}) { - unless ($updatedhere) { - &Apache::lonnet::do_cache_new('loadbalancing',$dom, - $defaultshash{'loadbalancing'}, - $cachetime); - $updatedhere = 1; - } - } else { - my $cachekey = &escape('loadbalancing').':'.&escape($dom); - &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); - } + } + } + if (keys(%toupdate)) { + my %thismachine; + my $updatedhere; + my $cachetime = 60*60*24; + map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); + foreach my $lonhost (keys(%toupdate)) { + if ($thismachine{$lonhost}) { + unless ($updatedhere) { + &Apache::lonnet::do_cache_new('loadbalancing',$dom, + $defaultshash{'loadbalancing'}, + $cachetime); + $updatedhere = 1; } + } else { + my $cachekey = &escape('loadbalancing').':'.&escape($dom); + &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); } } } @@ -19487,6 +21230,7 @@ function balancerChange(balnum,baltotal, END } + sub new_spares_js { my @sparestypes = ('primary','default'); my $types = join("','",@sparestypes); @@ -19560,7 +21304,7 @@ function updateNewSpares(formname,lonhos function checkNewSpares(lonhost,type) { var newSpare = document.getElementById('newspare_'+type+'_'+lonhost); var chosen = newSpare.options[newSpare.selectedIndex].value; - if (chosen != '') { + if (chosen != '') { var othertype; var othernewSpare; if (type == 'primary') { @@ -19694,7 +21438,7 @@ function toggleDisplay(domForm,caller) { var dispval = 'block'; var selfcreateRegExp = /^cancreate_emailverified/; if (caller == 'emailoptions') { - optionsElement = domForm.cancreate_email; + optionsElement = domForm.cancreate_email; } if (caller == 'studentsubmission') { optionsElement = domForm.postsubmit; @@ -19750,14 +21494,22 @@ sub devalidate_remote_domconfs { my %thismachine; map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); my @posscached = ('domainconfig','domdefaults','ltitools','usersessions', - 'directorysrch','passwdconf','cats'); + 'directorysrch','passwdconf','cats','proxyalias'); if (keys(%servers)) { foreach my $server (keys(%servers)) { next if ($thismachine{$server}); my @cached; foreach my $name (@posscached) { if ($cachekeys->{$name}) { - push(@cached,&escape($name).':'.&escape($dom)); + if ($name eq 'proxyalias') { + if (ref($cachekeys->{$name}) eq 'HASH') { + foreach my $key (keys(%{$cachekeys->{$name}})) { + push(@cached,&escape($name).':'.&escape($key)); + } + } + } else { + push(@cached,&escape($name).':'.&escape($dom)); + } } } if (@cached) {
    '.&mt('Hostname').': '. + &Apache::lonnet::hostname($server).''; + if ($othercontrol{$server}) { + my $current; + if (ref($aliases{$othercontrol{$server}}) eq 'HASH') { + $current = $aliases{$othercontrol{$server}{$server}}; + } + if ($current) { + $datatable .= $current; + } else { + $datatable .= &mt('None in effect'); + } + $datatable .= '
    ('. + &mt('WAF/Reverse Proxy controlled by domain: [_1]', + ''.$othercontrol{$server}.'').''; + } else { + my $current; + if (ref($aliases{$dom}) eq 'HASH') { + if ($aliases{$dom}{$server}) { + $current = $aliases{$dom}{$server}; + } + } + $datatable .= ''; + } + $datatable .= '
    '.&mt('Domain: [_1]',''.$dom.'').'

    '. + &mt('Format for comma separated IP blocks').':
    '. + &mt('A.B.C.D/N or A.B.C.D - E.F.G.H').'
    '; + foreach my $item ('ipheader','trusted','vpnint','vpnext') { + $datatable .= ''. + ''; + } + $datatable .= '
    '.$lt{$item}.': '; + if ($item eq 'ipheader') { + $datatable .= ''; + + } else { + $datatable .= ''; + } + $datatable .= '
    '.&mt('Domain: [_1]',$domain).''; + foreach my $item ('ipheader','trusted','vpnint','vpnext') { + my $showval = &mt('None'); + if ($values{$domain}{$item}) { + $showval = $values{$domain}{$item}; + } + $datatable .= ''. + ''; + } + $datatable .= '
    '.$lt{$item}.': '.$showval.'
    '. &mt('Nothing to set here, as the cluster to which this domain belongs only contains one server.'). @@ -6866,7 +7777,8 @@ sub current_offloads_to { } sub spares_row { - my ($dom,$servers,$spareid,$serverhomes,$altids,$curroffloadnow,$rowtotal) = @_; + my ($dom,$servers,$spareid,$serverhomes,$altids,$other_insts, + $curroffloadnow,$curroffloadoth,$rowtotal) = @_; my $css_class; my $numinrow = 4; my $itemcount = 1; @@ -6886,12 +7798,17 @@ sub spares_row { } } next unless (ref($spareid->{$server}) eq 'HASH'); - my $checkednow; + my ($checkednow,$checkedoth); if (ref($curroffloadnow) eq 'HASH') { if ($curroffloadnow->{$server}) { $checkednow = ' checked="checked"'; } } + if (ref($curroffloadoth) eq 'HASH') { + if ($curroffloadoth->{$server}) { + $checkedoth = ' checked="checked"'; + } + } $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; $datatable .= ' @@ -6900,8 +7817,15 @@ sub spares_row { ,''.$server.'').'
    '. ''."\n". ''. + ' '.&mt('Switch any active user on next access').''. + "\n"; + if ($other_insts) { + $datatable .= '
    '. + ''."\n". + ''. "\n"; + } my (%current,%canselect); my @choices = &possible_newspares($server,$spareid->{$server},$serverhomes,$altids); @@ -7420,8 +8344,8 @@ sub contact_titles { 'updatesmail' => 'E-mail from nightly check of LON-CAPA module integrity/updates', 'idconflictsmail' => 'E-mail from bi-nightly check for multiple users sharing same student/employee ID', 'hostipmail' => 'E-mail from nightly check of hostname/IP network changes', - 'errorthreshold' => 'Error/warning threshold for status e-mail', - 'errorsysmail' => 'Error threshold for e-mail to core group', + 'errorthreshold' => 'Error count threshold for status e-mail to admin(s)', + 'errorsysmail' => 'Error count threshold for e-mail to developer group', 'errorweights' => 'Weights used to compute error count', 'errorexcluded' => 'Servers with unsent updates excluded from count', ); @@ -8471,7 +9395,7 @@ sub print_defaults { $datatable .= ' '.&mt('Internal ID:').' '.$item.' '. ''. &mt('delete').'
    '.&mt('Name displayed:'). + ''.&mt('Name displayed').':'. ''. '
    '. - &mt('Name displayed:'). + &mt('Name displayed').':'. '