--- loncom/interface/lonuserutils.pm 2019/05/06 19:28:17 1.199 +++ loncom/interface/lonuserutils.pm 2024/01/05 04:01:40 1.221 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Utility functions for managing LON-CAPA user accounts # -# $Id: lonuserutils.pm,v 1.199 2019/05/06 19:28:17 raeburn Exp $ +# $Id: lonuserutils.pm,v 1.221 2024/01/05 04:01:40 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,6 +50,7 @@ use strict; use Apache::lonnet; use Apache::loncommon(); use Apache::lonhtmlcommon; +use Apache::loncoursequeueadmin; use Apache::lonlocal; use Apache::longroup; use HTML::Entities; @@ -103,7 +104,7 @@ sub modifystudent { sub modifyuserrole { my ($context,$setting,$changeauth,$cid,$udom,$uname,$uid,$umode,$upass, $first,$middle,$last,$gene,$sec,$forceid,$desiredhome,$email,$role, - $end,$start,$checkid,$inststatus) = @_; + $end,$start,$checkid,$inststatus,$emptyok) = @_; my ($scope,$userresult,$authresult,$roleresult,$idresult); if ($setting eq 'course' || $context eq 'course') { $scope = '/'.$cid; @@ -114,7 +115,11 @@ sub modifyuserrole { } elsif ($context eq 'domain') { $scope = '/'.$env{'request.role.domain'}.'/'; } elsif ($context eq 'author') { - $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'}; + if ($env{'request.role'} =~ m{^ca\.(/$match_domain/$match_username)$}) { + $scope = $1; + } else { + $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'}; + } } if ($context eq 'domain') { my $uhome = &Apache::lonnet::homeserver($uname,$udom); @@ -134,6 +139,25 @@ sub modifyuserrole { generation => $gene, id => $uid, ); + + # When "Update ID in user's course(s)" and "Force change of existing ID" + # checkboxes both checked, prevent replacement of name information + # in classlist.db file(s) for the user's course(s) with blank(s), + # in the case where the uploaded csv file was without column(s) for + # the particular field. Fields are: First Name, Middle Names/Initials, + # Last Name (or the composite: Last Name, First Names), and Generation. + + my %emptyallowed; + if ((ref($emptyok) eq 'HASH') && (keys(%{$emptyok}) > 0)) { + %emptyallowed = %{$emptyok}; + } + foreach my $field (keys(%userupdate)) { + if ($userupdate{$field} eq '') { + unless ($emptyallowed{$field}) { + delete($userupdate{$field}); + } + } + } $idresult = &propagate_id_change($uname,$udom,\%userupdate); } } @@ -153,6 +177,560 @@ sub modifyuserrole { return ($userresult,$authresult,$roleresult,$idresult); } +sub role_approval { + my ($dom,$context,$process_by,$notifydc) = @_; + if (ref($process_by) eq 'HASH') { + my %domconfig = &Apache::lonnet::get_dom('configuration',['privacy'],$dom); + if (ref($domconfig{'privacy'}) eq 'HASH') { + if (ref($notifydc) eq 'ARRAY') { + if ($domconfig{'privacy'}{'notify'} ne '') { + @{$notifydc} = split(/,/,$domconfig{'privacy'}{'notify'}); + } + } + if (ref($domconfig{'privacy'}{'approval'}) eq 'HASH') { + my %approvalconf = %{$domconfig{'privacy'}{'approval'}}; + foreach my $key ('instdom','extdom') { + if (ref($approvalconf{$key}) eq 'HASH') { + if (keys(%{$approvalconf{$key}})) { + $process_by->{$key} = $approvalconf{$key}{$context}; + } + } + } + } + } + } + return; +} + +sub get_instdoms { + my ($udom,$instdoms) = @_; + return unless (ref($instdoms) eq 'ARRAY'); + my @intdoms; + my %iphost = &Apache::lonnet::get_iphost(); + my $primary_id = &Apache::lonnet::domain($udom,'primary'); + my $primary_ip = &Apache::lonnet::get_host_ip($primary_id); + if (ref($iphost{$primary_ip}) eq 'ARRAY') { + foreach my $id (@{$iphost{$primary_ip}}) { + my $intdom = &Apache::lonnet::internet_dom($id); + unless(grep(/^\Q$intdom\E$/,@intdoms)) { + push(@intdoms,$intdom); + } + } + } + foreach my $ip (keys(%iphost)) { + if (ref($iphost{$ip}) eq 'ARRAY') { + foreach my $id (@{$iphost{$ip}}) { + my $location = &Apache::lonnet::internet_dom($id); + if ($location) { + if (grep(/^\Q$location\E$/,@intdoms)) { + my $dom = &Apache::lonnet::host_domain($id); + unless (grep(/^\Q$dom\E/,@{$instdoms})) { + push(@{$instdoms},$dom); + } + } + } + } + } + } + return; +} + +sub restricted_dom { + my ($context,$key,$udom,$uname,$role,$start,$end,$cdom,$cnum,$csec,$credits, + $process_by,$instdoms,$got_role_approvals,$got_instdoms,$reject,$pending, + $notifydc,$status,$unauthorized,$currqueued) = @_; + return if ($udom eq $cdom); + return unless ((ref($process_by) eq 'HASH') && (ref($instdoms) eq 'HASH') && + (ref($got_role_approvals) eq 'HASH') && (ref($got_instdoms) eq 'HASH') && + (ref($reject) eq 'HASH') && (ref($pending) eq 'HASH') && + (ref($notifydc) eq 'HASH') && (ref($status) eq 'HASH') && + (ref($unauthorized) eq 'HASH') && (ref($currqueued) eq 'HASH')); + my (%approval,@notify,$gotdata,$skip); + if (ref($got_role_approvals->{$context}) eq 'HASH') { + if ($got_role_approvals->{$context}{$udom}) { + $gotdata = 1; + if (ref($process_by->{$context}{$udom}) eq 'HASH') { + %approval = %{$process_by->{$context}{$udom}}; + } + } + } + unless ($gotdata) { + &role_approval($udom,$context,\%approval,\@notify); + $process_by->{$context} = { + $udom => \%approval, + }; + $got_role_approvals->{$context} = { + $udom => 1, + }; + $notifydc->{$udom} = \@notify; + } + if (ref($process_by->{$context}) eq 'HASH') { + if (ref($process_by->{$context}{$udom}) eq 'HASH') { + my @inst; + if ($got_instdoms->{$udom}) { + if (ref($instdoms->{$udom}) eq 'ARRAY') { + @inst = @{$instdoms->{$udom}}; + } + } else { + &get_instdoms(\@inst); + $instdoms->{$udom} = \@inst; + $got_instdoms->{$udom} = 1; + } + if (grep(/^\Q$cdom\E$/,@inst)) { + if (exists($approval{'instdom'})) { + my $rule = $approval{'instdom'}; + if (($rule eq 'none') || ($rule eq 'user') || ($rule eq 'domain')) { + my ($id,$currstatus,$curradj) = &get_othdomreq_status($key,$uname,$udom,$role,$cdom,$cnum,$csec); + if (($currstatus ne '') && ($curradj eq $rule)) { + $status->{$key}->{$uname.':'.$udom} = $currstatus; + } + if ($rule eq 'none') { + $reject->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + }; + $skip = 1; + } elsif (($rule eq 'user') || ($rule eq 'domain')) { + if ($curradj eq $rule) { + unless ($currstatus eq 'approved') { + if ($currstatus eq 'rejected') { + $unauthorized->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + }; + } elsif ($currstatus eq 'pending') { + $currqueued->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + adj => $rule, + }; + } + $skip = 1; + } + } else { + $pending->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + start => $start, + end => $end, + adj => $rule, + }; + if (($role eq 'st') && ($credits ne '')) { + $pending->{$key}->{$uname.':'.$udom}->{'credits'} = $credits; + } + $skip = 1; + } + } + } + } + } elsif (exists($approval{'extdom'})) { + my $rule = $approval{'extdom'}; + if (($rule eq 'none') || ($rule eq 'user') || ($rule eq 'domain')) { + my ($id,$currstatus,$curradj) = &get_othdomreq_status($key,$uname,$udom,$role,$cdom,$cnum,$csec); + if (($currstatus ne '') && ($curradj eq $rule)) { + $status->{$key}->{$uname.':'.$udom} = $currstatus; + } + if ($rule eq 'none') { + $reject->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + }; + $skip = 1; + } elsif (($rule eq 'user') || ($rule eq 'domain')) { + if ($curradj eq $rule) { + unless ($currstatus eq 'approved') { + if ($currstatus eq 'rejected') { + $unauthorized->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + }; + } elsif ($currstatus eq 'pending') { + $currqueued->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + adj => $rule, + }; + } + $skip = 1; + } + } else { + $pending->{$key}->{$uname.':'.$udom} = { + cdom => $cdom, + cnum => $cnum, + csec => $csec, + udom => $udom, + uname => $uname, + role => $role, + start => $start, + end => $end, + adj => $rule, + }; + if (($role eq 'st') && ($credits ne '')) { + $pending->{$key}->{$uname.':'.$udom}->{'credits'} = $credits; + } + $skip = 1; + } + } + } + } + } + } + return $skip; +} + +sub get_othdomreq_status { + my ($key,$uname,$udom,$role,$cdom,$cnum,$csec) = @_; + my $id = $uname.':'.$udom.':'.$role; + my ($dbnum,$currstatus,$curradj); + if (($role eq 'ca') || ($role eq 'aa')) { + $dbnum = $cnum; + } elsif ($key eq $cdom.'_'.$role) { + $dbnum = &Apache::lonnet::get_domainconfiguser($cdom); + } else { + $id .= ':'.$csec; + $dbnum = $cnum; + } + my $statusid = 'status&'.$id; + my %curr = &Apache::lonnet::get('nohist_othdomqueued',[$id,$statusid],$cdom,$dbnum); + if (ref($curr{$id}) eq 'HASH') { + $curradj = $curr{$id}{'adj'}; + } + $currstatus = $curr{$statusid}; + return ($id,$currstatus,$curradj); +} + +sub print_roles_rejected { + my ($context,$reject,$unauthorized) = @_; + return unless ((ref($reject) eq 'HASH') || (ref($unauthorized) eq 'HASH')); + my $output; + if (keys(%{$reject}) > 0) { + $output = '

