--- loncom/interface/lonuserutils.pm	2023/10/06 02:27:26	1.184.4.10.2.5
+++ loncom/interface/lonuserutils.pm	2022/11/23 02:55:37	1.212
@@ -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.5 2023/10/06 02:27:26 raeburn Exp $
+# $Id: lonuserutils.pm,v 1.212 2022/11/23 02:55:37 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;
@@ -153,6 +154,357 @@ 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) = @_;
+    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'));
+    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') {
+                        $reject->{$key} = {
+                                            cdom  => $cdom,
+                                            cnum  => $cnum,
+                                            csec  => $csec,
+                                            udom  => $udom,
+                                            uname => $uname,
+                                            role  => $role,
+                                          };
+                        $skip = 1;
+                    } elsif (($rule eq 'user') || ($rule eq 'domain')) {
+                        $pending->{$key} = {
+                                             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}->{'credits'} = $credits;
+                        }
+                        $skip = 1;
+                    }
+                }
+            } elsif (exists($approval{'extdom'})) {
+                my $rule = $approval{'extdom'};
+                if ($rule eq 'none') {
+                    $reject->{$key} = {
+                                        cdom  => $cdom,
+                                        cnum  => $cnum,
+                                        csec  => $csec,
+                                        udom  => $udom,
+                                        uname => $uname,
+                                        role  => $role,
+                                      };
+                    $skip = 1;
+                } elsif (($rule eq 'user') || ($rule eq 'domain')) {
+                    $pending->{$key} = {
+                                         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}->{'credits'} = $credits;
+                    }
+                    $skip = 1;
+                }
+            }
+        }
+    }
+    return $skip;
+}
+
+sub print_roles_rejected {
+    my ($context,$reject) = @_;
+    return unless (ref($reject) 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') {
+                my ($crstype,$role,$cdom,$cnum,$title);
+                $role = $reject->{$key}{'role'};
+                $cdom = $reject->{$key}{'cdom'};
+                $cnum = $reject->{$key}{'cnum'};
+                if ($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);
+                    }
+                } elsif ($context eq 'course') {
+                    $crstype = &Apache::loncommon::course_type();
+                }
+                my $plainrole = &Apache::lonnet::plaintext($reject->{$key}{'role'},$crstype);
+                $output .= '<li>'.&mt('User: [_1]',$reject->{$key}{'uname'}).' | '.
+                                  &mt('Domain: [_1]',$reject->{$key}{'udom'}).' | '.
+                                  &mt('Role: [_1]',$plainrole);
+                if ($crstype) {
+                    $output .= ' | '.&mt('Section: [_1]',$reject->{$key}{'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>';
+    }
+    return $output;
+}
+
+sub print_roles_queued {
+    my ($context,$pending,$notifydc) = @_;
+    return unless ((ref($pending) eq 'HASH') && (ref($notifydc) 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') {
+                my $role = $pending->{$key}{'role'};
+                my $uname = $pending->{$key}{'uname'};
+                my $udom = $pending->{$key}{'udom'};
+                my $csec = $pending->{$key}{'csec'};
+                my $cdom = $pending->{$key}{'cdom'};
+                my $cnum = $pending->{$key}{'cnum'};
+                my $adj = $pending->{$key}{'adj'};
+                my $start = $pending->{$key}{'start'};
+                my $end = $pending->{$key}{'end'};
+                my $credits = $pending->{$key}{'credits'};
+                my $now = time;
+                my ($crstype,$title);
+                if ($context eq 'course') {
+                    $crstype = &Apache::loncommon::course_type();
+                } 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);
+                    }
+                }
+                my $plainrole = &Apache::lonnet::plaintext($role,$crstype);
+                my $extent = "/$cdom/$cnum";
+                $output .= '<li>'.&mt('User: [_1]',$uname).' | '.
+                                  &mt('Domain: [_1]',$udom).' | '.
+                                  &mt('Role: [_1]',$plainrole).' | ';
+                if ($crstype) {
+                    if ($csec ne '') {
+                        $output .= &mt('Section: [_1]',$csec).' | ';
+                        $extent .= "/$csec";
+                    }
+                }
+                if ($adj eq 'user') {
+                    $output .= '<br />'.&mt('Message sent to user for approval');
+                    $touser{$uname.':'.$udom}{$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}{$uname.':'.$extent.':'.$role} = {
+                                                                    timestamp => $now,
+                                                                    requester => $requester,
+                                                                    start     => $start,
+                                                                    end       => $end,
+                                                                    credits   => $credits,
+                                                                    context   => $context,
+                                                                  };
+                }
+                $output .= '</li>';
+                my $id = $uname.':'.$udom.':'.$role;
+                if (($context eq 'course') || ($crstype)) {
+                    $id .= ':'.$csec;
+                    $crsqueue{$cdom.'_'.$cnum}{$id} = {
+                                                        timestamp => $now,
+                                                        requester => $requester,
+                                                        adj       => $adj,
+                                                      };
+                } elsif (($context eq 'author') ||
+                         (($context eq 'domain') && (($role eq 'ca') || ($role eq 'aa')))) {
+                    $caqueue{$cnum.':'.$cdom}{$id} = {
+                                                        timestamp => $now,
+                                                        requester => $requester,
+                                                        adj       => $adj,
+                                                      };
+                } elsif ($context eq 'domain') {
+                    $domqueue{$id} = {
+                                        timestamp => $now,
+                                        requester => $requester,
+                                        adj       => $adj,
+                                     };
+                }
+            }
+        }
+        $output .= '</ul></p>';
+        if (keys(%touser)) {
+            foreach my $key (keys(%touser)) {
+                my ($uname,$udom) = split(/:/,$touser{$key});
+                if (&Apache::lonnet::put('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('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('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('othdomqueued',$caqueue{$key},$audom,$auname);
+                }
+            }
+        }
+        if (keys(%domqueue)) {
+            my $confname = &Apache::lonnet::get_domainconfiguser($env{'request.role.domain'});
+            &Apache::lonnet::put('othdomqueued',\%domqueue,$env{'request.role.domain'},$confname);
+        }
+    }
+    return $output;
+}
+
 sub propagate_id_change {
     my ($uname,$udom,$user) = @_;
     my (@types,@roles);
@@ -510,7 +862,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') ) {
@@ -552,6 +904,7 @@ END
             case 'loc':
                 alertmsg = '';
                 break;
+            case 'lti':
             default:
                 alertmsg = '';
         }
