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

1.1       raeburn     1: # The LearningOnline Network
                      2: # Basic LTI Authentication Module
                      3: #
1.6     ! raeburn     4: # $Id: ltiauth.pm,v 1.5 2018/01/12 20:42:38 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'};
        !           487:                         $lcauthparm = $lti{$itemid}{'lcauthparm'};
        !           488:                     }
        !           489:                 } else {
        !           490:                     &invalid_request($r,12);
        !           491:                 }
        !           492:                 my @userinfo = ('firstname','middlename','lastname','generation',
        !           493:                                 'permanentemail','id');
        !           494:                 my %useinstdata;
        !           495:                 if (ref($lti{$itemid}{'instdata'}) eq 'ARRAY') {
        !           496:                     map { $useinstdata{$_} = 1; } @{$lti{$itemid}{'instdata'}};
        !           497:                 }
        !           498:                 foreach my $item (@userinfo) {
        !           499:                     if (($useinstdata{$item}) && (ref($inst_results{$uname.':'.$udom}) eq 'HASH') &&
        !           500:                         ($inst_results{$uname.':'.$udom}{$item} ne '')) {
        !           501:                         $info{$item} = $inst_results{$uname.':'.$udom}{$item};
        !           502:                     } else {
        !           503:                         if ($item eq 'permanentemail') {
        !           504:                             if ($env{'form.lis_person_contact_email_primary'} =~/^[^\@]+\@[^@]+$/) {
        !           505:                                 $info{$item} = $env{'form.lis_person_contact_email_primary'};
        !           506:                             }
        !           507:                         } elsif ($item eq 'firstname') {
        !           508:                             $info{$item} = $env{'form.lis_person_name_given'};
        !           509:                         } elsif ($item eq 'lastname') {
        !           510:                             $info{$item} = $env{'form.lis_person_name_family'};
        !           511:                         }
        !           512:                     }
        !           513:                 }
        !           514:                 if (($info{'middlename'} eq '') && ($env{'form.lis_person_name_full'} ne '')) {
        !           515:                     unless ($useinstdata{'middlename'}) {
        !           516:                         my $fullname = $env{'form.lis_person_name_full'};
        !           517:                         if ($info{'firstname'}) {
        !           518:                             $fullname =~ s/^\s*\Q$info{'firstname'}\E\s*//i;
        !           519:                         }
        !           520:                         if ($info{'lastname'}) {
        !           521:                             $fullname =~ s/\s*\Q$info{'lastname'}\E\s*$//i;
        !           522:                         }
        !           523:                         if ($fullname ne '') {
        !           524:                             $fullname =~ s/^\s+|\s+$//g;
        !           525:                             if ($fullname ne '') {
        !           526:                                 $info{'middlename'} = $fullname;
        !           527:                             }
        !           528:                         }
        !           529:                     }
        !           530:                 }
        !           531:                 if (ref($inst_results{$uname.':'.$udom}{'inststatus'}) eq 'ARRAY') {
        !           532:                     my @inststatuses = @{$inst_results{$uname.':'.$udom}{'inststatus'}};
        !           533:                     $info{'inststatus'} = join(':',map { &escape($_); } @inststatuses);
        !           534:                 }
        !           535:                 my $result =
        !           536:                     &Apache::lonnet::modifyuser($udom,$uname,$info{'id'},
        !           537:                                                 $lcauth,$lcauthparm,$info{'firstname'},
        !           538:                                                 $info{'middlename'},$info{'lastname'},
        !           539:                                                 $info{'generation'},undef,undef,
        !           540:                                                 $info{'permanentemail'},$info{'inststatus'});
        !           541:                 if ($result eq 'ok') {
        !           542:                     if (($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'mapcrs'}) &&
        !           543:                         ($lti{$itemid}{'makecrs'})) {
        !           544:                         unless (&Apache::lonnet::usertools_access($uname,$udom,'lti','reload','requestcourses')) {
        !           545:                             &Apache::lonnet::put('environment',{ 'requestcourses.lti' => 1, },$udom,$uname);
        !           546:                         }
        !           547:                     }
        !           548:                 } else {
        !           549:                     &invalid_request($r,13);
        !           550:                     return OK;
        !           551:                 }
