--- loncom/interface/lonuserutils.pm	2017/11/04 20:23:23	1.192
+++ loncom/interface/lonuserutils.pm	2023/11/04 00:06:00	1.219
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Utility functions for managing LON-CAPA user accounts
 #
-# $Id: lonuserutils.pm,v 1.192 2017/11/04 20:23:23 raeburn Exp $
+# $Id: lonuserutils.pm,v 1.219 2023/11/04 00:06:00 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;
@@ -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);
@@ -153,6 +158,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 = '<p class="LC_warning">'.
+                  &mt("The following roles could not be assigned because the user is from another domain, and that domain's policies disallow it").'<ul>';
+        foreach my $key (sort(keys(%{$reject}))) {
+            if (ref($reject->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$reject->{$key}}))) {
+                    if (ref($reject->{$key}->{$user}) eq 'HASH') {
+                        my ($crstype,$role,$cdom,$cnum,$csec,$title,$plainrole);
+                        $role = $reject->{$key}->{$user}{'role'};
+                        $cdom = $reject->{$key}->{$user}{'cdom'};
+                        $cnum = $reject->{$key}->{$user}{'cnum'};
+                        $csec = $reject->{$key}->{$user}{'csec'};
+                        if (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &Apache::loncommon::plainname($cnum,$cdom);
+                            } else {
+                                if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                    my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                    $crstype = $coursedata{'type'};
+                                    $title = $coursedata{'description'};
+                                }
+                            }
+                        } elsif ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        }
+                        my $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$reject->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$reject->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        } elsif (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa'))) {
+                            $output .= ' | '.&mt('Authoring Space belonging to: [_1]',$title);
+                                                
+                        }
+                        if (($context eq 'domain') && ($crstype)) {
+                            $output .= ' | '.&mt("$crstype: [_1]",$title);
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+    }
+    if (keys(%{$unauthorized}) > 0) {
+        $output = '<p class="LC_warning">'.
+                  &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.").'<ul>';
+        foreach my $key (sort(keys(%{$unauthorized}))) {
+            if (ref($unauthorized->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$unauthorized->{$key}}))) {
+                    if (ref($unauthorized->{$key}->{$user}) eq 'HASH') {
+                        my ($crstype,$role,$cdom,$cnum,$csec,$title,$plainrole);
+                        $role = $unauthorized->{$key}->{$user}{'role'};
+                        $cdom = $unauthorized->{$key}->{$user}{'cdom'};
+                        $cnum = $unauthorized->{$key}->{$user}{'cnum'};
+                        $csec = $unauthorized->{$key}->{$user}{'csec'};
+                        if (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &mt('Authoring Space belonging to: [_1]',
+                                             &Apache::loncommon::plainname($cnum,$cdom));
+                            } else {
+                                if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                    my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                    $crstype = $coursedata{'type'};
+                                    $title = &mt("$crstype: [_1]",$coursedata{'description'});
+                                }
+                            }
+                        } elsif ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$unauthorized->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$unauthorized->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        }
+                        if ($title ne '') {
+                            $output .= ' | '.$title;
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>'; 
+    }
+    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 = '<p class="LC_warning">'.
+                  &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").'<ul>';
+        my (%todom,%touser,%crsqueue,%caqueue,%domqueue);
+        my $requester = $env{'user.name'}.':'.$env{'user.domain'};
+        foreach my $key (sort(keys(%{$pending}))) {
+            if (ref($pending->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$pending->{$key}}))) {
+                    if (ref($pending->{$key}->{$user}) eq 'HASH') {
+                        my $role = $pending->{$key}->{$user}{'role'};
+                        my $uname = $pending->{$key}->{$user}{'uname'};
+                        my $udom = $pending->{$key}->{$user}{'udom'};
+                        my $csec = $pending->{$key}->{$user}{'csec'};
+                        my $cdom = $pending->{$key}->{$user}{'cdom'};
+                        my $cnum = $pending->{$key}->{$user}{'cnum'};
+                        my $adj = $pending->{$key}->{$user}{'adj'};
+                        my $start = $pending->{$key}->{$user}{'start'};
+                        my $end = $pending->{$key}->{$user}{'end'};
+                        my $credits = $pending->{$key}->{$user}{'credits'};
+                        my $now = time;
+                        my ($crstype,$title,$plainrole,$extent,$id,$status);
+                        if ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                            $title = $env{'course.'.$env{'request.course.id'}.'.description'};
+                        } elsif ($context eq 'domain') {
+                            if (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                $crstype = $coursedata{'type'};
+                                $title = $coursedata{'description'};
+                            } elsif (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &Apache::loncommon::plainname($cnum,$cdom);
+                            }
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $extent = "/$cdom/$cnum";
+                        $id = $uname.':'.$udom.':'.$role;
+                        if (($context eq 'course') || ($crstype)) {
+                            $id .= ':'.$csec;
+                        }
+                        $output .= '<li>'.&mt('User: [_1]',$uname).' | '.
+                                          &mt('Domain: [_1]',$udom).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($crstype) {
+                            if ($csec ne'') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        } elsif (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa'))) {
+                            $output .= ' | '.&mt('Authoring Space belonging to: [_1]',$title);
+                        }
+                        if (($context eq 'domain') && ($crstype)) {
+                            $output .= ' | '.&mt("$crstype: [_1]",$title);
+                        }
+                        if (($crstype) && ($csec ne '')) {
+                            $extent .= "/$csec";
+                        }
+                        if ($adj eq 'user') {
+                            $output .= '<br />'.&mt('Message sent to user for approval');
+                            $touser{$uname.':'.$udom}{'pending:'.$extent.':'.$role} = {
+                                                                                        timestamp => $now,
+                                                                                        requester => $requester,
+                                                                                        start     => $start,
+                                                                                        end       => $end,
+                                                                                        credits   => $credits,
+                                                                                        context   => $context,
+                                                                                      };
+                        } elsif ($adj eq 'domain') {
+                            $output .= '<br />'.&mt("Message sent to user's domain coordinator for approval");
+                            $todom{$udom}{'pending:'.$uname.':'.$extent.':'.$role} = {
+                                                                                       timestamp => $now,
+                                                                                       requester => $requester,
+                                                                                       start     => $start,
+                                                                                       end       => $end,
+                                                                                       credits   => $credits,
+                                                                                       context   => $context,
+                                                                                     };
+                        }
+                        $output .= '</li>';
+                        if (($context eq 'course') || ($crstype)) {
+                            $crsqueue{$cdom.'_'.$cnum}{$id} = {
+                                                                timestamp => $now,
+                                                                requester => $requester,
+                                                                adj       => $adj,
+                                                              };
+                            $crsqueue{$cdom.'_'.$cnum}{'status&'.$id} = 'pending';
+                        } elsif (($context eq 'author') ||
+                                 (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa')))) {
+                            $caqueue{$cnum.':'.$cdom}{$id} = {
+                                                               timestamp => $now,
+                                                               requester => $requester,
+                                                               adj       => $adj,
+                                                             };
+                            $caqueue{$cnum.':'.$cdom}{'status&'.$id} = 'pending';
+                        } elsif ($context eq 'domain') {
+                            $domqueue{$id} = {
+                                               timestamp => $now,
+                                               requester => $requester,
+                                               adj       => $adj,
+                                             };
+                            $domqueue{'status&'.$id} = 'pending';
+                        }
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+        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 = '<p class="LC_warning">'.
+                  &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").'<ul>';
+        my $requester = $env{'user.name'}.':'.$env{'user.domain'};
+        foreach my $key (sort(keys(%{$currqueued}))) {
+            if (ref($currqueued->{$key}) eq 'HASH') {
+                foreach my $user (sort(keys(%{$currqueued->{$key}}))) {
+                    if (ref($currqueued->{$key}->{$user}) eq 'HASH') {
+                        my $role = $currqueued->{$key}->{$user}{'role'};
+                        my $csec = $currqueued->{$key}->{$user}{'csec'};
+                        my $cdom = $currqueued->{$key}->{$user}{'cdom'};
+                        my $cnum = $currqueued->{$key}->{$user}{'cnum'};
+                        my ($crstype,$title,$plainrole);
+                        if ($context eq 'course') {
+                            $crstype = &Apache::loncommon::course_type();
+                        } elsif (($context eq 'domain') && ($cnum ne '')) {
+                            if (($role eq 'ca') || ($role eq 'aa')) {
+                                $title = &mt('Authoring Space belonging to: [_1]',
+                                             &Apache::loncommon::plainname($cnum,$cdom));
+                            } elsif (&Apache::lonnet::is_course($cdom,$cnum)) {
+                                my %coursedata = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
+                                $crstype = $coursedata{'type'};
+                                $title = &mt("$crstype: [_1]",$coursedata{'description'});
+                            }
+                        }
+                        $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                        $output .= '<li>'.&mt('User: [_1]',$currqueued->{$key}->{$user}{'uname'}).' | '.
+                                          &mt('Domain: [_1]',$currqueued->{$key}->{$user}{'udom'}).' | '.
+                                          &mt('Role: [_1]',$plainrole);
+                        if ($title ne '') {
+                            $output .= ' | '.$title;
+                        }
+                        if ($crstype) {
+                            if ($csec ne '') {
+                                $output .= ' | '.&mt('Section: [_1]',$csec);
+                            }
+                        }
+                        $output .= '</li>';
+                    }
+                }
+            }
+        }
+        $output .= '</ul></p>';
+    }
+    return $output;
+}
+
 sub propagate_id_change {
     my ($uname,$udom,$user) = @_;
     my (@types,@roles);
@@ -438,7 +997,7 @@ sub javascript_validations {
             } elsif ($context eq 'domain') {
                 $setsection_call = 'setCourse()';
                 $setsections_js = &dc_setcourse_js($param{'formname'},$mode,
-                                                   $context,$showcredits);
+                                                   $context,$showcredits,$domain);
             }
             $finish = "  var checkSec = $setsection_call\n".
                       "  if (checkSec == 'ok') {\n".
@@ -510,7 +1069,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') ) {
@@ -531,21 +1090,28 @@ END
 /* regexp here to check for non \d \. in credits */
 END
     } else {
+        my ($numrules,$intargjs) =
+            &Apache::loncommon::passwd_validation_js('vf.elements[current.argfield].value',$domain);
         $auth_checks .= (<<END);
     foundatype=1;
     if (current.argfield == null || current.argfield == '') {
+        // The login radiobutton checked does not have an associated textbox
+    } else if (vf.elements[current.argfield].value == '') {
         var alertmsg = '';
         switch (current.radiovalue) {
             case 'krb':
                 alertmsg = '$alert{'krb'}';
                 break;
-            case 'loc':
-            case 'fsys':
+            case 'int':
                 alertmsg = '$alert{'ipass'}';
                 break;
             case 'fsys':
+                alertmsg = '$alert{'ipass'}';
+                break;
+            case 'loc':
                 alertmsg = '';
                 break;
+            case 'lti':
             default:
                 alertmsg = '';
         }
@@ -553,6 +1119,10 @@ END
             alert(alertmsg);
             return;
         }
+    } else if (current.radiovalue == 'int') {
+        if ($numrules > 0) {
+$intargjs
+        }
     }
 END
     }
