--- loncom/interface/domainprefs.pm 2021/11/28 18:43:37 1.393 +++ loncom/interface/domainprefs.pm 2021/11/28 19:17:59 1.394 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.393 2021/11/28 18:43:37 raeburn Exp $ +# $Id: domainprefs.pm,v 1.394 2021/11/28 19:17:59 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -221,7 +221,7 @@ sub handler { 'coursedefaults','usersessions','loadbalancing', 'requestauthor','selfenrollment','inststatus', 'ltitools','ssl','trust','lti','privacy','passwords', - 'proctoring','wafproxy'],$dom); + 'proctoring','wafproxy','ipaccess'],$dom); my %encconfig = &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom,undef,1); if (ref($domconfig{'ltitools'}) eq 'HASH') { @@ -260,8 +260,8 @@ sub handler { } } } - my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas', - 'autoenroll','autoupdate','autocreate','directorysrch', + my @prefs_order = ('rolecolors','login','ipaccess','defaults','wafproxy','passwords', + 'quotas','autoenroll','autoupdate','autocreate','directorysrch', 'contacts','privacy','usercreation','selfcreation', 'usermodification','scantron','requestcourses','requestauthor', 'coursecategories','serverstatuses','helpsettings','coursedefaults', @@ -624,6 +624,14 @@ sub handler { print => \&print_lti, modify => \&modify_lti, }, + 'ipaccess' => + {text => 'IP-based access control', + help => 'Domain_Configuration_IP_Access', + header => [{col1 => 'Setting', + col2 => 'Value'},], + print => \&print_ipaccess, + modify => \&modify_ipaccess, + }, ); if (keys(%servers) > 1) { $prefs{'login'} = { text => 'Log-in page options', @@ -679,6 +687,8 @@ $javascript_validations $coursebrowserjs END + } elsif (grep(/^ipaccess$/,@actions)) { + $js .= &Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}); } if (grep(/^selfcreation$/,@actions)) { $js .= &selfcreate_javascript(); @@ -825,6 +835,8 @@ sub process_changes { $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig); } elsif ($action eq 'wafproxy') { $output = &modify_wafproxy($dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'ipaccess') { + $output = &modify_ipaccess($dom,$lastactref,%domconfig); } return $output; } @@ -867,6 +879,8 @@ sub print_config_box { $output .= &autoupdate_javascript(); } elsif ($action eq 'login') { $output .= &saml_javascript(); + } elsif ($action eq 'ipaccess') { + $output .= &ipaccess_javascript($settings); } $output .= ' @@ -1235,7 +1249,7 @@ sub print_config_box { } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || ($action eq 'ltitools') || ($action eq 'lti') || - ($action eq 'proctoring')) { + ($action eq 'proctoring') || ($action eq 'ipaccess')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); } } @@ -1707,6 +1721,183 @@ sub login_file_options { ); } +sub print_ipaccess { + my ($dom,$settings,$rowtotal) = @_; + my $css_class; + my $itemcount = 0; + my $datatable; + my %ordered; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + if ($num eq '') { + $num = scalar(keys(%{$settings})); + } + $ordered{$num} = $item; + } + } + } + my $maxnum = scalar(keys(%ordered)); + if (keys(%ordered)) { + my @items = sort { $a <=> $b } keys(%ordered); + for (my $i=0; $i<@items; $i++) { + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $item = $ordered{$items[$i]}; + my ($name,$ipranges,%commblocks,%courses); + if (ref($settings->{$item}) eq 'HASH') { + $name = $settings->{$item}->{'name'}; + $ipranges = $settings->{$item}->{'ip'}; + if (ref($settings->{$item}->{'commblocks'}) eq 'HASH') { + %commblocks = %{$settings->{$item}->{'commblocks'}}; + } + if (ref($settings->{$item}->{'courses'}) eq 'HASH') { + %courses = %{$settings->{$item}->{'courses'}}; + } + } + my $chgstr = ' onchange="javascript:reorderIPaccess(this.form,'."'ipaccess_pos_".$item."'".');"'; + $datatable .= ''. + ''; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderIPaccess(this.form,'."'ipaccess_pos_add'".');"'; + $datatable .= ''."\n". + ''."\n". + ''."\n"; + $$rowtotal ++; + return $datatable; +} + +sub ipaccess_options { + my ($num,$itemcount,$dom,$name,$ipranges,$blocksref,$coursesref) = @_; + my (%currblocks,%currcourses,$output); + if (ref($blocksref) eq 'HASH') { + %currblocks = %{$blocksref}; + } + if (ref($coursesref) eq 'HASH') { + %currcourses = %{$coursesref}; + } + $output = '
'.&mt('Location(s)').''. + ''.&mt('Name').': '. + ''. + '
'. + '
'.&mt('IP Range(s)').''. + &mt('Format for each IP range').': '.&mt('A.B.C.D/N or A.B.C.D-E.F.G.H').'
'. + &mt('Range(s) will be stored as IP netblock(s) in CIDR notation (comma separated)').'
'. + '
'. + '
'.&mt('Functionality Blocked?').''. + &blocker_checkboxes($num,$blocksref).'
'. + '
'.&mt('Courses/Communities allowed').''. + '
' + .''.(' 'x2). + ''. + &ipaccess_options($i,$itemcount,$dom,$name,$ipranges,\%commblocks,\%courses). + '
'."\n". + ''."\n". + ' '."\n". + ''.&mt('Add').''. + &ipaccess_options('add',$itemcount,$dom). + '
'; + foreach my $cid (sort(keys(%currcourses))) { + my %courseinfo = &Apache::lonnet::coursedescription($cid,{'one_time' => 1}); + $output .= ''; + } + $output .= '
'. + ''. + ' ('.$cid.')
'.&mt('Add').': '. + ''. + &Apache::loncommon::selectcourse_link('display','ipaccess_cnum_'.$num,'ipaccess_cdom_'.$num,'ipaccess_cdesc_'.$num,$dom,undef,'Course/Community'). + ''. + ''. + '
'."\n". + ''; + return $output; +} + +sub blocker_checkboxes { + my ($num,$blocks) = @_; + my ($typeorder,$types) = &commblocktype_text(); + my $numinrow = 6; + my $output = ''; + for (my $i=0; $i<@{$typeorder}; $i++) { + my $block = $typeorder->[$i]; + my $blockstatus; + if (ref($blocks) eq 'HASH') { + if ($blocks->{$block} eq 'on') { + $blockstatus = 'checked="checked"'; + } + } + my $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $output .= ''; + } + $output .= ''; + } + if ($i == scalar(@{$typeorder})-1) { + my $colsleft = $numinrow-$rem; + if ($colsleft > 1) { + $output .= ''; + } + $output .= '
'; + } else { + $output .= ''; + } + } else { + $output .= ''; + } + my $item = 'ipaccess_block_'.$num; + if ($blockstatus) { + $blockstatus = ' '.$blockstatus; + } + $output .= ''."\n". + '
'; + return $output; +} + +sub commblocktype_text { + my %types = &Apache::lonlocal::texthash( + 'com' => 'Messaging', + 'chat' => 'Chat Room', + 'boards' => 'Discussion', + 'port' => 'Portfolio', + 'groups' => 'Groups', + 'blogs' => 'Blogs', + 'about' => 'User Information', + 'printout' => 'Printouts', + 'passwd' => 'Change Password', + 'grades' => 'Gradebook', + ); + my $typeorder = ['com','chat','boards','port','groups','blogs','about','printout','grades','passwd']; + return ($typeorder,\%types); +} + sub print_rolecolors { my ($phase,$role,$dom,$confname,$settings,$rowtotal) = @_; my %choices = &color_font_choices(); @@ -3536,6 +3727,74 @@ function toggleSamlOptions(form,hostid) ENDSCRIPT } +sub ipaccess_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})); + } + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var ipaccess = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + sub print_autoenroll { my ($dom,$settings,$rowtotal) = @_; my $autorun = &Apache::lonnet::auto_run(undef,$dom), @@ -12144,6 +12403,281 @@ sub color_font_choices { return %choices; } +sub modify_ipaccess { + my ($dom,$lastactref,%domconfig) = @_; + my (@allpos,%changes,%confhash,$errors,$resulttext); + my (@items,%deletions,%itemids,@warnings); + my ($typeorder,$types) = &commblocktype_text(); + if ($env{'form.ipaccess_add'}) { + my $name = $env{'form.ipaccess_name_add'}; + my ($newid,$error) = &get_ipaccess_id($dom,$name); + if ($newid) { + $itemids{'add'} = $newid; + push(@items,'add'); + $changes{$newid} = 1; + } else { + $error = &mt('Failed to acquire unique ID for new IP access control item'); + $errors .= '
  • '.$error.'
  • '; + } + } + if (ref($domconfig{'ipaccess'}) eq 'HASH') { + my @todelete = &Apache::loncommon::get_env_multiple('form.ipaccess_del'); + if (@todelete) { + map { $deletions{$_} = 1; } @todelete; + } + my $maxnum = $env{'form.ipaccess_maxnum'}; + for (my $i=0; $i<$maxnum; $i++) { + my $itemid = $env{'form.ipaccess_id_'.$i}; + $itemid =~ s/\D+//g; + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + if ($deletions{$itemid}) { + $changes{$itemid} = $domconfig{'ipaccess'}{$itemid}{'name'}; + } else { + push(@items,$i); + $itemids{$i} = $itemid; + } + } + } + } + foreach my $idx (@items) { + my $itemid = $itemids{$idx}; + next unless ($itemid); + my %current; + unless ($idx eq 'add') { + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + %current = %{$domconfig{'ipaccess'}{$itemid}}; + } + } + my $position = $env{'form.ipaccess_pos_'.$itemid}; + $position =~ s/\D+//g; + if ($position ne '') { + $allpos[$position] = $itemid; + } + my $name = $env{'form.ipaccess_name_'.$idx}; + $name =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'name'} = $name; + my $possrange = $env{'form.ipaccess_range_'.$idx}; + $possrange =~ s/^\s+|\s+$//g; + unless ($possrange eq '') { + $possrange =~ s/[\r\n]+/\s/g; + $possrange =~ s/\s*-\s*/-/g; + $possrange =~ s/\s+/,/g; + $possrange =~ s/,+/,/g; + if ($possrange ne '') { + my (@ok,$count); + $count = 0; + foreach my $poss (split(/\,/,$possrange)) { + $count ++; + $poss = &validate_ip_pattern($poss); + if ($poss ne '') { + push(@ok,$poss); + } + } + my $diff = $count - scalar(@ok); + if ($diff) { + $errors .= '
  • '. + &mt('[quant,_1,IP] invalid and excluded from saved value for IP range(s) for [_2]', + $diff,$name). + '
  • '; + } + if (@ok) { + my @cidr_list; + foreach my $item (@ok) { + @cidr_list = &Net::CIDR::cidradd($item,@cidr_list); + } + $confhash{$itemid}{'ip'} = join(',',@cidr_list); + } + } + } + foreach my $field ('name','ip') { + unless (($idx eq 'add') || ($changes{$itemid})) { + if ($current{$field} ne $confhash{$itemid}{$field}) { + $changes{$itemid} = 1; + last; + } + } + } + $confhash{$itemid}{'commblocks'} = {}; + + my %commblocks; + map { $commblocks{$_} = 1; } &Apache::loncommon::get_env_multiple('form.ipaccess_block_'.$idx); + foreach my $type (@{$typeorder}) { + if ($commblocks{$type}) { + $confhash{$itemid}{'commblocks'}{$type} = 'on'; + } + unless (($idx eq 'add') || ($changes{$itemid})) { + if (ref($current{'commblocks'}) eq 'HASH') { + if ($confhash{$itemid}{'commblocks'}{$type} ne $current{'commblocks'}{$type}) { + $changes{$itemid} = 1; + } + } elsif ($confhash{$itemid}{'commblocks'}{$type}) { + $changes{$itemid} = 1; + } + } + } + $confhash{$itemid}{'courses'} = {}; + my %crsdeletions; + my @delcrs = &Apache::loncommon::get_env_multiple('form.ipaccess_course_delete_'.$idx); + if (@delcrs) { + map { $crsdeletions{$_} = 1; } @delcrs; + } + if (ref($current{'courses'}) eq 'HASH') { + foreach my $cid (sort(keys(%{$current{'courses'}}))) { + if ($crsdeletions{$cid}) { + $changes{$itemid} = 1; + } else { + $confhash{$itemid}{'courses'}{$cid} = 1; + } + } + } + $env{'form.ipaccess_cnum_'.$idx} =~ s/^\s+|\s+$//g; + $env{'form.ipaccess_cdom_'.$idx} =~ s/^\s+|\s+$//g; + if (($env{'form.ipaccess_cnum_'.$idx} =~ /^$match_courseid$/) && + ($env{'form.ipaccess_cdom_'.$idx} =~ /^$match_domain$/)) { + if (&Apache::lonnet::homeserver($env{'form.ipaccess_cnum_'.$idx}, + $env{'form.ipaccess_cdom_'.$idx}) eq 'no_host') { + $errors .= '
  • '. + &mt('Invalid courseID [_1] omitted from list of allowed courses', + $env{'form.ipaccess_cdom_'.$idx}.'_'.$env{'form.ipaccess_cnum_'.$idx}). + '
  • '; + } else { + $confhash{$itemid}{'courses'}{$env{'form.ipaccess_cdom_'.$idx}.'_'.$env{'form.ipaccess_cnum_'.$idx}} = 1; + $changes{$itemid} = 1; + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $itemid (@allpos) { + if ($itemid ne '') { + $confhash{$itemid}{'order'} = $idx; + unless ($changes{$itemid}) { + if (ref($domconfig{'ipaccess'}) eq 'HASH') { + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + if ($domconfig{'ipaccess'}{$itemid}{'order'} ne $idx) { + $changes{$itemid} = 1; + } + } + } + } + $idx ++; + } + } + } + if (keys(%changes)) { + my %defaultshash = ( + ipaccess => \%confhash, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, + $dom); + if ($putresult eq 'ok') { + my $cachetime = 1800; + &Apache::lonnet::do_cache_new('ipaccess',$dom,\%confhash,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'ipaccess'} = 1; + } + $resulttext = &mt('Changes made:').'