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

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.