@@ -641,6 +1211,7 @@ END
                  $section_checks.$authheader;
     return $result;
 }
+
 ###############################################################
 ###############################################################
 sub upload_manager_javascript_forward_associate {
@@ -898,6 +1469,7 @@ sub print_upload_manager_footer {
     my $krbform = &Apache::loncommon::authform_kerberos(%param);
     my $intform = &Apache::loncommon::authform_internal(%param);
     my $locform = &Apache::loncommon::authform_local(%param);
+    my $ltiform = &Apache::loncommon::authform_lti(%param);
     my $date_table = &date_setting_table(undef,undef,$context,undef,
                                          $formname,$permission,$crstype);
 
@@ -926,7 +1498,7 @@ sub print_upload_manager_footer {
             &Apache::loncommon::help_open_topic('Auth_Options').
             "</p>\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',
@@ -1132,6 +1704,11 @@ sub print_upload_manager_form {
             &Apache::loncommon::load_tmp_file($r,$datatoken);
         }
     }
+    if ($datatoken eq '') {
+        $r->print('<p class="LC_error">'.&mt('Error').': '.
+                  &mt('Invalid datatoken').'</p>');
+        return 'missingdata';
+    }
     my @records=&Apache::loncommon::upfile_record_sep();
     if($env{'form.noFirstLine'}){
         $firstLine=shift(@records);
@@ -1214,6 +1791,7 @@ sub print_upload_manager_form {
     }
     &print_upload_manager_footer($r,$i,$keyfields,$defdom,$today,$halfyear,
                                  $context,$permission,$crstype,$showcredits);
+    return 'ok';
 }
 
 sub setup_date_selectors {
@@ -1474,6 +2052,14 @@ 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;
@@ -1561,10 +2147,11 @@ sub my_custom_roles {
     my %rolehash=&Apache::lonnet::dump('roles',$udom,$uname);
     foreach my $key (keys(%rolehash)) {
         if ($key=~/^rolesdef\_(\w+)$/) {
+            my $role = $1;
             if ($crstype eq 'Community') {
                 next if ($rolehash{$key} =~ /bre\&S/); 
             }
-            $returnhash{$1}=$1;
+            $returnhash{$role}=$role;
         }
     }
     return %returnhash;
@@ -1730,12 +2317,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)) {
@@ -2026,7 +2632,10 @@ 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;
@@ -2067,6 +2676,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);
@@ -2158,6 +2772,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');
@@ -2287,7 +2902,6 @@ sub build_user_record {
 
 sub courses_selector {
     my ($cdom,$formname) = @_;
-    my %coursecodes = ();
     my %codes = ();
     my @codetitles = ();
     my %cat_titles = ();
@@ -2300,14 +2914,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);
@@ -2442,6 +3057,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;
     }
@@ -2516,6 +3132,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');
@@ -2833,6 +3452,7 @@ END
     foreach my $idx (@$keylist) {
         $index{$idx} = $i++;
     }
+    my $now = time;
     my $usercount = 0;
     my ($secfilter,$grpfilter);
     if ($context eq 'course') {
@@ -2850,10 +3470,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.
@@ -2953,6 +3575,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'};
@@ -3521,6 +4153,8 @@ END
         setSections(formname,'$crstype');
         if (seccheck == 'ok') {
             opener.document.$callingform.newsecs.value = formname.sections.value;
+        } else {
+            return;
         }
 END
     } else {
@@ -4105,7 +4739,7 @@ sub print_first_users_upload_form {
            .&Apache::lonhtmlcommon::end_pick_box();
 
     $str .= '<p>'
-           .'<input type="submit" name="fileupload" value="'.&mt('Next').'"'
+           .'<input type="button" name="fileupload" value="'.&mt('Next').'"'
            .' onclick="javascript:checkUpload(this.form);" />'
            .'</p>';
 
@@ -4146,7 +4780,7 @@ 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);
     if ($context eq 'domain') {
         $setting = $env{'form.roleaction'};
     }
@@ -4154,11 +4788,18 @@ sub upfile_drop_add {
         $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();
@@ -4179,7 +4820,7 @@ sub upfile_drop_add {
                     $r->print('<span class="LC_error">'.&mt('Error').': '.
                               &mt('Enrollment of users not permitted for specified default domain: [_1].',
                                   &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
-                    return;
+                    return 'untrusted';
                 }
             }
         } elsif ($context eq 'author') {
@@ -4193,7 +4834,7 @@ sub upfile_drop_add {
                     $r->print('<span class="LC_error">'.&mt('Error').': '.
                               &mt('Addition of users not permitted for specified default domain: [_1].',
                                   &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
-                    return;
+                    return 'untrusted';
                 }
             }
         } elsif (($context eq 'domain') && ($setting eq 'domain')) {
@@ -4206,7 +4847,7 @@ sub upfile_drop_add {
                     $r->print('<span class="LC_error">'.&mt('Error').': '.
                               &mt('Addition of users not permitted for specified default domain: [_1].',
                                   &Apache::lonnet::domain($env{'form.defaultdomain'},'description')).'</span>');
-                    return;
+                    return 'untrusted';
                 }
             }
         }
@@ -4219,9 +4860,9 @@ sub upfile_drop_add {
     } else {
         my %home_servers = &Apache::lonnet::get_servers($defdom,'library');
         if (! exists($home_servers{$desiredhost})) {
-            $r->print('<span class="LC_error">'.&mt('Error').': '.
-                      &mt('Invalid home server specified').'</span>');
-            return;
+            $r->print('<p class="LC_error">'.&mt('Error').': '.
+                      &mt('Invalid home server specified').'</p>');
+            return 'invalidhome';
         }
     }
     # Determine authentication mechanism
@@ -4231,6 +4872,7 @@ sub upfile_drop_add {
     }
     my $amode  = '';
     my $genpwd = '';
+    my @genpwdfail;
     if ($env{'form.login'} eq 'krb') {
         $amode='krb';
         $amode.=$env{'form.krbver'};
@@ -4239,16 +4881,20 @@ 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';
         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 '') {
-            $r->print('<span class="Error">'.': '.
+            $r->print('<span class="Error">'.
                       &mt('Unable to enroll users').' '.
                       &mt('No Kerberos domain was specified.').'</span></p>');
             $amode = ''; # This causes the loop below to be skipped
@@ -4317,10 +4963,14 @@ sub upfile_drop_add {
                                                   \@statuses,\@poss_roles);
                 &gather_userinfo($context,'view',\%userlist,$indexhash,\%info,
                              \%cstr_roles,$permission);
-
             }
         }
     }
+    if ($datatoken eq '') {
+        $r->print('<p class="LC_error">'.&mt('Error').': '.
+                  &mt('Invalid datatoken').'</p>');
+        return 'missingdata';
+    }
     if ( $domain eq &LONCAPA::clean_domain($domain)
         && ($amode ne '')) {
         #######################################
@@ -4334,6 +4984,8 @@ sub upfile_drop_add {
             $r->print('<h3>'.&mt('Adding/Modifying Users')."</h3>\n<p>\n");
         }
         $r->rflush;
+        my (%got_role_approvals,%got_instdoms,%process_by,%instdoms,
+            %pending,%reject,%notifydc,%status,%unauthorized,%currqueued);
 
         my %counts = (
                        user => 0,
@@ -4390,7 +5042,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) {
@@ -4541,11 +5194,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.',
+                                                                  '<b>'.$username.'</b>');
+                                        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',
+                                                              '<b>'.$username.'</b>');
+                                    unless ($haspasswdmap) {
+                                        map { $showpasswdrules{$_} = 1; } @genpwdfail;
+                                        $haspasswdmap = 1;
+                                    }
+                                }
+                                next;
+                            }
                         }
