--- loncom/interface/lonuserutils.pm 2024/08/24 23:49:48 1.184.4.10.2.8 +++ loncom/interface/lonuserutils.pm 2020/02/10 17:12:54 1.205 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Utility functions for managing LON-CAPA user accounts # -# $Id: lonuserutils.pm,v 1.184.4.10.2.8 2024/08/24 23:49:48 raeburn Exp $ +# $Id: lonuserutils.pm,v 1.205 2020/02/10 17:12:54 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -103,7 +103,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,$emptyok) = @_; + $end,$start,$checkid,$inststatus) = @_; my ($scope,$userresult,$authresult,$roleresult,$idresult); if ($setting eq 'course' || $context eq 'course') { $scope = '/'.$cid; @@ -114,11 +114,7 @@ sub modifyuserrole { } elsif ($context eq 'domain') { $scope = '/'.$env{'request.role.domain'}.'/'; } elsif ($context eq 'author') { - if ($env{'request.role'} =~ m{^ca\.(/$match_domain/$match_username)$}) { - $scope = $1; - } else { - $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'}; - } + $scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'}; } if ($context eq 'domain') { my $uhome = &Apache::lonnet::homeserver($uname,$udom); @@ -138,25 +134,6 @@ 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); } } @@ -533,7 +510,7 @@ END "; } elsif ($mode eq 'modifycourse') { $auth_checks .= " - if ((current.argfield !== null) && (current.argfield !== undefined) && (current.argfield !== '') && (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '')) { + if (vf.elements[current.argfield].value == null || vf.elements[current.argfield].value == '') { "; } if ( ($mode eq 'createcourse') || ($mode eq 'modifycourse') ) { @@ -555,7 +532,7 @@ END END } else { my ($numrules,$intargjs) = - &Apache::loncommon::passwd_validation_js('vf.elements[current.argfield].value',$domain); + &passwd_validation_js('vf.elements[current.argfield].value',$domain); $auth_checks .= (< $min) { + $min = $passwdconf{'min'}; + } + } + if ($passwdconf{'max'} =~ /^\d+$/) { + $max = $passwdconf{'max'}; + $numrules ++; + } + @chars = @{$passwdconf{'chars'}}; + if (@chars) { + $numrules ++; + } + } + if ($min > 0) { + $numrules ++; + } + if (($min > 0) || ($max ne '') || (@chars > 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\n"; } - $Str .= &set_login($defdom,$krbform,$intform,$locform); + $Str .= &set_login($defdom,$krbform,$intform,$locform,$ltiform); my ($home_server_pick,$numlib) = &Apache::loncommon::home_server_form_item($defdom,'lcserver', @@ -977,8 +1085,14 @@ sub print_upload_manager_footer { &Apache::lonhtmlcommon::row_closure(); } + my ($trusted,$untrusted); + if ($context eq 'course') { + ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('enroll',$defdom); + } elsif ($context eq 'author') { + ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('othcoau',$defdom); + } $Str .= &Apache::lonhtmlcommon::row_title(&mt('Default domain')) - .&Apache::loncommon::select_dom_form($defdom,'defaultdomain',undef,1) + .&Apache::loncommon::select_dom_form($defdom,'defaultdomain',undef,1,undef,$trusted,$untrusted) .&Apache::lonhtmlcommon::row_closure(); $Str .= &Apache::lonhtmlcommon::row_title(&mt('Starting and Ending Dates')) @@ -1508,14 +1622,6 @@ sub construction_space_roles { foreach my $role (@allroles) { if (&Apache::lonnet::allowed('c'.$role,$env{'user.domain'}.'/'.$env{'user.name'})) { push(@roles,$role); - } elsif ($env{'request.role'} =~ m{^ca\./($match_domain)/($match_username)$}) { - my ($audom,$auname) = ($1,$2); - if (($role eq 'ca') || ($role eq 'aa')) { - if ((&Apache::lonnet::allowed('v'.$role,,$audom.'/'.$auname)) && - ($env{"environment.internal.manager./$audom/$auname"})) { - push(@roles,$role); - } - } } } return @roles; @@ -1619,24 +1725,6 @@ sub print_userlist { if (! exists($env{'form.sortby'})) { $env{'form.sortby'} = 'username'; } - my ($showstart,$showend); - if (($env{'form.Status'} eq '') && ($env{'form.phase'} eq '') && - ($env{'form.showrole'} eq '') && ($context eq 'course') && - ($env{'request.course.id'} ne '') && - ($env{'course.'.$env{'request.course.id'}.'.internal.coursecode'} ne '')) { - my $now = time; - my $startaccess = $env{'course.'.$env{'request.course.id'}.'.default_enrollment_start_date'}; - my $endaccess = $env{'course.'.$env{'request.course.id'}.'.default_enrollment_end_date'}; - if (($startaccess) && ($startaccess > $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'; } @@ -1704,7 +1792,7 @@ sub print_userlist { $r->print(§ion_group_filter($cnum,$cdom)); } $r->print('
'. - &column_checkboxes($context,$mode,$formname,$showcredits,$showstart,$showend). + &column_checkboxes($context,$mode,$formname,$showcredits). '
'); if ($env{'form.phase'} eq '') { $r->print('
'. @@ -1790,31 +1878,12 @@ sub print_userlist { } else { my (%cstr_roles,%dom_roles); if ($context eq 'author') { - my @possroles = &roles_by_context($context); - 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); - } + 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); } elsif ($context eq 'domain') { if ($env{'form.roletype'} eq 'domain') { if (grep(/^authorusage$/,@cols)) { @@ -2105,17 +2174,14 @@ sub get_cols_array { push(@cols,'photo'); } if ($context eq 'domain') { - push(@cols,('authorusage','authorquota','extent')); - } - if ($context eq 'author') { - push(@cols,'manager'); + push (@cols,('authorusage','authorquota','extent')); } } return @cols; } sub column_checkboxes { - my ($context,$mode,$formname,$showcredits,$showstart,$showend) = @_; + my ($context,$mode,$formname,$showcredits) = @_; my @cols = &get_cols_array($context,$mode,$showcredits); my @showncols = &Apache::loncommon::get_env_multiple('form.showcol'); my (%disabledchk,%unchecked); @@ -2128,21 +2194,11 @@ sub column_checkboxes { if ($showcredits) { $unchecked{'credits'} = 1; } - my %curr_groups = &Apache::longroup::coursegroups(); - unless (keys(%curr_groups)) { - $unchecked{'groups'} = 1; - } - } elsif ($context eq 'domain') { + } elsif ($context eq 'domain') { $unchecked{'extent'} = 1; } - if ($showstart) { - $unchecked{'lastlogin'} = 1; - } else { - $unchecked{'start'} = 1; - } - unless ($showend) { - $unchecked{'end'} = 1; - } + $unchecked{'start'} = 1; + $unchecked{'end'} = 1; } else { if ($env{'form.Status'} ne 'Any') { $disabledchk{'status'} = 1; @@ -2159,11 +2215,6 @@ 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); @@ -2255,7 +2306,6 @@ 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'); @@ -2540,7 +2590,6 @@ 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; } @@ -2615,9 +2664,6 @@ 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'); @@ -2935,7 +2981,6 @@ END foreach my $idx (@$keylist) { $index{$idx} = $i++; } - my $now = time; my $usercount = 0; my ($secfilter,$grpfilter); if ($context eq 'course') { @@ -2953,12 +2998,10 @@ END Future => 'Future', Expired => 'Expired', ); - my (%crslogins,%camanagers); + # If this is for a single course get last course "log-in". + my %crslogins; 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. @@ -3058,16 +3101,6 @@ 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'}; @@ -4263,42 +4296,19 @@ sub upfile_drop_add { $fieldstype{$field.'_choice'} = 'scalar'; } &Apache::loncommon::store_course_settings('enrollment_upload',\%fieldstype); - my ($cid,$crstype,$setting,$crsdom,$crsnum,$oldcrsuserdoms,%emptyok); + my ($cid,$crstype,$setting,$crsdom); 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(); @@ -4309,7 +4319,47 @@ sub upfile_drop_add { my $defdom=$env{'request.role.domain'}; my $domain; if ($env{'form.defaultdomain'} ne '') { - $domain = $env{'form.defaultdomain'}; + if (($context eq 'course') || ($setting eq 'course')) { + if ($env{'form.defaultdomain'} eq $crsdom) { + $domain = $env{'form.defaultdomain'}; + } else { + if (&Apache::lonnet::will_trust('enroll',$crsdom,$env{'form.defaultdomain'})) { + $domain = $env{'form.defaultdomain'}; + } else { + $r->print(''.&mt('Error').': '. + &mt('Enrollment of users not permitted for specified default domain: [_1].', + &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).''); + return 'untrusted'; + } + } + } elsif ($context eq 'author') { + if ($env{'form.defaultdomain'} eq $defdom) { + $domain = $env{'form.defaultdomain'}; + } else { + if ((&Apache::lonnet::will_trust('othcoau',$defdom,$env{'form.defaultdomain'})) && + (&Apache::lonnet::will_trust('coaurem',$env{'form.defaultdomain'},$defdom))) { + $domain = $env{'form.defaultdomain'}; + } else { + $r->print(''.&mt('Error').': '. + &mt('Addition of users not permitted for specified default domain: [_1].', + &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).''); + return 'untrusted'; + } + } + } elsif (($context eq 'domain') && ($setting eq 'domain')) { + if ($env{'form.defaultdomain'} eq $defdom) { + $domain = $env{'form.defaultdomain'}; + } else { + if (&Apache::lonnet::will_trust('domroles',$defdom,$env{'form.defaultdomain'})) { + $domain = $env{'form.defaultdomain'}; + } else { + $r->print(''.&mt('Error').': '. + &mt('Addition of users not permitted for specified default domain: [_1].', + &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).''); + return 'untrusted'; + } + } + } } else { $domain = $defdom; } @@ -4321,7 +4371,6 @@ sub upfile_drop_add { if (! exists($home_servers{$desiredhost})) { $r->print('

'.&mt('Error').': '. &mt('Invalid home server specified').'

'); - $r->print(&Apache::loncommon::end_page()); return 'invalidhome'; } } @@ -4349,6 +4398,8 @@ sub upfile_drop_add { if ((defined($env{'form.locarg'})) && ($env{'form.locarg'})) { $genpwd=$env{'form.locarg'}; } + } elsif ($env{'form.login'} eq 'lti') { + $amode='lti'; } if ($amode =~ /^krb/) { if (! defined($genpwd) || $genpwd eq '') { @@ -4501,6 +4552,7 @@ sub upfile_drop_add { my (%existinguser,%userinfo,%disallow,%rulematch,%inst_results,%alerts,%checkuname, %showpasswdrules,$haspasswdmap); my $counter = -1; + my (%willtrust,%trustchecked); foreach my $line (@userdata) { $counter ++; my @secs; @@ -4548,6 +4600,28 @@ sub upfile_drop_add { '"'.$entries{$fields{'domain'}}.'"', $fname,$mname,$lname,$gen); next; + } elsif ($entries{$fields{'domain'}} ne $domain) { + my $possdom = $entries{$fields{'domain'}}; + if ($context eq 'course' || $setting eq 'course') { + unless ($trustchecked{$possdom}) { + $willtrust{$possdom} = &Apache::lonnet::will_trust('enroll',$domain,$possdom); + $trustchecked{$possdom} = 1; + } + } elsif ($context eq 'author') { + unless ($trustchecked{$possdom}) { + $willtrust{$possdom} = &Apache::lonnet::will_trust('othcoau',$domain,$possdom); + } + if ($willtrust{$possdom}) { + $willtrust{$possdom} = &Apache::lonnet::will_trust('coaurem',$possdom,$domain); + } + } + unless ($willtrust{$possdom}) { + $disallow{$counter} = + &mt('Unacceptable domain [_1] for user [_2] [_3] [_4] [_5]', + '"'.$possdom.'"', + $fname,$mname,$lname,$gen); + next; + } } my $username = $entries{$fields{'username'}}; my $userdomain = $entries{$fields{'domain'}}; @@ -4732,7 +4806,7 @@ sub upfile_drop_add { &mt('The user does not already exist, and you may not create a new user in a different domain.'); next; } else { - unless ($password || $env{'form.login'} eq 'loc') { + unless (($password ne '') || ($env{'form.login'} eq 'loc') || ($env{'form.login'} eq 'lti')) { $disallow{$counter} = &mt('[_1]: This is a new user but no default password was provided, and the authentication type requires one.', ''.$username.''); @@ -4935,16 +5009,6 @@ 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) { @@ -4995,7 +5059,7 @@ sub upfile_drop_add { $mname,$lname,$gen,$singlesec, $env{'form.forceid'},$desiredhost, $email,$role,$enddate,$startdate, - $checkid,$inststatus,\%emptyok); + $checkid,$inststatus); } } if ($multiple) { @@ -5017,13 +5081,6 @@ 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); @@ -5122,11 +5179,7 @@ sub passwdrule_alerts { my %passwdconf = &Apache::lonnet::get_passwdconf($domain); $warning = ''.&mt('Password requirement(s) unmet for one or more users:').'