# The LearningOnline Network with CAPA # LTI Consumer Module to respond to a course roster request. # # $Id: ltiroster.pm,v 1.1 2017/12/07 15:36:25 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # package Apache::ltiroster; use strict; use Apache::Constants qw(:common :http); use Encode; use Digest::SHA; use Apache::lonnet; use Apache::loncommon; use Apache::lonacc; use Apache::loncoursedata; use LONCAPA::ltiutils; sub handler { my $r = shift; my %errors; # # Retrieve data POSTed by LTI Provider # &Apache::lonacc::get_posted_cgi($r); my $params = {}; foreach my $key (sort(keys(%env))) { if ($key =~ /^form\.(.+)$/) { $params->{$1} = $env{$key}; } } unless (keys(%{$params})) { $errors{1} = 1; &invalid_request($r,\%errors); return OK; } # # Retrieve the signature, digested symb, and LON-CAPA courseID # from the ext_ims_lis_memberships_id in the POSTed data # unless ($params->{'ext_ims_lis_memberships_id'}) { $errors{2} = 1; &invalid_request($r,\%errors); return OK; } my ($rostersig,$digsymb,$cid) = split(/\Q:::\E/,$params->{'ext_ims_lis_memberships_id'}); unless ($rostersig && $digsymb && $cid) { $errors{3} = 1; &invalid_request($r,\%errors); return OK; } my ($cdom,$cnum,$marker,$symb); # # Determine the domain and the courseID of the LON-CAPA course to which the # launch of LON-CAPA should provide access. # ($cdom,$cnum) = &LONCAPA::ltiutils::get_loncapa_course($r->dir_config('lonHostID'), $cid,\%errors); unless ($cdom && $cnum) { $errors{4} = 1; &invalid_request($r,\%errors); return OK; } # # Use the digested symb to lookup the real symb in exttools.db # ($marker,$symb) = &LONCAPA::ltiutils::get_tool_instance($cdom,$cnum,$digsymb,undef,\%errors); unless ($marker) { $errors{5} = 1; &invalid_request($r,\%errors); return OK; } # # Retrieve the Consumer key and Consumer secret from the domain configuration # for the Tool Provider ID stored in the exttool_$marker.db # my (%toolsettings,%ltitools); my ($consumer_secret,$nonce_lifetime) = &LONCAPA::ltiutils::get_tool_secret($params->{'oauth_consumer_key'}, $marker,$symb,$cdom,$cnum, \%toolsettings,\%ltitools,\%errors); # # Verify the signed request using the consumer_key and # secret for the specific LTI Provider. # my $protocol = 'http'; if ($ENV{'SERVER_PORT'} == 443) { $protocol = 'https'; } unless (LONCAPA::ltiutils::verify_request($params,$protocol,$r->hostname,$r->uri, $env{'request.method'},$consumer_secret, \%errors)) { $errors{6} = 1; &invalid_request($r,\%errors); return OK; } # # Determine if nonce in POSTed data has expired. # If unexpired, confirm it has not already been used. unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'}, $nonce_lifetime,$cdom,$r->dir_config('lonLTIDir'))) { $errors{7} = 1; &invalid_request($r,\%errors); return OK; } # # Verify that the ext_ims_lis_memberships_id has not been tampered # with, and the rostersecret used to create it is still valid. # unless (&LONCAPA::ltiutils::verify_lis_item($rostersig,'roster',$digsymb,undef,$cdom,$cnum, \%toolsettings,\%ltitools,\%errors)) { $errors{8} = 1; &invalid_request($r,\%errors); return OK; } # # Retrieve users with active roles in course for all roles for which roles have been mapped # in domain configuration for the Tool Provider requesting the roster. # my %maproles; if (ref($ltitools{'roles'}) eq 'HASH') { %maproles = %{$ltitools{'roles'}}; } unless (keys(%maproles)) { $errors{9} = 1; &invalid_request($r,\%errors); return OK; } my $crstype; my @allroles = &Apache::lonuserutils::roles_by_context('course',0,$crstype); my (%availableroles,$coursepersonnel,$includestudents,%userdata, @needpersenv,@needstuenv,$needemail,$needfullname,$needuser, $needroles,$needsresult,$gradesecret); if ($ltitools{'passback'}) { my $now = time; if (&LONCAPA::ltiutils::set_service_secret($cdom,$cnum,$marker,'grade',$now, \%toolsettings,\%ltitools) eq 'ok') { if ($toolsettings{'gradesecret'} ne '') { $needsresult = 1; $gradesecret = $ltitools{'gradesecret'}; } } } foreach my $role (@allroles) { if (exists($maproles{$role})) { $availableroles{$role} = 1; if ($role eq 'st') { $includestudents = 1; } else { $coursepersonnel = 1; } } } if (keys(%availableroles)) { $needroles = 1; } if (ref($ltitools{'fields'}) eq 'HASH') { foreach my $field (keys(%{$ltitools{'fields'}})) { if (($field eq 'lastname') || ($field eq 'firstname')) { push(@needstuenv,$field); push(@needpersenv,$field); } elsif ($field eq 'email') { $needemail = 1; push(@needpersenv,'permanentemail'); } elsif ($field eq 'fullname') { $needfullname = 1; } elsif ($field eq 'user') { $needuser = 1; } } } my $statusidx = &Apache::loncoursedata::CL_STATUS(); my $emailidx = &Apache::loncoursedata::CL_PERMANENTEMAIL(); my %students; if ($includestudents) { my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cnum); if (ref($classlist) eq 'HASH') { %students = %{$classlist}; } } &Apache::loncommon::content_type($r,'text/xml'); $r->send_http_header; if ($r->header_only) { return; } $r->print(<<"END"); basic-lis-readmembershipsforcontext Success Status fullsuccess Roster retrieved END my %skipstu; if ($coursepersonnel) { my %personnel = &Apache::lonnet::get_my_roles($cnum,$cdom); foreach my $key (sort(keys(%personnel))) { my ($uname,$udom,$role) = split(/:/,$key); if ($availableroles{$role}) { $userdata{$uname.':'.$udom}{'ltiroles'}{$maproles{$role}} = 1; } } foreach my $user (sort(keys(%userdata))) { if (exists($students{$user})) { $skipstu{$user} = 1; } $r->print(" \n"); my ($uname,$udom) = split(/:/,$user); my $digest_user = &Encode::decode_utf8($uname.':'.$udom); $digest_user = &Digest::SHA::sha1_hex($digest_user); $r->print(' '.$digest_user.''."\n"); if (exists($students{$user})) { if (ref($students{$user}) eq 'ARRAY') { if ($students{$user}[$statusidx] eq 'Active') { $userdata{$uname.':'.$udom}{'ltiroles'}{$maproles{'st'}} = 1; } } } if ($needroles) { if (ref($userdata{$uname.':'.$udom}{'ltiroles'}) eq 'HASH') { $r->print(' '.join(',',sort(keys(%{$userdata{$uname.':'.$udom}{'ltiroles'}}))).''."\n"); } else { $r->print(" \n"); } } else { $r->print(" \n"); } if ($needuser) { $r->print(' '.$user.''."\n"); } else { $r->print(" \n"); } my %userinfo; if (@needpersenv) { %userinfo = &Apache::lonnet::userenvironment($udom,$uname,@needpersenv); } foreach my $item ('firstname','lastname','permanentemail') { my $info; if ((@needpersenv) && (grep(/^\Q$item\E$/,@needpersenv))) { $info = $userinfo{$item}; } if ($item eq 'firstname') { $r->print(' '.$info.''."\n"); } elsif ($item eq 'lastname') { $r->print(' '.$info.''."\n"); } elsif ($item eq 'permanentemail') { $r->print(' '.$info.''."\n"); } } if ($needfullname) { my $info = &Apache::loncommon::plainname($uname,$udom); if ($info eq $uname.':'.$udom) { $info = ''; } $r->print(' '.$info.''."\n"); } else { $r->print(' '."\n"); } if ($needsresult) { my $uniqid = $digsymb.':::'.$digest_user.':::'.$cid; my $sourcedid = &LONCAPA::ltiutils::get_service_id($gradesecret,$uniqid); $r->print(' '.$sourcedid.''."\n"); } else { $r->print(" \n"); } $r->print(" \n"); } } if (($includestudents) && (keys(%students))) { foreach my $user (keys(%students)) { next if ($skipstu{$user}); if (ref($students{$user}) eq 'ARRAY') { next unless ($students{$user}[$statusidx] eq 'Active'); $r->print(" \n"); my ($uname,$udom) = split(/:/,$user); my $digest_user = &Encode::decode_utf8($uname.':'.$udom); $digest_user = &Digest::SHA::sha1_hex($digest_user); $r->print(' '.$digest_user.''."\n"); if ($needroles) { $r->print(' '.$maproles{'st'}.''."\n"); } else { $r->print(" \n"); } if ($needuser) { $r->print(' '.$user.''."\n"); } else { $r->print(" \n"); } my %userinfo; if (@needstuenv) { %userinfo = &Apache::lonnet::userenvironment($udom,$uname,@needstuenv); } foreach my $item ('firstname','lastname') { my $info; if ((@needstuenv) && (grep(/^\Q$item\E$/,@needstuenv))) { $info = $userinfo{$item}; } if ($item eq 'firstname') { $r->print(' '.$info.''."\n"); } elsif ($item eq 'lastname') { $r->print(' '.$info.''."\n"); } } if ($needemail) { $r->print(' '.$students{$user}[$emailidx].''."\n"); } else { $r->print(' '."\n"); } if ($needfullname) { my $info = &Apache::loncommon::plainname($uname,$udom); if ($info eq $uname.':'.$udom) { $info = ''; } $r->print(' '.$info.''."\n"); } else { $r->print(' '."\n"); } if ($needsresult) { my $uniqid = $digsymb.':::'.$digest_user.':::'.$cid; my $sourcedid = &LONCAPA::ltiutils::get_service_id($gradesecret,$uniqid); $r->print(' '.$sourcedid.''."\n"); } else { $r->print(" \n"); } $r->print(" \n"); } } } $r->print(<<"END"); END return OK; } sub invalid_request { my ($r,$errors) = @_; my $errormsg; if (ref($errors) eq 'HASH') { $errormsg = join('&&',keys(%{$errors})); } &Apache::loncommon::content_type($r,'text/xml'); $r->send_http_header; if ($r->header_only) { return; } $r->print(<<"END"); basic-lis-updateresult Failure Error $errormsg END return; } 1;