--- loncom/interface/courseprefs.pm 2022/02/01 23:13:19 1.99 +++ loncom/interface/courseprefs.pm 2022/02/21 15:44:57 1.105 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set configuration settings for a course # -# $Id: courseprefs.pm,v 1.99 2022/02/01 23:13:19 raeburn Exp $ +# $Id: courseprefs.pm,v 1.105 2022/02/21 15:44:57 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -52,12 +52,16 @@ This module is used for configuration of =item process_changes() +=item process_linkprot() + =item get_sec_str() =item check_clone() =item store_changes() +=item store_linkprot() + =item update_env() =item display_disallowed() @@ -220,6 +224,7 @@ use Apache::lonparmset; use Apache::courseclassifier; use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); +use Crypt::CBC; my $registered_cleanup; my $modified_courses; @@ -365,28 +370,28 @@ sub handler { } my %values=&Apache::lonnet::dump('environment',$cdom,$cnum); - my %lti=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); + my %linkprot=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); my %ltienc = &Apache::lonnet::dump('nohist_ltienc',$cdom,$cnum,undef,undef,undef,1); - foreach my $id (keys(%lti)) { - if (ref($lti{$id}) eq 'HASH') { + foreach my $id (keys(%linkprot)) { + if (ref($linkprot{$id}) eq 'HASH') { if (ref($ltienc{$id}) eq 'HASH') { - $values{'linkprotection'}{$id} = { %{$lti{$id}}, %{$ltienc{$id}} }; + $values{'linkprot'}{$id} = { %{$linkprot{$id}}, %{$ltienc{$id}} }; } else { - $values{'linkprotection'}{$id} = $lti{$id}; + $values{'linkprot'}{$id} = $linkprot{$id}; } } unless ($phase eq 'process') { - if (ref($values{'linkprotection'}{$id}) eq 'HASH') { - delete($values{'linkprotection'}{$id}{'secret'}); + if (ref($values{'linkprot'}{$id}) eq 'HASH') { + delete($values{'linkprot'}{$id}{'secret'}); } } } - if ($lti{'lock'}) { - delete($lti{'lock'}); + if ($linkprot{'lock'}) { + delete($linkprot{'lock'}); } my @prefs_order = ('courseinfo','localization','feedback','discussion', 'classlists','appearance','grading','printouts', - 'menuitems','linkprotection','spreadsheet','bridgetasks', + 'menuitems','linkprot','spreadsheet','bridgetasks', 'lti','other'); my %prefs = ( @@ -578,7 +583,7 @@ sub handler { menucollections => 'Menu collections', }, }, - 'linkprotection' => + 'linkprot' => { text => 'Link protection', help => 'Course_Prefs_Linkprotection', @@ -793,8 +798,8 @@ sub print_config_box { $output .= &print_lti($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'menuitems') { $output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit); - } elsif ($action eq 'linkprotection') { - $output .= &print_linkprotection($cdom,$cnum,$settings,\$rowtotal,$crstype,$noedit); + } elsif ($action eq 'linkprot') { + $output .= &print_linkprotection($cdom,$cnum,$settings,\$rowtotal,$crstype,$noedit,'course'); } elsif ($action eq 'other') { $output .= &print_other($cdom,$settings,$allitems,\$rowtotal,$crstype,$noedit); } @@ -808,7 +813,7 @@ sub print_config_box { sub process_changes { my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_; - my (%newvalues,%lti,%ltienc,$errors); + my (%newvalues,$errors); if (ref($item) eq 'HASH') { if (ref($changes) eq 'HASH') { my @ordered; @@ -825,14 +830,11 @@ sub process_changes { } } } - } elsif ($action eq 'linkprotection') { + } elsif ($action eq 'linkprot') { if (ref($values->{$action}) eq 'HASH') { foreach my $id (keys(%{$values->{$action}})) { if ($id =~ /^\d+$/) { push(@ordered,$id); - unless (ref($values->{$action}->{$id}) eq 'HASH') { - $lti{$id} = ''; - } } } } @@ -977,105 +979,9 @@ sub process_changes { } elsif ($values->{'menucollections'}) { $changes->{'menucollections'} = ''; } - } elsif ($action eq 'linkprotection') { - my %menutitles = <imenu_titles(); - my $switchserver = &check_switchserver($cdom,$cnum); - my (@items,%deletions,%itemids,%haschanges); - if ($env{'form.linkprot_add'}) { - my $name = $env{'form.linkprot_name_add'}; - $name =~ s/(`)/'/g; - my ($newid,$error) = &get_courselti_id($cdom,$cnum,$name); - if ($newid) { - $itemids{'add'} = $newid; - push(@items,'add'); - $haschanges{$newid} = 1; - } else { - $errors .= ''. - &mt('Failed to acquire unique ID for link protection'). - ''; - } - } - if (ref($values->{$action}) eq 'HASH') { - my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del'); - my $maxnum = $env{'form.linkprot_maxnum'}; - for (my $i=0; $i<=$maxnum; $i++) { - my $itemid = $env{'form.linkprot_id_'.$i}; - $itemid =~ s/\D+//g; - if ($itemid) { - if (ref($values->{$action}->{$itemid}) eq 'HASH') { - push(@items,$i); - $itemids{$i} = $itemid; - if ((@todelete > 0) && (grep(/^$i$/,@todelete))) { - $deletions{$itemid} = $values->{$action}->{$itemid}->{'name'}; - } - } - } - } - } - - foreach my $idx (@items) { - my $itemid = $itemids{$idx}; - next unless ($itemid); - if (exists($deletions{$itemid})) { - $lti{$itemid} = $deletions{$itemid}; - $haschanges{$itemid} = 1; - next; - } - my %current; - if (ref($values->{$action}) eq 'HASH') { - if (ref($values->{$action}->{$itemid}) eq 'HASH') { - foreach my $key (keys(%{$values->{$action}->{$itemid}})) { - $current{$key} = $values->{$action}->{$itemid}->{$key}; - } - } - } - foreach my $inner ('name','lifetime','version') { - my $formitem = 'form.linkprot_'.$inner.'_'.$idx; - $env{$formitem} =~ s/(`)/'/g; - if ($inner eq 'lifetime') { - $env{$formitem} =~ s/[^\d.]//g; - } - unless ($idx eq 'add') { - if ($current{$inner} ne $env{$formitem}) { - $haschanges{$itemid} = 1; - } - } - if ($env{$formitem} ne '') { - $lti{$itemid}{$inner} = $env{$formitem}; - } - } - unless ($switchserver) { - my $keyitem = 'form.linkprot_key_'.$idx; - $env{$keyitem} =~ s/(`)/'/g; - unless ($idx eq 'add') { - if ($current{'key'} ne $env{$keyitem}) { - $haschanges{$itemid} = 1; - } - } - if ($env{$keyitem} ne '') { - $lti{$itemid}{'key'} = $env{$keyitem}; - } - my $secretitem = 'form.linkprot_secret_'.$idx; - $env{$secretitem} =~ s/(`)/'/g; - if ($current{'usable'}) { - if ($env{'form.linkprot_changesecret_'.$idx}) { - if ($env{$secretitem} ne '') { - $lti{$itemid}{'secret'} = $env{$secretitem}; - $haschanges{$itemid} = 1; - } - } else { - $lti{$itemid}{'secret'} = $current{'secret'}; - } - } elsif ($env{$secretitem} ne '') { - $lti{$itemid}{'secret'} = $env{$secretitem}; - $haschanges{$itemid} = 1; - } - } - } - if (keys(%haschanges)) { - foreach my $entry (keys(%haschanges)) { - $changes->{$entry} = $lti{$entry}; - } + } elsif ($action eq 'linkprot') { + if (ref($values) eq 'HASH') { + $errors = &process_linkprot($cdom,$cnum,$values->{$action},$changes,'course'); } } else { foreach my $entry (@ordered) { @@ -1570,23 +1476,237 @@ sub process_changes { return $errors; } -sub get_courselti_id { - my ($cdom,$cnum,$name) = @_; - # get lock on lti db in course +sub process_linkprot { + my ($cdom,$cnum,$values,$changes,$context) = @_; + my ($home,$dest,$ltiauth,$privkey,$privnum,$cipher,$errors,%linkprot); + if (ref($values) eq 'HASH') { + foreach my $id (keys(%{$values})) { + if ($id =~ /^\d+$/) { + unless (ref($values->{$id}) eq 'HASH') { + $linkprot{$id} = ''; + } + } + } + } + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + my @ids=&Apache::lonnet::current_machine_ids(); + if ($context eq 'domain') { + $home = &Apache::lonnet::domain($cdom,'primary'); + } else { + $home = &Apache::lonnet::homeserver($cnum,$cdom); + } + if ((($context eq 'domain') && ($domdefs{'linkprotenc_dom'})) || + (($context eq 'course') && ($domdefs{'linkprotenc_crs'}))) { + unless (($home eq 'no_host') || ($home eq '')) { + if (grep(/^\Q$home\E$/,@ids)) { + if (ref($domdefs{'privhosts'}) eq 'ARRAY') { + if (grep(/^\Q$home\E$/,@{$domdefs{'privhosts'}})) { + my %privhash = &Apache::lonnet::restore_dom('lti','private',$cdom,$home,1); + $privkey = $privhash{'key'}; + $privnum = $privhash{'version'}; + if (($privnum) && ($privkey ne '')) { + $cipher = Crypt::CBC->new({'key' => $privkey, + 'cipher' => 'DES'}); + } + } + } + } + } + } + if ($context eq 'domain') { + $dest = '/adm/domainprefs'; + $ltiauth = 1; + } else { + $dest = '/adm/courseprefs'; + if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) { + $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'}; + } else { + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + $ltiauth = $domdefs{'crsltiauth'}; + } + } + my $switchserver = &check_switchserver($cdom,$cnum,$context,$dest); + my (@items,%deletions,%itemids,%haschanges); + if ($env{'form.linkprot_add'}) { + my $name = $env{'form.linkprot_name_add'}; + $name =~ s/(`)/'/g; + my ($newid,$error) = &get_linkprot_id($cdom,$cnum,$name,$context); + if ($newid) { + $itemids{'add'} = $newid; + push(@items,'add'); + $haschanges{$newid} = 1; + } else { + $errors .= ''. + &mt('Failed to acquire unique ID for link protection'). + ''; + } + } + if (ref($values) eq 'HASH') { + my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del'); + my $maxnum = $env{'form.linkprot_maxnum'}; + for (my $i=0; $i<=$maxnum; $i++) { + my $itemid = $env{'form.linkprot_id_'.$i}; + $itemid =~ s/\D+//g; + if ($itemid) { + if (ref($values->{$itemid}) eq 'HASH') { + push(@items,$i); + $itemids{$i} = $itemid; + if ((@todelete > 0) && (grep(/^$i$/,@todelete))) { + $deletions{$itemid} = $values->{$itemid}->{'name'}; + } + } + } + } + } + foreach my $idx (@items) { + my $itemid = $itemids{$idx}; + next unless ($itemid); + if (exists($deletions{$itemid})) { + $linkprot{$itemid} = $deletions{$itemid}; + $haschanges{$itemid} = 1; + next; + } + my %current; + if (ref($values) eq 'HASH') { + if (ref($values->{$itemid}) eq 'HASH') { + foreach my $key (keys(%{$values->{$itemid}})) { + $current{$key} = $values->{$itemid}->{$key}; + } + } + } + foreach my $inner ('name','lifetime','version') { + my $formitem = 'form.linkprot_'.$inner.'_'.$idx; + $env{$formitem} =~ s/(`)/'/g; + if ($inner eq 'lifetime') { + $env{$formitem} =~ s/[^\d.]//g; + } + unless ($idx eq 'add') { + if ($current{$inner} ne $env{$formitem}) { + $haschanges{$itemid} = 1; + } + } + if ($env{$formitem} ne '') { + $linkprot{$itemid}{$inner} = $env{$formitem}; + } + } + if ($ltiauth) { + my $reqitem = 'form.linkprot_requser_'.$idx; + $env{$reqitem} =~ s/(`)/'/g; + unless ($idx eq 'add') { + if ((!$current{'requser'} && $env{$reqitem}) || + ($current{'requser'} && !$env{$reqitem})) { + $haschanges{$itemid} = 1; + } + } + if ($env{$reqitem} == 1) { + $linkprot{$itemid}{'requser'} = $env{$reqitem}; + foreach my $inner ('mapuser','notstudent') { + my $formitem = 'form.linkprot_'.$inner.'_'.$idx; + $env{$formitem} =~ s/(`)/'/g; + if ($inner eq 'mapuser') { + if ($env{$formitem} eq 'other') { + my $mapuser = $env{'form.linkprot_customuser_'.$idx}; + $mapuser =~ s/(`)/'/g; + $mapuser =~ s/^\s+|\s+$//g; + if ($mapuser ne '') { + $linkprot{$itemid}{$inner} = $mapuser; + } else { + delete($linkprot{$itemid}{'requser'}); + last; + } + } elsif ($env{$formitem} eq 'sourcedid') { + $linkprot{$itemid}{$inner} = 'lis_person_sourcedid'; + } elsif ($env{$formitem} eq 'email') { + $linkprot{$itemid}{$inner} = 'lis_person_contact_email_primary'; + } + } else { + $linkprot{$itemid}{$inner} = $env{$formitem}; + } + unless ($idx eq 'add') { + if ($current{$inner} ne $linkprot{$itemid}{$inner}) { + $haschanges{$itemid} = 1; + } + } + } + } + } + unless ($switchserver) { + my $keyitem = 'form.linkprot_key_'.$idx; + $env{$keyitem} =~ s/(`)/'/g; + unless ($idx eq 'add') { + if ($current{'key'} ne $env{$keyitem}) { + $haschanges{$itemid} = 1; + } + } + if ($env{$keyitem} ne '') { + $linkprot{$itemid}{'key'} = $env{$keyitem}; + } + my $secretitem = 'form.linkprot_secret_'.$idx; + $env{$secretitem} =~ s/(`)/'/g; + if ($current{'usable'}) { + if ($env{'form.linkprot_changesecret_'.$idx}) { + if ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $linkprot{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $linkprot{$itemid}{'cipher'} = $privnum; + } else { + $linkprot{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } else { + $linkprot{$itemid}{'secret'} = $current{'secret'}; + } + } elsif ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $linkprot{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $linkprot{$itemid}{'cipher'} = $privnum; + } else { + $linkprot{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } + } + if (keys(%haschanges)) { + foreach my $entry (keys(%haschanges)) { + $changes->{$entry} = $linkprot{$entry}; + } + } + return $errors; +} + +sub get_linkprot_id { + my ($cdom,$cnum,$name,$context) = @_; + # get lock on lti db in course or linkprot db in domain my $lockhash = { lock => $env{'user.name'}. ':'.$env{'user.domain'}, }; my $tries = 0; - my $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + my $gotlock; + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('linkprot',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + } my ($id,$error); while (($gotlock ne 'ok') && ($tries<10)) { $tries ++; sleep (0.1); - $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('linkprot',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + } } if ($gotlock eq 'ok') { - my %currids = &Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); + my %currids; + if ($context eq 'domain') { + %currids = &Apache::lonnet::dump_dom('linkprot',$cdom); + } else { + %currids = &Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); + } if ($currids{'lock'}) { delete($currids{'lock'}); if (keys(%currids)) { @@ -1600,14 +1720,25 @@ sub get_courselti_id { $id = 1; } if ($id) { - unless (&Apache::lonnet::newput('lti',{ $id => $name },$cdom,$cnum) eq 'ok') { - $error = 'nostore'; + if ($context eq 'domain') { + unless (&Apache::lonnet::newput_dom('linkprot',{ $id => $name },$cdom) eq 'ok') { + $error = 'nostore'; + } + } else { + unless (&Apache::lonnet::newput('lti',{ $id => $name },$cdom,$cnum) eq 'ok') { + $error = 'nostore'; + } } } else { $error = 'nonumber'; } } - my $dellockoutcome = &Apache::lonnet::del('lti',['lock'],$cdom,$cnum); + my $dellockoutcome; + if ($context eq 'domain') { + $dellockoutcome = &Apache::lonnet::del_dom('linkprot',['lock'],$cdom); + } else { + $dellockoutcome = &Apache::lonnet::del('lti',['lock'],$cdom,$cnum); + } } else { $error = 'nolock'; } @@ -1658,10 +1789,10 @@ sub store_changes { my ($chome,$output); my (%storehash,@delkeys,@need_env_update,@oldcloner,%oldlinkprot); if ((ref($values) eq 'HASH') && (ref($changes) eq 'HASH')) { - if (ref($values->{'linkprotection'}) eq 'HASH') { - %oldlinkprot = %{$values->{'linkprotection'}}; + if (ref($values->{'linkprot'}) eq 'HASH') { + %oldlinkprot = %{$values->{'linkprot'}}; } - delete($values->{'linkprotection'}); + delete($values->{'linkprot'}); %storehash = %{$values}; } else { if ($crstype eq 'Community') { @@ -1674,7 +1805,7 @@ sub store_changes { my ($numchanges,$skipstore); if (ref($changes) eq 'HASH') { $numchanges = scalar(keys(%{$changes})); - if (($numchanges == 1) && (exists($changes->{'linkprotection'}))) { + if (($numchanges == 1) && (exists($changes->{'linkprot'}))) { $skipstore = 1; } elsif (!$numchanges) { if ($crstype eq 'Community') { @@ -1710,103 +1841,8 @@ sub store_changes { "'$storehash{$key}'")).''; } } - } elsif ($item eq 'linkprotection') { - my (%ltienc,$lti_save_error); - if (ref($changes->{$item}) eq 'HASH') { - foreach my $id (sort { $a <=> $b } keys(%{$changes->{$item}})) { - if (ref($changes->{$item}->{$id}) eq 'HASH') { - if (exists($changes->{$item}->{$id}->{'key'})) { - $ltienc{$id}{'key'} = $changes->{$item}->{$id}->{'key'}; - delete($changes->{$item}->{$id}->{'key'}); - } - if (exists($changes->{$item}->{$id}->{'secret'})) { - $ltienc{$id}{'secret'} = $changes->{$item}->{$id}->{'secret'}; - delete($changes->{$item}->{$id}->{'secret'}); - } elsif (ref($oldlinkprot{$id}) eq 'HASH') { - if (exists($oldlinkprot{$id}{'usable'})) { - $changes->{$item}->{$id}->{'usable'} = 1; - } - } - } - } - } - if (keys(%ltienc) > 0) { - if (&Apache::lonnet::put('nohist_ltienc',\%ltienc,$cdom,$cnum,1) eq 'ok') { - foreach my $id (keys(%ltienc)) { - if (exists($ltienc{$id}{'secret'})) { - $changes->{$item}->{$id}->{'usable'} = 1; - } - } - } else { - $lti_save_error = 1; - } - } - unless ($lti_save_error) { - if (&Apache::lonnet::put('lti',$changes->{$item},$cdom,$cnum,1) eq 'ok') { - my $hashid=$cdom.'_'.$cnum; - &Apache::lonnet::devalidate_cache_new('courselti',$hashid); - $chome = &Apache::lonnet::homeserver($cnum,$cdom); - unless (($chome eq 'no_host') || ($chome eq '')) { - my @ids=&Apache::lonnet::current_machine_ids(); - unless (grep(/^\Q$chome\E$/,@ids)) { - &Apache::lonnet::devalidate_cache_new('courseltienc',$hashid); - } - } - foreach my $id (sort { $a <=> $b } %{$changes->{$item}}) { - if (ref($changes->{$item}->{$id}) eq 'HASH') { - my %values = %{$changes->{$item}->{$id}}; - my %desc = &linkprot_names(); - my $display; - foreach my $title ('name','lifetime','version','key','secret') { - if (($title eq 'key') || ($title eq 'secret')) { - if (ref($ltienc{$id}) eq 'HASH') { - if (exists($ltienc{$id}{$title})) { - if ($title eq 'secret') { - my $length = length($ltienc{$id}{$title}); - $display .= $desc{$title}.': '.('*' x $length); - } else { - $display .= $desc{$title}.': '.$ltienc{$id}{$title}.', '; - } - } - } - } elsif ($title eq 'version') { - if ($values{$title} eq 'LTI-1p0') { - $display .= $desc{$title}.': 1.1, '; - } - } else { - $display .= $desc{$title}.': '.$values{$title}.', '; - } - } - $display =~ s/, $//; - $output .= '