Annotation of loncom/lti/ltiauth.pm, revision 1.8

1.1       raeburn     1: # The LearningOnline Network
                      2: # Basic LTI Authentication Module
                      3: #
1.8     ! raeburn     4: # $Id: ltiauth.pm,v 1.7 2018/03/23 18:08:45 raeburn Exp $
1.1       raeburn     5: #
                      6: # Copyright Michigan State University Board of Trustees
                      7: #
                      8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                      9: #
                     10: # LON-CAPA is free software; you can redistribute it and/or modify
                     11: # it under the terms of the GNU General Public License as published by
                     12: # the Free Software Foundation; either version 2 of the License, or
                     13: # (at your option) any later version.
                     14: #
                     15: # LON-CAPA is distributed in the hope that it will be useful,
                     16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     18: # GNU General Public License for more details.
                     19: #
                     20: # You should have received a copy of the GNU General Public License
                     21: # along with LON-CAPA; if not, write to the Free Software
                     22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     23: #
                     24: # /home/httpd/html/adm/gpl.txt
                     25: #
                     26: # http://www.lon-capa.org/
                     27: #
                     28: 
                     29: package Apache::ltiauth;
                     30: 
                     31: use strict;
                     32: use LONCAPA qw(:DEFAULT :match);
                     33: use Apache::Constants qw(:common :http);
                     34: use Net::OAuth;
                     35: use Apache::lonlocal;
                     36: use Apache::lonnet;
                     37: use Apache::loncommon;
                     38: use Apache::lonacc;