+                        $password = $genpwd;
                     }
                     # determine user role
                     my $role = '';
@@ -4614,7 +5299,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.',
                                         '<b>'.$username.'</b>');
@@ -4817,12 +5502,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 =
@@ -4834,16 +5540,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,
@@ -4857,18 +5581,31 @@ sub upfile_drop_add {
                                         $singlesec = $secs[0];
                                     }
                                 }
-                            }
-                            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);
+                            } else {
+                                $possrole = $role;
                             }
                         }
+                        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);
+                        }
                     }
                     if ($multiple) {
                         foreach my $sec (sort(keys(%userres))) {
@@ -4889,6 +5626,13 @@ sub upfile_drop_add {
             } # end of loop
             $r->print('</ul>');
             &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);
@@ -4908,6 +5652,13 @@ sub upfile_drop_add {
                           $counts{'auth'})."</p>\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  #
         #####################################
@@ -4934,6 +5685,7 @@ sub upfile_drop_add {
             }
         }
     } # end of unless
+    return 'ok';
 }
 
 sub print_namespacing_alerts {
@@ -4976,6 +5728,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 = '<b>'.&mt('Password requirement(s) unmet for one or more users:').'</b><ul>';
+            if ($showrules{'min'}) {
+                my $min = $passwdconf{'min'};
+                if ($min eq '') {
+                    $min = $Apache::lonnet::passwdmin;
+                }
+                $warning .= '<li>'.&mt('minimum [quant,_1,character]',$min).'</li>';
+            }
+            if ($showrules{'max'}) {
+                $warning .= '<li>'.&mt('maximum [quant,_1,character]',$passwdconf{'max'}).'</li>';
+            }
+            if ($showrules{'uc'}) {
+                $warning .= '<li>'.&mt('contain at least one upper case letter').'</li>';
+            }
+            if ($showrules{'lc'}) {
+                $warning .= '<li>'.&mt('contain at least one lower case letter').'</li>';
+            }
+            if ($showrules{'num'}) {
+                $warning .= '<li>'.&mt('contain at least one number').'</li>';
+            }
+            if ($showrules{'spec'}) {
+                $warning .= '<li>'.&mt('contain at least one non-alphanumeric').'</li>';
+            }
+            $warning .= '</ul>';
+        }
+    }
+    return $warning;
+}
+
 sub user_change_result {
     my ($r,$userresult,$authresult,$roleresult,$idresult,$counts,$flushc,
         $username,$userdomain,$userchg) = @_;
@@ -5064,7 +5852,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 {
@@ -5094,7 +5883,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';
@@ -5111,6 +5900,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 '') {
@@ -5121,15 +5912,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;
@@ -5171,7 +5967,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 {
@@ -5180,6 +5981,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 {
@@ -5187,6 +5992,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 {
@@ -5396,6 +6205,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');
@@ -5479,7 +6294,7 @@ END
 }
 
 sub set_login {
-    my ($dom,$authformkrb,$authformint,$authformloc) = @_;
+    my ($dom,$authformkrb,$authformint,$authformloc,$authformlti) = @_;
     my %domconfig = &Apache::lonnet::get_dom('configuration',['usercreation'],$dom);
     my $response;
     my ($authnum,%can_assign) =
@@ -5501,6 +6316,11 @@ sub set_login {
                          '<td>'.$authformloc.'</td>'.
                          &Apache::loncommon::end_data_table_row()."\n";
         }
+        if ($can_assign{'lti'}) {
+            $response .= &Apache::loncommon::start_data_table_row().
+                         '<td>'.$authformlti.'</td>'.
+                         &Apache::loncommon::end_data_table_row()."\n";
+        }
         $response .= &Apache::loncommon::end_data_table();
     }
     return $response;
@@ -5845,6 +6665,70 @@ sub can_modify_userinfo {
     return %canmodify;
 }
 
+sub can_change_internalpass {
+    my ($uname,$udom,$crstype,$permission) = @_;
+    my $canchange;
+    if (&Apache::lonnet::allowed('mau',$udom)) {
+        $canchange = 1;
+    } elsif ((ref($permission) eq 'HASH') && ($permission->{'mip'}) &&
+             ($udom eq $env{'request.role.domain'})) {
+        unless ($env{'course.'.$env{'request.course.id'}.'.internal.nopasswdchg'}) {
+            my ($cnum,$cdom) = &get_course_identity();
+            if ((&Apache::lonnet::is_course_owner($cdom,$cnum)) && ($udom eq $env{'user.domain'})) {
+                my @userstatuses = ('default');
+                my %userenv = &Apache::lonnet::userenvironment($udom,$uname,'inststatus');
+                if ($userenv{'inststatus'} ne '') {
+                    @userstatuses =  split(/:/,$userenv{'inststatus'});
+                }
+                my $noupdate = 1;
+                my %passwdconf = &Apache::lonnet::get_passwdconf($cdom);
+                if (ref($passwdconf{'crsownerchg'}) eq 'HASH') {
+                    if (ref($passwdconf{'crsownerchg'}{'for'}) eq 'ARRAY') {
+                        foreach my $status (@userstatuses) {
+                            if (grep(/^\Q$status\E$/,@{$passwdconf{'crsownerchg'}{'for'}})) {
+                                undef($noupdate);
+                                last;
+                            }
+                        }
+                    }
+                }
+                if ($noupdate) {
+                    return;
+                }
+                my %owned = &Apache::lonnet::courseiddump($cdom,'.',1,'.',
+                                                          $env{'user.name'}.':'.$env{'user.domain'},
+                                                          undef,undef,undef,'.');
+                my %roleshash = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',
+                                                              ['active','future']);
+                foreach my $key (keys(%roleshash)) {
+                    my ($name,$domain,$role) = split(/:/,$key);
+                    if ($role eq 'st') {
+                        next if (($name eq $cnum) && ($domain eq $cdom));
+                        if ($owned{$domain.'_'.$name}) {
+                            if (ref($owned{$domain.'_'.$name}) eq 'HASH') {
+                                if ($owned{$domain.'_'.$name}{'nopasswdchg'}) {
+                                    $noupdate = 1;
+                                    last;
+                                }
+                            }
+                        } else {
+                            $noupdate = 1;
+                            last;
+                        }
+                    } else {
+                        $noupdate = 1;
+                        last;
+                    }
+                }
+                unless ($noupdate) {
+                    $canchange = 1;
+                }
+            }
+        }
+    }
+    return $canchange;
+}
+
 sub check_usertype {
     my ($dom,$uname,$rules,$curr_rules,$got_rules) = @_;
     my $usertype;
@@ -5965,12 +6849,21 @@ sub get_permission {
                     $permission{'selfenrolladmin'} = 1;
                 }
             }
+            unless ($permission{'selfenrolladmin'}) {
+                $permission{'selfenrollview'} = 1;
+            }
         }
         if ($env{'request.course.id'}) {
-            my $user = $env{'user.name'}.':'.$env{'user.domain'};
+            my $user;
+            if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
+                $user = $env{'user.name'}.':'.$env{'user.domain'};
+            }
             if (($user ne '') && ($env{'course.'.$env{'request.course.id'}.'.internal.courseowner'} eq
                                   $user)) {
                 $permission{'owner'} = 1;
+                if (&Apache::lonnet::allowed('mip',$env{'request.course.id'})) {
+                    $permission{'mip'} = 1;
+                }
             } elsif (($user ne '') && ($env{'course.'.$env{'request.course.id'}.'.internal.co-owners'} ne '')) {
                 if (grep(/^\Q$user\E$/,split(/,/,$env{'course.'.$env{'request.course.id'}.'.internal.co-owners'}))) {
                     $permission{'co-owner'} = 1;
@@ -5978,8 +6871,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) {
@@ -6007,8 +6915,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);
 }
@@ -6021,6 +6930,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);
@@ -6057,7 +6978,7 @@ sub get_course_identity {
 }
 
 sub dc_setcourse_js {
-    my ($formname,$mode,$context,$showcredits) = @_;
+    my ($formname,$mode,$context,$showcredits,$domain) = @_;
     my ($dc_setcourse_code,$authen_check);
     my $cctext = &Apache::lonnet::plaintext('cc');
     my $cotext = &Apache::lonnet::plaintext('co');
@@ -6066,7 +6987,7 @@ sub dc_setcourse_js {
     if ($mode eq 'upload') {
         $role = 'courserole';
     } else {
-        $authen_check = &verify_authen($formname,$context);
+        $authen_check = &verify_authen($formname,$context,$domain);
     }
     $dc_setcourse_code = (<<"SCRIPTTOP");
 $authen_check
@@ -6210,12 +7131,14 @@ ENDSCRIPT
 }
 
 sub verify_authen {
-    my ($formname,$context) = @_;
+    my ($formname,$context,$domain) = @_;
     my %alerts = &authcheck_alerts();
     my $finish = "return 'ok';";
     if ($context eq 'author') {
         $finish = "document.$formname.submit();";
     }
+    my ($numrules,$intargjs) =
+        &Apache::loncommon::passwd_validation_js('argpicked',$domain);
     my $outcome = <<"ENDSCRIPT";
 
 function auth_check() {
@@ -6249,6 +7172,7 @@ function auth_check() {
                 break;
             case 'int':
                 alertmsg = '$alerts{'ipass'}';
+                break;
             case 'fsys':
                 alertmsg = '$alerts{'ipass'}';
                 break;
@@ -6262,6 +7186,11 @@ function auth_check() {
             alert(alertmsg);
             return;
         }
+    } else if (logintype == 'int') {
+        var numrules = $numrules;
+        if (numrules > 0) {
+$intargjs
+        }
     }
     $finish
 }
@@ -6413,7 +7342,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);