'. + &mt("The following roles could not be assigned because the user is from another domain, and that domain's policies disallow it").'

'; + } + if (keys(%{$unauthorized}) > 0) { + $output = '

'. + &mt("The following roles could not be assigned because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain, and approval has been withheld.").'

'; + } + return $output; +} + +sub print_roles_queued { + my ($context,$pending,$notifydc,$currqueued) = @_; + return unless ((ref($pending) eq 'HASH') && (ref($notifydc) eq 'HASH') && + (ref($currqueued) eq 'HASH')); + my $output; + if (keys(%{$pending}) > 0) { + my $now = time; + $output = '

'. + &mt("The following role assignments have been queued because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain").'

'; + if (keys(%touser)) { + foreach my $key (keys(%touser)) { + my ($uname,$udom) = split(/:/,$key); + if (&Apache::lonnet::put('nohist_queuedrolereqs',$touser{$key},$udom,$uname) eq 'ok') { + my $owndomdesc = &Apache::lonnet::domain($udom); + &Apache::loncoursequeueadmin::send_selfserve_notification($uname.':'.$udom, + '','',$owndomdesc,$now,'othdomroleuser',$requester); + } + } + } + if (keys(%todom)) { + foreach my $dom (keys(%todom)) { + if (ref($todom{$dom}) eq 'HASH') { + my $confname = &Apache::lonnet::get_domainconfiguser($dom); + if (&Apache::lonnet::put('nohist_queuedrolereqs',$todom{$dom},$dom,$confname) eq 'ok') { + if (ref($notifydc->{$dom}) eq 'ARRAY') { + if (@{$notifydc->{$dom}} > 0) { + my $notifylist = join(',',@{$notifydc->{$dom}}); + &Apache::loncoursequeueadmin::send_selfserve_notification($notifylist, + '','','',$now,'othdomroledc',$requester); + } + } + } + } + } + } + if (keys(%crsqueue)) { + foreach my $key (keys(%crsqueue)) { + my ($cdom,$cnum) = split(/_/,$key); + if (ref($crsqueue{$key}) eq 'HASH') { + &Apache::lonnet::put('nohist_othdomqueued',$crsqueue{$key},$cdom,$cnum); + } + } + } + if (keys(%caqueue)) { + foreach my $key (keys(%caqueue)) { + my ($auname,$audom) = split(/:/,$key); + if (ref($caqueue{$key}) eq 'HASH') { + &Apache::lonnet::put('nohist_othdomqueued',$caqueue{$key},$audom,$auname); + } + } + } + if (keys(%domqueue)) { + my $confname = &Apache::lonnet::get_domainconfiguser($env{'request.role.domain'}); + &Apache::lonnet::put('nohist_othdomqueued',\%domqueue,$env{'request.role.domain'},$confname); + } + } + if (keys(%{$currqueued}) > 0) { + $output = '