1.6       raeburn    39: use Apache::lonrequestcourse;
1.2       raeburn    40: use LONCAPA::ltiutils;
1.1       raeburn    41: 
                     42: sub handler {
                     43:     my $r = shift;
                     44:     my $requri = $r->uri;
                     45: #
                     46: # Retrieve data POSTed by LTI Consumer on launch  
                     47: #
                     48:     &Apache::lonacc::get_posted_cgi($r);
                     49:     my $params = {};
                     50:     foreach my $key (sort(keys(%env))) {
                     51:         if ($key =~ /^form\.(.+)$/) {
                     52:             $params->{$1} = $env{$key};
                     53:         }
                     54:     }
                     55: 
                     56:     unless (keys(%{$params})) {
                     57:         &invalid_request($r,1);
                     58:         return OK;
                     59:     }
                     60: 
                     61:     unless ($params->{'oauth_consumer_key'} &&
                     62:             $params->{'oauth_nonce'} &&
                     63:             $params->{'oauth_timestamp'} &&
                     64:             $params->{'oauth_version'} &&
                     65:             $params->{'oauth_signature'} &&
                     66:             $params->{'oauth_signature_method'}) {
                     67:         &invalid_request($r,2);
                     68:         return OK;
                     69:     }
                     70: 
                     71: #
                     72: # Retrieve "internet domains" for all this institution's LON-CAPA
                     73: # nodes.
                     74: #
                     75:     my ($udom,$uname,$uhome,$cdom,$cnum,$symb,$mapurl,@intdoms);
                     76:     my $lonhost = $r->dir_config('lonHostID');
                     77:     my $internet_names = &Apache::lonnet::get_internet_names($lonhost);
                     78:     if (ref($internet_names) eq 'ARRAY') {
                     79:         @intdoms = @{$internet_names};
                     80:     }
                     81: 
                     82: #
                     83: # For user who launched LTI in Consumer, determine user's domain in 
                     84: # LON-CAPA.
                     85: #
                     86: # Order is:
                     87: #
                     88: # (a) from custom_userdomain item in POSTed data
                     89: # (b) from lis_person_sourcedid in POSTed data
                     90: # (c) from default "log-in" domain for node
                     91: #     (can support multidomain servers, where specific domain is 
                     92: #      first part of hostname).
                     93: #
                     94: # Note: "internet domain" for user's domain must be one of the
                     95: # "internet domain(s)" for the institution's LON-CAPA servers.
                     96: #
                     97:     if (exists($params->{'custom_userdomain'})) {
                     98:         if ($params->{'custom_userdomain'} =~ /^$match_domain$/) {
                     99:             my $uprimary_id = &Apache::lonnet::domain($params->{'custom_userdomain'},'primary');
                    100:             if ($uprimary_id ne '') {
                    101:                 my $uintdom = &Apache::lonnet::internet_dom($uprimary_id);
                    102:                 if (($uintdom ne '') && (grep(/^\Q$uintdom\E$/,@intdoms))) {
                    103:                     $udom = $params->{'custom_userdomain'};
                    104:                 }
                    105:             }
                    106:         }
                    107:     }
                    108:     my $defdom = &Apache::lonnet::default_login_domain();
                    109:     my ($domain,$possuname,$possudom,$possmapuser);
                    110:     if ($env{'form.lis_person_sourcedid'} =~ /^($match_username)\:($match_domain)$/) {
                    111:         ($possuname,$possudom) = ($1,$2);
                    112:         if ($udom eq '') {
                    113:             my $uintdom = &Apache::lonnet::domain($possudom,'primary');
                    114:             if (($uintdom ne '') && (grep(/^\Q$uintdom\E$/,@intdoms))) {
                    115:                 $udom = $possudom;
                    116:                 $possmapuser = 'lis_person_sourcedid';
                    117:             } else {
                    118:                 $udom = $defdom;
                    119:             }
                    120:         } elsif ($udom eq $possudom) {
                    121:             $possmapuser = 'lis_person_sourcedid';
                    122:         }
                    123:     }
                    124:     unless ($possuname) {
                    125:         if ($env{'form.lis_person_sourcedid'} =~ /^$match_username$/) {
                    126:             $possuname = $env{'form.lis_person_sourcedid'};
                    127:             $possmapuser = 'lis_person_sourcedid';
                    128:         } elsif ($env{'form.lis_person_contact_email_primary'} =~ /^$match_username$/) {
                    129:             $possuname = $env{'form.lis_person_contact_email_primary'};
                    130:             $possmapuser = 'lis_person_contact_email_primary';
                    131:         }
                    132:         unless ($udom) {
                    133:             $udom = $defdom;
                    134:         }
                    135:     }
                    136: 
                    137: #
                    138: # Determine course's domain in LON-CAPA
                    139: #
                    140: # Order is:
                    141: #
                    142: # (a) from custom_coursedomain item in POSTed data
                    143: # (b) from tail of requested URL (after /adm/lti) if it has format of a symb  
                    144: # (c) from tail of requested URL (after /adm/lti) if it has format of a map 
                    145: # (d) from tail of requested URL (after /adm/lti) if it has format /domain/courseID
1.5       raeburn   146: # (e) from tail of requested URL (after /adm/lti) if it has format /tiny/domain/\w+
                    147: #     i.e., a shortened URL (see bug #6400).
1.1       raeburn   148: # (f) same as user's domain 
                    149: #
                    150: # Request invalid if custom_coursedomain is defined and is inconsistent with
                    151: # domain contained in requested URL.
                    152: #
                    153: # Note: "internet domain" for course's domain must be one of the
                    154: # internet domains for the institution's LON-CAPA servers.
                    155: #
                    156: 
                    157:     if (exists($params->{'custom_coursedomain'})) {
                    158:         if ($params->{'custom_coursedomain'} =~ /^$match_domain$/) {
                    159:             my $cprimary_id = &Apache::lonnet::domain($params->{'custom_coursedomain'},'primary');
                    160:             if ($cprimary_id ne '') {
                    161:                 my $cintdom = &Apache::lonnet::internet_dom($cprimary_id);
                    162:                 if (($cintdom ne '') && (grep(/^\Q$cintdom\E$/,@intdoms))) {
                    163:                     $cdom = $params->{'custom_coursedomain'};
                    164:                 }
                    165:             }
                    166:         }
                    167:     }
                    168: 
                    169:     my ($tail) = ($requri =~ m{^/adm/lti(|/.*)$});
                    170:     my $urlcnum;
                    171:     if ($tail ne '') {
                    172:         my $urlcdom;
                    173:         if ($tail =~ m{^/uploaded/($match_domain)/($match_courseid)/(?:default|supplemental)(?:|_\d+)\.(?:sequence|page)(|___\d+___.+)$}) {
                    174:             ($urlcdom,$urlcnum,my $rest) = ($1,$2,$3);
                    175:             if (($cdom ne '') && ($cdom ne $urlcdom)) {
                    176:                 &invalid_request($r,3);
                    177:                 return OK;
                    178:             }
                    179:             if ($rest eq '') {
                    180:                 $mapurl = $tail;
                    181:             } else {
                    182:                 $symb = $tail;
                    183:                 $symb =~ s{^/+}{};
                    184:             }
                    185:         } elsif ($tail =~ m{^/($match_domain)/($match_courseid)$}) {
                    186:             ($urlcdom,$urlcnum) = ($1,$2);
                    187:             if (($cdom ne '') && ($cdom ne $urlcdom)) {
                    188:                 &invalid_request($r,4);
                    189:                 return OK;
                    190:             }
1.5       raeburn   191:         } elsif ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
                    192:             ($urlcdom,my $key) = ($1,$2);
                    193:             if (($cdom ne '') && ($cdom ne $urlcdom)) {
                    194:                 &invalid_request($r,5);
                    195:                 return OK;
                    196:             }
                    197:             my $tinyurl;
                    198:             my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$urlcdom."\0".$key);
                    199:             if (defined($cached)) {
                    200:                 $tinyurl = $result;
                    201:             } else {
                    202:                 my $configuname = &Apache::lonnet::get_domainconfiguser($urlcdom);
                    203:                 my %currtiny = &Apache::lonnet::get('tiny',[$key],$urlcdom,$configuname);
                    204:                 if ($currtiny{$key} ne '') {
                    205:                     $tinyurl = $currtiny{$key};
                    206:                     &Apache::lonnet::do_cache_new('tiny',$urlcdom."\0".$key,$currtiny{$key},600);
                    207:                 }
                    208:             }
                    209:             if ($tinyurl ne '') {
                    210:                 $urlcnum = (split(/\&/,$tinyurl))[0];
                    211:             }
1.1       raeburn   212:         }
                    213:         if (($cdom eq '') && ($urlcdom ne '')) { 
                    214:             my $cprimary_id = &Apache::lonnet::domain($urlcdom,'primary');
                    215:             if ($cprimary_id ne '') {
                    216:                 my $cintdom = &Apache::lonnet::internet_dom($cprimary_id);
                    217:                 if (($cintdom ne '') && (grep(/^\Q$cintdom\E$/,@intdoms))) {
                    218:                     $cdom = $urlcdom;
                    219:                 }
                    220:             } else {
                    221:                 $urlcnum = '';
                    222:             }
                    223:         }
                    224:     }
                    225:     if ($cdom eq '') {
                    226:         if ($udom ne '') {
                    227:             $cdom = $udom;
                    228:         } else {
                    229:             $cdom = $defdom;
                    230:         }
                    231:     }
                    232: 
                    233: #
                    234: # Retrieve information for LTI Consumers in course domain
                    235: # and populate hash --  %lti_by_key -- for which keys
                    236: # are those defined in domain configuration for LTI.
                    237: #
                    238:  
                    239:     my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
                    240:     unless (keys(%lti) > 0) {
1.5       raeburn   241:         &invalid_request($r,6);
1.1       raeburn   242:         return OK;
                    243:     }
                    244:     my %lti_by_key;
                    245:     if (keys(%lti)) {
                    246:         foreach my $id (keys(%lti)) {
                    247:             if (ref($lti{$id}) eq 'HASH') {
                    248:                 my $key = $lti{$id}{'key'};
                    249:                 push(@{$lti_by_key{$key}},$id);
                    250:             }
                    251:         }
                    252:     }
                    253: 
                    254: #
                    255: # Verify the signed request using the secret for those
                    256: # Consumers for which the key in the POSTed data matches 
                    257: # keys in the domain configuration for LTI.
                    258: #
                    259:     my $hostname = $r->hostname;
                    260:     my $protocol = 'http';
                    261:     if ($ENV{'SERVER_PORT'} == 443) {
                    262:         $protocol = 'https';
                    263:     }
                    264: 