@@ -909,6 +1262,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);
 
@@ -937,7 +1291,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',
@@ -954,8 +1308,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'))
@@ -1572,10 +1932,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;
@@ -4159,7 +4520,7 @@ sub upfile_drop_add {
         $fieldstype{$field.'_choice'} = 'scalar';
     }
     &Apache::loncommon::store_course_settings('enrollment_upload',\%fieldstype);
-    my ($cid,$crstype,$setting,$crsdom,$crsnum,$oldcrsuserdoms);
+    my ($cid,$crstype,$setting,$crsdom,$crsnum);
     if ($context eq 'domain') {
         $setting = $env{'form.roleaction'};
     }
@@ -4174,11 +4535,6 @@ sub upfile_drop_add {
             $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();
@@ -4189,7 +4545,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('<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 '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('<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 '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('<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 'untrusted';
+                }
+            }
+        }
     } else {
         $domain = $defdom;
     }
@@ -4201,7 +4597,6 @@ sub upfile_drop_add {
         if (! exists($home_servers{$desiredhost})) {
             $r->print('<p class="LC_error">'.&mt('Error').': '.
                       &mt('Invalid home server specified').'</p>');
-            $r->print(&Apache::loncommon::end_page());
             return 'invalidhome';
         }
     }
@@ -4229,6 +4624,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 '') {
@@ -4322,6 +4719,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);
 
         my %counts = (
                        user => 0,
@@ -4381,6 +4780,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;
@@ -4428,6 +4828,28 @@ sub upfile_drop_add {
                                 '"<b>'.$entries{$fields{'domain'}}.'</b>"',
                                 $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]',
+                                    '"<b>'.$possdom.'</b>"',
+                                    $fname,$mname,$lname,$gen);
+                            next;
+                        }
                     }
                     my $username = $entries{$fields{'username'}};
                     my $userdomain = $entries{$fields{'domain'}};