'. + &mt("The following role assignments were already queued because the user is from another domain, and that domain's policies require approval by the user themselves or by a domain coordinator in that domain").'

'; + } + return $output; +} + sub propagate_id_change { my ($uname,$udom,$user) = @_; my (@types,@roles); @@ -510,7 +1088,7 @@ END "; } elsif ($mode eq 'modifycourse') { $auth_checks .= " - if (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '') { + if ((current.argfield !== null) && (current.argfield !== undefined) && (current.argfield !== '') && (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '')) { "; } if ( ($mode eq 'createcourse') || ($mode eq 'modifycourse') ) { @@ -532,7 +1110,7 @@ END END } else { my ($numrules,$intargjs) = - &passwd_validation_js('vf.elements[current.argfield].value',$domain); + &Apache::loncommon::passwd_validation_js('vf.elements[current.argfield].value',$domain); $auth_checks .= (< 0)) { - my $alertmsg = &mt('Initial password did not satisfy requirement(s):').'\n\n'; - if ($min) { - $alert{'min'} = &mt('minimum [quant,_1,character]',$min).'\n'; - } - if ($max) { - $alert{'max'} = &mt('maximum [quant,_1,character]',$max).'\n'; - } - my (@charalerts,@charrules); - if (@chars) { - if (grep(/^uc$/,@chars)) { - push(@charalerts,&mt('contain at least one upper case letter')); - push(@charrules,'uc'); - } - if (grep(/^lc$/,@chars)) { - push(@charalerts,&mt('contain at least one lower case letter')); - push(@charrules,'lc'); - } - if (grep(/^num$/,@chars)) { - push(@charalerts,&mt('contain at least one number')); - push(@charrules,'num'); - } - if (grep(/^spec$/,@chars)) { - push(@charalerts,&mt('contain at least one non-alphanumeric')); - push(@charrules,'spec'); - } - } - $intargjs = qq| var rulesmsg = '';\n|. - qq| var currpwval = $currpasswdval;\n|; - if ($min) { - $intargjs .= qq| - if (currpwval.length < $min) { - rulesmsg += ' - $alert{min}'; - } -|; - } - if ($max) { - $intargjs .= qq| - if (currpwval.length > $max) { - rulesmsg += ' - $alert{max}'; - } -|; - } - if (@chars > 0) { - my $charrulestr = '"'.join('","',@charrules).'"'; - my $charalertstr = '"'.join('","',@charalerts).'"'; - $intargjs .= qq| var brokerules = new Array();\n|. - qq| var charrules = new Array($charrulestr);\n|. - qq| var charalerts = new Array($charalertstr);\n|; - my %rules; - map { $rules{$_} = 1; } @chars; - if ($rules{'uc'}) { - $intargjs .= qq| - var ucRegExp = /[A-Z]/; - if (!ucRegExp.test(currpwval)) { - brokerules.push('uc'); - } -|; - } - if ($rules{'lc'}) { - $intargjs .= qq| - var lcRegExp = /[a-z]/; - if (!lcRegExp.test(currpwval)) { - brokerules.push('lc'); - } -|; - } - if ($rules{'num'}) { - $intargjs .= qq| - var numRegExp = /[0-9]/; - if (!numRegExp.test(currpwval)) { - brokerules.push('num'); - } -|; - } - if ($rules{'spec'}) { - $intargjs .= q| - var specRegExp = /[!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{\|}~]/; - if (!specRegExp.test(currpwval)) { - brokerules.push('spec'); - } -|; - } - $intargjs .= qq| - if (brokerules.length > 0) { - for (var i=0; i $now)) { + $env{'form.Status'} = 'Future'; + $showstart = 1; + } + if (($endaccess ne '') && ($endaccess != 0) && ($endaccess < $now)) { + $env{'form.Status'} = 'Expired'; + undef($showstart); + $showend = 1; + } + } if ($env{'form.Status'} !~ /^(Any|Expired|Active|Future)$/) { $env{'form.Status'} = 'Active'; } @@ -1790,7 +2268,7 @@ sub print_userlist { $r->print(§ion_group_filter($cnum,$cdom)); } $r->print('
'. - &column_checkboxes($context,$mode,$formname,$showcredits). + &column_checkboxes($context,$mode,$formname,$showcredits,$showstart,$showend). '
'); if ($env{'form.phase'} eq '') { $r->print('
'. @@ -1876,12 +2354,31 @@ sub print_userlist { } else { my (%cstr_roles,%dom_roles); if ($context eq 'author') { - # List co-authors and assistant co-authors my @possroles = &roles_by_context($context); - %cstr_roles = &Apache::lonnet::get_my_roles(undef,undef,undef, - \@statuses,\@possroles); - &gather_userinfo($context,$format,\%userlist,$indexhash,\%userinfo, - \%cstr_roles,$permission); + my @allowedroles; + # List co-authors and assistant co-authors + my ($auname,$audom); + if ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$}) { + ($audom,$auname) = ($1,$2); + foreach my $role (@possroles) { + if ((&Apache::lonnet::allowed('v'.$role,"$audom/$auname")) || + (&Apache::lonnet::allowed('c'.$role,"$audom/$auname"))) { + push(@allowedroles,$role); + } + } + } elsif ($env{'request.role'} =~ m{^au\./($match_domain)/}) { + if ($1 eq $env{'user.domain'}) { + $auname = $env{'user.name'}; + $audom = $env{'user.domain'}; + } + @allowedroles = @possroles; + } + if (($auname ne '') && ($audom ne '')) { + %cstr_roles = &Apache::lonnet::get_my_roles($auname,$audom,undef, + \@statuses,\@allowedroles); + &gather_userinfo($context,$format,\%userlist,$indexhash,\%userinfo, + \%cstr_roles,$permission); + } } elsif ($context eq 'domain') { if ($env{'form.roletype'} eq 'domain') { if (grep(/^authorusage$/,@cols)) { @@ -2172,14 +2669,17 @@ sub get_cols_array { push(@cols,'photo'); } if ($context eq 'domain') { - push (@cols,('authorusage','authorquota','extent')); + push(@cols,('authorusage','authorquota','extent')); + } + if ($context eq 'author') { + push(@cols,'manager'); } } return @cols; } sub column_checkboxes { - my ($context,$mode,$formname,$showcredits) = @_; + my ($context,$mode,$formname,$showcredits,$showstart,$showend) = @_; my @cols = &get_cols_array($context,$mode,$showcredits); my @showncols = &Apache::loncommon::get_env_multiple('form.showcol'); my (%disabledchk,%unchecked); @@ -2192,11 +2692,21 @@ sub column_checkboxes { if ($showcredits) { $unchecked{'credits'} = 1; } - } elsif ($context eq 'domain') { + my %curr_groups = &Apache::longroup::coursegroups(); + unless (keys(%curr_groups)) { + $unchecked{'groups'} = 1; + } + } elsif ($context eq 'domain') { $unchecked{'extent'} = 1; } - $unchecked{'start'} = 1; - $unchecked{'end'} = 1; + if ($showstart) { + $unchecked{'lastlogin'} = 1; + } else { + $unchecked{'start'} = 1; + } + unless ($showend) { + $unchecked{'end'} = 1; + } } else { if ($env{'form.Status'} ne 'Any') { $disabledchk{'status'} = 1; @@ -2213,6 +2723,11 @@ sub column_checkboxes { } elsif ($env{'form.roletype'} eq 'domain') { $disabledchk{'extent'} = 1; } + } elsif ($context eq 'author') { + if (($env{'form.Status'} eq 'Expired') || + ($env{'form.showrole'} eq 'aa')) { + $disabledchk{'manager'} = 1; + } } } my $numposs = scalar(@cols); @@ -2304,6 +2819,7 @@ sub get_column_names { 'ca' => "check all", 'ua' => "uncheck all", 'clicker' => "clicker-ID", + 'manager' => "co-author manager", ); if ($context eq 'domain' && $env{'form.roletype'} eq 'course') { $lt{'extent'} = &mt('course(s): description, section(s), status'); @@ -2433,7 +2949,6 @@ sub build_user_record { sub courses_selector { my ($cdom,$formname) = @_; - my %coursecodes = (); my %codes = (); my @codetitles = (); my %cat_titles = (); @@ -2446,14 +2961,15 @@ sub courses_selector { my $jscript = ''; my $totcodes = 0; - $totcodes = - &Apache::courseclassifier::retrieve_instcodes(\%coursecodes, - $cdom,$totcodes); - if ($totcodes > 0) { - $format_reply = - &Apache::lonnet::auto_instcode_format($caller,$cdom,\%coursecodes, - \%codes,\@codetitles,\%cat_titles,\%cat_order); - if ($format_reply eq 'ok') { + my $instcats = &Apache::lonnet::get_dom_instcats($cdom); + if (ref($instcats) eq 'HASH') { + if ((ref($instcats->{'codetitles'}) eq 'ARRAY') && (ref($instcats->{'codes'}) eq 'HASH') && + (ref($instcats->{'cat_titles'}) eq 'HASH') && (ref($instcats->{'cat_order'}) eq 'HASH')) { + %codes = %{$instcats->{'codes'}}; + @codetitles = @{$instcats->{'codetitles'}}; + %cat_titles = %{$instcats->{'cat_titles'}}; + %cat_order = %{$instcats->{'cat_order'}}; + $totcodes = scalar(keys(%codes)); my $numtypes = @codetitles; &Apache::courseclassifier::build_code_selections(\%codes,\@codetitles,\%cat_titles,\%cat_order,\%idlist,\%idnums,\%idlist_titles); my ($scripttext,$longtitles) = &Apache::courseclassifier::javascript_definitions(\@codetitles,\%idlist,\%idlist_titles,\%idnums,\%cat_titles); @@ -2588,6 +3104,7 @@ sub make_keylist_array { $index->{'instsec'} = &Apache::loncoursedata::CL_INSTSEC(); $index->{'authorquota'} = &Apache::loncoursedata::CL_AUTHORQUOTA(); $index->{'authorusage'} = &Apache::loncoursedata::CL_AUTHORUSAGE(); + $index->{'manager'} = &Apache::loncoursedata::CL_CAMANAGER(); foreach my $key (keys(%{$index})) { $keylist->[$index->{$key}] = $key; } @@ -2662,6 +3179,9 @@ sub show_users_list { (($env{'form.showrole'} eq 'Any') || ($env{'form.showrole'} eq 'au'))) { push(@sortable,('authorusage','authorquota')); } + if ($context eq 'author') { + push(@sortable,'manager'); + } } if ($mode eq 'pickauthor') { @sortable = ('username','fullname','email','status'); @@ -2979,6 +3499,7 @@ END foreach my $idx (@$keylist) { $index{$idx} = $i++; } + my $now = time; my $usercount = 0; my ($secfilter,$grpfilter); if ($context eq 'course') { @@ -2996,10 +3517,12 @@ END Future => 'Future', Expired => 'Expired', ); - # If this is for a single course get last course "log-in". - my %crslogins; + my (%crslogins,%camanagers); if ($context eq 'course') { + # If this is for a single course get last course "log-in". %crslogins=&Apache::lonnet::dump('nohist_crslastlogin',$cdom,$cnum); + } elsif ($context eq 'author') { + map { $camanagers{$_.':ca'} = 1; } split(/,/,$env{'environment.authormanagers'}); } # Get groups, role, permanent e-mail so we can sort on them if # necessary. @@ -3099,6 +3622,16 @@ END } } } + if ($context eq 'author') { + if (($camanagers{$user}) && + ((!defined($userlist->{$user}->[$index{'end'}])) || + ($userlist->{$user}->[$index{'end'}] == 0) || + ($userlist->{$user}->[$index{'end'}] > $now))) { + $userlist->{$user}->[$index{'manager'}] = &mt('Yes'); + } else { + $userlist->{$user}->[$index{'manager'}] = &mt('No'); + } + } my %emails = &Apache::loncommon::getemails($uname,$udom); if ($emails{'permanentemail'} =~ /\S/) { $userlist->{$user}->[$index{'email'}] = $emails{'permanentemail'}; @@ -3667,6 +4200,8 @@ END setSections(formname,'$crstype'); if (seccheck == 'ok') { opener.document.$callingform.newsecs.value = formname.sections.value; + } else { + return; } END } else { @@ -4292,19 +4827,42 @@ sub upfile_drop_add { $fieldstype{$field.'_choice'} = 'scalar'; } &Apache::loncommon::store_course_settings('enrollment_upload',\%fieldstype); - my ($cid,$crstype,$setting,$crsdom); + my ($cid,$crstype,$setting,$crsdom,$crsnum,$oldcrsuserdoms,%emptyok); if ($context eq 'domain') { $setting = $env{'form.roleaction'}; + if (exists($fields{'names'})) { + map { $emptyok{$_} = 1; } ('lastname','firstname','middlename'); + } else { + if (exists($fields{'lname'})) { + $emptyok{'lastname'} = 1; + } + if (exists($fields{'fname'})) { + $emptyok{'firstname'} = 1; + } + if (exists($fields{'mname'})) { + $emptyok{'middlename'} = 1; + } + } + if (exists($fields{'gen'})) { + $emptyok{'generation'} = 1; + } } if ($env{'request.course.id'} ne '') { $cid = $env{'request.course.id'}; $crstype = &Apache::loncommon::course_type(); $crsdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $crsnum = $env{'course.'.$env{'request.course.id'}.'.num'}; } elsif ($setting eq 'course') { if (&Apache::lonnet::is_course($env{'form.dcdomain'},$env{'form.dccourse'})) { $cid = $env{'form.dcdomain'}.'_'.$env{'form.dccourse'}; $crstype = &Apache::loncommon::course_type($cid); $crsdom = $env{'form.dcdomain'}; + $crsnum = $env{'form.dccourse'}; + if (exists($env{'course.'.$cid.'.internal.userdomains'})) { + $oldcrsuserdoms = 1; + } + my %coursedesc = &Apache::lonnet::coursedescription($cid,{ one_time => 1 }); + $env{'course.'.$cid.'.internal.userdomains'} = $coursedesc{'internal.userdomains'}; } } my ($startdate,$enddate) = &get_dates_from_form(); @@ -4377,6 +4935,7 @@ sub upfile_drop_add { } my $amode = ''; my $genpwd = ''; + my @genpwdfail; if ($env{'form.login'} eq 'krb') { $amode='krb'; $amode.=$env{'form.krbver'}; @@ -4385,6 +4944,8 @@ sub upfile_drop_add { $amode='internal'; if ((defined($env{'form.intarg'})) && ($env{'form.intarg'})) { $genpwd=$env{'form.intarg'}; + @genpwdfail = + &Apache::loncommon::check_passwd_rules($domain,$genpwd); } } elsif ($env{'form.login'} eq 'loc') { $amode='localauth'; @@ -4465,7 +5026,6 @@ sub upfile_drop_add { \@statuses,\@poss_roles); &gather_userinfo($context,'view',\%userlist,$indexhash,\%info, \%cstr_roles,$permission); - } } } @@ -4487,6 +5047,8 @@ sub upfile_drop_add { $r->print('

