[LON-CAPA-cvs] cvs: doc /loncapafiles loncapafiles.lpml loncom loncapa_apache.conf lontrans.pm loncom/auth lonlti.pm migrateuser.pm switchserver.pm loncom/interface domainprefs.pm lonconfigsettings.pm loncom/lonnet/perl lonnet.pm
raeburn
raeburn at source.lon-capa.org
Thu Nov 30 10:15:23 EST 2017
raeburn Thu Nov 30 15:15:23 2017 EDT
Added files:
/loncom/auth lonlti.pm
Modified files:
/loncom/auth migrateuser.pm switchserver.pm
/loncom loncapa_apache.conf lontrans.pm
/loncom/lonnet/perl lonnet.pm
/loncom/interface domainprefs.pm lonconfigsettings.pm
/doc/loncapafiles loncapafiles.lpml
Log:
- Bug 6754 LTI Integration: LON-CAPA as LTI Provider
- Work in progress
-------------- next part --------------
Index: loncom/auth/migrateuser.pm
diff -u loncom/auth/migrateuser.pm:1.25 loncom/auth/migrateuser.pm:1.26
--- loncom/auth/migrateuser.pm:1.25 Mon Dec 5 00:51:43 2016
+++ loncom/auth/migrateuser.pm Thu Nov 30 15:14:51 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network
# Starts a user off based of an existing token.
#
-# $Id: migrateuser.pm,v 1.25 2016/12/05 00:51:43 raeburn Exp $
+# $Id: migrateuser.pm,v 1.26 2017/11/30 15:14:51 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -53,7 +53,7 @@
}
-sub sso_check {
+sub sso_lti_check {
my ($data) = @_;
my %extra_env;
if (ref($data) eq 'HASH') {
@@ -64,6 +64,21 @@
$extra_env{'request.sso.reloginserver'} =
$data->{'sso.reloginserver'};
}
+ if ($data->{'lti.login'}) {
+ $extra_env{'request.lti.login'} = $data->{'lti.login'};
+ }
+ if ($data->{'lti.passbackid'}) {
+ $extra_env{'request.lti.passbackid'} = $data->{'lti.passbackid'};
+ }
+ if ($data->{'lti.passbackurl'}) {
+ $extra_env{'request.lti.passbackurl'} = $data->{'lti.passbackurl'};
+ }
+ if ($data->{'lti.rosterid'}) {
+ $extra_env{'request.lti.rosterid'} = $data->{'lti.rosterid'};
+ }
+ if ($data->{'lti.rosterurl'}) {
+ $extra_env{'request.lti.rosterurl'} = $data->{'lti.rosterurl'};
+ }
}
return \%extra_env;
}
@@ -243,7 +258,7 @@
}
if ($home =~ /(con_lost|no_such_host)/) { return &goto_login($r,$udom); }
- my $extra_env = &sso_check(\%data);
+ my $extra_env = &sso_lti_check(\%data);
my %form;
if ($data{'symb'} ne '') {
Index: loncom/auth/switchserver.pm
diff -u loncom/auth/switchserver.pm:1.37 loncom/auth/switchserver.pm:1.38
--- loncom/auth/switchserver.pm:1.37 Tue Aug 8 20:10:55 2017
+++ loncom/auth/switchserver.pm Thu Nov 30 15:14:51 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network
# Switch Servers Handler
#
-# $Id: switchserver.pm,v 1.37 2017/08/08 20:10:55 raeburn Exp $
+# $Id: switchserver.pm,v 1.38 2017/11/30 15:14:51 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -228,6 +228,21 @@
if ($env{'request.sso.reloginserver'}) {
$info{'sso.reloginserver'} = $env{'request.sso.reloginserver'};
}
+ if ($env{'request.lti.login'}) {
+ $info{'lti.login'} = $env{'request.lti.login'};
+ }
+ if ($env{'request.lti.passbackid'}) {
+ $info{'lti.passbackid'} = $env{'request.lti.passbackid'};
+ }
+ if ($env{'request.lti.passbackurl'}) {
+ $info{'lti.passbackurl'} = $env{'request.lti.passbackurl'};
+ }
+ if ($env{'request.lti.rosterid'}) {
+ $info{'lti.rosterid'} = $env{'request.lti.rosterid'};
+ }
+ if ($env{'request.lti.rosterurl'}) {
+ $info{'lti.rosterurl'} = $env{'request.lti.rosterurl'};
+ }
my $token = &Apache::lonnet::tmpput(\%info,$env{'form.otherserver'});
my $url =$protocol.'://'.$switch_to.'/adm/login?'.
'domain='.$env{'user.domain'}.
Index: loncom/loncapa_apache.conf
diff -u loncom/loncapa_apache.conf:1.257 loncom/loncapa_apache.conf:1.258
--- loncom/loncapa_apache.conf:1.257 Tue Jun 6 13:37:04 2017
+++ loncom/loncapa_apache.conf Thu Nov 30 15:14:58 2017
@@ -2,7 +2,7 @@
## loncapa_apache.conf -- Apache HTTP LON-CAPA configuration file
##
-# $Id: loncapa_apache.conf,v 1.257 2017/06/06 13:37:04 raeburn Exp $
+# $Id: loncapa_apache.conf,v 1.258 2017/11/30 15:14:58 raeburn Exp $
#
# LON-CAPA Section (extensions to httpd.conf daemon configuration)
@@ -753,6 +753,11 @@
PerlHandler Apache::lonlogin
</Location>
+<LocationMatch "^/+adm/lti($|/)">
+SetHandler perl-script
+PerlHandler Apache::lonlti
+</LocationMatch>
+
<Location /adm/restrictedaccess>
PerlAccessHandler Apache::publiccheck
AuthType LONCAPA
@@ -1725,6 +1730,7 @@
PerlSetVar lonZipDir /home/httpd/zipspool
PerlSetVar lonCaptchaDir /home/httpd/captchaspool
PerlSetVar lonCaptchaDb /home/httpd/captchadb
+PerlSetVar lonLTIDir /home/httpd/lonLTItmp
PerlSetVar lonFontsDir /home/httpd/html/adm/fonts
# & separated list of % separated fields in order of
# - internal name to call it,
Index: loncom/lontrans.pm
diff -u loncom/lontrans.pm:1.16 loncom/lontrans.pm:1.17
--- loncom/lontrans.pm:1.16 Tue Aug 16 20:17:54 2016
+++ loncom/lontrans.pm Thu Nov 30 15:14:58 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network
# URL translation for User Files
#
-# $Id: lontrans.pm,v 1.16 2016/08/16 20:17:54 raeburn Exp $
+# $Id: lontrans.pm,v 1.17 2017/11/30 15:14:58 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -38,7 +38,34 @@
my $r = shift;
# FIXME line remove when mod_perl fixes BUG#4948
$r->notes->set('error-notes' => '');
- if ($r->uri=~m{^/raw/}) {
+ if ($r->uri =~ m{^/adm/lti/(.+)$}) {
+ my $realuri = $1;
+ my %user;
+ my $handle = &Apache::lonnet::check_for_valid_session($r,undef,\%user);
+ if (($handle ne '') && ($user{'lti'})) {
+ if ($realuri =~ m{^uploaded/$match_domain/$match_courseid/(default|supplemental)(|_\d+)\.(?:sequence|page)___\d+___.+$}) {
+ my ($map,$resid,$url) = split(/___/,$realuri);
+ $realuri = &Apache::lonnet::clutter($url).'?symb='.$realuri;
+ } elsif ($realuri =~ m{($match_domain)/($match_courseid)$}) {
+ $realuri = '/adm/navmaps';
+ } else {
+ $realuri = '/'.$realuri;
+ if ($realuri =~ m{/default_\d+\.sequence$}) {
+ $realuri .= (($realuri =~/\?/)?'&':'?').'navmap=1';
+ }
+ }
+ my $host = $r->headers_in->get('Host');
+ if ($host) {
+ my $protocol = 'http';
+ if ($r->get_server_port == 443) {
+ $protocol = 'https';
+ }
+ my $location = $protocol.'://'.$host.$realuri;
+ $r->headers_out->set(Location => $location);
+ return REDIRECT;
+ }
+ }
+ } elsif ($r->uri=~m{^/raw/}) {
my $host = $r->headers_in->get('Host');
if ($host) {
unless ($host =~ /^internal\-/) {
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1360 loncom/lonnet/perl/lonnet.pm:1.1361
--- loncom/lonnet/perl/lonnet.pm:1.1360 Thu Nov 30 14:41:38 2017
+++ loncom/lonnet/perl/lonnet.pm Thu Nov 30 15:15:06 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network
# TCP networking package
#
-# $Id: lonnet.pm,v 1.1360 2017/11/30 14:41:38 raeburn Exp $
+# $Id: lonnet.pm,v 1.1361 2017/11/30 15:15:06 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -707,6 +707,7 @@
if (ref($userhashref) eq 'HASH') {
$userhashref->{'name'} = $disk_env{'user.name'};
$userhashref->{'domain'} = $disk_env{'user.domain'};
+ $userhashref->{'lti'} = $disk_env{'request.lti.login'};
}
return $handle;
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.319 loncom/interface/domainprefs.pm:1.320
--- loncom/interface/domainprefs.pm:1.319 Thu Nov 30 02:17:50 2017
+++ loncom/interface/domainprefs.pm Thu Nov 30 15:15:14 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Handler to set domain-wide configuration settings
#
-# $Id: domainprefs.pm,v 1.319 2017/11/30 02:17:50 raeburn Exp $
+# $Id: domainprefs.pm,v 1.320 2017/11/30 15:15:14 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -219,13 +219,14 @@
'serverstatuses','requestcourses','helpsettings',
'coursedefaults','usersessions','loadbalancing',
'requestauthor','selfenrollment','inststatus',
- 'ltitools','ssl','trust'],$dom);
+ 'ltitools','ssl','trust','lti'],$dom);
+ my %encconfig =
+ &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom);
if (ref($domconfig{'ltitools'}) eq 'HASH') {
- my %encconfig =
- &Apache::lonnet::get_dom('encconfig',['ltitools'],$dom);
if (ref($encconfig{'ltitools'}) eq 'HASH') {
foreach my $id (keys(%{$domconfig{'ltitools'}})) {
- if (ref($domconfig{'ltitools'}{$id}) eq 'HASH') {
+ if ((ref($domconfig{'ltitools'}{$id}) eq 'HASH') &&
+ (ref($encconfig{'ltitools'}{$id}) eq 'HASH')) {
foreach my $item ('key','secret') {
$domconfig{'ltitools'}{$id}{$item} = $encconfig{'ltitools'}{$id}{$item};
}
@@ -233,12 +234,24 @@
}
}
}
+ if (ref($domconfig{'lti'}) eq 'HASH') {
+ if (ref($encconfig{'lti'}) eq 'HASH') {
+ foreach my $id (keys(%{$domconfig{'lti'}})) {
+ if ((ref($domconfig{'lti'}{$id}) eq 'HASH') &&
+ (ref($encconfig{'lti'}{$id}) eq 'HASH')) {
+ foreach my $item ('key','secret') {
+ $domconfig{'lti'}{$id}{$item} = $encconfig{'lti'}{$id}{$item};
+ }
+ }
+ }
+ }
+ }
my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll',
'autoupdate','autocreate','directorysrch','contacts',
'usercreation','selfcreation','usermodification','scantron',
'requestcourses','requestauthor','coursecategories',
'serverstatuses','helpsettings','coursedefaults',
- 'ltitools','selfenrollment','usersessions','ssl','trust');
+ 'ltitools','selfenrollment','usersessions','ssl','trust','lti');
my %existing;
if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
%existing = %{$domconfig{'loadbalancing'}};
@@ -545,6 +558,14 @@
print => \&print_trust,
modify => \&modify_trust,
},
+ 'lti' =>
+ {text => 'LTI Provider',
+ help => 'Domain_Configuration_LTI_Provider',
+ header => [{col1 => 'Setting',
+ col2 => 'Value',}],
+ print => \&print_lti,
+ modify => \&modify_lti,
+ },
);
if (keys(%servers) > 1) {
$prefs{'login'} = { text => 'Log-in page options',
@@ -730,6 +751,8 @@
$output = &modify_ssl($dom,$lastactref,%domconfig);
} elsif ($action eq 'trust') {
$output = &modify_trust($dom,$lastactref,%domconfig);
+ } elsif ($action eq 'lti') {
+ $output = &modify_lti($r,$dom,$action,$lastactref,%domconfig);
}
return $output;
}
@@ -1065,7 +1088,7 @@
$output .= &print_quotas($dom,$settings,\$rowtotal,$action);
} elsif (($action eq 'autoenroll') || ($action eq 'autocreate') ||
($action eq 'serverstatuses') || ($action eq 'loadbalancing') ||
- ($action eq 'ltitools')) {
+ ($action eq 'ltitools') || ($action eq 'lti')) {
$output .= $item->{'print'}->($dom,$settings,\$rowtotal);
} elsif ($action eq 'scantron') {
$output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal);
@@ -2697,6 +2720,165 @@
ENDSCRIPT
}
+sub lti_javascript {
+ my ($settings) = @_;
+ my $togglejs = <i_toggle_js();
+ unless (ref($settings) eq 'HASH') {
+ return $togglejs;
+ }
+ my (%ordered,$total,%jstext);
+ $total = 0;
+ foreach my $item (keys(%{$settings})) {
+ if (ref($settings->{$item}) eq 'HASH') {
+ my $num = $settings->{$item}{'order'};
+ $ordered{$num} = $item;
+ }
+ }
+ $total = scalar(keys(%{$settings}));
+ my @jsarray = ();
+ foreach my $item (sort {$a <=> $b } (keys(%ordered))) {
+ push(@jsarray,$ordered{$item});
+ }
+ my $jstext = ' var lti = Array('."'".join("','", at jsarray)."'".');'."\n";
+ return <<"ENDSCRIPT";
+<script type="text/javascript">
+// <![CDATA[
+function reorderLTI(form,item) {
+ var changedVal;
+$jstext
+ var newpos = 'lti_pos_add';
+ var maxh = 1 + $total;
+ var current = new Array;
+ var newitemVal = form.elements[newpos].options[form.elements[newpos].selectedIndex].value;
+ if (item == newpos) {
+ changedVal = newitemVal;
+ } else {
+ changedVal = form.elements[item].options[form.elements[item].selectedIndex].value;
+ current[newitemVal] = newpos;
+ }
+ for (var i=0; i<lti.length; i++) {
+ var elementName = 'lti_pos_'+lti[i];
+ if (elementName != item) {
+ if (form.elements[elementName]) {
+ var currVal = form.elements[elementName].options[form.elements[elementName].selectedIndex].value;
+ current[currVal] = elementName;
+ }
+ }
+ }
+ var oldVal;
+ for (var j=0; j<maxh; j++) {
+ if (current[j] == undefined) {
+ oldVal = j;
+ }
+ }
+ if (oldVal < changedVal) {
+ for (var k=oldVal+1; k<=changedVal ; k++) {
+ var elementName = current[k];
+ form.elements[elementName].selectedIndex = form.elements[elementName].selectedIndex - 1;
+ }
+ } else {
+ for (var k=changedVal; k<oldVal; k++) {
+ var elementName = current[k];
+ form.elements[elementName].selectedIndex = form.elements[elementName].selectedIndex + 1;
+ }
+ }
+ return;
+}
+// ]]>
+</script>
+
+$togglejs
+
+ENDSCRIPT
+}
+
+sub lti_toggle_js {
+ return <<"ENDSCRIPT";
+<script type="text/javascript">
+// <![CDATA[
+
+function toggleLTI(form,setting,item) {
+ if ((setting == 'user') || (setting == 'crs')) {
+ var radioname = '';
+ var divid = '';
+ if (setting == 'user') {
+ radioname = 'lti_mapuser_'+item;
+ divid = 'lti_userfield_'+item;
+ } else {
+ radioname = 'lti_mapcrs_'+item;
+ divid = 'lti_crsfield_'+item;
+ }
+ var num = form.elements[radioname].length;
+ if (num) {
+ var setvis = '';
+ for (var i=0; i<num; i++) {
+ if (form.elements[radioname][i].checked) {
+ if (form.elements[radioname][i].value == 'other') {
+ if (document.getElementById(divid)) {
+ document.getElementById(divid).style.display = 'inline-block';
+ }
+ setvis = 1;
+ break;
+ }
+ }
+ }
+ if (!setvis) {
+ if (document.getElementById(divid)) {
+ document.getElementById(divid).style.display = 'none';
+ }
+ }
+ }
+ } else if ((setting == 'sec') || (setting == 'secsrc')) {
+ var numsec = form.elements['lti_crssec_'+item].length;
+ if (numsec) {
+ var setvis = '';
+ for (var i=0; i<numsec; i++) {
+ if (form.elements['lti_crssec_'+item][i].checked) {
+ if (form.elements['lti_crssec_'+item][i].value == '1') {
+ if (document.getElementById('lti_crssecfield_'+item)) {
+ document.getElementById('lti_crssecfield_'+item).style.display = 'inline-block';
+ setvis = 1;
+ var numsrcsec = form.elements['lti_crssecsrc_'+item].length;
+ if (numsrcsec) {
+ var setsrcvis = '';
+ for (var j=0; j<numsrcsec; j++) {
+ if (form.elements['lti_crssecsrc_'+item][j].checked) {
+ if (form.elements['lti_crssecsrc_'+item][j].value == 'other') {
+ if (document.getElementById('lti_secsrcfield_'+item)) {
+ document.getElementById('lti_secsrcfield_'+item).style.display = 'inline-block';
+ setsrcvis = 1;
+ }
+ }
+ }
+ }
+ if (!setsrcvis) {
+ if (document.getElementById('lti_secsrcfield_'+item)) {
+ document.getElementById('lti_secsrcfield_'+item).style.display = 'none';
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!setvis) {
+ if (document.getElementById('lti_crssecfield_'+item)) {
+ document.getElementById('lti_crssecfield_'+item).style.display = 'none';
+ }
+ if (document.getElementById('lti_secsrcfield_'+item)) {
+ document.getElementById('lti_secsrcfield_'+item).style.display = 'none';
+ }
+ }
+ }
+ }
+ return;
+}
+// ]]>
+</script>
+
+ENDSCRIPT
+}
+
sub print_autoenroll {
my ($dom,$settings,$rowtotal) = @_;
my $autorun = &Apache::lonnet::auto_run(undef,$dom),
@@ -4298,6 +4480,327 @@
return %lt;
}
+sub print_lti {
+ my ($dom,$settings,$rowtotal) = @_;
+ my $itemcount = 1;
+ my $maxnum = 0;
+ my $css_class;
+ my %ordered;
+ if (ref($settings) eq 'HASH') {
+ foreach my $item (keys(%{$settings})) {
+ if (ref($settings->{$item}) eq 'HASH') {
+ my $num = $settings->{$item}{'order'};
+ $ordered{$num} = $item;
+ }
+ }
+ }
+ my $maxnum = scalar(keys(%ordered));
+ my $datatable = <i_javascript($settings);
+ my %lt = <i_names();
+ if (keys(%ordered)) {
+ my @items = sort { $a <=> $b } keys(%ordered);
+ for (my $i=0; $i<@items; $i++) {
+ $css_class = $itemcount%2?' class="LC_odd_row"':'';
+ my $item = $ordered{$items[$i]};
+ my ($key,$secret,$lifetime,$consumer,$current);
+ if (ref($settings->{$item}) eq 'HASH') {
+ $key = $settings->{$item}->{'key'};
+ $secret = $settings->{$item}->{'secret'};
+ $lifetime = $settings->{$item}->{'lifetime'};
+ $consumer = $settings->{$item}->{'consumer'};
+ $current = $settings->{$item};
+ }
+ my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_".$item."'".');"';
+ $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'
+ .'<select name="lti_pos_'.$item.'"'.$chgstr.'>';
+ for (my $k=0; $k<=$maxnum; $k++) {
+ my $vpos = $k+1;
+ my $selstr;
+ if ($k == $i) {
+ $selstr = ' selected="selected" ';
+ }
+ $datatable .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
+ }
+ $datatable .= '</select>'.(' 'x2).
+ '<label><input type="checkbox" name="lti_del" value="'.$item.'" />'.
+ &mt('Delete?').'</label></span></td>'.
+ '<td colspan="2">'.
+ '<fieldset><legend>'.&mt('Required settings').'</legend>'.
+ '<span class="LC_nobreak">'.$lt{'consumer'}.
+ ':<input type="text" size="20" name="lti_consumer_'.$i.'" value="'.$consumer.'" /></span> '.
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_'.$i.'">'.
+ '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '.
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="lti_lifetime_'.$i.'"'.
+ 'value="'.$lifetime.'" size="5" /></span>'.
+ '<br /><br />'.
+ '<span class="LC_nobreak">'.$lt{'key'}.
+ ':<input type="text" size="25" name="lti_key_'.$i.'" value="'.$key.'" /></span> '.
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'secret'}.':'.
+ '<input type="password" size="20" name="lti_secret_'.$i.'" value="'.$secret.'" />'.
+ '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_'.$i.'.type='."'text'".' } else { this.form.lti_secret_'.$i.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'.
+ '<input type="hidden" name="lti_id_'.$i.'" value="'.$item.'" /></span>'.
+ '</fieldset>'.<i_options($i,$current,%lt).'</td></tr>';
+ $itemcount ++;
+ }
+ }
+ $css_class = $itemcount%2?' class="LC_odd_row"':'';
+ my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_add'".');"';
+ $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n".
+ '<input type="hidden" name="lti_maxnum" value="'.$maxnum.'" />'."\n".
+ '<select name="lti_pos_add"'.$chgstr.'>';
+ for (my $k=0; $k<$maxnum+1; $k++) {
+ my $vpos = $k+1;
+ my $selstr;
+ if ($k == $maxnum) {
+ $selstr = ' selected="selected" ';
+ }
+ $datatable .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>';
+ }
+ $datatable .= '</select> '."\n".
+ '<input type="checkbox" name="lti_add" value="1" />'.&mt('Add').'</td>'."\n".
+ '<td colspan="2">'.
+ '<fieldset><legend>'.&mt('Required settings').'</legend>'.
+ '<span class="LC_nobreak">'.$lt{'consumer'}.
+ ':<input type="text" size="20" name="lti_consumer_add" value="" /></span> '."\n".
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_add">'.
+ '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="5" name="lti_lifetime_add" value="" /></span> '."\n".
+ '<br /><br />'.
+ '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="lti_key_add" value="" /></span> '."\n".
+ (' 'x2).
+ '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="lti_secret_add" value="" />'.
+ '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_add.type='."'text'".' } else { this.form.lti_secret_add.type='."'password'".' }" />'.&mt('Visible input').'</label></span> '."\n".
+ '</fieldset>'.<i_options('add',undef,%lt).
+ '</td>'."\n".
+ '</tr>'."\n";
+ $$rowtotal ++;
+ return $datatable;;
+}
+
+sub lti_names {
+ my %lt = &Apache::lonlocal::texthash(
+ 'version' => 'LTI Version',
+ 'url' => 'URL',
+ 'key' => 'Key',
+ 'lifetime' => 'Nonce lifetime (seconds)',
+ 'consumer' => 'LTI Consumer',
+ 'secret' => 'Secret',
+ 'email' => 'Email address',
+ 'sourcedid' => 'User ID',
+ 'other' => 'Other',
+ 'passback' => 'Can return grades to Consumer:',
+ 'roster' => 'Can retrieve roster from Consumer:',
+ );
+ return %lt;
+}
+
+sub lti_options {
+ my ($num,$current,%lt) = @_;
+ my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield);
+ $checked{'mapuser'}{'sourcedid'} = ' checked="checked"';
+ $checked{'mapcrs'}{'course_offering_sourcedid'} = ' checked="checked"';
+ $checked{'makecrs'}{'N'} = ' checked="checked"';
+ $checked{'mapcrstype'} = {};
+ $checked{'makeuser'} = {};
+ $checked{'selfenroll'} = {};
+ $checked{'crssec'} = {};
+ $checked{'crssecsrc'} = {};
+
+ my $userfieldsty = 'none';
+ my $crsfieldsty = 'none';
+ my $crssecfieldsty = 'none';
+ my $secsrcfieldsty = 'none';
+
+ if (ref($current) eq 'HASH') {
+ if (($current->{'mapuser'} ne '') && ($current->{'mapuser'} ne 'lis_person_sourcedid')) {
+ $checked{'mapuser'}{'sourcedid'} = '';
+ if ($current->{'mapuser'} eq 'lis_person_contact_email_primary') {
+ $checked{'mapuser'}{'email'} = ' checked="checked"';
+ } else {
+ $checked{'mapuser'}{'other'} = ' checked="checked"';
+ $userfield = $current->{'mapuser'};
+ $userfieldsty = 'inline-block';
+ }
+ }
+ if (($current->{'mapcrs'} ne '') && ($current->{'mapcrs'} ne 'course_offering_sourcedid')) {
+ $checked{'mapcrs'}{'course_offering_sourcedid'} = '';
+ if ($current->{'mapcrs'} eq 'context_id') {
+ $checked{'mapcrs'}{'context_id'} = ' checked="checked"';
+ } else {
+ $checked{'mapcrs'}{'other'} = ' checked="checked"';
+ $cidfield = $current->{'mapcrs'};
+ $crsfieldsty = 'inline-block';
+ }
+ }
+ if (ref($current->{'mapcrstype'}) eq 'ARRAY') {
+ foreach my $type (@{$current->{'mapcrstype'}}) {
+ $checked{'mapcrstype'}{$type} = ' checked="checked"';
+ }
+ }
+ if ($current->{'makecrs'}) {
+ $checked{'makecrs'}{'Y'} = ' checked="checked"';
+ }
+ if (ref($current->{'makeuser'}) eq 'ARRAY') {
+ foreach my $role (@{$current->{'makeuser'}}) {
+ $checked{'makeuser'}{$role} = ' checked="checked"';
+ }
+ }
+ if (ref($current->{'selfenroll'}) eq 'ARRAY') {
+ foreach my $role (@{$current->{'selfenroll'}}) {
+ $checked{'selfenroll'}{$role} = ' checked="checked"';
+ }
+ }
+ if (ref($current->{'maproles'}) eq 'HASH') {
+ %rolemaps = %{$current->{'maproles'}};
+ }
+ if ($current->{'section'} ne '') {
+ $checked{'crssec'}{'Y'} = ' checked="checked"';
+ $crssecfieldsty = 'inline-block';
+ if ($current->{'section'} eq 'course_section_sourcedid') {
+ $checked{'crssecsrc'}{'sourcedid'} = ' checked="checked"';
+ } else {
+ $checked{'crssecsrc'}{'other'} = ' checked="checked"';
+ $crssecsrc = $current->{'section'};
+ $secsrcfieldsty = 'inline-block';
+ }
+ } else {
+ $checked{'crssec'}{'N'} = ' checked="checked"';
+ }
+ } else {
+ $checked{'makecrs'}{'N'} = ' checked="checked"';
+ $checked{'crssec'}{'N'} = ' checked="checked"';
+ }
+ my @coursetypes = ('official','unofficial','community','textbook','placement');
+ my %coursetypetitles = &Apache::lonlocal::texthash (
+ official => 'Official',
+ unofficial => 'Unofficial',
+ community => 'Community',
+ textbook => 'Textbook',
+ placement => 'Placement Test',
+ );
+ my @ltiroles = qw(Learner Instructor ContentDeveloper TeachingAssistant Mentor Member Manager Administrator);
+ my @lticourseroles = qw(Learner Instructor TeachingAssistant Mentor);
+ my @courseroles = ('cc','in','ta','ep','st');
+ my $onclickuser = ' onclick="toggleLTI(this.form,'."'user','$num'".');"';
+ my $onclickcrs = ' onclick="toggleLTI(this.form,'."'crs','$num'".');"';
+ my $onclicksec = ' onclick="toggleLTI(this.form,'."'sec','$num'".');"';
+ my $onclicksecsrc = ' onclick="toggleLTI(this.form,'."'secsrc','$num'".')"';
+ my $output = '<fieldset><legend>'.&mt('Mapping users').'</legend>'.
+ '<div class="LC_floatleft"><span class="LC_nobreak">'.&mt('LON-CAPA username').': ';
+ foreach my $option ('sourcedid','email','other') {
+ $output .= '<label><input type="radio" name="lti_mapuser_'.$num.'" value="'.$option.'"'.
+ $checked{'mapuser'}{$option}.$onclickuser.' />'.$lt{$option}.'</label>'.
+ ($option eq 'other' ? '' : (' 'x2) );
+ }
+ $output .= '</span></div>'.
+ '<div class="LC_floatleft" style="display:'.$userfieldsty.';" id="lti_userfield_'.$num.'">'.
+ '<input type="text" name="lti_customuser_'.$num.'" '.
+ 'value="'.$userfield.'" /></div></fieldset>'.
+ '<fieldset><legend>'.&mt('Mapping course roles').'</legend><table><tr>';
+ foreach my $ltirole (@lticourseroles) {
+ my ($selected,$selectnone);
+ if ($rolemaps{$ltirole} eq '') {
+ $selectnone = ' selected="selected"';
+ }
+ $output .= '<td style="text-align: center">'.$ltirole.'<br />'.
+ '<select name="lti_maprole_'.$ltirole.'_'.$num.'">'.
+ '<option value=""'.$selectnone.'>'.&mt('Select').'</option>';
+ foreach my $role (@courseroles) {
+ unless ($selectnone) {
+ if ($rolemaps{$ltirole} eq $role) {
+ $selected = ' selected="selected"';
+ } else {
+ $selected = '';
+ }
+ }
+ $output .= '<option value="'.$role.'"'.$selected.'>'.
+ &Apache::lonnet::plaintext($role,'Course').
+ '</option>';
+ }
+ $output .= '</select></td>';
+ }
+ $output .= '</tr></table></fieldset>'.
+ '<fieldset><legend>'.&mt('Roles which may create user accounts').'</legend>';
+ foreach my $ltirole (@ltiroles) {
+ $output .= '<span class="LC_nobreak"><label><input type="checkbox" name="lti_makeuser_'.$num.'" value="'.$ltirole.'"'.
+ $checked{'makeuser'}{$ltirole}.' />'.$ltirole.'</label> </span> ';
+ }
+ $output .= '</fieldset>'.
+ '<fieldset><legend>'.&mt('Mapping courses').'</legend>'.
+ '<div class="LC_floatleft"><span class="LC_nobreak">'.
+ &mt('Unique course identifier').': ';
+ foreach my $option ('course_offering_sourcedid','context_id','other') {
+ $output .= '<label><input type="radio" name="lti_mapcrs_'.$num.'" value="'.$option.'"'.
+ $checked{'mapcrs'}{$option}.$onclickcrs.' />'.$option.'</label>'.
+ ($option eq 'other' ? '' : (' 'x2) );
+ }
+ $output .= '</div><div class="LC_floatleft" style="display:'.$crsfieldsty.';" id="lti_crsfield_'.$num.'".>'.
+ '<input type="text" name="lti_mapcrsfield_'.$num.'" value="'.$cidfield.'" />'.
+ '</div><div style="padding:0;clear:both;margin:0;border:0"></div>'.
+ '<span class="LC_nobreak">'.&mt('LON-CAPA course type(s)').': ';
+ foreach my $type (@coursetypes) {
+ $output .= '<label><input type="checkbox" name="lti_mapcrstype_'.$num.'" value="'.$type.'"'.
+ $checked{'mapcrstype'}{$type}.' />'.$coursetypetitles{$type}.'</label>'.
+ (' 'x2);
+ }
+ $output .= '</span></fieldset>'.
+ '<fieldset><legend>'.&mt('Creating courses').'</legend>'.
+ '<span class="LC_nobreak">'.&mt('Course created (if absent) on Instructor access').': '.
+ '<label><input type="radio" name="lti_makecrs_'.$num.'" value="0"'.
+ $checked{'makecrs'}{'N'}.' />'.&mt('No').'</label>'.(' 'x2).
+ '<label><input type="radio" name="lti_makecrs_'.$num.'" value="1"'.
+ $checked{'makecrs'}{'Y'}.' />'.&mt('Yes').'</label></span>'.
+ '</fieldset>'.
+ '<fieldset><legend>'.&mt('Roles which may self-enroll').'</legend>';
+ foreach my $lticrsrole (@lticourseroles) {
+ $output .= '<span class="LC_nobreak"><label><input type="checkbox" name="lti_selfenroll_'.$num.'" value="'.$lticrsrole.'"'.
+ $checked{'selfenroll'}{$lticrsrole}.' />'.$lticrsrole.'</label> </span> ';
+ }
+ $output .= '</fieldset>'.
+ '<fieldset><legend>'.&mt('Course options').'</legend>'.
+ '<div class="LC_floatleft"><span class="LC_nobreak">'.&mt('Assign users to sections').': '.
+ '<label><input type="radio" name="lti_crssec_'.$num.'" value="0"'.
+ $checked{'crssec'}{'N'}.$onclicksec.' />'.&mt('No').'</label>'.(' 'x2).
+ '<label><input type="radio" name="lti_crssec_'.$num.'" value="1"'.
+ $checked{'crssec'}{'Y'}.$onclicksec.' />'.&mt('Yes').'</label><span></div>'.
+ '<div class="LC_floatleft" style="display:'.$crssecfieldsty.';" id="lti_crssecfield_'.$num.'">'.
+ '<span class="LC_nobreak">'.&mt('From').':<label>'.
+ '<input type="radio" name="lti_crssecsrc_'.$num.'" value="course_section_sourcedid"'.
+ $checked{'crssecsrc'}{'sourcedid'}.$onclicksecsrc.' />'.
+ &mt('Standard field').'</label>'.(' 'x2).
+ '<label><input type="radio" name="lti_crssecsrc_'.$num.'" value="other"'.
+ $checked{'crssecsrc'}{'other'}.$onclicksecsrc.' />'.&mt('Other').
+ '</label></div><div class="LC_floatleft" style="display:'.$secsrcfieldsty.';" id="lti_secsrcfield_'.$num.'">'.
+ '<input type="text" name="lti_customsection_'.$num.'" value="'.$crssecsrc.'" />'.
+ '</div><div style="padding:0;clear:both;margin:0;border:0"></div>';
+ foreach my $extra ('passback','roster') {
+ my $checkedon = '';
+ my $checkedoff = ' checked="checked"';
+ if (ref($current) eq 'HASH') {
+ if (($current->{$extra})) {
+ $checkedon = $checkedoff;
+ $checkedoff = '';
+ }
+ }
+ $output .= $lt{$extra}.' '.
+ '<label><input type="radio" name="lti_'.$extra.'_'.$num.'" value="0"'.$checkedoff.' />'.
+ &mt('No').'</label>'.(' 'x2).
+ '<label><input type="radio" name="lti_'.$extra.'_'.$num.'" value="1"'.$checkedon.' />'.
+ &mt('Yes').'</label><br />';
+ }
+ $output .= '</span></fieldset>';
+# '<fieldset><legend>'.&mt('Assigning author roles').'</legend>';
+#
+# $output .= '</fieldset>'.
+# '<fieldset><legend>'.&mt('Assigning domain roles').'</legend>';
+ return $output;
+}
+
sub print_coursedefaults {
my ($position,$dom,$settings,$rowtotal) = @_;
my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked, at toggles);
@@ -10726,6 +11229,422 @@
return ($id,$error);
}
+sub modify_lti {
+ my ($r,$dom,$action,$lastactref,%domconfig) = @_;
+ my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1);
+ my ($newid, at allpos,%changes,%confhash,%encconfig,$errors,$resulttext);
+ my (%posslti,%posslticrs,%posscrstype);
+ my @courseroles = ('cc','in','ta','ep','st');
+ my @ltiroles = qw(Learner Instructor ContentDeveloper TeachingAssistant Mentor Member Manager Administrator);
+ my @lticourseroles = qw(Instructor TeachingAssistant Mentor Learner);
+ my @coursetypes = ('official','unofficial','community','textbook','placement');
+ my %coursetypetitles = &Apache::lonlocal::texthash (
+ official => 'Official',
+ unofficial => 'Unofficial',
+ community => 'Community',
+ textbook => 'Textbook',
+ placement => 'Placement Test',
+ );
+ my %lt = <i_names();
+ map { $posslti{$_} = 1; } @ltiroles;
+ map { $posslticrs{$_} = 1; } @lticourseroles;
+ map { $posscrstype{$_} = 1; } @coursetypes;
+
+ my (@items,%deletions,%itemids);
+ if ($env{'form.lti_add'}) {
+ my $consumer = $env{'form.lti_consumer_add'};
+ $consumer =~ s/(`)/'/g;
+ ($newid,my $error) = &get_lti_id($dom,$consumer);
+ if ($newid) {
+ $itemids{'add'} = $newid;
+ push(@items,'add');
+ $changes{$newid} = 1;
+ } else {
+ my $error = &mt('Failed to acquire unique ID for new LTI configuration');
+ $errors .= '<li><span class="LC_error">'.$error.'</span></li>';
+ }
+ }
+ if (ref($domconfig{$action}) eq 'HASH') {
+ my @todelete = &Apache::loncommon::get_env_multiple('form.lti_del');
+ if (@todelete) {
+ map { $deletions{$_} = 1; } @todelete;
+ }
+ my $maxnum = $env{'form.lti_maxnum'};
+ for (my $i=0; $i<=$maxnum; $i++) {
+ my $itemid = $env{'form.lti_id_'.$i};
+ $itemid =~ s/\D+//g;
+ if (ref($domconfig{$action}{$itemid}) eq 'HASH') {
+ if ($deletions{$itemid}) {
+ $changes{$itemid} = $domconfig{$action}{$itemid}{'consumer'};
+ } else {
+ push(@items,$i);
+ $itemids{$i} = $itemid;
+ }
+ }
+ }
+ }
+ foreach my $idx (@items) {
+ my $itemid = $itemids{$idx};
+ next unless ($itemid);
+ my $position = $env{'form.lti_pos_'.$idx};
+ $position =~ s/\D+//g;
+ if ($position ne '') {
+ $allpos[$position] = $itemid;
+ }
+ foreach my $item ('consumer','key','secret','lifetime') {
+ my $formitem = 'form.lti_'.$item.'_'.$idx;
+ $env{$formitem} =~ s/(`)/'/g;
+ if ($item eq 'lifetime') {
+ $env{$formitem} =~ s/[^\d.]//g;
+ }
+ if ($env{$formitem} ne '') {
+ if (($item eq 'key') || ($item eq 'secret')) {
+ $encconfig{$itemid}{$item} = $env{$formitem};
+ } else {
+ $confhash{$itemid}{$item} = $env{$formitem};
+ unless (($idx eq 'add') || ($changes{$itemid})) {
+ if ($domconfig{$action}{$itemid}{$item} ne $confhash{$itemid}{$item}) {
+ $changes{$itemid} = 1;
+ }
+ }
+ }
+ }
+ }
+ if ($env{'form.lti_version_'.$idx} eq 'LTI-1p0') {
+ $confhash{$itemid}{'version'} = $env{'form.lti_version_'.$idx};
+ }
+ if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') {
+ $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid';
+ } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') {
+ $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary';
+ } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') {
+ my $mapuser = $env{'form.lti_customuser_'.$idx};
+ $mapuser =~ s/(`)/'/g;
+ $mapuser =~ s/^\s+|\s+$//g;
+ $confhash{$itemid}{'mapuser'} = $mapuser;
+ }
+ foreach my $ltirole (@lticourseroles) {
+ my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx};
+ if (grep(/^\Q$possrole\E$/, at courseroles)) {
+ $confhash{$itemid}{'maproles'}{$ltirole} = $possrole;
+ }
+ }
+ my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx);
+ my @makeuser;
+ foreach my $ltirole (sort(@possmakeuser)) {
+ if ($posslti{$ltirole}) {
+ push(@makeuser,$ltirole);
+ }
+ }
+ $confhash{$itemid}{'makeuser'} = \@makeuser;
+ if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') ||
+ ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) {
+ $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx};
+ } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') {
+ my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx};
+ $mapcrs =~ s/(`)/'/g;
+ $mapcrs =~ s/^\s+|\s+$//g;
+ $confhash{$itemid}{'mapcrs'} = $mapcrs;
+ }
+ my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx);
+ my @crstypes;
+ foreach my $type (sort(@posstypes)) {
+ if ($posscrstype{$type}) {
+ push(@crstypes,$type);
+ }
+ }
+ $confhash{$itemid}{'mapcrstype'} = \@crstypes;
+ if ($env{'form.lti_makecrs_'.$idx}) {
+ $confhash{$itemid}{'makecrs'} = 1;
+ }
+ my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx);
+ my @selfenroll;
+ foreach my $type (sort(@possenroll)) {
+ if ($posslticrs{$type}) {
+ push(@selfenroll,$type);
+ }
+ }
+ $confhash{$itemid}{'selfenroll'} = \@selfenroll;
+ if ($env{'form.lti_crssec_'.$idx}) {
+ if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') {
+ $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx};
+ } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') {
+ my $section = $env{'form.lti_customsection_'.$idx};
+ $section =~ s/(`)/'/g;
+ $section =~ s/^\s+|\s+$//g;
+ if ($section ne '') {
+ $confhash{$itemid}{'section'} = $section;
+ }
+ }
+ }
+ foreach my $field ('passback','roster') {
+ if ($env{'form.ltitools_'.$idx.'_'.$field}) {
+ $confhash{$itemid}{$field} = 1;
+ }
+ }
+ unless (($idx eq 'add') || ($changes{$itemid})) {
+ foreach my $field ('mapuser','mapcrs','section','passback','roster') {
+ if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) {
+ $changes{$itemid} = 1;
+ }
+ }
+ foreach my $field ('makeuser','mapcrstype','selfenroll') {
+ unless ($changes{$itemid}) {
+ if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') {
+ if (ref($confhash{$itemid}{$field}) eq 'ARRAY') {
+ my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field},
+ $confhash{$itemid}{$field});
+ if (@diffs) {
+ $changes{$itemid} = 1;
+ }
+ } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) {
+ $changes{$itemid} = 1;
+ }
+ } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') {
+ if (@{$confhash{$itemid}{$field}} > 0) {
+ $changes{$itemid} = 1;
+ }
+ }
+ }
+ }
+ unless ($changes{$itemid}) {
+ if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') {
+ if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') {
+ foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) {
+ if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne
+ $confhash{$itemid}{'maproles'}{$ltirole}) {
+ $changes{$itemid} = 1;
+ last;
+ }
+ }
+ unless ($changes{$itemid}) {
+ foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) {
+ if ($confhash{$itemid}{'maproles'}{$ltirole} ne
+ $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) {
+ $changes{$itemid} = 1;
+ last;
+ }
+ }
+ }
+ } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) {
+ $changes{$itemid} = 1;
+ }
+ } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') {
+ unless ($changes{$itemid}) {
+ if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) {
+ $changes{$itemid} = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (@allpos > 0) {
+ my $idx = 0;
+ foreach my $itemid (@allpos) {
+ if ($itemid ne '') {
+ $confhash{$itemid}{'order'} = $idx;
+ if (ref($domconfig{$action}) eq 'HASH') {
+ if (ref($domconfig{$action}{$itemid}) eq 'HASH') {
+ if ($domconfig{$action}{$itemid}{'order'} ne $idx) {
+ $changes{$itemid} = 1;
+ }
+ }
+ }
+ $idx ++;
+ }
+ }
+ }
+ my %ltihash = (
+ $action => { %confhash }
+ );
+ my $putresult = &Apache::lonnet::put_dom('configuration',\%ltihash,
+ $dom);
+ if ($putresult eq 'ok') {
+ my %ltienchash = (
+ $action => { %encconfig }
+ );
+ &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom);
+ if (keys(%changes) > 0) {
+ my $cachetime = 24*60*60;
+ my %ltiall = %confhash;
+ foreach my $id (keys(%ltiall)) {
+ if (ref($encconfig{$id}) eq 'HASH') {
+ foreach my $item ('key','secret') {
+ $ltiall{$id}{$item} = $encconfig{$id}{$item};
+ }
+ }
+ }
+ &Apache::lonnet::do_cache_new('lti',$dom,\%ltiall,$cachetime);
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'lti'} = 1;
+ }
+ $resulttext = &mt('Changes made:').'<ul>';
+ my %bynum;
+ foreach my $itemid (sort(keys(%changes))) {
+ my $position = $confhash{$itemid}{'order'};
+ $bynum{$position} = $itemid;
+ }
+ foreach my $pos (sort { $a <=> $b } keys(%bynum)) {
+ my $itemid = $bynum{$pos};
+ if (ref($confhash{$itemid}) ne 'HASH') {
+ $resulttext .= '<li>'.&mt('Deleted: [_1]',$changes{$itemid}).'</li>';
+ } else {
+ $resulttext .= '<li><b>'.$confhash{$itemid}{'consumer'}.'</b></li><ul>';
+ my $position = $pos + 1;
+ $resulttext .= '<li>'.&mt('Order: [_1]',$position).'</li>';
+ foreach my $item ('version','lifetime') {
+ if ($confhash{$itemid}{$item} ne '') {
+ $resulttext .= '<li>'.$lt{$item}.': '.$confhash{$itemid}{$item}.'</li>';
+ }
+ }
+ if ($encconfig{$itemid}{'key'} ne '') {
+ $resulttext .= '<li>'.$lt{'key'}.': '.$encconfig{$itemid}{'key'}.'</li>';
+ }
+ if ($encconfig{$itemid}{'secret'} ne '') {
+ $resulttext .= '<li>'.$lt{'secret'}.': ';
+ my $num = length($encconfig{$itemid}{'secret'});
+ $resulttext .= ('*'x$num).'</li>';
+ }
+ if ($confhash{$itemid}{'mapuser'}) {
+ my $shownmapuser;
+ if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') {
+ $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)';
+ } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') {
+ $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)';
+ } else {
+ $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')';
+ }
+ $resulttext .= '<li>'.&mt('LON-CAPA username').': '.$shownmapuser.'</li>';
+ }
+ if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') {
+ my $rolemaps;
+ foreach my $role (@ltiroles) {
+ if ($confhash{$itemid}{'maproles'}{$role}) {
+ $rolemaps .= (' 'x2).$role.'='.
+ &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role},
+ 'Course').',';
+ }
+ }
+ if ($rolemaps) {
+ $rolemaps =~ s/,$//;
+ $resulttext .= '<li>'.&mt('Role mapping:').$rolemaps.'</li>';
+ }
+ }
+ if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') {
+ if (@{$confhash{$itemid}{'makeuser'}} > 0) {
+ $resulttext .= '<li>'.&mt('Following roles may create user accounts: [_1]',
+ join(', ',@{$confhash{$itemid}{'makeuser'}})).'</li>';
+ } else {
+ $resulttext .= '<li>'.&mt('User account creation not permitted.').'</li>';
+ }
+ }
+ if ($confhash{$itemid}{'mapcrs'}) {
+ $resulttext .= '<li>'.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'</li>';
+ }
+ if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') {
+ if (@{$confhash{$itemid}{'mapcrstype'}} > 0) {
+ $resulttext .= '<li>'.&mt('Mapping for the following LON-CAPA course types: [_1]',
+ join(', ',map { $coursetypetitles{$_}; } @coursetypes)).
+ '</li>';
+ } else {
+ $resulttext .= '<li>'.&mt('No mapping to LON-CAPA courses').'</li>';
+ }
+ }
+ if ($confhash{$itemid}{'makecrs'}) {
+ $resulttext .= '<li>'.&mt('Instructor may create course (if absent).').'</li>';
+ } else {
+ $resulttext .= '<li>'.&mt('Instructor may not create course (if absent).').'</li>';
+ }
+ if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') {
+ if (@{$confhash{$itemid}{'selfenroll'}} > 0) {
+ $resulttext .= '<li>'.&mt('Self-enrollment for following roles: [_1]',
+ join(', ',@{$confhash{$itemid}{'selfenroll'}})).
+ '</li>';
+ } else {
+ $resulttext .= '<li>'.&mt('Self-enrollment not permitted').'</li>';
+ }
+ }
+ if ($confhash{$itemid}{'section'}) {
+ if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') {
+ $resulttext .= '<li>'.&mt('User section from standard field:').
+ ' (course_section_sourcedid)'.'</li>';
+ } else {
+ $resulttext .= '<li>'.&mt('User section from:').' '.
+ $confhash{$itemid}{'section'}.'</li>';
+ }
+ } else {
+ $resulttext .= '<li>'.&mt('No section assignment').'</li>';
+ }
+ foreach my $item ('passback','roster') {
+ $resulttext .= '<li>'.$lt{$item}.' ';
+ if ($confhash{$itemid}{$item}) {
+ $resulttext .= &mt('Yes');
+ } else {
+ $resulttext .= &mt('No');
+ }
+ $resulttext .= '</li>';
+ }
+ $resulttext .= '</ul></li>';
+ }
+ }
+ $resulttext .= '</ul>';
+ } else {
+ $resulttext = &mt('No changes made.');
+ }
+ } else {
+ $errors .= '<li><span class="LC_error">'.&mt('Failed to save changes').'</span></li>';
+ }
+ if ($errors) {
+ $resulttext .= &mt('The following errors occurred: ').'<ul>'.
+ $errors.'</ul>';
+ }
+ return $resulttext;
+}
+
+sub get_lti_id {
+ my ($domain,$consumer) = @_;
+ # get lock on lti db
+ my $lockhash = {
+ lock => $env{'user.name'}.
+ ':'.$env{'user.domain'},
+ };
+ my $tries = 0;
+ my $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain);
+ my ($id,$error);
+
+ while (($gotlock ne 'ok') && ($tries<10)) {
+ $tries ++;
+ sleep (0.1);
+ $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain);
+ }
+ if ($gotlock eq 'ok') {
+ my %currids = &Apache::lonnet::dump_dom('lti',$domain);
+ if ($currids{'lock'}) {
+ delete($currids{'lock'});
+ if (keys(%currids)) {
+ my @curr = sort { $a <=> $b } keys(%currids);
+ if ($curr[-1] =~ /^\d+$/) {
+ $id = 1 + $curr[-1];
+ }
+ } else {
+ $id = 1;
+ }
+ if ($id) {
+ unless (&Apache::lonnet::newput_dom('lti',{ $id => $consumer },$domain) eq 'ok') {
+ $error = 'nostore';
+ }
+ } else {
+ $error = 'nonumber';
+ }
+ }
+ my $dellockoutcome = &Apache::lonnet::del_dom('lti',['lock'],$domain);
+ } else {
+ $error = 'nolock';
+ }
+ return ($id,$error);
+}
+
sub modify_autoenroll {
my ($dom,$lastactref,%domconfig) = @_;
my ($resulttext,%changes);
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.39 loncom/interface/lonconfigsettings.pm:1.40
--- loncom/interface/lonconfigsettings.pm:1.39 Thu Nov 30 12:45:55 2017
+++ loncom/interface/lonconfigsettings.pm Thu Nov 30 15:15:15 2017
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Handler to set domain-wide configuration settings
#
-# $Id: lonconfigsettings.pm,v 1.39 2017/11/30 12:45:55 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.40 2017/11/30 15:15:15 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -215,6 +215,21 @@
my $optionsprefix = 'LC_options_helpdesk_';
$onload .= "toggleHelpdeskRow(document.display,'overrides','$customclass','$optionsprefix');";
}
+ if (grep(/^lti$/, at actions)) {
+ $onload .= "toggleLTI(document.display,'user','add');".
+ "toggleLTI(document.display,'crs','add');".
+ "toggleLTI(document.display,'sec','add');";
+ if (ref($values) eq 'HASH') {
+ if (ref($values->{'lti'}) eq 'HASH') {
+ my $numlti = scalar(keys(%{$values->{'lti'}}));
+ for (my $i=0; $i<$numlti; $i++) {
+ $onload .= "toggleLTI(document.display,'user','$i');".
+ "toggleLTI(document.display,'crs','$i');".
+ "toggleLTI(document.display,'sec','$i');";
+ }
+ }
+ }
+ }
if (grep(/^ltitools$/, at actions)) {
$onload .= "toggleLTITools(document.display,'passback','add');".
"toggleLTITools(document.display,'roster','add');";
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.957 doc/loncapafiles/loncapafiles.lpml:1.958
--- doc/loncapafiles/loncapafiles.lpml:1.957 Mon Nov 6 03:08:36 2017
+++ doc/loncapafiles/loncapafiles.lpml Thu Nov 30 15:15:23 2017
@@ -2,7 +2,7 @@
"http://lpml.sourceforge.net/DTD/lpml.dtd">
<!-- loncapafiles.lpml -->
-<!-- $Id: loncapafiles.lpml,v 1.957 2017/11/06 03:08:36 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.958 2017/11/30 15:15:23 raeburn Exp $ -->
<!--
@@ -417,6 +417,12 @@
</directory>
<directory dist='default'>
<protectionlevel>modest_delete</protectionlevel>
+ <targetdir dist='default'>home/httpd/lonLTItmp</targetdir>
+ <categoryname>server standard</categoryname>
+ <description>for temporary storage of LTI nonces</description>
+</directory>
+<directory dist='default'>
+ <protectionlevel>modest_delete</protectionlevel>
<targetdir dist='default'>home/httpd/webdav</targetdir>
<categoryname>server standard</categoryname>
<description>for storage of webdav DAVLock files</description>
@@ -2530,6 +2536,15 @@
<status>works/unverified</status>
</file>
<file>
+<source>loncom/auth/lonlti.pm</source>
+<target dist='default'>home/httpd/lib/perl/Apache/lonlti.pm</target>
+<categoryname>handler</categoryname>
+<description>
+Handler to allow LON-CAPA to operate as an LTI Provider
+</description>
+<status>works/unverified</status>
+</file>
+<file>
<source>loncom/interface/lonpickcode.pm</source>
<target dist='default'>home/httpd/lib/perl/Apache/lonpickcode.pm</target>
<categoryname>handler</categoryname>
Index: loncom/auth/lonlti.pm
+++ loncom/auth/lonlti.pm
# The LearningOnline Network
# Basic LTI Authentication Module
#
# $Id: lonlti.pm,v 1.1 2017/11/30 15:14:51 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::lonlti;
use strict;
use LONCAPA qw(:DEFAULT :match);
use Apache::Constants qw(:common :http);
use Net::OAuth;
use Apache::lonlocal;
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonacc;
sub handler {
my $r = shift;
my $requri = $r->uri;
#
# Retrieve data POSTed by LTI Consumer on launch
#
&Apache::lonacc::get_posted_cgi($r);
my $params = {};
foreach my $key (sort(keys(%env))) {
if ($key =~ /^form\.(.+)$/) {
$params->{$1} = $env{$key};
}
}
unless (keys(%{$params})) {
&invalid_request($r,1);
return OK;
}
unless ($params->{'oauth_consumer_key'} &&
$params->{'oauth_nonce'} &&
$params->{'oauth_timestamp'} &&
$params->{'oauth_version'} &&
$params->{'oauth_signature'} &&
$params->{'oauth_signature_method'}) {
&invalid_request($r,2);
return OK;
}
#
# Retrieve "internet domains" for all this institution's LON-CAPA
# nodes.
#
my ($udom,$uname,$uhome,$cdom,$cnum,$symb,$mapurl, at intdoms);
my $lonhost = $r->dir_config('lonHostID');
my $internet_names = &Apache::lonnet::get_internet_names($lonhost);
if (ref($internet_names) eq 'ARRAY') {
@intdoms = @{$internet_names};
}
#
# For user who launched LTI in Consumer, determine user's domain in
# LON-CAPA.
#
# Order is:
#
# (a) from custom_userdomain item in POSTed data
# (b) from lis_person_sourcedid in POSTed data
# (c) from default "log-in" domain for node
# (can support multidomain servers, where specific domain is
# first part of hostname).
#
# Note: "internet domain" for user's domain must be one of the
# "internet domain(s)" for the institution's LON-CAPA servers.
#
if (exists($params->{'custom_userdomain'})) {
if ($params->{'custom_userdomain'} =~ /^$match_domain$/) {
my $uprimary_id = &Apache::lonnet::domain($params->{'custom_userdomain'},'primary');
if ($uprimary_id ne '') {
my $uintdom = &Apache::lonnet::internet_dom($uprimary_id);
if (($uintdom ne '') && (grep(/^\Q$uintdom\E$/, at intdoms))) {
$udom = $params->{'custom_userdomain'};
}
}
}
}
my $defdom = &Apache::lonnet::default_login_domain();
my ($domain,$possuname,$possudom,$possmapuser);
if ($env{'form.lis_person_sourcedid'} =~ /^($match_username)\:($match_domain)$/) {
($possuname,$possudom) = ($1,$2);
if ($udom eq '') {
my $uintdom = &Apache::lonnet::domain($possudom,'primary');
if (($uintdom ne '') && (grep(/^\Q$uintdom\E$/, at intdoms))) {
$udom = $possudom;
$possmapuser = 'lis_person_sourcedid';
} else {
$udom = $defdom;
}
} elsif ($udom eq $possudom) {
$possmapuser = 'lis_person_sourcedid';
}
}
unless ($possuname) {
if ($env{'form.lis_person_sourcedid'} =~ /^$match_username$/) {
$possuname = $env{'form.lis_person_sourcedid'};
$possmapuser = 'lis_person_sourcedid';
} elsif ($env{'form.lis_person_contact_email_primary'} =~ /^$match_username$/) {
$possuname = $env{'form.lis_person_contact_email_primary'};
$possmapuser = 'lis_person_contact_email_primary';
}
unless ($udom) {
$udom = $defdom;
}
}
#
# Determine course's domain in LON-CAPA
#
# Order is:
#
# (a) from custom_coursedomain item in POSTed data
# (b) from tail of requested URL (after /adm/lti) if it has format of a symb
# (c) from tail of requested URL (after /adm/lti) if it has format of a map
# (d) from tail of requested URL (after /adm/lti) if it has format /domain/courseID
# (e) from tail of requested URL (after /adm/lti) if it has format /tiny/domain/...
# i.e., a shortened URL (see bug #6400) -- not implemented yet.
# (f) same as user's domain
#
# Request invalid if custom_coursedomain is defined and is inconsistent with
# domain contained in requested URL.
#
# Note: "internet domain" for course's domain must be one of the
# internet domains for the institution's LON-CAPA servers.
#
if (exists($params->{'custom_coursedomain'})) {
if ($params->{'custom_coursedomain'} =~ /^$match_domain$/) {
my $cprimary_id = &Apache::lonnet::domain($params->{'custom_coursedomain'},'primary');
if ($cprimary_id ne '') {
my $cintdom = &Apache::lonnet::internet_dom($cprimary_id);
if (($cintdom ne '') && (grep(/^\Q$cintdom\E$/, at intdoms))) {
$cdom = $params->{'custom_coursedomain'};
}
}
}
}
my ($tail) = ($requri =~ m{^/adm/lti(|/.*)$});
my $urlcnum;
if ($tail ne '') {
my $urlcdom;
if ($tail =~ m{^/uploaded/($match_domain)/($match_courseid)/(?:default|supplemental)(?:|_\d+)\.(?:sequence|page)(|___\d+___.+)$}) {
($urlcdom,$urlcnum,my $rest) = ($1,$2,$3);
if (($cdom ne '') && ($cdom ne $urlcdom)) {
&invalid_request($r,3);
return OK;
}
if ($rest eq '') {
$mapurl = $tail;
} else {
$symb = $tail;
$symb =~ s{^/+}{};
}
#FIXME Need to handle encrypted URLs
} elsif ($tail =~ m{^/($match_domain)/($match_courseid)$}) {
($urlcdom,$urlcnum) = ($1,$2);
if (($cdom ne '') && ($cdom ne $urlcdom)) {
&invalid_request($r,4);
return OK;
}
}
if (($cdom eq '') && ($urlcdom ne '')) {
my $cprimary_id = &Apache::lonnet::domain($urlcdom,'primary');
if ($cprimary_id ne '') {
my $cintdom = &Apache::lonnet::internet_dom($cprimary_id);
if (($cintdom ne '') && (grep(/^\Q$cintdom\E$/, at intdoms))) {
$cdom = $urlcdom;
}
} else {
$urlcnum = '';
}
}
}
if ($cdom eq '') {
if ($udom ne '') {
$cdom = $udom;
} else {
$cdom = $defdom;
}
}
#
# Retrieve information for LTI Consumers in course domain
# and populate hash -- %lti_by_key -- for which keys
# are those defined in domain configuration for LTI.
#
my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
unless (keys(%lti) > 0) {
&invalid_request($r,5);
return OK;
}
my %lti_by_key;
if (keys(%lti)) {
foreach my $id (keys(%lti)) {
if (ref($lti{$id}) eq 'HASH') {
my $key = $lti{$id}{'key'};
push(@{$lti_by_key{$key}},$id);
}
}
}
#
# Verify the signed request using the secret for those
# Consumers for which the key in the POSTed data matches
# keys in the domain configuration for LTI.
#
my $hostname = $r->hostname;
my $protocol = 'http';
if ($ENV{'SERVER_PORT'} == 443) {
$protocol = 'https';
}
my $itemid;
my $key = $params->{'oauth_consumer_key'};
my @ltiroles;
if (ref($lti_by_key{$key}) eq 'ARRAY') {
foreach my $id (@{$lti_by_key{$key}}) {
if (ref($lti{$id}) eq 'HASH') {
my $secret = $lti{$id}{'secret'};
my $request = Net::OAuth->request('request token')->from_hash($params,
request_url => $protocol.'://'.$hostname.$requri,
request_method => $env{'request.method'},
consumer_secret => $secret,);
if ($request->verify()) {
$itemid = $id;
last;
}
}
}
}
#
# Request is invalid if the signed request could not be verified
# for the Consumer key and Consumer secret from the domain
# configuration in LON-CAPA for that LTI Consumer.
#
unless (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
&invalid_request($r,6);
return OK;
}
#
# Determine if nonce in POSTed data has expired.
# If unexpired, confirm it has not already been used.
#
unless (&check_nonce($r,$params->{'oauth_nonce'},$params->{'oauth_timestamp'},$lti{$itemid}{'lifetime'},$cdom)) {
&invalid_request($r,7);
return OK;
}
#
# Determinine if source of username matches requirement from the
# domain configuration for the specific LTI Consumer.
#
if ($lti{$itemid}{'mapuser'} eq $possmapuser) {
$uname = $possuname;
} elsif ($lti{$itemid}{'mapuser'} eq 'lis_person_sourcedid') {
if ($params->{'lis_person_sourcedid'} =~ /^$match_username$/) {
$uname = $possuname;
}
} elsif ($lti{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') {
if ($params->{'lis_person_contact_email_primary'} =~ /^$match_username$/) {
$uname = $params->{'lis_person_contact_email_primary'};
}
} elsif (exists($params->{$lti{$itemid}{'mapuser'}})) {
if ($params->{$lti{$itemid}{'mapuser'}} =~ /^$match_username$/) {
$uname = $params->{$lti{$itemid}{'mapuser'}};
}
}
#
# Determine the courseID of the LON-CAPA course to which the
# launch of LON-CAPA should provide access.
#
# Order is:
#
# (a) from course mapping (if the link between Consumer "course" and
# Provider "course" has been established previously).
# (b) from tail of requested URL (after /adm/lti) if it has format of a symb
# (c) from tail of requested URL (after /adm/lti) if it has format of a map
# (d) from tail of requested URL (after /adm/lti) if it has format /domain/courseID
# (e) from tail of requested URL (after /adm/lti) if it has format /tiny/domain/...
# i.e., a shortened URL (see bug #6400) -- not implemented yet.
#
# If Consumer course included in POSTed data points as a target course which
# has a format which matches a LON-CAPA courseID, but the course does not
# exist, the request is invalid.
#
my ($sourcecrs,%consumers);
if ($lti{$itemid}{'mapcrs'} eq 'course_offering_sourcedid') {
$sourcecrs = $params->{'course_offering_sourcedid'};
} elsif ($lti{$itemid}{'mapcrs'} eq 'context_id') {
$sourcecrs = $params->{'context_id'};
} elsif ($lti{$itemid}{'mapcrs'} ne '') {
$sourcecrs = $params->{$lti{$itemid}{'mapcrs'}};
}
my $posscnum;
if ($sourcecrs ne '') {
%consumers = &Apache::lonnet::get_dom('lticonsumers',[$sourcecrs],$cdom);
if (exists($consumers{$sourcecrs})) {
if ($consumers{$sourcecrs} =~ /^$match_courseid$/) {
my $crshome = &Apache::lonnet::homeserver($consumers{$sourcecrs},$cdom);
if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
&invalid_request($r,8);
return OK;
} else {
$posscnum = $consumers{$sourcecrs};
}
}
}
}
if ($urlcnum ne '') {
if ($posscnum ne '') {
if ($posscnum ne $urlcnum) {
&invalid_request($r,9);
return OK;
} else {
$cnum = $posscnum;
}
} else {
my $crshome = &Apache::lonnet::homeserver($urlcnum,$cdom);
if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
&invalid_request($r,10);
return OK;
} else {
$cnum = $urlcnum;
}
}
} elsif ($posscnum ne '') {
$cnum = $posscnum;
}
#
# Get LON-CAPA role to use from role-mapping of Consumer roles
# defined in domain configuration for the appropriate LTI
# Consumer.
#
# If multiple LON-CAPA roles are indicated, choose based
# on the order: cc, in, ta, ep, st
#
my $reqrole;
my @roleorder = ('cc','in','ta','ep','st');
if ($params->{'roles'} =~ /,/) {
@ltiroles = split(/\s*,\s*/,$params->{'role'});
} else {
my $singlerole = $params->{'roles'};
$singlerole =~ s/^\s|\s+$//g;
@ltiroles = ($singlerole);
}
if (@ltiroles) {
if (ref($lti{$itemid}{maproles}) eq 'HASH') {
my %possroles;
map { $possroles{$lti{$itemid}{maproles}{$_}} = 1; } @ltiroles;
my @possibles = keys(%possroles);
if (@possibles == 1) {
if (grep(/^\Q$possibles[0]\E$/, at roleorder)) {
$reqrole = $possibles[0];
}
} elsif (@possibles > 1) {
foreach my $item (@roleorder) {
if ($possroles{$item}) {
$reqrole = $item;
last;
}
}
}
}
}
#
# If no LON-CAPA username -- is user allowed to create one?
#
my $selfcreate;
if (($uname ne '') && ($udom ne '')) {
$uhome = &Apache::lonnet::homeserver($uname,$udom);
if ($uhome =~ /(con_lost|no_host|no_such_host)/) {
&Apache::lonnet::logthis(" LTI authorized unknown user $uname:$udom ");
if (ref($lti{$itemid}{'makeuser'}) eq 'ARRAY') {
if (@{$lti{$itemid}{'makeuser'}} > 0) {
foreach my $ltirole (@ltiroles) {
if (grep(/^\Q$ltirole\E$/,@{$lti{$itemid}{'makeuser'}})) {
$selfcreate = 1;
}
}
}
}
if ($selfcreate) {
#FIXME Do user creation here.
return OK
} else {
&invalid_request($r,11);
return OK;
}
}
} else {
&invalid_request($r,12);
return OK;
}
#
# If no LON-CAPA course available, check if domain's configuration
# for the specific LTI Consumer allows a new course to be created
# (requires role in Consumer to be: Instructor).
#
if ($cnum eq '') {
if ((@ltiroles) && (grep(/^Instructor$/, at ltiroles)) &&
($lti{$itemid}{'mapcrs'})) {
#FIXME Create a new LON-CAPA course here.
return OK;
} else {
&invalid_request($r,13);
return OK;
}
}
#
# If LON-CAPA course is a Community, and LON-CAPA role
# indicated is cc, change role indicated to co.
#
if ($reqrole eq 'cc') {
if (($cdom ne '') && ($cnum ne '')) {
my %crsenv = &Apache::lonnet::coursedescription($cnum.'_'.$cdom,{ 'one_time' => 1,});
if ($crsenv{'type'} eq 'Community') {
$reqrole = 'co';
}
}
}
#
# Determine if user has required LON-CAPA role
# in the mapped LON-CAPA course.
#
my $role;
my %crsroles = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',undef,[$reqrole],[$cdom]);
if (exists($crsroles{$cnum.':'.$cdom.':'.$reqrole})) {
$role = $reqrole.'./'.$cdom.'/'.$cnum;
#FIXME Need to accommodate sections
} elsif (ref($lti{$itemid}{'selfenroll'}) eq 'ARRAY') {
if (grep(/^\Q$reqrole\E$/,@{$lti{$itemid}{'selfenroll'}})) {
#FIXME Do self-enrollment here
return OK;
} else {
&invalid_request($r,14);
}
}
#
# Store consumer-to-LON-CAPA course mapping
#
if (($sourcecrs ne '') && ($consumers{$sourcecrs} eq '') && ($cnum ne '')) {
&Apache::lonnet::put_dom('lticonsumers',{ $sourcecrs => $cnum },$cdom);
}
#
# Check if user should be hosted here or switched to another server.
#
&Apache::lonnet::logthis(" LTI authorized user: $uname:$udom role: $role course: $cnum:$cdom");
$r->user($uname);
my ($is_balancer,$otherserver,$hosthere);
($is_balancer,$otherserver) =
&Apache::lonnet::check_loadbalancing($uname,$udom,'login');
if ($is_balancer) {
if ($otherserver eq '') {
my $lowest_load;
($otherserver,undef,undef,undef,$lowest_load) = &Apache::lonnet::choose_server($udom);
if ($lowest_load > 100) {
$otherserver = &Apache::lonnet::spareserver($lowest_load,$lowest_load,1,$udom);
}
}
if ($otherserver ne '') {
my @hosts = &Apache::lonnet::current_machine_ids();
if (grep(/^\Q$otherserver\E$/, at hosts)) {
$hosthere = $otherserver;
}
}
}
if (($is_balancer) && (!$hosthere)) {
# login but immediately go to switch server.
&Apache::lonauth::success($r,$uname,$udom,$uhome,'noredirect');
if ($symb) {
$env{'form.symb'} = $symb;
}
if ($role) {
$env{'form.role'} = $role;
}
if ($lti{$itemid}{'passback'}) {
if ($params->{'lis_result_sourcedid'}) {
$env{'request.lti.passbackid'} = $params->{'lis_result_sourcedid'};
}
if ($params->{'lis_outcome_service_url'}) {
$env{'request.lti.passbackurl'} = $params->{'lis_outcome_service_url'};
}
}
if (($lti{$itemid}{'roster'}) && (grep(/^Instructor$/, at ltiroles))) {
if ($params->{'ext_ims_lis_memberships_id'}) {
$env{'request.lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
}
if ($params->{'ext_ims_lis_memberships_url'}) {
$env{'request.lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
}
}
$env{'request.lti.login'} = 1;
foreach my $key (%{$params}) {
delete($env{'form.'.$key});
}
my $redirecturl = '/adm/switchserver';
if ($otherserver ne '') {
$redirecturl .= '?otherserver='.$otherserver;
}
$r->internal_redirect($redirecturl);
$r->set_handlers('PerlHandler'=> undef);
} else {
# need to login them in, so generate the need data that
# migrate expects to do login
foreach my $key (%{$params}) {
delete($env{'form.'.$key});
}
my $ip = $r->get_remote_host();
my %info=('ip' => $ip,
'domain' => $udom,
'username' => $uname,
'server' => $lonhost,
'lti.login' => 1,
);
if ($role) {
$info{'role'} = $role;
}
if ($symb) {
$info{'symb'} = $symb;
}
if ($lti{$itemid}{'passback'}) {
if ($params->{'lis_result_sourcedid'}) {
$info{'lti.passbackid'} = $params->{'lis_result_sourcedid'}
}
if ($params->{'lis_outcome_service_url'}) {
$info{'lti.passbackurl'} = $params->{'lis_outcome_service_url'}
}
}
if (($lti{$itemid}{'roster'}) && (grep(/^Instructor$/, at ltiroles))) {
if ($params->{'ext_ims_lis_memberships_id'}) {
$info{'lti.rosterid'} = $params->{'ext_ims_lis_memberships_id'};
}
if ($params->{'ext_ims_lis_memberships_url'}) {
$info{'lti.rosterurl'} = $params->{'ext_ims_lis_memberships_url'};
}
}
unless ($info{'symb'}) {
if ($mapurl) {
$info{'origurl'} = $mapurl;
if ($mapurl =~ m{/default_\d+\.sequence$}) {
$info{'origurl'} .= (($mapurl =~/\?/)?'&':'?').'navmap=1';
}
} else {
unless ($tail eq '/adm/roles') {
$info{'origurl'} = '/adm/navmaps';
}
}
}
if (($is_balancer) && ($hosthere)) {
$info{'noloadbalance'} = $hosthere;
}
my $token = &Apache::lonnet::tmpput(\%info,$lonhost);
$env{'form.token'} = $token;
$r->internal_redirect('/adm/migrateuser');
$r->set_handlers('PerlHandler'=> undef);
}
return OK;
}
sub check_nonce {
my ($r,$nonce,$timestamp,$lifetime,$domain) = @_;
if (($timestamp eq '') || ($timestamp =~ /^\D/) || ($lifetime eq '') || ($lifetime =~ /\D/) || ($domain eq '')) {
return 0;
}
my $now = time;
if (($timestamp) && ($timestamp < ($now - $lifetime))) {
return 0;
}
if ($nonce eq '') {
return 0;
}
my $lonltidir = $r->dir_config('lonLTIDir');
if (-e "$lonltidir/$domain/$nonce") {
return 0;
} else {
unless (-e "$lonltidir/$domain") {
mkdir("$lonltidir/$domain",0755);
}
if (open(my $fh,'>',"$lonltidir/$domain/$nonce")) {
print $fh $now;
close($fh);
} else {
return 0;
}
}
return 1;
}
sub invalid_request {
my ($r,$num) = @_;
&Apache::loncommon::content_type($r,'text/html');
$r->send_http_header;
if ($r->header_only) {
return;
}
&Apache::lonlocal::get_language_handle($r);
$r->print(
&Apache::loncommon::start_page('Invalid LTI call').
&mt('Invalid LTI call [_1]',$num).
&Apache::loncommon::end_page());
return;
}
1;
More information about the LON-CAPA-cvs
mailing list