1.6       raeburn   265:     my ($itemid,$consumer_key,$secret);
1.3       raeburn   266:     $consumer_key = $params->{'oauth_consumer_key'};
                    267:     if (ref($lti_by_key{$consumer_key}) eq 'ARRAY') {
                    268:         foreach my $id (@{$lti_by_key{$consumer_key}}) {
1.1       raeburn   269:             if (ref($lti{$id}) eq 'HASH') {
1.2       raeburn   270:                 $secret = $lti{$id}{'secret'};
1.1       raeburn   271:                 my $request = Net::OAuth->request('request token')->from_hash($params,
                    272:                                                    request_url => $protocol.'://'.$hostname.$requri,
                    273:                                                    request_method => $env{'request.method'},
                    274:                                                    consumer_secret => $secret,);
                    275:                 if ($request->verify()) {
                    276:                     $itemid = $id;
                    277:                     last;
                    278:                 }
                    279:             }
                    280:         }
                    281:     }
                    282: 
                    283: #
                    284: # Request is invalid if the signed request could not be verified
                    285: # for the Consumer key and Consumer secret from the domain
                    286: # configuration in LON-CAPA for that LTI Consumer.
                    287: #
                    288:     unless (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
1.5       raeburn   289:         &invalid_request($r,7);
1.1       raeburn   290:         return OK;
                    291:     }
                    292: 
                    293: #
                    294: # Determine if nonce in POSTed data has expired.
                    295: # If unexpired, confirm it has not already been used.
                    296: #
1.2       raeburn   297:     unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
                    298:                                             $lti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
1.5       raeburn   299:         &invalid_request($r,8);
1.1       raeburn   300:         return OK;
                    301:     }
                    302: 
                    303: #
1.6       raeburn   304: # Determine if source of username matches requirement from the 
1.1       raeburn   305: # domain configuration for the specific LTI Consumer.
                    306: # 
                    307: 
                    308:     if ($lti{$itemid}{'mapuser'} eq $possmapuser) {
                    309:         $uname = $possuname;
                    310:     } elsif ($lti{$itemid}{'mapuser'} eq 'lis_person_sourcedid') {
                    311:         if ($params->{'lis_person_sourcedid'} =~ /^$match_username$/) {
                    312:             $uname = $possuname;
                    313:         }
                    314:     } elsif ($lti{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') {
                    315:         if ($params->{'lis_person_contact_email_primary'} =~ /^$match_username$/) {
                    316:             $uname = $params->{'lis_person_contact_email_primary'};
                    317:         }
                    318:     } elsif (exists($params->{$lti{$itemid}{'mapuser'}})) {
                    319:         if ($params->{$lti{$itemid}{'mapuser'}} =~ /^$match_username$/) {
                    320:             $uname = $params->{$lti{$itemid}{'mapuser'}};
                    321:         }
                    322:     }
                    323: 
                    324: #
                    325: # Determine the courseID of the LON-CAPA course to which the
                    326: # launch of LON-CAPA should provide access.
                    327: #
                    328: # Order is:
                    329: #
                    330: # (a) from course mapping (if the link between Consumer "course" and 
                    331: # Provider "course" has been established previously).
                    332: # (b) from tail of requested URL (after /adm/lti) if it has format of a symb
                    333: # (c) from tail of requested URL (after /adm/lti) if it has format of a map
                    334: # (d) from tail of requested URL (after /adm/lti) if it has format /domain/courseID
1.5       raeburn   335: # (e) from tail of requested URL (after /adm/lti) if it has format /tiny/domain/\w+
                    336: #     i.e., a shortened URL (see bug #6400).
1.1       raeburn   337: #
                    338: # If Consumer course included in POSTed data points as a target course which
                    339: # has a format which matches a LON-CAPA courseID, but the course does not
                    340: # exist, the request is invalid.
                    341: # 
                    342: 
                    343:     my ($sourcecrs,%consumers);
                    344:     if ($lti{$itemid}{'mapcrs'} eq 'course_offering_sourcedid') {
                    345:         $sourcecrs = $params->{'course_offering_sourcedid'};
                    346:     } elsif ($lti{$itemid}{'mapcrs'} eq 'context_id') {
                    347:         $sourcecrs = $params->{'context_id'};
                    348:     } elsif ($lti{$itemid}{'mapcrs'} ne '') {
                    349:         $sourcecrs = $params->{$lti{$itemid}{'mapcrs'}};
                    350:     }
                    351: 
                    352:     my $posscnum;
                    353:     if ($sourcecrs ne '') {
                    354:         %consumers = &Apache::lonnet::get_dom('lticonsumers',[$sourcecrs],$cdom);
                    355:         if (exists($consumers{$sourcecrs})) {
                    356:             if ($consumers{$sourcecrs} =~ /^$match_courseid$/) {
                    357:                 my $crshome = &Apache::lonnet::homeserver($consumers{$sourcecrs},$cdom);
                    358:                 if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
1.5       raeburn   359:                     &invalid_request($r,9);
1.1       raeburn   360:                     return OK;
                    361:                 } else {
                    362:                     $posscnum = $consumers{$sourcecrs};
                    363:                 }
                    364:             }
                    365:         }
                    366:     }
                    367: 
                    368:     if ($urlcnum ne '') {
                    369:         if ($posscnum ne '') {
                    370:             if ($posscnum ne $urlcnum) {
1.5       raeburn   371:                 &invalid_request($r,10);
1.1       raeburn   372:                 return OK;
                    373:             } else {
                    374:                 $cnum = $posscnum;
                    375:             }
                    376:         } else {
                    377:             my $crshome = &Apache::lonnet::homeserver($urlcnum,$cdom);
                    378:             if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
1.5       raeburn   379:                 &invalid_request($r,11);
1.1       raeburn   380:                 return OK;
                    381:             } else {
                    382:                 $cnum = $urlcnum;
                    383:             }
                    384:         }
                    385:     } elsif ($posscnum ne '') {
                    386:         $cnum = $posscnum;
                    387:     }
                    388: 
                    389: #