1.1       raeburn   552:             } else {
1.6     ! raeburn   553:                 &invalid_request($r,14);
1.1       raeburn   554:                 return OK;
1.6     ! raeburn   555:             }
        !           556:         }
1.1       raeburn   557:     } else {
1.6     ! raeburn   558:         &invalid_request($r,15);
1.1       raeburn   559:         return OK;
                    560:     }
                    561: 
                    562: #
                    563: # If no LON-CAPA course available, check if domain's configuration
                    564: # for the specific LTI Consumer allows a new course to be created 
1.6     ! raeburn   565: # (requires role in Consumer to be: Instructor and Instructor to map to CC)
1.1       raeburn   566: #
                    567: 
1.6     ! raeburn   568:     my $reqcrs;
1.1       raeburn   569:     if ($cnum eq '') {
1.6     ! raeburn   570:         if ((@ltiroles) && ($lti{$itemid}{'mapcrs'}) &&
        !           571:             ($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'makecrs'})) {
        !           572:             my (%can_request,%request_domains);
        !           573:             &Apache::lonnet::check_can_request($cdom,\%can_request,\%request_domains,$uname,$udom);
        !           574:             if ($can_request{'lti'}) {
        !           575:                 $reqcrs = 1;
        !           576:                 &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,undef,$mapurl,$tail,
        !           577:                              $symb,$cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,
        !           578:                              $reqcrs,$sourcecrs);
        !           579:             } else {
        !           580:                 &invalid_request($r,16);
        !           581:             }
1.1       raeburn   582:         } else {
1.6     ! raeburn   583:             &invalid_request($r,17);
1.1       raeburn   584:         }
1.6     ! raeburn   585:         return OK;
1.1       raeburn   586:     }
                    587: 
                    588: #
                    589: # If LON-CAPA course is a Community, and LON-CAPA role
                    590: # indicated is cc, change role indicated to co.
                    591: # 
                    592: 
1.6     ! raeburn   593:     my %crsenv;
        !           594:     if ($lcroles[0] eq 'cc') {
1.1       raeburn   595:         if (($cdom ne '') && ($cnum ne '')) {
1.6     ! raeburn   596:             %crsenv = &Apache::lonnet::coursedescription($cdom.'_'.$cnum,{ 'one_time' => 1,});
1.1       raeburn   597:             if ($crsenv{'type'} eq 'Community') {
1.6     ! raeburn   598:                 $lcroles[0] = 'co';
        !           599:             }
        !           600:         }
        !           601:     }
        !           602: 
        !           603: #
        !           604: # Determine if user has a LON-CAPA role in the mapped LON-CAPA course.
        !           605: # If multiple LON-CAPA roles are available for the user's assigned LTI roles,
        !           606: # choose the first available LON-CAPA role in the order: cc/co, in, ta, ep, st
        !           607: #
        !           608: 
        !           609:     my ($role,$usec,$withsec);
        !           610:     unless ((($lcroles[0] eq 'cc') || ($lcroles[0] eq 'co')) && (@lcroles == 1)) {
        !           611:         if ($lti{$itemid}{'section'} ne '') {
        !           612:             if ($lti{$itemid}{'section'} eq 'course_section_sourcedid') {
        !           613:                 if ($env{'form.course_section_sourcedid'} !~ /\W/) {
        !           614:                     $usec = $env{'form.course_section_sourcedid'};
        !           615:                 }
        !           616:             } elsif ($env{'form.'.$lti{$itemid}{'section'}} !~ /\W/) {
        !           617:                 $usec = $env{'form.'.$lti{$itemid}{'section'}};
        !           618:             }
        !           619:         }
        !           620:         if ($usec ne '') {
        !           621:             $withsec = 1;
        !           622:         }
        !           623:     }
        !           624: 
        !           625:     if (@lcroles) {
        !           626:         my %crsroles = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',undef,\@lcroles,
        !           627:                                                      [$cdom],$withsec);
        !           628:         foreach my $reqrole (@lcroles) {
        !           629:             if ($withsec) {
        !           630:                 my $incsec;
        !           631:                 if (($reqrole eq 'cc') || ($reqrole eq 'co')) {
        !           632:                     $incsec = '';
        !           633:                 } else {
        !           634:                     $incsec = $usec;
        !           635:                 }
        !           636:                 if (exists($crsroles{$cnum.':'.$cdom.':'.$reqrole.':'.$incsec})) {
        !           637:                     $role = $reqrole.'./'.$cdom.'/'.$cnum;
        !           638:                     if ($incsec ne '') {
        !           639:                         $role .= '/'.$usec;
        !           640:                     }
        !           641:                     last;
        !           642:                 }
        !           643:             } else {
        !           644:                 if (exists($crsroles{$cnum.':'.$cdom.':'.$reqrole})) {
        !           645:                     $role = $reqrole.'./'.$cdom.'/'.$cnum;
        !           646:                     last;
        !           647:                 }
1.1       raeburn   648:             }
                    649:         }
                    650:     }
                    651: 
                    652: #
