--- loncom/lonnet/perl/lonnet.pm 2016/08/05 11:14:06 1.1172.2.69 +++ loncom/lonnet/perl/lonnet.pm 2017/01/30 19:29:57 1.1172.2.90 @@ -1,7 +1,7 @@ # The LearningOnline Network # TCP networking package # -# $Id: lonnet.pm,v 1.1172.2.69 2016/08/05 11:14:06 raeburn Exp $ +# $Id: lonnet.pm,v 1.1172.2.90 2017/01/30 19:29:57 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -89,7 +89,7 @@ use GDBM_File; use HTML::LCParser; use Fcntl qw(:flock); use Storable qw(thaw nfreeze); -use Time::HiRes qw( gettimeofday tv_interval ); +use Time::HiRes qw( sleep gettimeofday tv_interval ); use Cache::Memcached; use Digest::MD5; use Math::Random; @@ -102,7 +102,7 @@ use LONCAPA::Lond; use File::Copy; my $readit; -my $max_connection_retries = 10; # Or some such value. +my $max_connection_retries = 20; # Or some such value. require Exporter; @@ -370,7 +370,7 @@ sub subreply { my $lockfile=$peerfile.".lock"; while (-e $lockfile) { # Need to wait for the lockfile to disappear. - sleep(1); + sleep(0.1); } # At this point, either a loncnew parent is listening or an old lonc # or loncnew child is listening so we can connect or everything's dead. @@ -388,7 +388,7 @@ sub subreply { } else { &create_connection(&hostname($server),$server); } - sleep(1); # Try again later if failed connection. + sleep(0.1); # Try again later if failed connection. } my $answer; if ($client) { @@ -417,8 +417,8 @@ sub reply { sub reconlonc { my ($lonid) = @_; - my $hostname = &hostname($lonid); if ($lonid) { + my $hostname = &hostname($lonid); my $peerfile="$perlvar{'lonSockDir'}/$hostname"; if ($hostname && -e $peerfile) { &logthis("Trying to reconnect lonc for $lonid ($hostname)"); @@ -464,7 +464,7 @@ sub critical { } my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { - &reconlonc("$perlvar{'lonSockDir'}/$server"); + &reconlonc($server); my $answer=reply($cmd,$server); if ($answer eq 'con_lost') { my $now=time; @@ -481,7 +481,7 @@ sub critical { close($dfh); } } - sleep 2; + sleep 1; my $wcmd=''; { my $dfh; @@ -1002,7 +1002,7 @@ sub choose_server { if ($login_host ne '') { $hostname = &hostname($login_host); } - return ($login_host,$hostname,$portal_path,$isredirect); + return ($login_host,$hostname,$portal_path,$isredirect,$lowest_load); } # --------------------------------------------- Try to change a user's password @@ -1274,7 +1274,7 @@ sub get_lonbalancer_config { } sub check_loadbalancing { - my ($uname,$udom) = @_; + my ($uname,$udom,$caller) = @_; my ($is_balancer,$currtargets,$currrules,$dom_in_use,$homeintdom, $rule_in_effect,$offloadto,$otherserver); my $lonhost = $perlvar{'lonHostID'}; @@ -1283,7 +1283,7 @@ sub check_loadbalancing { my $uintdom = &Apache::lonnet::internet_dom($uprimary_id); my $intdom = &Apache::lonnet::internet_dom($lonhost); my $serverhomedom = &host_domain($lonhost); - + my $domneedscache; my $cachetime = 60*60*24; if (($uintdom ne '') && ($uintdom eq $intdom)) { @@ -1298,6 +1298,8 @@ sub check_loadbalancing { &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use); if (ref($domconfig{'loadbalancing'}) eq 'HASH') { $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + } else { + $domneedscache = $dom_in_use; } } if (ref($result) eq 'HASH') { @@ -1356,7 +1358,9 @@ sub check_loadbalancing { my %domconfig = &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom); if (ref($domconfig{'loadbalancing'}) eq 'HASH') { - $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime); + $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime); + } else { + $domneedscache = $serverhomedom; } } if (ref($result) eq 'HASH') { @@ -1376,12 +1380,21 @@ sub check_loadbalancing { $is_balancer = 1; $offloadto = &this_host_spares($dom_in_use); } + unless (defined($cached)) { + $domneedscache = $serverhomedom; + } } } else { if ($perlvar{'lonBalancer'} eq 'yes') { $is_balancer = 1; $offloadto = &this_host_spares($dom_in_use); } + unless (defined($cached)) { + $domneedscache = $serverhomedom; + } + } + if ($domneedscache) { + &do_cache_new('loadbalancing',$domneedscache,$is_balancer,$cachetime); } if ($is_balancer) { my $lowest_load = 30000; @@ -1412,13 +1425,15 @@ sub check_loadbalancing { } } } - if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) { - $is_balancer = 0; - if ($uname ne '' && $udom ne '') { - if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { + unless ($caller eq 'login') { + if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) { + $is_balancer = 0; + if ($uname ne '' && $udom ne '') { + if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { - &appenv({'user.loadbalexempt' => $lonhost, - 'user.loadbalcheck.time' => time}); + &appenv({'user.loadbalexempt' => $lonhost, + 'user.loadbalcheck.time' => time}); + } } } } @@ -1554,7 +1569,7 @@ sub idget { my %servers = &get_servers($udom,'library'); foreach my $tryserver (keys(%servers)) { - my $idlist=join('&',@ids); + my $idlist=join('&', map { &escape($_); } @ids); $idlist=~tr/A-Z/a-z/; my $reply=&reply("idget:$udom:".$idlist,$tryserver); my @answer=(); @@ -1564,7 +1579,7 @@ sub idget { my $i; for ($i=0;$i<=$#ids;$i++) { if ($answer[$i]) { - $returnhash{$ids[$i]}=$answer[$i]; + $returnhash{$ids[$i]}=&unescape($answer[$i]); } } } @@ -1793,7 +1808,7 @@ sub retrieve_inst_usertypes { sub is_domainimage { my ($url) = @_; - if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+-) { + if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+[^/]-) { if (&domain($1) ne '') { return '1'; } @@ -2087,7 +2102,8 @@ sub get_domain_defaults { 'requestcourses','inststatus', 'coursedefaults','usersessions', 'requestauthor','selfenrollment', - 'coursecategories'],$domain); + 'coursecategories','autoenroll', + 'helpsettings'],$domain); my @coursetypes = ('official','unofficial','community','textbook'); if (ref($domconfig{'defaults'}) eq 'HASH') { $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; @@ -2212,6 +2228,15 @@ sub get_domain_defaults { $domdefaults{'catunauth'} = $domconfig{'coursecategories'}{'unauth'}; } } + if (ref($domconfig{'autoenroll'}) eq 'HASH') { + $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'}; + } + if (ref($domconfig{'helpsettings'}) eq 'HASH') { + $domdefaults{'submitbugs'} = $domconfig{'helpsettings'}{'submitbugs'}; + if (ref($domconfig{'helpsettings'}{'adhoc'}) eq 'HASH') { + $domdefaults{'adhocroles'} = $domconfig{'helpsettings'}{'adhoc'}; + } + } &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime); return %domdefaults; } @@ -2452,21 +2477,25 @@ sub make_key { sub devalidate_cache_new { my ($name,$id,$debug) = @_; if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); } + my $remembered_id=$name.':'.$id; $id=&make_key($name,$id); $memcache->delete($id); - delete($remembered{$id}); - delete($accessed{$id}); + delete($remembered{$remembered_id}); + delete($accessed{$remembered_id}); } sub is_cached_new { my ($name,$id,$debug) = @_; - $id=&make_key($name,$id); - if (exists($remembered{$id})) { - if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); } - $accessed{$id}=[&gettimeofday()]; + my $remembered_id=$name.':'.$id; # this is to avoid make_key (which is slow) for + # keys in %remembered hash, which persists for + # duration of request (no restriction on key length). + if (exists($remembered{$remembered_id})) { + if ($debug) { &Apache::lonnet::logthis("Early return $remembered_id of $remembered{$remembered_id} "); } + $accessed{$remembered_id}=[&gettimeofday()]; $hits++; - return ($remembered{$id},1); + return ($remembered{$remembered_id},1); } + $id=&make_key($name,$id); my $value = $memcache->get($id); if (!(defined($value))) { if ($debug) { &Apache::lonnet::logthis("getting $id is not defined"); } @@ -2476,13 +2505,14 @@ sub is_cached_new { if ($debug) { &Apache::lonnet::logthis("getting $id is __undef__"); } $value=undef; } - &make_room($id,$value,$debug); + &make_room($remembered_id,$value,$debug); if ($debug) { &Apache::lonnet::logthis("getting $id is $value"); } return ($value,1); } sub do_cache_new { my ($name,$id,$value,$time,$debug) = @_; + my $remembered_id=$name.':'.$id; $id=&make_key($name,$id); my $setvalue=$value; if (!defined($setvalue)) { @@ -2498,17 +2528,17 @@ sub do_cache_new { $memcache->disconnect_all(); } # need to make a copy of $value - &make_room($id,$value,$debug); + &make_room($remembered_id,$value,$debug); return $value; } sub make_room { - my ($id,$value,$debug)=@_; + my ($remembered_id,$value,$debug)=@_; - $remembered{$id}= (ref($value)) ? &Storable::dclone($value) + $remembered{$remembered_id}= (ref($value)) ? &Storable::dclone($value) : $value; if ($to_remember<0) { return; } - $accessed{$id}=[&gettimeofday()]; + $accessed{$remembered_id}=[&gettimeofday()]; if (scalar(keys(%remembered)) <= $to_remember) { return; } my $to_kick; my $max_time=0; @@ -3887,7 +3917,7 @@ sub flushcourselogs { } } # -# Reverse lookup of domain roles (dc, ad, li, sc, au) +# Reverse lookup of domain roles (dc, ad, li, sc, dh, da, au) # my %domrolebuffer = (); foreach my $entry (keys(%domainrolehash)) { @@ -3902,10 +3932,19 @@ sub flushcourselogs { delete $domainrolehash{$entry}; } foreach my $dom (keys(%domrolebuffer)) { - my %servers = &get_servers($dom,'library'); + my %servers; + if (defined(&domain($dom,'primary'))) { + my $primary=&domain($dom,'primary'); + my $hostname=&hostname($primary); + $servers{$primary} = $hostname; + } else { + %servers = &get_servers($dom,'library'); + } foreach my $tryserver (keys(%servers)) { - unless (&reply('domroleput:'.$dom.':'. - $domrolebuffer{$dom},$tryserver) eq 'ok') { + if (&reply('domroleput:'.$dom.':'. + $domrolebuffer{$dom},$tryserver) eq 'ok') { + last; + } else { &logthis('Put of domain roles failed for '.$dom.' and '.$tryserver); } } @@ -4025,7 +4064,7 @@ sub userrolelog { {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'} =$tend.':'.$tstart; } - if ($trole =~ /^(dc|ad|li|au|dg|sc)/ ) { + if ($trole =~ /^(dc|ad|li|au|dg|sc|dh|da)/ ) { my (undef,$rudom,$runame,$rsec)=split(/\//,$area); $domainrolehash {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec} @@ -4252,6 +4291,195 @@ sub get_my_roles { return %returnhash; } +sub get_all_adhocroles { + my ($dom) = @_; + my @roles_by_num = (); + my %domdefaults = &get_domain_defaults($dom); + my (%description,%access_in_dom,%access_info); + if (ref($domdefaults{'adhocroles'}) eq 'HASH') { + my $count = 0; + my %domcurrent = %{$domdefaults{'adhocroles'}}; + my %ordered; + foreach my $role (sort(keys(%domcurrent))) { + my ($order,$desc,$access_in_dom); + if (ref($domcurrent{$role}) eq 'HASH') { + $order = $domcurrent{$role}{'order'}; + $desc = $domcurrent{$role}{'desc'}; + $access_in_dom{$role} = $domcurrent{$role}{'access'}; + $access_info{$role} = $domcurrent{$role}{$access_in_dom{$role}}; + } + if ($order eq '') { + $order = $count; + } + $ordered{$order} = $role; + if ($desc ne '') { + $description{$role} = $desc; + } else { + $description{$role}= $role; + } + $count++; + } + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@roles_by_num,$ordered{$item}); + } + } + return (\@roles_by_num,\%description,\%access_in_dom,\%access_info); +} + +sub get_my_adhocroles { + my ($cid,$checkreg) = @_; + my ($cdom,$cnum,%info,@possroles,$description,$roles_by_num); + if ($env{'request.course.id'} eq $cid) { + $cdom = $env{'course.'.$cid.'.domain'}; + $cnum = $env{'course.'.$cid.'.num'}; + $info{'internal.coursecode'} = $env{'course.'.$cid.'.internal.coursecode'}; + } elsif ($cid =~ /^($match_domain)_($match_courseid)$/) { + $cdom = $1; + $cnum = $2; + %info = &Apache::lonnet::get('environment',['internal.coursecode'], + $cdom,$cnum); + } + if (($info{'internal.coursecode'} ne '') && ($checkreg)) { + my $user = $env{'user.name'}.':'.$env{'user.domain'}; + my %rosterhash = &get('classlist',[$user],$cdom,$cnum); + if ($rosterhash{$user} ne '') { + my $type = (split(/:/,$rosterhash{$user}))[5]; + return ([],{}) if ($type eq 'auto'); + } + } + if (($cdom ne '') && ($cnum ne '')) { + if (($env{"user.role.dh./$cdom/"}) || ($env{"user.role.da./$cdom/"})) { + my $then=$env{'user.login.time'}; + my $update=$env{'user.update.time'}; + if (!$update) { + $update = $then; + } + my @liveroles; + foreach my $role ('dh','da') { + if ($env{"user.role.$role./$cdom/"}) { + my ($tstart,$tend)=split(/\./,$env{"user.role.$role./$cdom/"}); + my $limit = $update; + if ($env{'request.role'} eq "$role./$cdom/") { + $limit = $then; + } + my $activerole = 1; + if ($tstart && $tstart>$limit) { $activerole = 0; } + if ($tend && $tend <$limit) { $activerole = 0; } + if ($activerole) { + push(@liveroles,$role); + } + } + } + if (@liveroles) { + if (&homeserver($cnum,$cdom) ne 'no_host') { + my ($accessref,$accessinfo,%access_in_dom); + ($roles_by_num,$description,$accessref,$accessinfo) = &get_all_adhocroles($cdom); + if (ref($roles_by_num) eq 'ARRAY') { + if (@{$roles_by_num}) { + my %settings; + if ($env{'request.course.id'} eq $cid) { + foreach my $envkey (keys(%env)) { + if ($envkey =~ /^\Qcourse.$cid.\E(internal\.adhoc.+)$/) { + $settings{$1} = $env{$envkey}; + } + } + } else { + %settings = &dump('environment',$cdom,$cnum,'internal\.adhoc'); + } + my %setincrs; + if ($settings{'internal.adhocaccess'}) { + map { $setincrs{$_} = 1; } split(/,/,$settings{'internal.adhocaccess'}); + } + my @statuses; + if ($env{'environment.inststatus'}) { + @statuses = split(/,/,$env{'environment.inststatus'}); + } + my $user = $env{'user.name'}.':'.$env{'user.domain'}; + if (ref($accessref) eq 'HASH') { + %access_in_dom = %{$accessref}; + } + foreach my $role (@{$roles_by_num}) { + my ($curraccess,@okstatus,@personnel); + if ($setincrs{$role}) { + ($curraccess,my $rest) = split(/=/,$settings{'internal.adhoc.'.$role}); + if ($curraccess eq 'status') { + @okstatus = split(/\&/,$rest); + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + @personnel = split(/\&/,$rest); + } + } else { + $curraccess = $access_in_dom{$role}; + if (ref($accessinfo) eq 'HASH') { + if ($curraccess eq 'status') { + if (ref($accessinfo->{$role}) eq 'ARRAY') { + @okstatus = @{$accessinfo->{$role}}; + } + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + if (ref($accessinfo->{$role}) eq 'ARRAY') { + @personnel = @{$accessinfo->{$role}}; + } + } + } + } + if ($curraccess eq 'none') { + next; + } elsif ($curraccess eq 'all') { + push(@possroles,$role); + } elsif ($curraccess eq 'dh') { + if (grep(/^dh$/,@liveroles)) { + push(@possroles,$role); + } else { + next; + } + } elsif ($curraccess eq 'da') { + if (grep(/^da$/,@liveroles)) { + push(@possroles,$role); + } else { + next; + } + } elsif ($curraccess eq 'status') { + if (@okstatus) { + if (!@statuses) { + if (grep(/^default$/,@okstatus)) { + push(@possroles,$role); + } + } else { + foreach my $status (@okstatus) { + if (grep(/^\Q$status\E$/,@statuses)) { + push(@possroles,$role); + last; + } + } + } + } + } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) { + if (grep(/^\Q$user\E$/,@personnel)) { + if ($curraccess eq 'exc') { + push(@possroles,$role); + } + } elsif ($curraccess eq 'inc') { + push(@possroles,$role); + } + } + } + } + } + } + } + } + } + unless (ref($description) eq 'HASH') { + if (ref($roles_by_num) eq 'ARRAY') { + my %desc; + map { $desc{$_} = $_; } (@{$roles_by_num}); + $description = \%desc; + } else { + $description = {}; + } + } + return (\@possroles,$description); +} + # ----------------------------------------------------- Frontpage Announcements # # @@ -4492,6 +4720,21 @@ sub get_domain_roles { return %personnel; } +sub get_active_domroles { + my ($dom,$roles) = @_; + return () unless (ref($roles) eq 'ARRAY'); + my $now = time; + my %dompersonnel = &get_domain_roles($dom,$roles,$now,$now); + my %domroles; + foreach my $server (keys(%dompersonnel)) { + foreach my $user (sort(keys(%{$dompersonnel{$server}}))) { + my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,$user); + $domroles{$uname.':'.$udom} = $dompersonnel{$server}{$user}; + } + } + return %domroles; +} + # ----------------------------------------------------------- Interval timing { @@ -5468,6 +5711,10 @@ sub custom_roleprivs { $$allroles{$spec.'./'.$tdomain.'/'}.=':'.$dompriv; } if (($trest ne '') && (defined($coursepriv))) { + if ($trole =~ m{^cr/$tdomain/$tdomain\Q-domainconfig\E/([^/]+)$}) { + my $rolename = $1; + $coursepriv = &course_adhocrole_privs($rolename,$tdomain,$trest,$coursepriv); + } $$allroles{'cm.'.$area}.=':'.$coursepriv; $$allroles{$spec.'.'.$area}.=':'.$coursepriv; } @@ -5476,6 +5723,48 @@ sub custom_roleprivs { } } +sub course_adhocrole_privs { + my ($rolename,$cdom,$cnum,$coursepriv) = @_; + my %overrides = &get('environment',["internal.adhocpriv.$rolename"],$cdom,$cnum); + if ($overrides{"internal.adhocpriv.$rolename"}) { + my (%currprivs,%storeprivs); + foreach my $item (split(/:/,$coursepriv)) { + my ($priv,$restrict) = split(/\&/,$item); + $currprivs{$priv} = $restrict; + } + my (%possadd,%possremove,%full); + foreach my $item (split(/\:/,$Apache::lonnet::pr{'cr:c'})) { + my ($priv,$restrict)=split(/\&/,$item); + $full{$priv} = $restrict; + } + foreach my $item (split(/,/,$overrides{"internal.adhocpriv.$rolename"})) { + next if ($item eq ''); + my ($rule,$rest) = split(/=/,$item); + next unless (($rule eq 'off') || ($rule eq 'on')); + foreach my $priv (split(/:/,$rest)) { + if ($priv ne '') { + if ($rule eq 'off') { + $possremove{$priv} = 1; + } else { + $possadd{$priv} = 1; + } + } + } + } + foreach my $priv (sort(keys(%full))) { + if (exists($currprivs{$priv})) { + unless (exists($possremove{$priv})) { + $storeprivs{$priv} = $currprivs{$priv}; + } + } elsif (exists($possadd{$priv})) { + $storeprivs{$priv} = $full{$priv}; + } + } + $coursepriv = ':'.join(':',map { $_.'&'.$storeprivs{$_}; } sort(keys(%storeprivs))); + } + return $coursepriv; +} + sub group_roleprivs { my ($allgroups,$area,$group_privs,$tend,$tstart) = @_; my $access = 1; @@ -5688,39 +5977,56 @@ sub delete_env_groupprivs { } sub check_adhoc_privs { - my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_; + my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller,$sec) = @_; my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum; + if ($sec) { + $cckey .= '/'.$sec; + } my $setprivs; if ($env{$cckey}) { my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend); &role_status($cckey,$update,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend); unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) { - &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller,$sec); $setprivs = 1; } } else { - &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller); + &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller,$sec); $setprivs = 1; } return $setprivs; } sub set_adhoc_privileges { -# role can be cc or ca - my ($dcdom,$pickedcourse,$role,$caller) = @_; +# role can be cc, ca, or cr//-domainconfig/role + my ($dcdom,$pickedcourse,$role,$caller,$sec) = @_; my $area = '/'.$dcdom.'/'.$pickedcourse; + if ($sec ne '') { + $area .= '/'.$sec; + } my $spec = $role.'.'.$area; my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'}, $env{'user.name'},1); - my %ccrole = (); - &standard_roleprivs(\%ccrole,$role,$dcdom,$spec,$pickedcourse,$area); - my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole); + my %rolehash = (); + if ($role =~ m{^\Qcr/$dcdom/$dcdom\E\-domainconfig/(\w+)$}) { + my $rolename = $1; + &custom_roleprivs(\%rolehash,$role,$dcdom,$pickedcourse,$spec,$area); + my %domdef = &get_domain_defaults($dcdom); + if (ref($domdef{'adhocroles'}) eq 'HASH') { + if (ref($domdef{'adhocroles'}{$rolename}) eq 'HASH') { + &appenv({'request.role.desc' => $domdef{'adhocroles'}{$rolename}{'desc'},}); + } + } + } else { + &standard_roleprivs(\%rolehash,$role,$dcdom,$spec,$pickedcourse,$area); + } + my ($author,$adv)= &set_userprivs(\%userroles,\%rolehash); &appenv(\%userroles,[$role,'cm']); &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role); unless ($caller eq 'constructaccess' && $env{'request.course.id'}) { &appenv( {'request.role' => $spec, 'request.role.domain' => $dcdom, - 'request.course.sec' => '' + 'request.course.sec' => $sec, } ); my $tadv=0; @@ -6102,9 +6408,11 @@ sub tmpget { if (!defined($server)) { $server = $perlvar{'lonHostID'}; } my $rep=&reply("tmpget:$token",$server); my %returnhash; + if ($rep =~ /^(con_lost|error|no_such_host)/i) { + return %returnhash; + } foreach my $item (split(/\&/,$rep)) { my ($key,$value)=split(/=/,$item); - next if ($key =~ /^error: 2 /); $returnhash{&unescape($key)}=&thaw_unescape($value); } return %returnhash; @@ -6219,9 +6527,9 @@ sub sixnum_code { # -------------------------------------------------- portfolio access checking sub portfolio_access { - my ($requrl) = @_; + my ($requrl,$clientip) = @_; my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl); - my $result = &get_portfolio_access($udom,$unum,$file_name,$group); + my $result = &get_portfolio_access($udom,$unum,$file_name,$group,$clientip); if ($result) { my %setters; if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') { @@ -6247,7 +6555,7 @@ sub portfolio_access { } sub get_portfolio_access { - my ($udom,$unum,$file_name,$group,$access_hash) = @_; + my ($udom,$unum,$file_name,$group,$clientip,$access_hash) = @_; if (!ref($access_hash)) { my $current_perms = &get_portfile_permissions($udom,$unum); @@ -6256,7 +6564,7 @@ sub get_portfolio_access { $access_hash = $access_controls{$file_name}; } - my ($public,$guest,@domains,@users,@courses,@groups); + my ($public,$guest,@domains,@users,@courses,@groups,@ips); my $now = time; if (ref($access_hash) eq 'HASH') { foreach my $key (keys(%{$access_hash})) { @@ -6280,10 +6588,25 @@ sub get_portfolio_access { push(@courses,$key); } elsif ($scope eq 'group') { push(@groups,$key); + } elsif ($scope eq 'ip') { + push(@ips,$key); } } if ($public) { return 'ok'; + } elsif (@ips > 0) { + my $allowed; + foreach my $ipkey (@ips) { + if (ref($access_hash->{$ipkey}{'ip'}) eq 'ARRAY') { + if (&Apache::loncommon::check_ip_acc(join(',',@{$access_hash->{$ipkey}{'ip'}}),$clientip)) { + $allowed = 1; + last; + } + } + } + if ($allowed) { + return 'ok'; + } } if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') { if ($guest) { @@ -7003,7 +7326,7 @@ sub allowed { && $thisallowed ne 'F' && $thisallowed ne '2' && &is_portfolio_url($uri)) { - $thisallowed = &portfolio_access($uri); + $thisallowed = &portfolio_access($uri,$clientip); } # Full access at system, domain or course-wide level? Exit. @@ -7272,7 +7595,7 @@ sub constructaccess { my ($ownername,$ownerdomain,$ownerhome); ($ownerdomain,$ownername) = - ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)/priv/($match_domain)/($match_username)/}); + ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)/priv/($match_domain)/($match_username)(?:/|$)}); # The URL does not really point to any authorspace, forget it unless (($ownername) && ($ownerdomain)) { return ''; } @@ -7610,7 +7933,7 @@ sub get_symb_from_alias { sub definerole { if (allowed('mcr','/')) { - my ($rolename,$sysrole,$domrole,$courole)=@_; + my ($rolename,$sysrole,$domrole,$courole,$uname,$udom)=@_; foreach my $role (split(':',$sysrole)) { my ($crole,$cqual)=split(/\&/,$role); if ($pr{'cr:s'}!~/\Q$crole\E/) { return "refused:s:$crole"; } @@ -7638,11 +7961,19 @@ sub definerole { } } } + my $uhome; + if (($uname ne '') && ($udom ne '')) { + $uhome = &homeserver($uname,$udom); + return $uhome if ($uhome eq 'no_host'); + } else { + $uname = $env{'user.name'}; + $udom = $env{'user.domain'}; + $uhome = $env{'user.home'}; + } my $command="encrypt:rolesput:$env{'user.domain'}:$env{'user.name'}:". - "$env{'user.domain'}:$env{'user.name'}:". - "rolesdef_$rolename=". + "$udom:$uname:rolesdef_$rolename=". escape($sysrole.'_'.$domrole.'_'.$courole); - return reply($command,$env{'user.home'}); + return reply($command,$uhome); } else { return 'refused'; } @@ -7724,10 +8055,12 @@ sub update_allusers_table { sub fetch_enrollment_query { my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_; - my $homeserver; + my ($homeserver,$sleep,$loopmax); my $maxtries = 1; if ($context eq 'automated') { $homeserver = $perlvar{'lonHostID'}; + $sleep = 2; + $loopmax = 100; $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout } else { $homeserver = &homeserver($cnum,$dom); @@ -7745,17 +8078,17 @@ sub fetch_enrollment_query { &logthis('fetch_enrollment_query: invalid queryid: '.$queryid.' for host: '.$host.' and homeserver: '.$homeserver.' context: '.$context.' '.$cnum); return 'error: '.$queryid; } - my $reply = &get_query_reply($queryid); + my $reply = &get_query_reply($queryid,$sleep.$loopmax); my $tries = 1; while (($reply=~/^timeout/) && ($tries < $maxtries)) { - $reply = &get_query_reply($queryid); + $reply = &get_query_reply($queryid,$sleep,$loopmax); $tries ++; } if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) { &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$env{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries); } else { my @responses = split(/:/,$reply); - if ($homeserver eq $perlvar{'lonHostID'}) { + if (grep { $_ eq $homeserver } ¤t_machine_ids()) { foreach my $line (@responses) { my ($key,$value) = split(/=/,$line,2); $$replyref{$key} = $value; @@ -7790,11 +8123,17 @@ sub fetch_enrollment_query { } sub get_query_reply { - my $queryid=shift; + my ($queryid,$sleep,$loopmax) = @_; + if (($sleep eq '') || ($sleep !~ /^\d+\.?\d*$/)) { + $sleep = 0.2; + } + if (($loopmax eq '') || ($loopmax =~ /\D/)) { + $loopmax = 100; + } my $replyfile=LONCAPA::tempdir().$queryid; my $reply=''; - for (1..100) { - sleep 2; + for (1..$loopmax) { + sleep($sleep); if (-e $replyfile.'.end') { if (open(my $fh,$replyfile)) { $reply = join('',<$fh>); @@ -8216,6 +8555,33 @@ sub auto_crsreq_update { return \%crsreqresponse; } +sub auto_export_grades { + my ($cdom,$cnum,$inforef,$gradesref) = @_; + my ($homeserver,%exportresponse); + if ($cdom =~ /^$match_domain$/) { + $homeserver = &domain($cdom,'primary'); + } + unless (($homeserver eq 'no_host') || ($homeserver eq '')) { + my $info; + if (ref($inforef) eq 'HASH') { + $info = &freeze_escape($inforef); + } + if (ref($gradesref) eq 'HASH') { + my $grades = &freeze_escape($gradesref); + my $response=&reply('encrypt:autoexportgrades:'.$cdom.':'.$cnum.':'. + $info.':'.$grades,$homeserver); + unless ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/) { + my @items = split(/&/,$response); + foreach my $item (@items) { + my ($key,$value) = split('=',$item); + $exportresponse{&unescape($key)} = &thaw_unescape($value); + } + } + } + } + return \%exportresponse; +} + sub check_instcode_cloning { my ($codedefaults,$code_order,$cloner,$clonefromcode,$clonetocode) = @_; unless ((ref($codedefaults) eq 'HASH') && (ref($code_order) eq 'ARRAY')) { @@ -8654,7 +9020,8 @@ sub assignrole { &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag, $selfenroll,$context); } elsif (($role eq 'li') || ($role eq 'dg') || ($role eq 'sc') || - ($role eq 'au') || ($role eq 'dc')) { + ($role eq 'au') || ($role eq 'dc') || ($role eq 'dh') || + ($role eq 'da')) { &domainrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag, $context); } elsif (($role eq 'ca') || ($role eq 'aa')) { @@ -8959,7 +9326,7 @@ sub modifyuser { sub modifystudent { my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec, $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid, - $selfenroll,$context,$inststatus,$credits)=@_; + $selfenroll,$context,$inststatus,$credits,$instsec)=@_; if (!$cid) { unless ($cid=$env{'request.course.id'}) { return 'not_in_class'; @@ -8975,13 +9342,13 @@ sub modifystudent { $uid = undef if (!$forceid); $reply = &modify_student_enrollment($udom,$uname,$uid,$first,$middle,$last, $gene,$usec,$end,$start,$type,$locktype, - $cid,$selfenroll,$context,$credits); + $cid,$selfenroll,$context,$credits,$instsec); return $reply; } sub modify_student_enrollment { my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type, - $locktype,$cid,$selfenroll,$context,$credits) = @_; + $locktype,$cid,$selfenroll,$context,$credits,$instsec) = @_; my ($cdom,$cnum,$chome); if (!$cid) { unless ($cid=$env{'request.course.id'}) { @@ -9028,7 +9395,7 @@ sub modify_student_enrollment { my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum); my $reply=cput('classlist', {$user => - join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits) }, + join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) }, $cdom,$cnum); if (($reply eq 'ok') || ($reply eq 'delayed')) { &devalidate_getsection_cache($udom,$uname,$cid); @@ -9552,9 +9919,9 @@ sub modify_access_controls { my $tries = 0; my $gotlock = &newput('file_permissions',$lockhash,$domain,$user); - while (($gotlock ne 'ok') && $tries <3) { + while (($gotlock ne 'ok') && $tries < 10) { $tries ++; - sleep 1; + sleep(0.1); $gotlock = &newput('file_permissions',$lockhash,$domain,$user); } if ($gotlock eq 'ok') { @@ -9847,7 +10214,23 @@ sub dirlist { foreach my $user (sort(keys(%allusers))) { push(@alluserslist,$user.'&user'); } - return (\@alluserslist); + if (!%listerror) { + # no errors + return (\@alluserslist); + } elsif (scalar(keys(%servers)) == 1) { + # one library server, one error + my ($key) = keys(%listerror); + return (\@alluserslist, $listerror{$key}); + } elsif ( grep { $_ eq 'con_lost' } values(%listerror) ) { + # con_lost indicates that we might miss data from at least one + # library server + return (\@alluserslist, 'con_lost'); + } else { + # multiple library servers and no con_lost -> data should be + # complete. + return (\@alluserslist); + } + } else { return ([],'missing username'); } @@ -10064,10 +10447,12 @@ sub get_userresdata { } #error 2 occurs when the .db doesn't exist if ($tmp!~/error: 2 /) { - &logthis("WARNING:". - " Trying to get resource data for ". - $uname." at ".$udom.": ". - $tmp.""); + if ((!defined($cached)) || ($tmp ne 'con_lost')) { + &logthis("WARNING:". + " Trying to get resource data for ". + $uname." at ".$udom.": ". + $tmp.""); + } } elsif ($tmp=~/error: 2 /) { #&EXT_cache_set($udom,$uname); &do_cache_new('userres',$hashid,undef,600); @@ -12284,8 +12669,8 @@ sub fetch_dns_checksums { } sub load_domain_tab { - my ($ignore_cache) = @_; - &get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache); + my ($ignore_cache,$nocache) = @_; + &get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache,$nocache); my $fh; if (open($fh,"<".$perlvar{'lonTabDir'}.'/domain.tab')) { my @lines = <$fh>; @@ -12371,8 +12756,8 @@ sub fetch_dns_checksums { } sub load_hosts_tab { - my ($ignore_cache) = @_; - &get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache); + my ($ignore_cache,$nocache) = @_; + &get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache,$nocache); open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab"); my @config = <$config>; &parse_hosts_tab(\@config); @@ -12394,7 +12779,8 @@ sub fetch_dns_checksums { } sub all_names { - &load_hosts_tab() if (!$loaded); + my ($ignore_cache,$nocache) = @_; + &load_hosts_tab($ignore_cache,$nocache) if (!$loaded); return %name_to_host; } @@ -12516,7 +12902,7 @@ sub fetch_dns_checksums { } sub get_iphost { - my ($ignore_cache) = @_; + my ($ignore_cache,$nocache) = @_; if (!$ignore_cache) { if (%iphost) { @@ -12540,7 +12926,7 @@ sub fetch_dns_checksums { %old_name_to_ip = %{$ip_info->[1]}; } - my %name_to_host = &all_names(); + my %name_to_host = &all_names($ignore_cache,$nocache); foreach my $name (keys(%name_to_host)) { my $ip; if (!exists($name_to_ip{$name})) { @@ -12565,9 +12951,11 @@ sub fetch_dns_checksums { } push(@{$iphost{$ip}},@{$name_to_host{$name}}); } - &do_cache_new('iphost','iphost', - [\%iphost,\%name_to_ip,\%lonid_to_ip], - 48*60*60); + unless ($nocache) { + &do_cache_new('iphost','iphost', + [\%iphost,\%name_to_ip,\%lonid_to_ip], + 48*60*60); + } return %iphost; } @@ -13125,9 +13513,10 @@ in which case the null string is returne =item * -definerole($rolename,$sysrole,$domrole,$courole) : define role; define a custom -role rolename set privileges in format of lonTabs/roles.tab for system, domain, -and course level +definerole($rolename,$sysrole,$domrole,$courole,$uname,$udom) : define role; +define a custom role rolename set privileges in format of lonTabs/roles.tab +for system, domain, and course level. $uname and $udom are optional (current +user's username and domain will be used when either of $uname or $udom are absent. =item * @@ -13331,6 +13720,8 @@ Inputs: =item $credits, number of credits student will earn from this class +=item $instsec, institutional course section code for student + =back