1.6       raeburn   390: # Get LON-CAPA role(s) to use from role-mapping of Consumer roles
1.1       raeburn   391: # defined in domain configuration for the appropriate LTI
                    392: # Consumer.
                    393: #
1.6       raeburn   394: # If multiple LON-CAPA roles are indicated for the current user,
                    395: # ordering (from first to last) is: cc/co, in, ta, ep, st.
1.1       raeburn   396: #
                    397: 
1.6       raeburn   398:     my (@ltiroles,@lcroles);
1.1       raeburn   399: 
1.6       raeburn   400:     my @lcroleorder = ('cc','in','ta','ep','st');
                    401:     my @ltiroleorder = ('Instructor','TeachingAssistant','Mentor','Learner');
1.1       raeburn   402:     if ($params->{'roles'} =~ /,/) {
1.6       raeburn   403:         my @possltiroles = split(/\s*,\s*/,$params->{'role'});
                    404:         foreach my $ltirole (@ltiroleorder) {
                    405:             if (grep(/^\Q$ltirole\E$/,@possltiroles)) {
                    406:                 push(@ltiroles,$ltirole);
                    407:             }
                    408:         }
1.1       raeburn   409:     } else {
                    410:         my $singlerole = $params->{'roles'};
                    411:         $singlerole =~ s/^\s|\s+$//g;
1.6       raeburn   412:         if (grep(/^\Q$singlerole\E$/,@ltiroleorder)) {
                    413:             @ltiroles = ($singlerole);
                    414:         }
1.1       raeburn   415:     }
                    416:     if (@ltiroles) {
                    417:         if (ref($lti{$itemid}{maproles}) eq 'HASH') {
                    418:             my %possroles;
                    419:             map { $possroles{$lti{$itemid}{maproles}{$_}} = 1; } @ltiroles;
1.6       raeburn   420:             if (keys(%possroles) > 0) {
                    421:                 foreach my $item (@lcroleorder) {
1.1       raeburn   422:                     if ($possroles{$item}) {
1.6       raeburn   423:                         push(@lcroles,$item);
1.1       raeburn   424:                     }
                    425:                 }
                    426:             }
                    427:         }
                    428:     }
                    429: 
                    430: #
                    431: # If no LON-CAPA username  -- is user allowed to create one?
                    432: #
                    433: 
                    434:     my $selfcreate;
                    435:     if (($uname ne '') && ($udom ne '')) {
                    436:         $uhome = &Apache::lonnet::homeserver($uname,$udom);
                    437:         if ($uhome =~ /(con_lost|no_host|no_such_host)/) {
                    438:             &Apache::lonnet::logthis(" LTI authorized unknown user $uname:$udom ");
                    439:             if (ref($lti{$itemid}{'makeuser'}) eq 'ARRAY') {
                    440:                 if (@{$lti{$itemid}{'makeuser'}} > 0) {
                    441:                     foreach my $ltirole (@ltiroles) {
                    442:                         if (grep(/^\Q$ltirole\E$/,@{$lti{$itemid}{'makeuser'}})) {
                    443:                             $selfcreate = 1;
1.6       raeburn   444:                             last;
1.1       raeburn   445:                         }
                    446:                     }
                    447:                 }
                    448:             }
                    449:             if ($selfcreate) {
1.6       raeburn   450:                 my (%rulematch,%inst_results,%curr_rules,%got_rules,%alerts,%info);
                    451:                 my $checkhash = { "$uname:$udom" => { 'newuser' => 1, }, };
                    452:                 my $checks = { 'username' => 1, };
                    453:                 &Apache::loncommon::user_rule_check($checkhash,$checks,\%alerts,\%rulematch,
                    454:                                                     \%inst_results,\%curr_rules,\%got_rules);
                    455:                 my ($userchkmsg,$lcauth,$lcauthparm);
                    456:                 my $allowed = 1;
                    457:                 if (ref($alerts{'username'}) eq 'HASH') {
                    458:                     if (ref($alerts{'username'}{$udom}) eq 'HASH') {
                    459:                         my $domdesc =
                    460:                             &Apache::lonnet::domain($udom,'description');
                    461:                         if ($alerts{'username'}{$udom}{$uname}) {
                    462:                             if (ref($curr_rules{$udom}) eq 'HASH') {
                    463:                                 $userchkmsg =
                    464:                                     &Apache::loncommon::instrule_disallow_msg('username',$domdesc,1).
                    465:                                     &Apache::loncommon::user_rule_formats($udom,$domdesc,
                    466:                                                                           $curr_rules{$udom}{'username'},
                    467:                                                                            'username');
                    468:                             }
                    469:                             $allowed = 0;
                    470:                         }
                    471:                     }
                    472:                 }
                    473:                 if ($allowed) {
                    474:                     if (ref($rulematch{$uname.':'.$udom}) eq 'HASH') {
                    475:                         my $matchedrule = $rulematch{$uname.':'.$udom}{'username'};
                    476:                         my ($rules,$ruleorder) =
                    477:                             &Apache::lonnet::inst_userrules($udom,'username');
                    478:                         if (ref($rules) eq 'HASH') {
                    479:                             if (ref($rules->{$matchedrule}) eq 'HASH') {
                    480:                                 $lcauth = $rules->{$matchedrule}{'authtype'};
                    481:                                 $lcauthparm = $rules->{$matchedrule}{'authparm'};
                    482:                             }
                    483:                         }
                    484:                     }
                    485:                     if ($lcauth eq '') {
                    486:                         $lcauth = $lti{$itemid}{'lcauth'};
1.7       raeburn   487:                         if ($lcauth eq 'internal') {
                    488:                             $lcauthparm = &create_passwd();
                    489:                         } else {
                    490:                             $lcauthparm = $lti{$itemid}{'lcauthparm'};
                    491:                         }
1.6       raeburn   492:                     }
                    493:                 } else {
                    494:                     &invalid_request($r,12);
                    495:                 }
                    496:                 my @userinfo = ('firstname','middlename','lastname','generation',
                    497:                                 'permanentemail','id');
                    498:                 my %useinstdata;
                    499:                 if (ref($lti{$itemid}{'instdata'}) eq 'ARRAY') {
                    500:                     map { $useinstdata{$_} = 1; } @{$lti{$itemid}{'instdata'}};
                    501:                 }
                    502:                 foreach my $item (@userinfo) {
                    503:                     if (($useinstdata{$item}) && (ref($inst_results{$uname.':'.$udom}) eq 'HASH') &&
                    504:                         ($inst_results{$uname.':'.$udom}{$item} ne '')) {
                    505:                         $info{$item} = $inst_results{$uname.':'.$udom}{$item};
                    506:                     } else {
                    507:                         if ($item eq 'permanentemail') {
                    508:                             if ($env{'form.lis_person_contact_email_primary'} =~/^[^\@]+\@[^@]+$/) {
                    509:                                 $info{$item} = $env{'form.lis_person_contact_email_primary'};
                    510:                             }
                    511:                         } elsif ($item eq 'firstname') {
                    512:                             $info{$item} = $env{'form.lis_person_name_given'};
                    513:                         } elsif ($item eq 'lastname') {
                    514:                             $info{$item} = $env{'form.lis_person_name_family'};
                    515:                         }
                    516:                     }
                    517:                 }
                    518:                 if (($info{'middlename'} eq '') && ($env{'form.lis_person_name_full'} ne '')) {
                    519:                     unless ($useinstdata{'middlename'}) {
                    520:                         my $fullname = $env{'form.lis_person_name_full'};
                    521:                         if ($info{'firstname'}) {
                    522:                             $fullname =~ s/^\s*\Q$info{'firstname'}\E\s*//i;
                    523:                         }
                    524:                         if ($info{'lastname'}) {
                    525:                             $fullname =~ s/\s*\Q$info{'lastname'}\E\s*$//i;
                    526:                         }
                    527:                         if ($fullname ne '') {
                    528:                             $fullname =~ s/^\s+|\s+$//g;
                    529:                             if ($fullname ne '') {
                    530:                                 $info{'middlename'} = $fullname;
                    531:                             }
                    532:                         }
                    533:                     }
                    534:                 }
                    535:                 if (ref($inst_results{$uname.':'.$udom}{'inststatus'}) eq 'ARRAY') {
                    536:                     my @inststatuses = @{$inst_results{$uname.':'.$udom}{'inststatus'}};
                    537:                     $info{'inststatus'} = join(':',map { &escape($_); } @inststatuses);
                    538:                 }
                    539:                 my $result =
                    540:                     &Apache::lonnet::modifyuser($udom,$uname,$info{'id'},
                    541:                                                 $lcauth,$lcauthparm,$info{'firstname'},
                    542:                                                 $info{'middlename'},$info{'lastname'},
                    543:                                                 $info{'generation'},undef,undef,
                    544:                                                 $info{'permanentemail'},$info{'inststatus'});
                    545:                 if ($result eq 'ok') {
                    546:                     if (($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'mapcrs'}) &&
                    547:                         ($lti{$itemid}{'makecrs'})) {
                    548:                         unless (&Apache::lonnet::usertools_access($uname,$udom,'lti','reload','requestcourses')) {
                    549:                             &Apache::lonnet::put('environment',{ 'requestcourses.lti' => 1, },$udom,$uname);
                    550:                         }
                    551:                     }
                    552:                 } else {
                    553:                     &invalid_request($r,13);
                    554:                     return OK;
                    555:                 }
1.1       raeburn   556:             } else {
1.6       raeburn   557:                 &invalid_request($r,14);
1.1       raeburn   558:                 return OK;
1.6       raeburn   559:             }
                    560:         }