'.&mt('Adding/Modifying Users')."

\n

\n"); } $r->rflush; + my (%got_role_approvals,%got_instdoms,%process_by,%instdoms, + %pending,%reject,%notifydc,%status,%unauthorized,%currqueued); my %counts = ( user => 0, @@ -4543,7 +5105,8 @@ sub upfile_drop_add { my $newuserdom = $env{'request.role.domain'}; map { $cancreate{$_} = &can_create_user($newuserdom,$context,$_); } keys(%longtypes); # Get new users list - my (%existinguser,%userinfo,%disallow,%rulematch,%inst_results,%alerts,%checkuname); + my (%existinguser,%userinfo,%disallow,%rulematch,%inst_results,%alerts,%checkuname, + %showpasswdrules,$haspasswdmap); my $counter = -1; my (%willtrust,%trustchecked); foreach my $line (@userdata) { @@ -4694,11 +5257,43 @@ sub upfile_drop_add { } } # determine user password - my $password = $genpwd; + my $password; + my $passwdfromfile; if (defined($fields{'ipwd'})) { if ($entries{$fields{'ipwd'}}) { $password=$entries{$fields{'ipwd'}}; + $passwdfromfile = 1; + if ($env{'form.login'} eq 'int') { + my $uhome=&Apache::lonnet::homeserver($username,$userdomain); + if (($uhome eq 'no_host') || ($changeauth)) { + my @brokepwdrules = + &Apache::loncommon::check_passwd_rules($domain,$password); + if (@brokepwdrules) { + $disallow{$counter} = &mt('[_1]: Password included in file for this user did not meet requirements.', + ''.$username.''); + map { $showpasswdrules{$_} = 1; } @brokepwdrules; + next; + } + } + } + } + } + unless ($passwdfromfile) { + if ($env{'form.login'} eq 'int') { + if (@genpwdfail) { + my $uhome=&Apache::lonnet::homeserver($username,$userdomain); + if (($uhome eq 'no_host') || ($changeauth)) { + $disallow{$counter} = &mt('[_1]: No specific password in file for this user; default password did not meet requirements', + ''.$username.''); + unless ($haspasswdmap) { + map { $showpasswdrules{$_} = 1; } @genpwdfail; + $haspasswdmap = 1; + } + } + next; + } } + $password = $genpwd; } # determine user role my $role = ''; @@ -4970,12 +5565,33 @@ sub upfile_drop_add { my (%userres,%authres,%roleres,%idres); my $singlesec = ''; if ($role eq 'st') { + if (($context eq 'domain') && ($changeauth eq 'Yes') && (!$newuser)) { + if ((&Apache::lonnet::allowed('mau',$userdomain)) && + (&Apache::lonnet::homeserver($username,$userdomain) ne 'no_host')) { + if ((($amode =~ /^krb4|krb5|internal$/) && $password ne '') || + ($amode eq 'localauth')) { + $authresult = + &Apache::lonnet::modifyuserauth($userdomain,$username,$amode,$password); + } + } + } my $sec; if (ref($userinfo{$i}{'sections'}) eq 'ARRAY') { if (@secs > 0) { $sec = $secs[0]; } } + if ($userdomain ne $env{'request.role.domain'}) { + my $item = "/$crsdom/$crsnum" ; + if ($sec ne '') { + $item .= "/$sec"; + } + $item .= '_st'; + next if (&restricted_dom($context,$item,$userdomain,$username,$role,$startdate, + $enddate,$crsdom,$crsnum,$sec,$credits,\%process_by, + \%instdoms,\%got_role_approvals,\%got_instdoms,\%reject, + \%pending,\%notifydc,\%status,\%unauthorized,\%currqueued)); + } &modifystudent($userdomain,$username,$cid,$sec, $desiredhost,$context); $roleresult = @@ -4987,16 +5603,34 @@ sub upfile_drop_add { '',$context,$inststatus,$credits); $userresult = $roleresult; } else { - if ($role ne '') { + my $possrole; + if ($role ne '') { if ($context eq 'course' || $setting eq 'course') { if ($customroles{$role}) { $role = 'cr_'.$env{'user.domain'}.'_'. $env{'user.name'}.'_'.$role; } - if (($role ne 'cc') && ($role ne 'co')) { + $possrole = $role; + if ($possrole =~ /^cr_/) { + $possrole =~ s{_}{/}g; + } + if (($role ne 'cc') && ($role ne 'co')) { if (@secs > 1) { $multiple = 1; + my $prefix = "/$crsdom/$crsnum"; foreach my $sec (@secs) { + if ($userdomain ne $env{'request.role.domain'}) { + my $item = $prefix; + if ($sec ne '') { + $item .= "/$sec"; + } + $item .= '_'.$possrole; + next if (&restricted_dom($context,$item,$userdomain,$username,$possrole, + $startdate,$enddate,$crsdom,$crsnum,$sec, + $credits,\%process_by,\%instdoms,\%got_role_approvals, + \%got_instdoms,\%reject,\%pending,\%notifydc, + \%status,\%unauthorized,\%currqueued)); + } ($userres{$sec},$authres{$sec},$roleres{$sec},$idres{$sec}) = &modifyuserrole($context,$setting, $changeauth,$cid,$userdomain,$username, @@ -5010,17 +5644,30 @@ sub upfile_drop_add { $singlesec = $secs[0]; } } + } else { + $possrole = $role; } - if (!$multiple) { - ($userresult,$authresult,$roleresult,$idresult) = - &modifyuserrole($context,$setting, - $changeauth,$cid,$userdomain,$username, - $id,$amode,$password,$fname, - $mname,$lname,$gen,$singlesec, - $env{'form.forceid'},$desiredhost, - $email,$role,$enddate,$startdate, - $checkid,$inststatus); - } + } + if (!$multiple) { + if (($userdomain ne $env{'request.role.domain'}) && ($role ne '')) { + my $item = "/$crsdom/$crsnum"; + if ($singlesec ne '') { + $item .= "/$singlesec"; + } + $item .= '_'.$possrole; + next if (&restricted_dom($context,$item,$userdomain,$username,$possrole,$startdate,$enddate, + $crsdom,$crsnum,$singlesec,$credits,\%process_by,\%instdoms, + \%got_role_approvals,\%got_instdoms,\%reject,\%pending,\%notifydc, + \%status,\%unauthorized,\%currqueued)); + } + ($userresult,$authresult,$roleresult,$idresult) = + &modifyuserrole($context,$setting, + $changeauth,$cid,$userdomain,$username, + $id,$amode,$password,$fname, + $mname,$lname,$gen,$singlesec, + $env{'form.forceid'},$desiredhost, + $email,$role,$enddate,$startdate, + $checkid,$inststatus,\%emptyok); } } if ($multiple) { @@ -5042,6 +5689,13 @@ sub upfile_drop_add { } # end of loop $r->print(''); &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + if (($context eq 'domain') && ($setting eq 'course')) { + unless ($oldcrsuserdoms) { + if (exists($env{'course.'.$cid.'.internal.userdomains'})) { + delete($env{'course.'.$cid.'.internal.userdomains'}); + } + } + } } # Flush the course logs so reverse user roles immediately updated $r->register_cleanup(\&Apache::lonnet::flushcourselogs); @@ -5061,6 +5715,13 @@ sub upfile_drop_add { $counts{'auth'})."