@@ -4612,7 +5034,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>');
@@ -4831,6 +5253,17 @@ sub upfile_drop_add {
                                 $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));
+                        }
                         &modifystudent($userdomain,$username,$cid,$sec,
                                        $desiredhost,$context);
                         $roleresult =
@@ -4842,16 +5275,33 @@ 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));
+                                            }
                                             ($userres{$sec},$authres{$sec},$roleres{$sec},$idres{$sec}) =
                                             &modifyuserrole($context,$setting,
                                                 $changeauth,$cid,$userdomain,$username,
@@ -4865,9 +5315,21 @@ sub upfile_drop_add {
                                         $singlesec = $secs[0];
                                     }
                                 }
+                            } 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));
+                            }
                             ($userresult,$authresult,$roleresult,$idresult) = 
                                 &modifyuserrole($context,$setting,
                                                 $changeauth,$cid,$userdomain,$username, 
@@ -4897,13 +5359,6 @@ 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);
@@ -4924,6 +5379,12 @@ sub upfile_drop_add {
         }
         $r->print(&print_namespacing_alerts($domain,\%alerts,\%curr_rules));
         $r->print(&passwdrule_alerts($domain,\%showpasswdrules));
+        if (keys(%reject)) {
+            $r->print(&print_roles_rejected($context,\%reject));
+        }
+        if (keys(%pending)) {
+            $r->print(&print_roles_queued($context,\%pending,\%notifydc));
+        }
         #####################################
         # Display list of students to drop  #
         #####################################
@@ -5117,7 +5578,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);
     if ($choice eq 'drop') {
         @changelist = &Apache::loncommon::get_env_multiple('form.droplist');
     } else {
@@ -5147,7 +5609,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';
@@ -5164,6 +5626,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 '') {
@@ -5174,15 +5638,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;
@@ -5224,7 +5693,11 @@ 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));
                 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 {
@@ -5233,6 +5706,9 @@ 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));
                 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 {
@@ -5240,6 +5716,9 @@ 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));
                 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 {
@@ -5449,6 +5928,12 @@ sub update_user_list {
             $r->print(&make_dates_default($startdate,$enddate,$context,$crstype));
         }
     }
+    if (keys(%reject)) {
+        $r->print(&print_roles_rejected($context,\%reject));
+    }
+    if (keys(%pending)) {
+        $r->print(&print_roles_queued($context,\%pending,\%notifydc));
+    }
     my $linktext = &mt('Display User Lists');
     if ($choice eq 'drop') {
         $linktext = &mt('Display current class roster');
@@ -5532,7 +6017,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) =
@@ -5554,6 +6039,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;
@@ -6082,9 +6572,6 @@ sub get_permission {
                     $permission{'selfenrolladmin'} = 1;
                 }
             }
-            unless ($permission{'selfenrolladmin'}) {
-                $permission{'selfenrollview'} = 1;
-            }
         }
         if ($env{'request.course.id'}) {
             my $user;
@@ -6548,7 +7035,7 @@ sub selfenrollment_administration {
         }
     }
     if ($settings{'internal.selfenrollmgrdc'} ne '') {
-        @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'});
+        my @in_domain = split(/,/,$settings{'internal.selfenrollmgrdc'});
         my @diffs = &Apache::loncommon::compare_arrays(\@in_domain,$possconfigs);
         unless (@diffs) {
             return (\@in_course,\@in_domain);