1.6     ! raeburn   653: # Determine if user can selfenroll
1.1       raeburn   654: #
                    655: 
1.6     ! raeburn   656:     my ($reqrole,$selfenrollrole);
        !           657:     if ($role eq '') {
        !           658:         if ((@ltiroles) && (ref($lti{$itemid}{'selfenroll'}) eq 'ARRAY')) {
        !           659:             foreach my $ltirole (@ltiroles) {
        !           660:                 if (grep(/^\Q$ltirole\E$/,@{$lti{$itemid}{'selfenroll'}})) {
        !           661:                     if (ref($lti{$itemid}{maproles}) eq 'HASH') {
        !           662:                         $reqrole = $lti{$itemid}{maproles}{$ltirole};
        !           663:                         last;
        !           664:                     }
        !           665:                 }
        !           666:             }
        !           667:         }
        !           668:         if ($reqrole eq '') {
        !           669:             &invalid_request($r,18);
1.1       raeburn   670:             return OK;
                    671:         } else {
1.6     ! raeburn   672:             unless (%crsenv) {
        !           673:                 %crsenv = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
        !           674:             }
        !           675:             my $default_enrollment_start_date = $crsenv{'default_enrollment_start_date'};
        !           676:             my $default_enrollment_end_date   = $crsenv{'default_enrollment_end_date'};
        !           677:             my $now = time;
        !           678:             if ($default_enrollment_end_date && $default_enrollment_end_date <= $now) {
        !           679:                 &invalid_request($r,19);
        !           680:                 return OK;
        !           681:             } elsif ($default_enrollment_start_date && $default_enrollment_start_date >$now) {
        !           682:                 &invalid_request($r,20);
        !           683:                 return OK;
        !           684:             } else {
        !           685:                 $selfenrollrole = $reqrole.'./'.$cdom.'/'.$cnum;
        !           686:                 if (($withsec) && ($reqrole ne 'cc') && ($reqrole ne 'co')) {
        !           687:                     if ($usec ne '') {
        !           688:                         $selfenrollrole .= '/'.$usec;
        !           689:                     }
        !           690:                 }
        !           691:             }
1.1       raeburn   692:         }
                    693:     }
                    694: 
                    695: #
                    696: # Store consumer-to-LON-CAPA course mapping
                    697: #
1.6     ! raeburn   698: 
1.1       raeburn   699:     if (($sourcecrs ne '')  && ($consumers{$sourcecrs} eq '') && ($cnum ne '')) {
                    700:         &Apache::lonnet::put_dom('lticonsumers',{ $sourcecrs => $cnum },$cdom);
                    701:     }
                    702: 
                    703: #