1.1       raeburn   561:     } else {
1.6       raeburn   562:         &invalid_request($r,15);
1.1       raeburn   563:         return OK;
                    564:     }
                    565: 
                    566: #
                    567: # If no LON-CAPA course available, check if domain's configuration
                    568: # for the specific LTI Consumer allows a new course to be created 
1.6       raeburn   569: # (requires role in Consumer to be: Instructor and Instructor to map to CC)
1.1       raeburn   570: #
                    571: 
1.6       raeburn   572:     my $reqcrs;
1.1       raeburn   573:     if ($cnum eq '') {
1.6       raeburn   574:         if ((@ltiroles) && ($lti{$itemid}{'mapcrs'}) &&
                    575:             ($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'makecrs'})) {
                    576:             my (%can_request,%request_domains);
                    577:             &Apache::lonnet::check_can_request($cdom,\%can_request,\%request_domains,$uname,$udom);
                    578:             if ($can_request{'lti'}) {
                    579:                 $reqcrs = 1;
                    580:                 &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,undef,$mapurl,$tail,
                    581:                              $symb,$cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,
                    582:                              $reqcrs,$sourcecrs);
                    583:             } else {
                    584:                 &invalid_request($r,16);
                    585:             }
1.1       raeburn   586:         } else {
1.6       raeburn   587:             &invalid_request($r,17);
1.1       raeburn   588:         }
1.6       raeburn   589:         return OK;
1.1       raeburn   590:     }
                    591: 
                    592: #
                    593: # If LON-CAPA course is a Community, and LON-CAPA role
                    594: # indicated is cc, change role indicated to co.
                    595: # 
                    596: 
