diff --git a/conf/authen_LTI.conf.dist b/conf/authen_LTI.conf.dist index 5018e39249..d0e5e7f92b 100644 --- a/conf/authen_LTI.conf.dist +++ b/conf/authen_LTI.conf.dist @@ -169,7 +169,9 @@ $LTIMassUpdateInterval = 86400; #in seconds # the tab will not be shown. Note that the default values for the variables that will be shown # in the LTI tab are the values that are set above. Further note that only the commented out # variables listed below may be added to the LTI config tab. In addition, only the variables that -# pertain to the active LTI version will be shown in the tab. +# pertain to the active LTI version will be shown in the tab. Warning: Allowing users to modify +# the BasicConsumerSecret for LTI 1.1 or the IDs, URLs, etc for LTI 1.3 can expose the values +# of the variables and allow users to lock themselves out of logging in via an LMS. @LTIConfigVariables = ( #'LTI{v1p1}{LMS_name}', #'LTI{v1p3}{LMS_name}', @@ -177,11 +179,33 @@ $LTIMassUpdateInterval = 86400; #in seconds #'LTI{v1p3}{LMS_url}', #'external_auth', #'LTIGradeMode', + #'LTIGradeOnSubmit', + #'LTIMassUpdateInterval', #'LMSManageUserData', + #'LTI{v1p1}{BasicConsumerSecret}', + #'LTI{v1p3}{PlatformID}', + #'LTI{v1p3}{ClientID}', + #'LTI{v1p3}{DeploymentID}', + #'LTI{v1p3}{PublicKeysetURL}', + #'LTI{v1p3}{AccessTokenURL}', + #'LTI{v1p3}{AccessTokenAUD}', + #'LTI{v1p3}{AuthReqURL}', #'debug_lti_parameters', #'lms_context_id' ); +# By default only admin users can modify the LTI secrets and lms_context_id. The following +# permissions need to be modified to allow other users the permission to modify the values. +#$permissionLevels{'change_config_LTI{v1p1}{BasicConsumerSecret}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{PlatformID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{ClientID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{DeploymentID}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{PublicKeysetURL}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AccessTokenURL}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AccessTokenAUD}'} = "admin", +#$permissionLevels{'change_config_LTI{v1p3}{AuthReqURL}'} = "admin", +#$permissionLevels{'change_config_lms_context_id'} = "admin", + # Note that the lms_context_id is actually a database setting. It must be set for a course in # order for the instructor to utilize LTI content selection. This can also be set in the admin # course. diff --git a/conf/defaults.config b/conf/defaults.config index dbebc7c6b4..9592a305d8 100644 --- a/conf/defaults.config +++ b/conf/defaults.config @@ -874,7 +874,15 @@ $authen{admin_module} = ['WeBWorK::Authen::Basic_TheLastOption']; # sufficient to change a configuration setting. #change_config_courseTitle => "admin", - change_config_lms_context_id => "admin", + change_config_lms_context_id => "admin", + 'change_config_LTI{v1p1}{BasicConsumerSecret}' => "admin", + 'change_config_LTI{v1p3}{PlatformID}' => "admin", + 'change_config_LTI{v1p3}{ClientID}' => "admin", + 'change_config_LTI{v1p3}{DeploymentID}' => "admin", + 'change_config_LTI{v1p3}{PublicKeysetURL}' => "admin", + 'change_config_LTI{v1p3}{AccessTokenURL}' => "admin", + 'change_config_LTI{v1p3}{AccessTokenAUD}' => "admin", + 'change_config_LTI{v1p3}{AuthReqURL}' => "admin", # Do not confuse the permission to change a configuration permission with # the actual permission as in the following example. If this us uncommented, diff --git a/lib/WeBWorK/ConfigObject.pm b/lib/WeBWorK/ConfigObject.pm index 611308c0b7..6f23fcf1aa 100644 --- a/lib/WeBWorK/ConfigObject.pm +++ b/lib/WeBWorK/ConfigObject.pm @@ -2,6 +2,7 @@ package WeBWorK::ConfigObject; use Mojo::Base -signatures; # Base object class for all config objects +use constant SECRET_STRING => '*****'; sub new ($class, $data, $c) { return bless { @@ -13,7 +14,7 @@ sub new ($class, $data, $c) { # Only input is a value to display, and should produce an html string. sub display_value ($self, $val) { - return $val; + return $self->{secret} ? SECRET_STRING : $val; } # This should return the value to compare to the new value. This is *not* what is displayed. @@ -47,16 +48,16 @@ sub convert_newval_source ($self, $use_current) { # will be the value of the perl variable, and newval will be whatever an entry widget produces. sub save_string ($self, $oldval, $use_current = 0) { my $newval = $self->convert_newval_source($use_current); - return '' if $self->comparison_value($oldval) eq $newval; + return '' if $self->comparison_value($oldval) eq $newval || ($self->{secret} && $newval eq SECRET_STRING); $newval =~ s/['"`]//g; return "\$$self->{var} = '$newval';\n"; } # A widget to interact with the user -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { return $self->{c}->text_field( - $self->{name} => $default, + $self->{name} => $is_secret ? SECRET_STRING : $default, id => $self->{name}, size => $self->{width} || 15, class => 'form-control form-control-sm' diff --git a/lib/WeBWorK/ConfigObject/boolean.pm b/lib/WeBWorK/ConfigObject/boolean.pm index 2f8ad7766a..be10bb1dfb 100644 --- a/lib/WeBWorK/ConfigObject/boolean.pm +++ b/lib/WeBWorK/ConfigObject/boolean.pm @@ -29,7 +29,7 @@ sub save_string ($self, $oldval, $use_current = 0) { return "\$$self->{var} = $newval;\n"; } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { return $self->{c}->select_field( $self->{name} => [ [ $self->{c}->maketext('True') => 1, $default == 1 ? (selected => undef) : () ], diff --git a/lib/WeBWorK/ConfigObject/checkboxlist.pm b/lib/WeBWorK/ConfigObject/checkboxlist.pm index 1f2aa764cd..746adec2be 100644 --- a/lib/WeBWorK/ConfigObject/checkboxlist.pm +++ b/lib/WeBWorK/ConfigObject/checkboxlist.pm @@ -44,7 +44,7 @@ sub comparison_value ($self, $val) { return join(',', @{ $val // [] }); } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { my $c = $self->{c}; return $c->c( map { diff --git a/lib/WeBWorK/ConfigObject/list.pm b/lib/WeBWorK/ConfigObject/list.pm index 17ebf03b92..f2ec5b8112 100644 --- a/lib/WeBWorK/ConfigObject/list.pm +++ b/lib/WeBWorK/ConfigObject/list.pm @@ -43,7 +43,7 @@ sub save_string ($self, $oldval, $use_current = 0) { return "\$$self->{var} = [" . join(',', map {"'$_'"} map { $_ =~ s/['"`]//gr } split(',', $newval)) . "];\n"; } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { my $str = join(', ', @{ $default // [] }); return $self->{c}->text_area( $self->{name} => $str =~ /\S/ ? $str : '', diff --git a/lib/WeBWorK/ConfigObject/lms_context_id.pm b/lib/WeBWorK/ConfigObject/lms_context_id.pm index 140042dc83..42636efb20 100644 --- a/lib/WeBWorK/ConfigObject/lms_context_id.pm +++ b/lib/WeBWorK/ConfigObject/lms_context_id.pm @@ -101,7 +101,7 @@ sub save_string ($self, $oldval, $use_current = 0) { # This ensures that the input for this setting always shows what is in the database. If the form is submitted, and the # requested context id is rejected above, then that rejected value should not be shown when the page reloads. -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { $self->{c}->param($self->{name}, $default); return $self->SUPER::entry_widget($default); } diff --git a/lib/WeBWorK/ConfigObject/permission.pm b/lib/WeBWorK/ConfigObject/permission.pm index 1bf28ed72d..80d51e8c30 100644 --- a/lib/WeBWorK/ConfigObject/permission.pm +++ b/lib/WeBWorK/ConfigObject/permission.pm @@ -34,7 +34,7 @@ sub save_string ($self, $oldval, $use_current = 0) { return "\$$self->{var} = '$newval';\n"; } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { my $c = $self->{c}; # The value of a permission can be undefined (for nobody), a standard permission number, or some other number diff --git a/lib/WeBWorK/ConfigObject/permission_checkboxlist.pm b/lib/WeBWorK/ConfigObject/permission_checkboxlist.pm index 1fce4c5982..790ff3e681 100644 --- a/lib/WeBWorK/ConfigObject/permission_checkboxlist.pm +++ b/lib/WeBWorK/ConfigObject/permission_checkboxlist.pm @@ -51,7 +51,7 @@ sub comparison_value ($self, $val) { return join(',', @{ $val // [] }); } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { my $c = $self->{c}; $default = role_and_above($self->{c}->ce->{userRoles}, $default) unless ref($default) eq 'ARRAY'; return $c->c( diff --git a/lib/WeBWorK/ConfigObject/popuplist.pm b/lib/WeBWorK/ConfigObject/popuplist.pm index 50f6a29397..30995adb59 100644 --- a/lib/WeBWorK/ConfigObject/popuplist.pm +++ b/lib/WeBWorK/ConfigObject/popuplist.pm @@ -29,7 +29,7 @@ sub save_string ($self, $oldval, $use_current = 0) { return ("\$$self->{var} = '$newval';\n"); } -sub entry_widget ($self, $default) { +sub entry_widget ($self, $default, $is_secret = 0) { my $c = $self->{c}; return $c->select_field( $self->{name} => [ diff --git a/lib/WeBWorK/ConfigValues.pm b/lib/WeBWorK/ConfigValues.pm index dc0ae1ce29..b5d1229c7b 100644 --- a/lib/WeBWorK/ConfigValues.pm +++ b/lib/WeBWorK/ConfigValues.pm @@ -899,6 +899,25 @@ sub getConfigValues ($ce) { labels => { '' => 'None', 'course' => 'Course', 'homework' => 'Homework' }, type => 'popuplist' }, + LTIGradeOnSubmit => { + var => 'LTIGradeOnSubmit', + doc => x('Update LMS Grade Each Submit'), + doc2 => x( + 'Sets if webwork sends grades back to the LMS every time a user submits an answer. ' + . 'This keeps students grades up to date, but can cause additional server load.' + ), + type => 'boolean' + }, + LTIMassUpdateInterval => { + var => 'LTIMassUpdateInterval', + doc => x('Time in seconds to periodically update LMS grades (-1 to disable)'), + doc2 => x( + 'Sets the time in seconds to periodically update the LMS grades. WeBWorK will update all grades on ' + . 'the LMS if it has been longer than this time since the completion of the last update. This is ' + . 'only an approximate time. 86400 seconds is one day. -1 disables periodic updates.' + ), + type => 'number' + }, LMSManageUserData => { var => 'LMSManageUserData', doc => x('Allow the LMS to update user account data'), @@ -911,6 +930,65 @@ sub getConfigValues ($ce) { ), type => 'boolean' }, + 'LTI{v1p1}{BasicConsumerSecret}' => { + var => 'LTI{v1p1}{BasicConsumerSecret}', + doc => x('LMS shared secret for LTI 1.1 authentication'), + doc2 => x( + 'This secret word is used to validate logins from an LMS using LTI 1.1. ' + . 'This secret word must match the word configured in the LMS.' + ), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{PlatfromID}' => { + var => 'LTI{v1p3}{PlatformID}', + doc => x('LMS platform ID for LTI 1.3'), + doc2 => x('LMS platform ID used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{ClientID}' => { + var => 'LTI{v1p3}{ClientID}', + doc => x('LMS client ID for LTI 1.3'), + doc2 => x('LMS client ID used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{DeploymentID}' => { + var => 'LTI{v1p3}{DeploymentID}', + doc => x('LMS deployment ID for LTI 1.3'), + doc2 => x('LMS deployment ID used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{PublicKeysetURL}' => { + var => 'LTI{v1p3}{PublicKeysetURL}', + doc => x('LMS public keyset URL for LTI 1.3'), + doc2 => x('LMS public keyset URL used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{AccessTokenURL}' => { + var => 'LTI{v1p3}{AccessTokenURL}', + doc => x('LMS access token URL for LTI 1.3'), + doc2 => x('LMS access token URL used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{AccessTokenAUD}' => { + var => 'LTI{v1p3}{AccessTokenAUD}', + doc => x('LMS access token AUD for LTI 1.3'), + doc2 => x('LMS access token AUD used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, + 'LTI{v1p3}{AuthReqURL}' => { + var => 'LTI{v1p3}{AuthReqURL}', + doc => x('LMS authorization request URL for LTI 1.3'), + doc2 => x('LMS authorization request URL used to validate logins from an LMS using LTI 1.3.'), + type => 'text', + secret => 1 + }, debug_lti_parameters => { var => 'debug_lti_parameters', doc => x('Show LTI parameters (for debugging)'), @@ -997,6 +1075,15 @@ sub getConfigValues ($ce) { { %{ delete $LTIConfigValues->{'LTI{v1p1}{LMS_name}'} }, var => 'LTI{v1p3}{LMS_name}' }; $LTIConfigValues->{'LTI{v1p3}{LMS_url}'} = { %{ delete $LTIConfigValues->{'LTI{v1p1}{LMS_url}'} }, var => 'LTI{v1p3}{LMS_url}' }; + delete $LTIConfigValues->{'LTI{v1p1}{BasicConsumerSecret}'}; + } else { + for my $key ( + 'PlatformID', 'ClientID', 'DeploymentID', 'PublicKeysetURL', + 'AccessTokenURL', 'AccessTokenAUD', 'AuthReqURL' + ) + { + delete $LTIConfigValues->{"LTI{v1p3}{$key}"}; + } } push( diff --git a/templates/ContentGenerator/Instructor/Config.html.ep b/templates/ContentGenerator/Instructor/Config.html.ep index 0384c04d93..6c05f9e613 100644 --- a/templates/ContentGenerator/Instructor/Config.html.ep +++ b/templates/ContentGenerator/Instructor/Config.html.ep @@ -56,12 +56,16 @@ % defined $ce->{permissionLevels}{"change_config_$conobject->{var}"} % && !$authz->hasPermissions(param('user'), "change_config_$conobject->{var}") % ); + % # Hide sensitive variables from being displayed. + % my $default_value = $conobject->get_value($default_ce); + % my $current_value = $conobject->get_value($ce3); + % my $is_secret = $conobject->{secret} && $default_value eq $current_value ? 1 : 0; <%= $conobject->what_string %> <%= $conobject->display_value($conobject->get_value($default_ce)) %> - <%= $conobject->entry_widget($conobject->get_value($ce3)) =%> + <%= $conobject->entry_widget($conobject->get_value($ce3), $is_secret) =%> % }