1.6     ! raeburn   704: # Start user session
        !           705: #
        !           706: 
        !           707:     &lti_session($r,$itemid,$uname,$udom,$uhome,$lonhost,$role,$mapurl,$tail,$symb,
        !           708:                  $cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,undef,$sourcecrs,
        !           709:                  $selfenrollrole);
        !           710:     return OK;
        !           711: }
        !           712: 
        !           713: sub lti_enroll {
        !           714:     my ($uname,$udom,$selfenrollrole) = @_;
        !           715:     my $enrollresult;
        !           716:     my ($role,$cdom,$cnum,$sec) =
        !           717:            ($selfenrollrole =~ m{^(\w+)\./($match_domain)/($match_courseid)(?:|/(\w*))$});
        !           718:     if (($cnum ne '') && ($cdom ne '')) {
        !           719:         my $chome = &Apache::lonnet::homeserver($cnum,$cdom);
        !           720:         if ($chome ne 'no_host') {
        !           721:             my %coursehash = &Apache::lonnet::coursedescription($cdom.'_'.$cnum);
        !           722:             my $start = $coursehash{'default_enrollment_start_date'};
        !           723:             my $end = $coursehash{'default_enrollment_end_date'};
        !           724:             my $area = "/$cdom/$cnum";
        !           725:             if (($role ne 'cc') && ($role ne 'co') && ($sec ne '')) {
        !           726:                 $area .= '/'.$sec;
        !           727:             }
        !           728:             my $spec = $role.'.'.$area;
        !           729:             my $instcid;
        !           730:             if ($role eq 'st') {
        !           731:                 $enrollresult =
        !           732:                     &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,
        !           733:                                                                undef,undef,$sec,$end,$start,
        !           734:                                                                'ltienroll',undef,$cdom.'_'.$cnum,1,
        !           735:                                                                'ltienroll','',$instcid);
        !           736:             } elsif ($role =~ /^(cc|in|ta|ep)$/) {
        !           737:                 $enrollresult =
        !           738:                     &Apache::lonnet::assignrole($udom,$uname,$area,$role,$end,$start,
        !           739:                                                 undef,1,'ltienroll');
        !           740:             }
        !           741:             if ($enrollresult eq 'ok') {
        !           742:                 my (%userroles,%newrole,%newgroups);
        !           743:                 &Apache::lonnet::standard_roleprivs(\%newrole,$role,$cdom,$spec,$cnum,
        !           744:                                                     $area);
        !           745:                 &Apache::lonnet::set_userprivs(\%userroles,\%newrole,\%newgroups);
        !           746:                 $userroles{'user.role.'.$spec} = $start.'.'.$end;
        !           747:                 &Apache::lonnet::appenv(\%userroles,[$role,'cm']);
        !           748:             }
        !           749:         }
        !           750:     }
        !           751:     return $enrollresult;
        !           752: }
        !           753: 
        !           754: sub lti_reqcrs {
        !           755:     my ($r,$cdom,$form,$uname,$udom) = @_;
        !           756:     my (%can_request,%request_domains);
        !           757:     &Apache::lonnet::check_can_request($cdom,\%can_request,\%request_domains,$uname,$udom);
        !           758:     if ($can_request{'lti'}) {
        !           759:         my %domconfig = &Apache::lonnet::get_dom('configuration',['requestcourses'],$cdom);
        !           760:         my %domdefs = &Apache::lonnet::get_domain_defaults($cdom);
        !           761:         &Apache::lonrequestcourse::print_textbook_form($r,$cdom,[$cdom],\%domdefs,
        !           762:                                                        $domconfig{'requestcourses'},
        !           763:                                                        \%can_request,'lti',$form);
        !           764:     } else {
        !           765:         $r->print(
        !           766:               &Apache::loncommon::start_page('Invalid LTI call',undef,{'only_body' => 1}).
        !           767:               &mt('Invalid LTI call').
        !           768:               &Apache::loncommon::end_page()
        !           769:         );
        !           770:     }
        !           771: }
        !           772: 
        !           773: sub lti_session {
        !           774:     my ($r,$itemid,$uname,$udom,$uhome,$lonhost,$role,$mapurl,$tail,$symb,$cdom,$cnum,
        !           775:         $params,$ltiroles,$ltihash,$lcroles,$reqcrs,$sourcecrs,$selfenrollrole) = @_;
        !           776:     return unless ((ref($params) eq 'HASH') && (ref($ltiroles) eq 'ARRAY') &&
        !           777:                    (ref($ltihash) eq 'HASH') && (ref($lcroles) eq 'ARRAY'));
        !           778: #
1.1       raeburn   779: # Check if user should be hosted here or switched to another server.
                    780: #
                    781:     $r->user($uname);
1.6     ! raeburn   782:     if ($cnum) {
        !           783:         if ($role) {
        !           784:             &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, role: $role, course: $cdom\_$cnum");
        !           785:         } elsif ($selfenrollrole =~ m{^(\w+)\./$cdom/$cnum}) {
        !           786:             &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, desired role: $1 course: $cdom\_$cnum");
        !           787:         }
        !           788:     } else {
        !           789:         &Apache::lonnet::logthis(" LTI authorized user ($itemid): $uname:$udom, course dom: $cdom");
        !           790:     }
1.1       raeburn   791:     my ($is_balancer,$otherserver,$hosthere);
                    792:     ($is_balancer,$otherserver) =
                    793:         &Apache::lonnet::check_loadbalancing($uname,$udom,'login');
                    794:     if ($is_balancer) {
                    795:         if ($otherserver eq '') {
                    796:             my $lowest_load;
                    797:             ($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($udom);
                    798:             if ($lowest_load > 100) {
                    799:                 $otherserver = &Apache::lonnet::spareserver($lowest_load,$lowest_load,1,$udom);
                    800:             }
                    801:         }
                    802:         if ($otherserver ne '') {
                    803:             my @hosts = &Apache::lonnet::current_machine_ids();
                    804:             if (grep(/^\Q$otherserver\E$/,@hosts)) {
                    805:                 $hosthere = $otherserver;
                    806:             }
                    807:         }
                    808:     }
                    809:     if (($is_balancer) && (!$hosthere)) {
                    810:         # login but immediately go to switch server.
                    811:         &Apache::lonauth::success($r,$uname,$udom,$uhome,'noredirect');
                    812:         if ($symb) {
                    813:             $env{'form.symb'} = $symb;
1.6     ! raeburn   814:         } else {
        !           815:             if ($mapurl) {
        !           816:                 $env{'form.origurl'} = $mapurl;
        !           817:             } elsif ($tail =~ m{^\Q/tiny/$cdom/\E\w+$}) {
        !           818:                 $env{'form.origurl'} = $tail;
        !           819:             } else {
        !           820:                 unless ($tail eq '/adm/roles') {
        !           821:                     $env{'form.origurl'} = '/adm/navmaps';
        !           822:                 }
        !           823:             }
1.1       raeburn   824:         }
                    825:         if ($role) {
                    826:             $env{'form.role'} = $role;
                    827:         }
1.6     ! raeburn   828:         if (($lcroles->[0] eq 'cc') && ($reqcrs)) {
        !           829:             $env{'request.lti.reqcrs'} = 1;
        !           830:             $env{'request.lti.reqrole'} = 'cc';
        !           831:             $env{'request.lti.sourcecrs'} = $sourcecrs;
        !           832:         }
        !           833:         if ($selfenrollrole) {
        !           834:             $env{'request.lti.selfenroll'} = $selfenrollrole;
        !           835:             $env{'request.lti.sourcecrs'} = $sourcecrs;
        !           836:         }
        !           837:         if ($ltihash->{'passback'}) {
1.1       raeburn   838:             if ($params->{'lis_result_sourcedid'}) {
                    839:                 $env{'request.lti.passbackid'} = $params->{'lis_result_sourcedid'};
                    840:             }
                    841:             if ($params->{'lis_outcome_service_url'}) {
                    842:                 $env{'request.lti.passbackurl'} = $params->{'lis_outcome_service_url'};
                    843:             }
                    844:         }
1.6     ! raeburn   845:         if (($ltihash->{'roster'}) && (grep(/^Instructor$/,@{$ltiroles}))) {
1.1       raeburn   846:             if ($params->{'ext_ims_lis_memberships_id'}) {
1.6     ! raeburn   847:                 $env{'request.lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
1.1       raeburn   848:             }
                    849:             if ($params->{'ext_ims_lis_memberships_url'}) {
                    850:                 $env{'request.lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
                    851:             }
                    852:         }
                    853:         $env{'request.lti.login'} = 1;
                    854:         foreach my $key (%{$params}) {
                    855:             delete($env{'form.'.$key});
                    856:         }
                    857:         my $redirecturl = '/adm/switchserver';
                    858:         if ($otherserver ne '') {
                    859:             $redirecturl .= '?otherserver='.$otherserver;
                    860:         }
                    861:         $r->internal_redirect($redirecturl);
                    862:         $r->set_handlers('PerlHandler'=> undef);
                    863:     } else {
                    864:         # need to login them in, so generate the need data that
                    865:         # migrate expects to do login
                    866:         foreach my $key (%{$params}) {
                    867:             delete($env{'form.'.$key});
                    868:         }
                    869:         my $ip = $r->get_remote_host();
                    870:         my %info=('ip'        => $ip,
                    871:                   'domain'    => $udom,
                    872:                   'username'  => $uname,
                    873:                   'server'    => $lonhost,
                    874:                   'lti.login' => 1,
                    875:                  );
                    876:         if ($role) {
                    877:             $info{'role'} = $role;
                    878:         }
                    879:         if ($symb) {
1.6     ! raeburn   880:             $info{'symb'} = $symb;
        !           881:         }
        !           882:         if (($lcroles->[0] eq 'cc') && ($reqcrs)) {
        !           883:             $info{'lti.reqcrs'} = 1;
        !           884:             $info{'lti.reqrole'} = 'cc';
        !           885:             $info{'lti.sourcecrs'} = $sourcecrs;
        !           886:         }
        !           887:         if ($selfenrollrole) {
        !           888:             $info{'lti.selfenrollrole'} = $selfenrollrole;
1.1       raeburn   889:         }
1.6     ! raeburn   890:         if ($ltihash->{'passback'}) {
1.1       raeburn   891:             if ($params->{'lis_result_sourcedid'}) {
                    892:                 $info{'lti.passbackid'} = $params->{'lis_result_sourcedid'}
                    893:             }
                    894:             if ($params->{'lis_outcome_service_url'}) {
                    895:                 $info{'lti.passbackurl'} = $params->{'lis_outcome_service_url'}
                    896:             }
                    897:         }
1.6     ! raeburn   898:         if (($ltihash->{'roster'}) && (grep(/^Instructor$/,@{$ltiroles}))) {
1.1       raeburn   899:             if ($params->{'ext_ims_lis_memberships_id'}) {
                    900:                 $info{'lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
                    901:             }
                    902:             if ($params->{'ext_ims_lis_memberships_url'}) {
                    903:                 $info{'lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
                    904:             }
                    905:         }
                    906:         unless ($info{'symb'}) {
                    907:             if ($mapurl) {
                    908:                 $info{'origurl'} = $mapurl;
1.6     ! raeburn   909:             } elsif ($tail =~ m{^\Q/tiny/$cdom/\E\w+$}) {
        !           910:                 $info{'origurl'} = $tail;
1.1       raeburn   911:             } else {
                    912:                 unless ($tail eq '/adm/roles') {
                    913:                     $info{'origurl'} = '/adm/navmaps';
                    914:                 }
                    915:             }
                    916:         }
                    917:         if (($is_balancer) && ($hosthere)) {
                    918:             $info{'noloadbalance'} = $hosthere;
                    919:         }
                    920:         my $token = &Apache::lonnet::tmpput(\%info,$lonhost);
                    921:         $env{'form.token'} = $token;
                    922:         $r->internal_redirect('/adm/migrateuser');
                    923:         $r->set_handlers('PerlHandler'=> undef);
                    924:     }
1.6     ! raeburn   925:     return;
1.1       raeburn   926: }
                    927: 
                    928: sub invalid_request {
                    929:     my ($r,$num) = @_;
                    930:     &Apache::loncommon::content_type($r,'text/html');
                    931:     $r->send_http_header;
                    932:     if ($r->header_only) {
                    933:         return;
                    934:     }
                    935:     &Apache::lonlocal::get_language_handle($r);
                    936:     $r->print(
                    937:         &Apache::loncommon::start_page('Invalid LTI call').
                    938:         &mt('Invalid LTI call [_1]',$num).
                    939:         &Apache::loncommon::end_page());
                    940:     return;
                    941: }
                    942: 
                    943: 1;

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