\n"); } $r->print(&print_namespacing_alerts($domain,\%alerts,\%curr_rules)); + $r->print(&passwdrule_alerts($domain,\%showpasswdrules)); + if ((keys(%reject)) || (keys(%unauthorized))) { + $r->print(&print_roles_rejected($context,\%reject,\%unauthorized)); + } + if ((keys(%pending)) || (keys(%currqueued))) { + $r->print(&print_roles_queued($context,\%pending,\%notifydc,\%currqueued)); + } ##################################### # Display list of students to drop # ##################################### @@ -5130,6 +5791,42 @@ sub print_namespacing_alerts { } } +sub passwdrule_alerts { + my ($domain,$passwdrules) = @_; + my $warning; + if (ref($passwdrules) eq 'HASH') { + my %showrules = %{$passwdrules}; + if (keys(%showrules)) { + my %passwdconf = &Apache::lonnet::get_passwdconf($domain); + $warning = ''.&mt('Password requirement(s) unmet for one or more users:').''; + } + } + return $warning; +} + sub user_change_result { my ($r,$userresult,$authresult,$roleresult,$idresult,$counts,$flushc, $username,$userdomain,$userchg) = @_; @@ -5218,7 +5915,8 @@ sub update_user_list { if ($context eq 'course') { $crstype = &Apache::loncommon::course_type(); } - my @changelist; + my (@changelist,%got_role_approvals,%got_instdoms,%process_by,%instdoms, + %pending,%reject,%notifydc,%status,%unauthorized,%currqueued); if ($choice eq 'drop') { @changelist = &Apache::loncommon::get_env_multiple('form.droplist'); } else { @@ -5248,7 +5946,7 @@ sub update_user_list { foreach my $item (@changelist) { my ($role,$uname,$udom,$cid,$sec,$scope,$result,$type,$locktype, @sections,$scopestem,$singlesec,$showsecs,$warn_singlesec, - $nothingtodo,$keepnosection,$credits,$instsec); + $nothingtodo,$keepnosection,$credits,$instsec,$cdom,$cnum); if ($choice eq 'drop') { ($uname,$udom,$sec) = split(/:/,$item,-1); $role = 'st'; @@ -5265,6 +5963,8 @@ sub update_user_list { split(/\:/,$item,8); $instsec = &unescape($instsec); $cid = $env{'request.course.id'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; $scopestem = '/'.$cid; $scopestem =~s/\_/\//g; if ($sec eq '') { @@ -5275,15 +5975,20 @@ sub update_user_list { } elsif ($context eq 'author') { ($uname,$udom,$role) = split(/\:/,$item,-1); $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'}; + $cdom = $env{'user.domain'}; + $cnum = $env{'user.name'}; } elsif ($context eq 'domain') { if ($setting eq 'domain') { ($role,$uname,$udom) = split(/\:/,$item,-1); $scope = '/'.$env{'request.role.domain'}.'/'; + $cdom = $env{'request.role.domain'}; } elsif ($setting eq 'author') { ($uname,$udom,$role,$scope) = split(/\:/,$item); + (undef,$cdom,$cnum) = split(/\//,$scope); } elsif ($setting eq 'course') { ($uname,$udom,$role,$cid,$sec,$type,$locktype,$credits,$instsec) = split(/\:/,$item,9); + ($cdom,$cnum) = split('_',$cid); $instsec = &unescape($instsec); $scope = '/'.$cid; $scope =~s/\_/\//g; @@ -5325,7 +6030,12 @@ sub update_user_list { $start = $startdate; $end = $enddate; } + my $id = $scope.'_'.$role; if ($choice eq 'reenable') { + next if (&restricted_dom($context,$id,$udom,$uname,$role,$now,$end,$cdom,$cnum, + $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals, + \%got_instdoms,\%reject,\%pending,\%notifydc, + \%status,\%unauthorized,\%currqueued)); if ($role eq 'st') { $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec); } else { @@ -5334,6 +6044,10 @@ sub update_user_list { $now,'','',$context); } } elsif ($choice eq 'activate') { + next if (&restricted_dom($context,$id,$udom,$uname,$role,$now,$end,$cdom,$cnum, + $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals, + \%got_instdoms,\%reject,\%pending,\%notifydc, + \%status,\%unauthorized,\%currqueued)); if ($role eq 'st') { $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec); } else { @@ -5341,6 +6055,10 @@ sub update_user_list { $now,'','',$context); } } elsif ($choice eq 'chgdates') { + next if (&restricted_dom($context,$id,$udom,$uname,$role,$start,$end,$cdom,$cnum, + $sec,$credits,\%process_by,\%instdoms,\%got_role_approvals, + \%got_instdoms,\%reject,\%pending,\%notifydc, + \%status,\%unauthorized,\%currqueued)); if ($role eq 'st') { $result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,$type,$locktype,$cid,'',$context,$credits,$instsec); } else { @@ -5550,6 +6268,12 @@ sub update_user_list { $r->print(&make_dates_default($startdate,$enddate,$context,$crstype)); } } + if ((keys(%reject)) || (keys(%unauthorized))) { + $r->print(&print_roles_rejected($context,\%reject,\%unauthorized)); + } + if ((keys(%pending)) || (keys(%currqueued))) { + $r->print(&print_roles_queued($context,\%pending,\%notifydc,\%currqueued)); + } my $linktext = &mt('Display User Lists'); if ($choice eq 'drop') { $linktext = &mt('Display current class roster'); @@ -6188,6 +6912,9 @@ sub get_permission { $permission{'selfenrolladmin'} = 1; } } + unless ($permission{'selfenrolladmin'}) { + $permission{'selfenrollview'} = 1; + } } if ($env{'request.course.id'}) { my $user; @@ -6207,8 +6934,23 @@ sub get_permission { } } } elsif ($context eq 'author') { - $permission{'cusr'} = &authorpriv($env{'user.name'},$env{'request.role.domain'}); - $permission{'view'} = $permission{'cusr'}; + my $audom = $env{'request.role.domain'}; + my $auname = $env{'user.name'}; + if ((&Apache::lonnet::allowed('cca',"$audom/$auname")) || + (&Apache::lonnet::allowed('caa',"$audom/$auname"))) { + $permission{'author'} = 1; + $permission{'cusr'} = 1; + $permission{'view'} = 1; + } + } elsif ($context eq 'coauthor') { + my ($audom,$auname) = ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$}); + if ((&Apache::lonnet::allowed('vca',"$audom/$auname")) || + (&Apache::lonnet::allowed('vaa',"$audom/$auname"))) { + if ($env{"environment.internal.manager./$audom/$auname"}) { + $permission{'cusr'} = 1; + $permission{'view'} = 1; + } + } } else { my @allroles = &roles_by_context($context); foreach my $role (@allroles) { @@ -6236,8 +6978,9 @@ sub get_permission { } } my $allowed = 0; - foreach my $perm (values(%permission)) { - if ($perm) { $allowed=1; last; } + foreach my $key (keys(%permission)) { + next if (($key eq 'owner') || ($key eq 'co-owner') || ($key eq 'author')); + if ($permission{$key}) { $allowed=1; last; } } return (\%permission,$allowed); } @@ -6250,6 +6993,18 @@ sub authorpriv { || (&Apache::lonnet::allowed('caa',$audom.'/'.$auname))) { return ''; } return 1; } +sub coauthorpriv { + my ($auname,$audom)=@_; + my $uname = $env{'user.name'}; + my $udom = $env{'user.domain'}; + if (((&Apache::lonnet::allowed('vca',"$udom/$uname")) || + (&Apache::lonnet::allowed('vaa',"$udom/$uname"))) && + ($env{"environment.internal.manager./$audom/$auname"})) { + return 1; + } + return ''; +} + sub roles_on_upload { my ($context,$setting,$crstype,%customroles) = @_; my (@possible_roles,@permitted_roles); @@ -6446,7 +7201,7 @@ sub verify_authen { $finish = "document.$formname.submit();"; } my ($numrules,$intargjs) = - &passwd_validation_js('argpicked',$domain); + &Apache::loncommon::passwd_validation_js('argpicked',$domain); my $outcome = <<"ENDSCRIPT"; function auth_check() { @@ -6650,7 +7405,7 @@ sub selfenrollment_administration { } } if ($settings{'internal.selfenrollmgrdc'} ne '') { - my @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'}); + @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'}); my @diffs = &Apache::loncommon::compare_arrays(\@in_domain,$possconfigs); unless (@diffs) { return (\@in_course,\@in_domain);