1.6       raeburn   597:     my %crsenv;
                    598:     if ($lcroles[0] eq 'cc') {
1.1       raeburn   599:         if (($cdom ne '') && ($cnum ne '')) {
1.6       raeburn   600:             %crsenv = &Apache::lonnet::coursedescription($cdom.'_'.$cnum,{ 'one_time' => 1,});
1.1       raeburn   601:             if ($crsenv{'type'} eq 'Community') {
1.6       raeburn   602:                 $lcroles[0] = 'co';
                    603:             }
                    604:         }
                    605:     }
                    606: 
                    607: #
                    608: # Determine if user has a LON-CAPA role in the mapped LON-CAPA course.
                    609: # If multiple LON-CAPA roles are available for the user's assigned LTI roles,
                    610: # choose the first available LON-CAPA role in the order: cc/co, in, ta, ep, st
                    611: #
                    612: 
                    613:     my ($role,$usec,$withsec);
                    614:     unless ((($lcroles[0] eq 'cc') || ($lcroles[0] eq 'co')) && (@lcroles == 1)) {
                    615:         if ($lti{$itemid}{'section'} ne '') {
                    616:             if ($lti{$itemid}{'section'} eq 'course_section_sourcedid') {
                    617:                 if ($env{'form.course_section_sourcedid'} !~ /\W/) {
                    618:                     $usec = $env{'form.course_section_sourcedid'};
                    619:                 }
                    620:             } elsif ($env{'form.'.$lti{$itemid}{'section'}} !~ /\W/) {
                    621:                 $usec = $env{'form.'.$lti{$itemid}{'section'}};
                    622:             }
                    623:         }
                    624:         if ($usec ne '') {
                    625:             $withsec = 1;
                    626:         }
                    627:     }
                    628: 
                    629:     if (@lcroles) {
                    630:         my %crsroles = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',undef,\@lcroles,
                    631:                                                      [$cdom],$withsec);
                    632:         foreach my $reqrole (@lcroles) {
                    633:             if ($withsec) {
                    634:                 my $incsec;
                    635:                 if (($reqrole eq 'cc') || ($reqrole eq 'co')) {
                    636:                     $incsec = '';
                    637:                 } else {
                    638:                     $incsec = $usec;
                    639:                 }
                    640:                 if (exists($crsroles{$cnum.':'.$cdom.':'.$reqrole.':'.$incsec})) {
                    641:                     $role = $reqrole.'./'.$cdom.'/'.$cnum;
                    642:                     if ($incsec ne '') {
                    643:                         $role .= '/'.$usec;
                    644:                     }
                    645:                     last;
                    646:                 }
                    647:             } else {
                    648:                 if (exists($crsroles{$cnum.':'.$cdom.':'.$reqrole})) {
                    649:                     $role = $reqrole.'./'.$cdom.'/'.$cnum;
                    650:                     last;
                    651:                 }
1.1       raeburn   652:             }
                    653:         }
                    654:     }
                    655: 
                    656: #
1.6       raeburn   657: # Determine if user can selfenroll
1.1       raeburn   658: #
                    659: 
1.6       raeburn   660:     my ($reqrole,$selfenrollrole);
                    661:     if ($role eq '') {
                    662:         if ((@ltiroles) && (ref($lti{$itemid}{'selfenroll'}) eq 'ARRAY')) {
                    663:             foreach my $ltirole (@ltiroles) {
                    664:                 if (grep(/^\Q$ltirole\E$/,@{$lti{$itemid}{'selfenroll'}})) {
                    665:                     if (ref($lti{$itemid}{maproles}) eq 'HASH') {
                    666:                         $reqrole = $lti{$itemid}{maproles}{$ltirole};
                    667:                         last;
                    668:                     }
                    669:                 }
                    670:             }
                    671:         }
                    672:         if ($reqrole eq '') {
                    673:             &invalid_request($r,18);
1.1       raeburn   674:             return OK;
                    675:         } else {
1.6       raeburn   676:             unless (%crsenv) {
                    677:                 %crsenv = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
                    678:             }
                    679:             my $default_enrollment_start_date = $crsenv{'default_enrollment_start_date'};
                    680:             my $default_enrollment_end_date   = $crsenv{'default_enrollment_end_date'};
                    681:             my $now = time;
                    682:             if ($default_enrollment_end_date && $default_enrollment_end_date <= $now) {
                    683:                 &invalid_request($r,19);
                    684:                 return OK;
                    685:             } elsif ($default_enrollment_start_date && $default_enrollment_start_date >$now) {
                    686:                 &invalid_request($r,20);
                    687:                 return OK;
                    688:             } else {
                    689:                 $selfenrollrole = $reqrole.'./'.$cdom.'/'.$cnum;
                    690:                 if (($withsec) && ($reqrole ne 'cc') && ($reqrole ne 'co')) {
                    691:                     if ($usec ne '') {
                    692:                         $selfenrollrole .= '/'.$usec;
                    693:                     }
                    694:                 }
                    695:             }
1.1       raeburn   696:         }
                    697:     }
                    698: 
                    699: #
                    700: # Store consumer-to-LON-CAPA course mapping
                    701: #
1.6       raeburn   702: 
1.1       raeburn   703:     if (($sourcecrs ne '')  && ($consumers{$sourcecrs} eq '') && ($cnum ne '')) {
                    704:         &Apache::lonnet::put_dom('lticonsumers',{ $sourcecrs => $cnum },$cdom);
                    705:     }
                    706: 
                    707: #
1.6       raeburn   708: # Start user session
                    709: #
                    710: 
                    711:     &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,$role,$mapurl,$tail,$symb,
                    712:                  $cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,undef,$sourcecrs,
                    713:                  $selfenrollrole);
                    714:     return OK;
                    715: }
                    716: 
                    717: sub lti_enroll {
                    718:     my ($uname,$udom,$selfenrollrole) = @_;
                    719:     my $enrollresult;
                    720:     my ($role,$cdom,$cnum,$sec) =
                    721:            ($selfenrollrole =~ m{^(\w+)\./($match_domain)/($match_courseid)(?:|/(\w*))$});
                    722:     if (($cnum ne '') && ($cdom ne '')) {
                    723:         my $chome = &Apache::lonnet::homeserver($cnum,$cdom);
                    724:         if ($chome ne 'no_host') {
                    725:             my %coursehash = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
                    726:             my $start = $coursehash{'default_enrollment_start_date'};
                    727:             my $end = $coursehash{'default_enrollment_end_date'};
                    728:             my $area = "/$cdom/$cnum";
                    729:             if (($role ne 'cc') && ($role ne 'co') && ($sec ne '')) {
                    730:                 $area .= '/'.$sec;
                    731:             }
                    732:             my $spec = $role.'.'.$area;
                    733:             my $instcid;
                    734:             if ($role eq 'st') {
                    735:                 $enrollresult =
                    736:                     &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,
                    737:                                                                undef,undef,$sec,$end,$start,
                    738:                                                                'ltienroll',undef,$cdom.'_'.$cnum,1,
                    739:                                                                'ltienroll','',$instcid);
                    740:             } elsif ($role =~ /^(cc|in|ta|ep)$/) {
                    741:                 $enrollresult =
                    742:                     &Apache::lonnet::assignrole($udom,$uname,$area,$role,$end,$start,
                    743:                                                 undef,1,'ltienroll');
                    744:             }
                    745:             if ($enrollresult eq 'ok') {
                    746:                 my (%userroles,%newrole,%newgroups);
                    747:                 &Apache::lonnet::standard_roleprivs(\%newrole,$role,$cdom,$spec,$cnum,
                    748:                                                     $area);
                    749:                 &Apache::lonnet::set_userprivs(\%userroles,\%newrole,\%newgroups);
                    750:                 $userroles{'user.role.'.$spec} = $start.'.'.$end;
                    751:                 &Apache::lonnet::appenv(\%userroles,[$role,'cm']);
                    752:             }
                    753:         }
                    754:     }
                    755:     return $enrollresult;
                    756: }
                    757: 
                    758: sub lti_reqcrs {
                    759:     my ($r,$cdom,$form,$uname,$udom) = @_;
                    760:     my (%can_request,%request_domains);
                    761:     &Apache::lonnet::check_can_request($cdom,\%can_request,\%request_domains,$uname,$udom);
                    762:     if ($can_request{'lti'}) {
                    763:         my %domconfig = &Apache::lonnet::get_dom('configuration',['requestcourses'],$cdom);
                    764:         my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
                    765:         &Apache::lonrequestcourse::print_textbook_form($r,$cdom,[$cdom],\%domdefs,
                    766:                                                        $domconfig{'requestcourses'},
                    767:                                                        \%can_request,'lti',$form);
                    768:     } else {
                    769:         $r->print(
                    770:               &Apache::loncommon::start_page('Invalid LTI call',undef,{'only_body' => 1}).
                    771:               &mt('Invalid LTI call').
                    772:               &Apache::loncommon::end_page()
                    773:         );
                    774:     }
                    775: }
                    776: 
                    777: sub lti_session {
                    778:     my ($r,$itemid,$uname,$udom,$uhome,$lonhost,$role,$mapurl,$tail,$symb,$cdom,$cnum,
                    779:         $params,$ltiroles,$ltihash,$lcroles,$reqcrs,$sourcecrs,$selfenrollrole) = @_;
                    780:     return unless ((ref($params) eq 'HASH') && (ref($ltiroles) eq 'ARRAY') &&
                    781:                    (ref($ltihash) eq 'HASH') && (ref($lcroles) eq 'ARRAY'));
                    782: #
1.1       raeburn   783: # Check if user should be hosted here or switched to another server.
                    784: #
                    785:     $r->user($uname);
1.6       raeburn   786:     if ($cnum) {
                    787:         if ($role) {
                    788:             &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, role: $role, course: $cdom\_$cnum");
                    789:         } elsif ($selfenrollrole =~ m{^(\w+)\./$cdom/$cnum}) {
                    790:             &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, desired role: $1 course: $cdom\_$cnum");
                    791:         }
                    792:     } else {
                    793:         &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, course dom: $cdom");
                    794:     }
1.1       raeburn   795:     my ($is_balancer,$otherserver,$hosthere);
                    796:     ($is_balancer,$otherserver) =
                    797:         &Apache::lonnet::check_loadbalancing($uname,$udom,'login');
                    798:     if ($is_balancer) {
                    799:         if ($otherserver eq '') {
                    800:             my $lowest_load;
                    801:             ($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($udom);
                    802:             if ($lowest_load > 100) {
                    803:                 $otherserver = &Apache::lonnet::spareserver($lowest_load,$lowest_load,1,$udom);
                    804:             }
                    805:         }
                    806:         if ($otherserver ne '') {
                    807:             my @hosts = &Apache::lonnet::current_machine_ids();
                    808:             if (grep(/^\Q$otherserver\E$/,@hosts)) {
                    809:                 $hosthere = $otherserver;
                    810:             }
                    811:         }
                    812:     }
                    813:     if (($is_balancer) && (!$hosthere)) {
                    814:         # login but immediately go to switch server.
                    815:         &Apache::lonauth::success($r,$uname,$udom,$uhome,'noredirect');
                    816:         if ($symb) {
                    817:             $env{'form.symb'} = $symb;
1.8     ! raeburn   818:             $env{'request.lti.uri'} = $symb;
1.6       raeburn   819:         } else {
                    820:             if ($mapurl) {
                    821:                 $env{'form.origurl'} = $mapurl;
1.8     ! raeburn   822:                 $env{'request.lti.uri'} = $mapurl;
1.6       raeburn   823:             } elsif ($tail =~ m{^\Q/tiny/$cdom/\E\w+$}) {
                    824:                 $env{'form.origurl'} = $tail;
1.8     ! raeburn   825:                 $env{'request.lti.uri'} = $tail;
1.6       raeburn   826:             } else {
                    827:                 unless ($tail eq '/adm/roles') {
                    828:                     $env{'form.origurl'} = '/adm/navmaps';
                    829:                 }
                    830:             }
1.1       raeburn   831:         }
                    832:         if ($role) {
                    833:             $env{'form.role'} = $role;
                    834:         }
1.6       raeburn   835:         if (($lcroles->[0] eq 'cc') && ($reqcrs)) {
                    836:             $env{'request.lti.reqcrs'} = 1;
                    837:             $env{'request.lti.reqrole'} = 'cc';
                    838:             $env{'request.lti.sourcecrs'} = $sourcecrs;
                    839:         }
                    840:         if ($selfenrollrole) {
                    841:             $env{'request.lti.selfenroll'} = $selfenrollrole;
                    842:             $env{'request.lti.sourcecrs'} = $sourcecrs;
                    843:         }
                    844:         if ($ltihash->{'passback'}) {
1.1       raeburn   845:             if ($params->{'lis_result_sourcedid'}) {
                    846:                 $env{'request.lti.passbackid'} = $params->{'lis_result_sourcedid'};
                    847:             }
                    848:             if ($params->{'lis_outcome_service_url'}) {
                    849:                 $env{'request.lti.passbackurl'} = $params->{'lis_outcome_service_url'};
                    850:             }
                    851:         }
1.6       raeburn   852:         if (($ltihash->{'roster'}) && (grep(/^Instructor$/,@{$ltiroles}))) {
1.1       raeburn   853:             if ($params->{'ext_ims_lis_memberships_id'}) {
1.6       raeburn   854:                 $env{'request.lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
1.1       raeburn   855:             }
                    856:             if ($params->{'ext_ims_lis_memberships_url'}) {
                    857:                 $env{'request.lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
                    858:             }
                    859:         }
                    860:         $env{'request.lti.login'} = 1;
1.8     ! raeburn   861:         if ($params->{'launch_presentation_document_target'}) {
        !           862:             $env{'request.lti.target'} = $params->{'launch_presentation_document_target'};
        !           863:         }
1.1       raeburn   864:         foreach my $key (%{$params}) {
                    865:             delete($env{'form.'.$key});
                    866:         }
                    867:         my $redirecturl = '/adm/switchserver';
                    868:         if ($otherserver ne '') {
                    869:             $redirecturl .= '?otherserver='.$otherserver;
                    870:         }
                    871:         $r->internal_redirect($redirecturl);
                    872:         $r->set_handlers('PerlHandler'=> undef);
                    873:     } else {
                    874:         # need to login them in, so generate the need data that
                    875:         # migrate expects to do login
                    876:         foreach my $key (%{$params}) {
                    877:             delete($env{'form.'.$key});
                    878:         }
                    879:         my $ip = $r->get_remote_host();
                    880:         my %info=('ip'        => $ip,
                    881:                   'domain'    => $udom,
                    882:                   'username'  => $uname,
                    883:                   'server'    => $lonhost,
                    884:                   'lti.login' => 1,
1.8     ! raeburn   885:                   'lti.uri'   => $tail,
1.1       raeburn   886:                  );
                    887:         if ($role) {
                    888:             $info{'role'} = $role;
                    889:         }
                    890:         if ($symb) {
1.6       raeburn   891:             $info{'symb'} = $symb;
                    892:         }
                    893:         if (($lcroles->[0] eq 'cc') && ($reqcrs)) {
                    894:             $info{'lti.reqcrs'} = 1;
                    895:             $info{'lti.reqrole'} = 'cc';
                    896:             $info{'lti.sourcecrs'} = $sourcecrs;
                    897:         }
                    898:         if ($selfenrollrole) {
                    899:             $info{'lti.selfenrollrole'} = $selfenrollrole;
1.1       raeburn   900:         }
1.6       raeburn   901:         if ($ltihash->{'passback'}) {
1.1       raeburn   902:             if ($params->{'lis_result_sourcedid'}) {
                    903:                 $info{'lti.passbackid'} = $params->{'lis_result_sourcedid'}
                    904:             }
                    905:             if ($params->{'lis_outcome_service_url'}) {
                    906:                 $info{'lti.passbackurl'} = $params->{'lis_outcome_service_url'}
                    907:             }
                    908:         }
1.6       raeburn   909:         if (($ltihash->{'roster'}) && (grep(/^Instructor$/,@{$ltiroles}))) {
1.1       raeburn   910:             if ($params->{'ext_ims_lis_memberships_id'}) {
                    911:                 $info{'lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
                    912:             }
                    913:             if ($params->{'ext_ims_lis_memberships_url'}) {
                    914:                 $info{'lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
                    915:             }
                    916:         }
1.8     ! raeburn   917:         if ($params->{'launch_presentation_document_target'}) {
        !           918:             $info{'lti.target'} = $params->{'launch_presentation_document_target'};
        !           919:         }
        !           920: 
1.1       raeburn   921:         unless ($info{'symb'}) {
                    922:             if ($mapurl) {
                    923:                 $info{'origurl'} = $mapurl;
1.6       raeburn   924:             } elsif ($tail =~ m{^\Q/tiny/$cdom/\E\w+$}) {
                    925:                 $info{'origurl'} = $tail;
1.1       raeburn   926:             } else {
                    927:                 unless ($tail eq '/adm/roles') {
                    928:                     $info{'origurl'} = '/adm/navmaps';
                    929:                 }
                    930:             }
                    931:         }
                    932:         if (($is_balancer) && ($hosthere)) {
                    933:             $info{'noloadbalance'} = $hosthere;
                    934:         }
                    935:         my $token = &Apache::lonnet::tmpput(\%info,$lonhost);
                    936:         $env{'form.token'} = $token;
                    937:         $r->internal_redirect('/adm/migrateuser');
                    938:         $r->set_handlers('PerlHandler'=> undef);
                    939:     }
1.6       raeburn   940:     return;
1.1       raeburn   941: }
                    942: 
                    943: sub invalid_request {
                    944:     my ($r,$num) = @_;
                    945:     &Apache::loncommon::content_type($r,'text/html');
                    946:     $r->send_http_header;
                    947:     if ($r->header_only) {
                    948:         return;
                    949:     }
                    950:     &Apache::lonlocal::get_language_handle($r);
                    951:     $r->print(
                    952:         &Apache::loncommon::start_page('Invalid LTI call').
                    953:         &mt('Invalid LTI call [_1]',$num).
                    954:         &Apache::loncommon::end_page());
                    955:     return;
                    956: }
                    957: 
1.7       raeburn   958: sub create_passwd {
                    959:     my $passwd = '';
                    960:     my @letts = ("a".."z");
                    961:     for (my $i=0; $i<8; $i++) {
                    962:         my $lettnum = int(rand(2));
                    963:         my $item = '';
                    964:         if ($lettnum) {
                    965:             $item = $letts[int(rand(26))];
                    966:             my $uppercase = int(rand(2));
                    967:             if ($uppercase) {
                    968:                 $item =~ tr/a-z/A-Z/;
                    969:             }
                    970:         } else {
                    971:             $item = int(rand(10));
                    972:         }
                    973:         $passwd .= $item;
                    974:     }
                    975:     return ($passwd);
                    976: }
                    977: 
1.1       raeburn   978: 1;

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>