From 2af489e293b0a06a96a204eec90c65039a355e4a Mon Sep 17 00:00:00 2001 From: Andrew Gardener Date: Thu, 20 May 2021 12:07:35 -0700 Subject: [PATCH] IMS Service refactor - Added delayed jobs for: - LTI grade pass back: user submit homework - LTI grade pass back: entire class (currently via cron job) - LTI membership sync (currently via cron job) - Caliper event emit - Renamed WebworkBridge to LTIAdvantage and moved services and parsers from Auth folder to new LTIAdvantage folder (cleaner lib implementation) - Added manual LTI class roster sync option to Class List Editor screen - Added manual LTI grade sync option to Homework Set editor screen - Added LTI configuration to Course Settings Screen - Option to control if users are automatically assigned to sets on launch/membership sync - Option to control if course grades is automatically updated via Cron - Option to control if course roster is automatically updated via Cron - Removed old Auth options: UBCCWL and VISTALogin2 --- .gitignore | 1 - Dockerfile | 1 + Dockerfile-prod | 1 + bin/check_modules.pl | 1 + conf/database.conf.dist | 118 +++++-- conf/defaults.config | 17 + conf/localOverrides.conf.dist | 45 ++- docker-compose.yml | 7 +- lib/Caliper/Sensor.pm | 14 +- lib/DelayedJob/GetClassMembership.pm | 50 +++ lib/DelayedJob/PushClassGrades.pm | 41 +++ lib/DelayedJob/PushUserGrades.pm | 44 +++ lib/DelayedJob/Run/run_all_jobs.pl | 47 +++ lib/DelayedJob/SendCaliperEvent.pm | 33 ++ lib/DelayedJob/Service.pm | 115 +++++++ lib/DelayedJob/Worker.pm | 21 ++ .../Cron}/lti_update_classlist.pl | 40 ++- .../Cron}/lti_update_grades.pl | 28 +- .../Bridge.pm => LTIAdvantage/Entrypoint.pm} | 14 +- .../Entrypoint/Launch.pm} | 110 +----- .../Entrypoint/Login.pm} | 24 +- lib/LTIAdvantage/EntrypointManager.pm | 92 +++++ .../ExtraLog.pm | 2 +- .../Importer/CourseCreator.pm | 12 +- .../Importer/CourseUpdater.pm | 51 ++- .../Importer/Error.pm | 2 +- .../Parser/LaunchParser.pm} | 14 +- .../Parser/NamesAndRoleServiceParser.pm} | 10 +- .../Service}/AccessTokenRequest.pm | 14 +- .../Service}/AssignmentAndGradeService.pm | 14 +- .../Service}/NamesAndRoleService.pm | 20 +- lib/WeBWorK.pm | 22 +- lib/WeBWorK/Authen/CWL/CWL.pm | 282 ---------------- lib/WeBWorK/Authen/CWL/Session.pm | 318 ------------------ lib/WeBWorK/Authen/LTIAdvantage.pm | 29 +- lib/WeBWorK/Authen/UBCCWL.pm | 191 ----------- lib/WeBWorK/Authen/VistaLogin2.pm | 140 -------- lib/WeBWorK/ContentGenerator/GatewayQuiz.pm | 21 +- .../Instructor/ProblemSetList2.pm | 31 +- .../ContentGenerator/Instructor/UserList2.pm | 36 +- ...kBridgeStatus.pm => LTIAdvantageStatus.pm} | 4 +- .../ProblemUtil/ProblemUtil.pm | 20 +- lib/WeBWorK/DB.pm | 7 +- lib/WeBWorK/DB/Record/DelayedJob/Error.pm | 37 ++ .../DB/Record/DelayedJob/Exitstatus.pm | 38 +++ lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm | 35 ++ lib/WeBWorK/DB/Record/DelayedJob/Job.pm | 42 +++ lib/WeBWorK/DB/Record/DelayedJob/Note.pm | 36 ++ .../AccessTokens.pm} | 8 +- .../Contexts.pm} | 9 +- .../{LTINonces.pm => LTIAdvantage/Nonces.pm} | 8 +- .../ResourceLink.pm} | 8 +- .../{LTIUser.pm => LTIAdvantage/User.pm} | 8 +- lib/WeBWorK/DB/Schema/NewSQL.pm | 6 - lib/WeBWorK/Localize.pm | 17 + lib/WebworkBridge/BridgeManager.pm | 92 ----- 56 files changed, 1070 insertions(+), 1378 deletions(-) create mode 100644 lib/DelayedJob/GetClassMembership.pm create mode 100644 lib/DelayedJob/PushClassGrades.pm create mode 100644 lib/DelayedJob/PushUserGrades.pm create mode 100755 lib/DelayedJob/Run/run_all_jobs.pl create mode 100644 lib/DelayedJob/SendCaliperEvent.pm create mode 100644 lib/DelayedJob/Service.pm create mode 100644 lib/DelayedJob/Worker.pm rename lib/{WebworkBridge => LTIAdvantage/Cron}/lti_update_classlist.pl (63%) rename lib/{WebworkBridge => LTIAdvantage/Cron}/lti_update_grades.pl (60%) rename lib/{WebworkBridge/Bridge.pm => LTIAdvantage/Entrypoint.pm} (77%) rename lib/{WebworkBridge/Bridges/LTILaunchBridge.pm => LTIAdvantage/Entrypoint/Launch.pm} (78%) rename lib/{WebworkBridge/Bridges/LTILoginBridge.pm => LTIAdvantage/Entrypoint/Login.pm} (82%) create mode 100644 lib/LTIAdvantage/EntrypointManager.pm rename lib/{WebworkBridge => LTIAdvantage}/ExtraLog.pm (97%) rename lib/{WebworkBridge => LTIAdvantage}/Importer/CourseCreator.pm (89%) rename lib/{WebworkBridge => LTIAdvantage}/Importer/CourseUpdater.pm (89%) rename lib/{WebworkBridge => LTIAdvantage}/Importer/Error.pm (92%) rename lib/{WeBWorK/Authen/LTIAdvantage/LTILaunchParser.pm => LTIAdvantage/Parser/LaunchParser.pm} (93%) rename lib/{WeBWorK/Authen/LTIAdvantage/LTINamesAndRoleServiceParser.pm => LTIAdvantage/Parser/NamesAndRoleServiceParser.pm} (92%) rename lib/{WeBWorK/Authen/LTIAdvantage => LTIAdvantage/Service}/AccessTokenRequest.pm (93%) rename lib/{WeBWorK/Authen/LTIAdvantage => LTIAdvantage/Service}/AssignmentAndGradeService.pm (98%) rename lib/{WeBWorK/Authen/LTIAdvantage => LTIAdvantage/Service}/NamesAndRoleService.pm (91%) delete mode 100644 lib/WeBWorK/Authen/CWL/CWL.pm delete mode 100644 lib/WeBWorK/Authen/CWL/Session.pm delete mode 100644 lib/WeBWorK/Authen/UBCCWL.pm delete mode 100644 lib/WeBWorK/Authen/VistaLogin2.pm rename lib/WeBWorK/ContentGenerator/{WebworkBridgeStatus.pm => LTIAdvantageStatus.pm} (78%) create mode 100644 lib/WeBWorK/DB/Record/DelayedJob/Error.pm create mode 100644 lib/WeBWorK/DB/Record/DelayedJob/Exitstatus.pm create mode 100644 lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm create mode 100644 lib/WeBWorK/DB/Record/DelayedJob/Job.pm create mode 100644 lib/WeBWorK/DB/Record/DelayedJob/Note.pm rename lib/WeBWorK/DB/Record/{LTIAccessTokens.pm => LTIAdvantage/AccessTokens.pm} (77%) rename lib/WeBWorK/DB/Record/{LTIContexts.pm => LTIAdvantage/Contexts.pm} (77%) rename lib/WeBWorK/DB/Record/{LTINonces.pm => LTIAdvantage/Nonces.pm} (77%) rename lib/WeBWorK/DB/Record/{LTIResourceLink.pm => LTIAdvantage/ResourceLink.pm} (81%) rename lib/WeBWorK/DB/Record/{LTIUser.pm => LTIAdvantage/User.pm} (77%) delete mode 100644 lib/WebworkBridge/BridgeManager.pm diff --git a/.gitignore b/.gitignore index 132f8affa3..0226b573a5 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,6 @@ DATA/* *.log httpd2.pid httpd.pid -lib/WebworkBridge/classlists/ .dump_past_answers_salt .data .idea diff --git a/Dockerfile b/Dockerfile index d2145932bc..2ff8be252f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -196,6 +196,7 @@ RUN apt-get update \ libmoox-options-perl \ libnet-https-nb-perl \ libhttp-async-perl \ + libtheschwartz-perl \ libcrypt-jwt-perl \ libjson-validator-perl \ make \ diff --git a/Dockerfile-prod b/Dockerfile-prod index 84c5b181dc..9a52dc2809 100644 --- a/Dockerfile-prod +++ b/Dockerfile-prod @@ -117,6 +117,7 @@ RUN apt-get update \ libmoox-options-perl \ libnet-https-nb-perl \ libhttp-async-perl \ + libtheschwartz-perl \ libcrypt-jwt-perl \ libjson-validator-perl \ libmoose-perl \ diff --git a/bin/check_modules.pl b/bin/check_modules.pl index 67775b49da..a53ed0a9f6 100755 --- a/bin/check_modules.pl +++ b/bin/check_modules.pl @@ -124,6 +124,7 @@ XML::Simple App::Genpass HTTP::Async + TheSchwartz Crypt::JWT JSON::Validator ); diff --git a/conf/database.conf.dist b/conf/database.conf.dist index bc392189d1..37c00dca70 100644 --- a/conf/database.conf.dist +++ b/conf/database.conf.dist @@ -119,36 +119,6 @@ $dbLayouts{sql_single} = { non_native => 1, }, }, - lti_contexts => { - record => "WeBWorK::DB::Record::LTIContexts", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - lti_nonces => { - record => "WeBWorK::DB::Record::LTINonces", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, - lti_access_tokens => { - record => "WeBWorK::DB::Record::LTIAccessTokens", - schema => "WeBWorK::DB::Schema::NewSQL::Std", - driver => "WeBWorK::DB::Driver::SQL", - source => $database_dsn, - engine => $database_storage_engine, - params => { %sqlParams, - non_native => 1, - }, - }, password => { record => "WeBWorK::DB::Record::Password", schema => "WeBWorK::DB::Schema::NewSQL::Std", @@ -395,8 +365,9 @@ $dbLayouts{sql_single} = { tableOverride => "${courseName}_global_user_achievement" }, }, + # LTI Advantage lti_resource_link => { - record => "WeBWorK::DB::Record::LTIResourceLink", + record => "WeBWorK::DB::Record::LTIAdvantage::ResourceLink", schema => "WeBWorK::DB::Schema::NewSQL::Std", driver => "WeBWorK::DB::Driver::SQL", source => $database_dsn, @@ -406,7 +377,7 @@ $dbLayouts{sql_single} = { }, }, lti_user => { - record => "WeBWorK::DB::Record::LTIUser", + record => "WeBWorK::DB::Record::LTIAdvantage::User", schema => "WeBWorK::DB::Schema::NewSQL::Std", driver => "WeBWorK::DB::Driver::SQL", source => $database_dsn, @@ -414,7 +385,88 @@ $dbLayouts{sql_single} = { params => { %sqlParams, tableOverride => "${courseName}_lti_user" }, - } + }, + lti_contexts => { + record => "WeBWorK::DB::Record::LTIAdvantage::Contexts", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + lti_nonces => { + record => "WeBWorK::DB::Record::LTIAdvantage::Nonces", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + lti_access_tokens => { + record => "WeBWorK::DB::Record::LTIAdvantage::AccessTokens", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + # Delayed Job Tables + funcmap => { + record => "WeBWorK::DB::Record::DelayedJob::Funcmap", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + job => { + record => "WeBWorK::DB::Record::DelayedJob::Job", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + note => { + record => "WeBWorK::DB::Record::DelayedJob::Note", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + error => { + record => "WeBWorK::DB::Record::DelayedJob::Error", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + }, + exitstatus => { + record => "WeBWorK::DB::Record::DelayedJob::Exitstatus", + schema => "WeBWorK::DB::Schema::NewSQL::Std", + driver => "WeBWorK::DB::Driver::SQL", + source => $database_dsn, + engine => $database_storage_engine, + params => { %sqlParams, + non_native => 1, + }, + } }; # include ("conf/database.conf"); # uncomment to provide local overrides diff --git a/conf/defaults.config b/conf/defaults.config index 0fb06c6562..07d46e3156 100644 --- a/conf/defaults.config +++ b/conf/defaults.config @@ -1759,6 +1759,23 @@ $ConfigValues = [ }, ], + ['LTI', + { var => 'lti_advantage{auto_assign_users_to_sets}', + doc => 'Automatically assign users to sets', + doc2 => 'By default, users are automatically assigned to all sets when they launch into WeBWorK from the LMS or when the class roster is synced.', + type => 'boolean' + }, + { var => 'lti_advantage{cron_grade_sync}', + doc => 'Automatically send student grades to LMS via cron job', + doc2 => 'By default, student grades are automatically sent to the LRS via a cron job.', + type => 'boolean' + }, + { var => 'lti_advantage{cron_roster_sync}', + doc => 'Automatically get roster from LMS via cron job', + doc2 => 'By default, class roster is automatically fetched from the LMS via a cron job.', + type => 'boolean' + }, + ], # PGSimple is not used for now GG # ['Editor', # { var => 'editor{author}', diff --git a/conf/localOverrides.conf.dist b/conf/localOverrides.conf.dist index 0700411ba6..00e99c9c6b 100644 --- a/conf/localOverrides.conf.dist +++ b/conf/localOverrides.conf.dist @@ -584,15 +584,19 @@ $webworkURLs{bugReporter} = "mailto:webwork.support\@ubc.ca"; # Add in custom authen modules $authen{lti} = "WeBWorK::Authen::LTIAdvantage"; -$authen{vista_login} = "WeBWorK::Authen::VistaLogin2"; # xmlrpc webservice requests will use the default auth instead of shib $authen{'xmlrpc'} = "WeBWorK::Authen"; ################################################################################ +################################################################################ +# Delayed Job +################################################################################ + +$delayed_job{enabled} = 1; ################################################################################ -# Webwork Caliper +# Caliper ################################################################################ # enable/disable Caliper for install @@ -630,20 +634,17 @@ $caliper{custom_actor_generator} = sub { ################################################################################ -# Webwork Bridge +# LTI Advantage ################################################################################ -$bridge{studentlog} = $webworkDirs{logs} . "/studentupdates.log"; +$lti_advantage{studentlog} = $webworkDirs{logs} . "/studentupdates.log"; # Set password for the admin user created for all imported courses. -$bridge{adminuserpw} = "admin"; -$bridge{push_grades_on_submit} = 0; -$bridge{hide_new_courses} = 1; -# define which LTI roles will launch the membership request on launch. -$bridge{roles_can_update} = [ - "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor", - "http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper", - "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator" -]; -$bridge{lti_clients} = { +$lti_advantage{adminuserpw} = "admin"; +$lti_advantage{push_grades_on_submit} = 1; +$lti_advantage{hide_new_courses} = 1; +$lti_advantage{auto_assign_users_to_sets} = 1; +$lti_advantage{cron_grade_sync} = 1; +$lti_advantage{cron_roster_sync} = 1; +$lti_advantage{lti_clients} = { 'example_client_id' => { # will look inside of nested object if `|` is used as seperator. Doing this allow using fields inside of claims # set to $Canvas.user.sisIntegrationId in canvas @@ -660,7 +661,7 @@ $bridge{lti_clients} = { } }; -$bridge{lti_clients}{'example_client_id'}{tool_public_key} = <<'EOF'; +$lti_advantage{lti_clients}{'example_client_id'}{tool_public_key} = <<'EOF'; -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6sJx68cYViPEty86G2KB nWh62Aoc99zguv69ni4y+augier+1IArQ5apF8j657zbk/EYUf3kRka1fGmALvQY @@ -670,7 +671,7 @@ lQIDAQAB -----END PUBLIC KEY----- EOF -$bridge{lti_clients}{'example_client_id'}{tool_private_key} = <<'EOF'; +$lti_advantage{lti_clients}{'example_client_id'}{tool_private_key} = <<'EOF'; -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEA6sJx68cYViPEty86G2KBnWh62Aoc99zguv69ni4y+augier+ 1IArQ5apF8j657zbk/EYUf3kRka1fGmALvQYZ+MWjjomR64u7rofgdetgnfIOQQa @@ -682,18 +683,12 @@ wqw0N02hAoGAbpjOnPTul04AerLoehExqsBxTw255n/k70kWizKITmuPKFLwECKV -----END RSA PRIVATE KEY----- EOF -# List of Vista ids that are blacklisted from import. Note that id matching -# is done using m//, so any occurance of the string below in an id will mark -# that id as blacklisted. -$bridge{vista_blacklist} = ( - "webct_demo" -); # Note that the mapped Webwork course name still goes through sanitization. # Please make sure they match these requirements or the courses will not match. # - Allowed characters in regex form: [a-zA-Z0-9_-] # - Max length 40 characters -# See WeBWorK::Authen::LTIAdvantage::LTILaunchParser() for implementation -$bridge{custom_course_title_parser} = sub { +# See LTIAdvantage::Parser::LaunchParser() for implementation +$lti_advantage{custom_course_title_parser} = sub { my $parser = shift; # else use term name (if available) and context_label @@ -707,7 +702,7 @@ $bridge{custom_course_title_parser} = sub { return $course_title; }; -$bridge{course_template} = "modelCourse"; +$lti_advantage{course_template} = "modelCourse"; $shibboleth{login_script} = "/Shibboleth.sso/Login"; $shibboleth{logout_script} = "/Shibboleth.sso/Logout?return=".$server_root_url.$webwork_url; diff --git a/docker-compose.yml b/docker-compose.yml index 71a5c88033..132a275d35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: MYSQL_USER: ${WEBWORK_DB_USER} MYSQL_PASSWORD: ${WEBWORK_DB_PASSWORD} - app: + app: &app image: webwork # Select the appropriate "build:" block: @@ -185,6 +185,11 @@ services: # WEBWORK_TIMEZONE: America/New_York # R_HOST: r + worker: + <<: *app + command: ./webwork2/lib/DelayedJob/Run/run_all_jobs.pl + ports: [] + r: image: ubcctlt/rserve # # The R server need not be available from outside the local Docker network. diff --git a/lib/Caliper/Sensor.pm b/lib/Caliper/Sensor.pm index 7a65988314..67bed1a9ac 100644 --- a/lib/Caliper/Sensor.pm +++ b/lib/Caliper/Sensor.pm @@ -17,6 +17,7 @@ use HTTP::Async; use Caliper::Event; use Caliper::ResourceIri; +#$WeBWorK::Debug::Enabled = 1; # Constructor sub new @@ -54,7 +55,18 @@ sub sendEvents Caliper::Event::add_defaults($r, $event_hash); } - my $ce = $r->{ce}; + if ($self->{ce}->{delayed_job}{enabled}) { + my $delayed_job_service = DelayedJob::Service->new($self->{ce}); + $delayed_job_service->sendEvents($array_of_events); + } else { + $self->_sendEvents($array_of_events); + } +} +sub _sendEvents +{ + my ($self, $array_of_events) = @_; + + my $ce = $self->{ce}; my $resource_iri = Caliper::ResourseIri->new($ce); my $async = HTTP::Async->new; $async->timeout( 5 ); diff --git a/lib/DelayedJob/GetClassMembership.pm b/lib/DelayedJob/GetClassMembership.pm new file mode 100644 index 0000000000..d5d10300b2 --- /dev/null +++ b/lib/DelayedJob/GetClassMembership.pm @@ -0,0 +1,50 @@ +package DelayedJob::GetClassMembership; +use base qw( DelayedJob::Worker ); + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use Data::Dumper; +use TheSchwartz::Job; +use LTIAdvantage::Service::NamesAndRoleService; +use LTIAdvantage::Importer::CourseUpdater; + +sub work { + my $class = shift; + my TheSchwartz::Job $job = shift; + my $args = $job->arg; + + my $courseName = $args->{courseName}; + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $courseName, + }); + my $db = new WeBWorK::DB($ce->{dbLayout}); + + print "Fetching Course LTI Names and Roles for course_id: $courseName\n"; + my $names_and_roles_service = LTIAdvantage::Service::NamesAndRoleService->new($ce, $db); + my $membership = $names_and_roles_service->getAllNamesAndRole(); + unless ($membership) { + my $error_msg = "There was an issue fetching the class roster for course_id: $courseName. ".$names_and_roles_service->{error}; + debug($error_msg); + print $error_msg."\n"; + $job->failed($error_msg); + return; + } + my $updater = LTIAdvantage::Importer::CourseUpdater->new($ce, $db, $membership); + my $ret = $updater->updateCourse(); + if ($ret) { + my $error_msg = "Update Class Roster failed for course_id: $courseName: $ret"; + debug($error_msg); + print $error_msg."\n"; + $job->failed($error_msg); + return; + } + print "Successfully fetched LTI Names and Roles for course_id: $courseName\n"; + $job->completed(); +} + +1; diff --git a/lib/DelayedJob/PushClassGrades.pm b/lib/DelayedJob/PushClassGrades.pm new file mode 100644 index 0000000000..583ba411a8 --- /dev/null +++ b/lib/DelayedJob/PushClassGrades.pm @@ -0,0 +1,41 @@ +package DelayedJob::PushClassGrades; +use base qw( DelayedJob::Worker ); + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use Data::Dumper; +use TheSchwartz::Job; +use LTIAdvantage::Service::AssignmentAndGradeService; + +sub work { + my $class = shift; + my TheSchwartz::Job $job = shift; + my $args = $job->arg; + + my $courseName = $args->{courseName}; + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $courseName, + }); + my $db = new WeBWorK::DB($ce->{dbLayout}); + + print "Sending Course LTI Assignment and Grades for course_id: $courseName\n"; + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($ce, $db); + $assignment_and_grade_service->pushAllAssignmentGrades(); + + if ($assignment_and_grade_service->{error}) { + my $error_msg = "There was an issue pushing the class grades for course_id: $courseName. ".$assignment_and_grade_service->{error}; + debug($error_msg); + print $error_msg."\n"; + $job->failed($error_msg); + } else { + print "Successfully sent LTI Assignment and Grades for course_id: $courseName\n"; + $job->completed(); + } +} + +1; diff --git a/lib/DelayedJob/PushUserGrades.pm b/lib/DelayedJob/PushUserGrades.pm new file mode 100644 index 0000000000..8298f9bf34 --- /dev/null +++ b/lib/DelayedJob/PushUserGrades.pm @@ -0,0 +1,44 @@ +package DelayedJob::PushUserGrades; +use base qw( DelayedJob::Worker ); + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use Data::Dumper; +use TheSchwartz::Job; +use LTIAdvantage::Service::AssignmentAndGradeService; + +sub work { + my $class = shift; + my TheSchwartz::Job $job = shift; + my $args = $job->arg; + + my $courseName = $args->{courseName}; + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $courseName, + }); + my $db = new WeBWorK::DB($ce->{dbLayout}); + + my $user_id = $args->{user_id}; + my $set_id = $args->{set_id}; + + print "Sending User LTI Assignment and Grades for course_id: $courseName user_id: $user_id set_id: $set_id\n"; + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($ce, $db); + $assignment_and_grade_service->pushUserGradesOnSubmit($user_id, $set_id); + + if ($assignment_and_grade_service->{error}) { + my $error_msg = "There was an issue pushing the user grades for course_id: $courseName user_id: $user_id set_id: $set_id. ".$assignment_and_grade_service->{error}; + debug($error_msg); + print $error_msg."\n"; + $job->failed($error_msg); + } else { + print "Successfully sent LTI Assignment and Grades for course_id: $courseName user_id: $user_id set_id: $set_id\n"; + $job->completed(); + } +} + +1; diff --git a/lib/DelayedJob/Run/run_all_jobs.pl b/lib/DelayedJob/Run/run_all_jobs.pl new file mode 100755 index 0000000000..8262b54ea5 --- /dev/null +++ b/lib/DelayedJob/Run/run_all_jobs.pl @@ -0,0 +1,47 @@ +#!/usr/bin/env perl + +=head1 NAME + +run_all_jobs - Script for all running delayed job workers + +=head1 SYNOPSIS + +run_all_jobs + +=back + +=cut + +use strict; +use warnings; + +BEGIN +{ + die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; +} + +use lib "$ENV{WEBWORK_ROOT}/lib"; +use WeBWorK::CourseEnvironment; +use Getopt::Long; +use Pod::Usage; +use WeBWorK::Debug; +use Data::Dumper; +use DelayedJob::Service; + +my $man = 0; +my $help = 0; + +GetOptions ( + 'help|?' => \$help, + man => \$man +); + +pod2usage(1) if $help; + +# bring up a minimal course environment +my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, +}); + +my $delayed_job_service = DelayedJob::Service->new($ce); +$delayed_job_service->work(); diff --git a/lib/DelayedJob/SendCaliperEvent.pm b/lib/DelayedJob/SendCaliperEvent.pm new file mode 100644 index 0000000000..77fac4c1ed --- /dev/null +++ b/lib/DelayedJob/SendCaliperEvent.pm @@ -0,0 +1,33 @@ +package DelayedJob::SendCaliperEvent; +use base qw( DelayedJob::Worker ); + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use Data::Dumper; +use TheSchwartz::Job; +use Caliper::Sensor; + +sub work { + my $class = shift; + my TheSchwartz::Job $job = shift; + my $args = $job->arg; + + my $courseName = $args->{courseName}; + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, + courseName => $courseName, + }); + my $array_of_events = $args->{array_of_events}; + + print "Sending Caliper events for course_id: $courseName\n"; + my $caliper_sensor = Caliper::Sensor->new($ce); + $caliper_sensor->_sendEvents($array_of_events); + $job->completed(); + print "Successfully sent Caliper events for course_id: $courseName\n"; +} + +1; diff --git a/lib/DelayedJob/Service.pm b/lib/DelayedJob/Service.pm new file mode 100644 index 0000000000..338d3bc5d1 --- /dev/null +++ b/lib/DelayedJob/Service.pm @@ -0,0 +1,115 @@ +package DelayedJob::Service; + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use Data::Dumper; +use TheSchwartz; +use TheSchwartz::Job; +use DBI; +use DelayedJob::GetClassMembership; +use DelayedJob::PushClassGrades; +use DelayedJob::PushUserGrades; +use DelayedJob::SendCaliperEvent; + +#$WeBWorK::Debug::Enabled = 1; + +# Constructor +sub new +{ + my ($class, $ce) = @_; + my $dbh = DBI->connect( + $ce->{database_dsn}, + $ce->{database_username}, + $ce->{database_password}, + { + RaiseError => 1, + PrintError => 0, + AutoCommit => 1, + }, + ); + my $driver = Data::ObjectDriver::Driver::DBI->new(dbh => $dbh); + my $client = TheSchwartz->new( + databases => [{ driver => $driver }], + verbose => 1, + prioritize => 1 + ); + $client->can_do('DelayedJob::GetClassMembership'); + $client->can_do('DelayedJob::PushClassGrades'); + $client->can_do('DelayedJob::PushUserGrades'); + $client->can_do('DelayedJob::SendCaliperEvent'); + my $self = { + ce => $ce, + client => $client + }; + bless $self, $class; + return $self; +} + +sub work { + my ($self) = @_; + $self->{client}->work(15); +} + +sub work_until_done { + my ($self) = @_; + $self->{client}->work_until_done(); +} + +sub pushUserGradesOnSubmit { + my ($self, $user_id, $set_id) = @_; + my $job = TheSchwartz::Job->new( + funcname => 'DelayedJob::PushUserGrades', + priority => 100, + arg => { + courseName => $self->{ce}->{courseName}, + user_id => $user_id, + set_id => $set_id + }, + ); + $self->{client}->insert($job); +} + +sub pushClassGrades { + my ($self) = @_; + my $job = TheSchwartz::Job->new( + funcname => 'DelayedJob::PushClassGrades', + priority => 100, + arg => { + courseName => $self->{ce}->{courseName} + }, + ); + $self->{client}->insert($job); +} + +sub getClassMembership { + my ($self) = @_; + my $job = TheSchwartz::Job->new( + funcname => 'DelayedJob::GetClassMembership', + priority => 50, + arg => { + courseName => $self->{ce}->{courseName} + }, + ); + $self->{client}->insert($job); +} + +sub sendEvents { + my ($self, $array_of_events) = @_; + my $job = TheSchwartz::Job->new( + funcname => 'DelayedJob::SendCaliperEvent', + priority => 0, + arg => { + courseName => $self->{ce}->{courseName}, + array_of_events => $array_of_events + }, + ); + debug("Delayed Job sendEvents"); + debug(Dumper($job)); + $self->{client}->insert($job); +} + +1; diff --git a/lib/DelayedJob/Worker.pm b/lib/DelayedJob/Worker.pm new file mode 100644 index 0000000000..444f47b63d --- /dev/null +++ b/lib/DelayedJob/Worker.pm @@ -0,0 +1,21 @@ +package DelayedJob::Worker; +use base qw( TheSchwartz::Worker ); + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::Debug; +use Data::Dumper; + +sub max_retries { + return 3; +} + +sub retry_delay { + my $class = shift; + my $num_failures = shift; + + return $num_failures * 30; +} + +1; diff --git a/lib/WebworkBridge/lti_update_classlist.pl b/lib/LTIAdvantage/Cron/lti_update_classlist.pl similarity index 63% rename from lib/WebworkBridge/lti_update_classlist.pl rename to lib/LTIAdvantage/Cron/lti_update_classlist.pl index d570e7aae3..9ae69ff951 100755 --- a/lib/WebworkBridge/lti_update_classlist.pl +++ b/lib/LTIAdvantage/Cron/lti_update_classlist.pl @@ -2,7 +2,7 @@ =head1 NAME -lti_update_classlist - Will try to update all courses with automatic_updates enabled +lti_update_classlist =head1 SYNOPSIS @@ -17,8 +17,7 @@ =head1 SYNOPSIS BEGIN { - die "WEBWORK_ROOT not found in environment.\n" - unless exists $ENV{WEBWORK_ROOT}; + die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; } use lib "$ENV{WEBWORK_ROOT}/lib"; @@ -28,8 +27,9 @@ BEGIN use WeBWorK::Debug; use Data::Dumper; use WeBWorK::DB; -use WeBWorK::Authen::LTIAdvantage::NamesAndRoleService; -use WebworkBridge::Importer::CourseUpdater; +use LTIAdvantage::Service::NamesAndRoleService; +use LTIAdvantage::Importer::CourseUpdater; +use DelayedJob::Service; my $man = 0; my $help = 0; @@ -55,7 +55,7 @@ BEGIN # LTI Update -my @lti_contexts = $db->getAllLTIContextsByAutomaticUpdates(1); +my @lti_contexts = $db->getAllLTIContexts(); # get unique list of course ids from lti_contexts my $course_hash = {}; @@ -79,16 +79,26 @@ BEGIN }); my $tmp_db = new WeBWorK::DB($tmp_ce->{dbLayout}); + # skip grade sync if disabled for course + if (!$tmp_ce->{lti_advantage}{cron_roster_sync}) { + next; + } + eval { - my $names_and_roles_service = WeBWorK::Authen::LTIAdvantage::NamesAndRoleService->new($tmp_ce, $tmp_db); - my $membership = $names_and_roles_service->getAllNamesAndRole(); - unless ($membership) { - return "There was an issue fetching the class roster. ".$names_and_roles_service->{error}; - } - my $updater = WebworkBridge::Importer::CourseUpdater->new($tmp_ce, $tmp_db, $membership); - my $ret = $updater->updateCourse(); - if ($ret) { - die "Update Class Roster failed: $ret"; + if ($tmp_ce->{delayed_job}{enabled}) { + my $delayed_job_service = DelayedJob::Service->new($tmp_ce); + $delayed_job_service->getClassMembership(); + } else { + my $names_and_roles_service = LTIAdvantage::Service::NamesAndRoleService->new($tmp_ce, $tmp_db); + my $membership = $names_and_roles_service->getAllNamesAndRole(); + unless ($membership) { + return "There was an issue fetching the class roster. ".$names_and_roles_service->{error}; + } + my $updater = LTIAdvantage::Importer::CourseUpdater->new($tmp_ce, $tmp_db, $membership); + my $ret = $updater->updateCourse(); + if ($ret) { + die "Update Class Roster failed: $ret"; + } } }; if ($@) { diff --git a/lib/WebworkBridge/lti_update_grades.pl b/lib/LTIAdvantage/Cron/lti_update_grades.pl similarity index 60% rename from lib/WebworkBridge/lti_update_grades.pl rename to lib/LTIAdvantage/Cron/lti_update_grades.pl index 4af90e1f57..b96d5ccb1c 100755 --- a/lib/WebworkBridge/lti_update_grades.pl +++ b/lib/LTIAdvantage/Cron/lti_update_grades.pl @@ -2,7 +2,7 @@ =head1 NAME -lti_update_classlist - Will try to update all courses with automatic_updates enabled +lti_update_classlist =head1 SYNOPSIS @@ -17,8 +17,7 @@ =head1 SYNOPSIS BEGIN { - die "WEBWORK_ROOT not found in environment.\n" - unless exists $ENV{WEBWORK_ROOT}; + die "WEBWORK_ROOT not found in environment.\n" unless exists $ENV{WEBWORK_ROOT}; } use lib "$ENV{WEBWORK_ROOT}/lib"; @@ -28,7 +27,8 @@ BEGIN use WeBWorK::Debug; use Data::Dumper; use WeBWorK::DB; -use WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +use LTIAdvantage::Service::AssignmentAndGradeService; +use DelayedJob::Service; my $man = 0; my $help = 0; @@ -48,7 +48,7 @@ BEGIN # LTI Update -my @lti_contexts = $db->getAllLTIContextsByAutomaticUpdates(1); +my @lti_contexts = $db->getAllLTIContexts(); # get unique list of course ids from lti_contexts my $course_hash = {}; @@ -68,11 +68,21 @@ BEGIN }); my $tmp_db = new WeBWorK::DB($tmp_ce->{dbLayout}); + # skip grade sync if disabled for course + if (!$tmp_ce->{lti_advantage}{cron_grade_sync}) { + next; + } + eval { - my $assignment_and_grade_service = WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService->new($tmp_ce, $tmp_db); - $assignment_and_grade_service->pushAllAssignmentGrades(); - if ($assignment_and_grade_service->{error}) { - die "There was an issue updating class grades. ".$assignment_and_grade_service->{error}; + if ($tmp_ce->{delayed_job}{enabled}) { + my $delayed_job_service = DelayedJob::Service->new($tmp_ce); + $delayed_job_service->pushClassGrades(); + } else { + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($tmp_ce, $tmp_db); + $assignment_and_grade_service->pushAllAssignmentGrades(); + if ($assignment_and_grade_service->{error}) { + die "There was an issue updating class grades. ".$assignment_and_grade_service->{error}; + } } }; if ($@) { diff --git a/lib/WebworkBridge/Bridge.pm b/lib/LTIAdvantage/Entrypoint.pm similarity index 77% rename from lib/WebworkBridge/Bridge.pm rename to lib/LTIAdvantage/Entrypoint.pm index 018fe9401a..dcb78b2988 100644 --- a/lib/WebworkBridge/Bridge.pm +++ b/lib/LTIAdvantage/Entrypoint.pm @@ -1,4 +1,4 @@ -package WebworkBridge::Bridge; +package LTIAdvantage::Entrypoint; ##### Library Imports ##### use strict; @@ -7,9 +7,9 @@ use WeBWorK::CourseEnvironment; use WeBWorK::DB; use WeBWorK::Debug; -use WebworkBridge::Importer::Error; -use WebworkBridge::Importer::CourseCreator; -use WebworkBridge::Importer::CourseUpdater; +use LTIAdvantage::Importer::Error; +use LTIAdvantage::Importer::CourseCreator; +use LTIAdvantage::Importer::CourseUpdater; # Constructor sub new @@ -48,7 +48,7 @@ sub useAuthenModule sub getErrorDisplayModule { my $self = shift; - return "WeBWorK::ContentGenerator::WebworkBridgeStatus"; + return "WeBWorK::ContentGenerator::LTIAdvantageStatus"; } sub getAuthenModule @@ -70,7 +70,7 @@ sub createCourse my $ce = $r->ce; my $db = $r->db; - my $creator = WebworkBridge::Importer::CourseCreator->new($ce, $db, $courseID, $courseTitle); + my $creator = LTIAdvantage::Importer::CourseCreator->new($ce, $db, $courseID, $courseTitle); my $ret = $creator->createCourse(); if ($ret) { @@ -84,7 +84,7 @@ sub updateCourse { my ($self, $ce, $db, $users) = @_; - my $creator = WebworkBridge::Importer::CourseUpdater->new($ce, $db, $users); + my $creator = LTIAdvantage::Importer::CourseUpdater->new($ce, $db, $users); my $ret = $creator->updateCourse(); if ($ret) { diff --git a/lib/WebworkBridge/Bridges/LTILaunchBridge.pm b/lib/LTIAdvantage/Entrypoint/Launch.pm similarity index 78% rename from lib/WebworkBridge/Bridges/LTILaunchBridge.pm rename to lib/LTIAdvantage/Entrypoint/Launch.pm index 0ab26c9478..5fe25e2e4d 100644 --- a/lib/WebworkBridge/Bridges/LTILaunchBridge.pm +++ b/lib/LTIAdvantage/Entrypoint/Launch.pm @@ -1,5 +1,5 @@ -package WebworkBridge::Bridges::LTILaunchBridge; -use base qw(WebworkBridge::Bridge); +package LTIAdvantage::Entrypoint::Launch; +use base qw(LTIAdvantage::Entrypoint); ##### Library Imports ##### use strict; @@ -13,12 +13,11 @@ use WeBWorK::CourseEnvironment; use WeBWorK::DB; use WeBWorK::Debug; -use WebworkBridge::Importer::Error; -use WeBWorK::Authen::LTIAdvantage::LTILaunchParser; +use LTIAdvantage::Importer::Error; +use LTIAdvantage::Parser::LaunchParser; use WeBWorK::Authen::LTIAdvantage; -use WeBWorK::Authen::LTIAdvantage::NamesAndRoleService; -use WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +use LTIAdvantage::Service::NamesAndRoleService; #$WeBWorK::Debug::Enabled = 1; @@ -28,7 +27,7 @@ sub new my ($class, $r) = @_; my $self = $class->SUPER::new($r); my $ce = $r->ce; - $self->{parser} = WeBWorK::Authen::LTIAdvantage::LTILaunchParser->new($ce, $r->param("id_token")); + $self->{parser} = LTIAdvantage::Parser::LaunchParser->new($ce, $r->param("id_token")); bless $self, $class; return $self; } @@ -153,16 +152,8 @@ sub run $redir .= "?lti=1"; if (-e $tmpce->{courseDirs}->{root}) { # course exists + $self->_updateLTISettings(); $self->_updateLaunchUser(); - - $ret = $self->updateCourse(); - if ($ret) { - debug("updateCourse error: ". $ret); - my $error_message = CGI::h2("LTI Launch Failed"); - $error_message .= CGI::p("Unfortunately, the LTI launch failed. This might be a temporary condition. If it persists, please mail an error report with the time that the error occured and the exact error message below:"); - $error_message .= CGI::div({class=>"ResultsWithError"}, CGI::pre($ret) ); - return $error_message; - } } else { # course does not exist debug("Course does not exist, try LTI import."); @@ -229,60 +220,6 @@ sub createCourse return 0; } -sub updateCourse -{ - my $self = shift; - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - my $parser = $self->{parser}; - - # store LTI credentials for auto-update - $self->_updateLTISettings(); - - debug("Checking to see if we can update the course."); - # check roles to see if we can run update - if (!defined($parser->get_claim("roles"))) { - return error("LTI launch missing roles, NOT updating course.", "#e025"); - } - - my @roles = @{$parser->get_claim("roles")}; - my $allowedUpdate = 0; - foreach my $role (@roles) { - foreach my $update_role (@{$ce->{bridge}{roles_can_update}}) { - if ($update_role eq $role) { - debug("Role $role allowed to update course."); - $allowedUpdate = 1; - last; - } - } - last if ($allowedUpdate); - } - - if (!$allowedUpdate) { - debug("User not allowed to update course."); - return 0; - } - - my $ret = undef; - - # try to update roster if names and role service enabled - $ret = $self->_updateClassRoster(); - if ($ret) { - return $ret; - } - - # try to push out grades back to the LMS - if (defined($parser->get_claim_param("custom", "gradesync"))) { - $ret = $self->_updateClassGrades(); - if ($ret) { - return $ret; - } - } - - return 0; -} - sub _updateLTISettings() { my $self = shift; @@ -304,9 +241,8 @@ sub _updateLTISettings() $lti_context = $db->newLTIContext( client_id => $client_id, context_id => $context_id, - # only set course_title and automatic_updates are only set up new lti contexts - course_id => $ce->{courseName}, - automatic_updates => 1 + # course_title is only set up new for lti contexts + course_id => $ce->{courseName} ); } @@ -388,7 +324,7 @@ sub _updateLaunchUser() debug(Dumper(\%user)); - my $updater = WebworkBridge::Importer::CourseUpdater->new($ce, $db, ''); + my $updater = LTIAdvantage::Importer::CourseUpdater->new($ce, $db, ''); # check if user exists if ($db->existsUser($user{'loginid'})) { debug("Attempt to update user & assign assignments."); @@ -417,7 +353,7 @@ sub _updateClassRoster() # try to update course enrolment if ($parser->get_nrps_claim()) { - my $names_and_roles_service = WeBWorK::Authen::LTIAdvantage::NamesAndRoleService->new($ce, $db); + my $names_and_roles_service = LTIAdvantage::Service::NamesAndRoleService->new($ce, $db); my $membership = $names_and_roles_service->getAllNamesAndRole(); unless ($membership) { debug("There was an issue fetching the class roster. ".$names_and_roles_service->{error}); @@ -433,30 +369,6 @@ sub _updateClassRoster() return 0; } -sub _updateClassGrades() -{ - my $self = shift; - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - my $parser = $self->{parser}; - - debug("Update class assignment grades."); - - # try to update course enrolment - if ($parser->get_ags_claim()) { - my $assignment_and_grade_service = WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService->new($ce, $db); - $assignment_and_grade_service->pushAllAssignmentGrades(); - if ($assignment_and_grade_service->{error}) { - debug("There was an issue updating class grades. ".$assignment_and_grade_service->{error}); - return error("There was an issue updating class grades. ".$assignment_and_grade_service->{error}, "#e017"); - } - } - - debug("Done."); - return 0; -} - sub _verifyMessage() { my $self = shift; diff --git a/lib/WebworkBridge/Bridges/LTILoginBridge.pm b/lib/LTIAdvantage/Entrypoint/Login.pm similarity index 82% rename from lib/WebworkBridge/Bridges/LTILoginBridge.pm rename to lib/LTIAdvantage/Entrypoint/Login.pm index 82673a36db..cb4133d4ed 100644 --- a/lib/WebworkBridge/Bridges/LTILoginBridge.pm +++ b/lib/LTIAdvantage/Entrypoint/Login.pm @@ -1,5 +1,5 @@ -package WebworkBridge::Bridges::LTILoginBridge; -use base qw(WebworkBridge::Bridge); +package LTIAdvantage::Entrypoint::Login; +use base qw(LTIAdvantage::Entrypoint); ##### Library Imports ##### use strict; @@ -15,12 +15,8 @@ use WeBWorK::CourseEnvironment; use WeBWorK::DB; use WeBWorK::Debug; -use WebworkBridge::Importer::Error; -use WeBWorK::Authen::LTIAdvantage::LTILaunchParser; - -use WeBWorK::Authen::LTIAdvantage; -use WeBWorK::Authen::LTIAdvantage::NamesAndRoleService; -use WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +use LTIAdvantage::Importer::Error; +use LTIAdvantage::Parser::LaunchParser; # Constructor sub new @@ -28,7 +24,7 @@ sub new my ($class, $r) = @_; my $self = $class->SUPER::new($r); my $ce = $r->ce; - $self->{parser} = WeBWorK::Authen::LTIAdvantage::LTILaunchParser->new($ce, $r->param("id_token")); + $self->{parser} = LTIAdvantage::Parser::LaunchParser->new($ce, $r->param("id_token")); bless $self, $class; return $self; } @@ -84,12 +80,12 @@ sub run ); my $oidc_auth_url = ""; - foreach my $client_id (keys %{$ce->{bridge}{lti_clients}}) { - if (defined($ce->{bridge}{lti_clients}{$client_id}{platform_id}) && - defined($ce->{bridge}{lti_clients}{$client_id}{oidc_auth_url}) && - $ce->{bridge}{lti_clients}{$client_id}{platform_id} eq $platform_id) + foreach my $client_id (keys %{$ce->{lti_advantage}{lti_clients}}) { + if (defined($ce->{lti_advantage}{lti_clients}{$client_id}{platform_id}) && + defined($ce->{lti_advantage}{lti_clients}{$client_id}{oidc_auth_url}) && + $ce->{lti_advantage}{lti_clients}{$client_id}{platform_id} eq $platform_id) { - $oidc_auth_url = $ce->{bridge}{lti_clients}{$client_id}{oidc_auth_url}; + $oidc_auth_url = $ce->{lti_advantage}{lti_clients}{$client_id}{oidc_auth_url}; debug("oidc_auth_url is $oidc_auth_url for platform $platform_id with client_id $client_id."); last; } diff --git a/lib/LTIAdvantage/EntrypointManager.pm b/lib/LTIAdvantage/EntrypointManager.pm new file mode 100644 index 0000000000..4f5e1d00c1 --- /dev/null +++ b/lib/LTIAdvantage/EntrypointManager.pm @@ -0,0 +1,92 @@ +package LTIAdvantage::EntrypointManager; + +##### Library Imports ##### +use strict; +use warnings; +use WeBWorK::CourseEnvironment; +use WeBWorK::DB; +use WeBWorK::Debug; +use WeBWorK::Utils qw(runtime_use); + +use LTIAdvantage::Importer::Error; + +# Constructor +sub new +{ + my ($class, $r) = @_; + my $self = { + r => $r, + entrypoint => undef + }; + bless $self, $class; + return $self; +} + +sub run +{ + my ($self) = @_; + my $r = $self->{r}; + + debug("Importer running."); + + my @entrypoints = ( + "LTIAdvantage::Entrypoint::Launch", + "LTIAdvantage::Entrypoint::Login", + ); + + # find a compatible entrypoint + my $entrypoint; + foreach (@entrypoints) + { + debug("Testing entrypoint $_ for compatibility."); + runtime_use($_); + $entrypoint = $_->new($r); + last if ($entrypoint->accept()); + } + + if ($entrypoint->accept()) + { + debug("Compatible entrypoint found!"); + $self->{entrypoint} = $entrypoint; + return $entrypoint->run(); + } + # could've ended the loop without finding a compatible entrypoint + return 0; +} + +sub useAuthenModule +{ + my ($self) = @_; + my $entrypoint = $self->{entrypoint}; + return $entrypoint ? $entrypoint->useAuthenModule() : ""; +} + +sub getAuthenModule +{ + my ($self) = @_; + my $entrypoint = $self->{entrypoint}; + return $entrypoint ? $entrypoint->getAuthenModule() : ""; +} + +sub getErrorDisplayModule +{ + my ($self) = @_; + my $entrypoint = $self->{entrypoint}; + return $entrypoint ? $entrypoint->getErrorDisplayModule() : ""; +} + +sub useRedirect +{ + my ($self) = @_; + my $entrypoint = $self->{entrypoint}; + return $entrypoint ? $entrypoint->useRedirect() : ""; +} + +sub getRedirect +{ + my ($self) = @_; + my $entrypoint = $self->{entrypoint}; + return $entrypoint ? $entrypoint->getRedirect() : ""; +} + +1; diff --git a/lib/WebworkBridge/ExtraLog.pm b/lib/LTIAdvantage/ExtraLog.pm similarity index 97% rename from lib/WebworkBridge/ExtraLog.pm rename to lib/LTIAdvantage/ExtraLog.pm index 13747aa938..27d67e22db 100644 --- a/lib/WebworkBridge/ExtraLog.pm +++ b/lib/LTIAdvantage/ExtraLog.pm @@ -1,4 +1,4 @@ -package WebworkBridge::ExtraLog; +package LTIAdvantage::ExtraLog; # This is additional debug logging separate from the normal debug. # Meant for use on production where turning on the normal debug diff --git a/lib/WebworkBridge/Importer/CourseCreator.pm b/lib/LTIAdvantage/Importer/CourseCreator.pm similarity index 89% rename from lib/WebworkBridge/Importer/CourseCreator.pm rename to lib/LTIAdvantage/Importer/CourseCreator.pm index 0762ac8dae..0210768a7f 100644 --- a/lib/WebworkBridge/Importer/CourseCreator.pm +++ b/lib/LTIAdvantage/Importer/CourseCreator.pm @@ -1,4 +1,4 @@ -package WebworkBridge::Importer::CourseCreator; +package LTIAdvantage::Importer::CourseCreator; ##### Library Imports ##### use strict; @@ -12,7 +12,7 @@ use Data::Dumper; use WeBWorK::Utils qw(cryptPassword); use WeBWorK::Utils::CourseManagement qw(addCourse); -use WebworkBridge::Importer::Error; +use LTIAdvantage::Importer::Error; use Text::CSV; @@ -47,9 +47,9 @@ sub createCourse my %courseOptions = ( dbLayoutName => $ce2->{dbLayoutName} ); my %dbOptions; my %optional_arguments; - if ($ce->{bridge}{course_template}) + if ($ce->{lti_advantage}{course_template}) { - $optional_arguments{templatesFrom} = $ce->{bridge}{course_template}; + $optional_arguments{templatesFrom} = $ce->{lti_advantage}{course_template}; } if ($courseTitle ne "") { $optional_arguments{courseTitle} = $courseTitle; @@ -66,7 +66,7 @@ sub createCourse ); my $AdminPassword = $db->newPassword( user_id => "admin", - password => cryptPassword($ce->{bridge}{adminuserpw}), + password => cryptPassword($ce->{lti_advantage}{adminuserpw}), ); my $AdminPermissionLevel = $db->newPermissionLevel( user_id => "admin", @@ -99,7 +99,7 @@ sub createCourse return error("Add course failed, failure: $error","#e018"); } - if ($ce->{bridge}{hide_new_courses}) { + if ($ce->{lti_advantage}{hide_new_courses}) { my $message = 'Place a file named "hide_directory" in a course or other directory '. 'and it will not show up in the courses list on the WeBWorK home page. '. 'It will still appear in the Course Administration listing.'; diff --git a/lib/WebworkBridge/Importer/CourseUpdater.pm b/lib/LTIAdvantage/Importer/CourseUpdater.pm similarity index 89% rename from lib/WebworkBridge/Importer/CourseUpdater.pm rename to lib/LTIAdvantage/Importer/CourseUpdater.pm index 3bb5490535..8d5523857a 100644 --- a/lib/WebworkBridge/Importer/CourseUpdater.pm +++ b/lib/LTIAdvantage/Importer/CourseUpdater.pm @@ -1,4 +1,4 @@ -package WebworkBridge::Importer::CourseUpdater; +package LTIAdvantage::Importer::CourseUpdater; ##### Library Imports ##### use strict; @@ -193,7 +193,7 @@ sub updateUser } # assign all visible homeworks to user - $self->assignAllVisibleSetsToUser($id, $db); + $self->assignAllVisibleSetsToUser($id); if (defined($newInfo->{'client_id'}) && defined($newInfo->{'lti_user_id'}) ) { $self->addOrUpdateLTIUser($newInfo); @@ -259,7 +259,7 @@ sub addUser } # assign all visible homeworks to user - $self->assignAllVisibleSetsToUser($id, $db); + $self->assignAllVisibleSetsToUser($id); if (defined($new_user_info->{'client_id'}) && defined($new_user_info->{'lti_user_id'}) ) { $self->addOrUpdateLTIUser($new_user_info); @@ -305,7 +305,7 @@ sub addlog $msg = "[$date] $msg\n"; - my $logfile = $self->{ce}->{bridge}{studentlog}; + my $logfile = $self->{ce}->{lti_advantage}{studentlog}; if ($logfile ne "") { if (open my $f, ">>", $logfile) { print $f $msg; @@ -321,51 +321,44 @@ sub addlog # Taken from assignAllSetsToUser() in WeBWorK::ContentGenerator::Instructor sub assignAllVisibleSetsToUser { - my ($self, $userID, $db) = @_; + my ($self, $userID) = @_; + my $ce = $self->{ce}; + my $db = $self->{db}; # skip automatically assigning homeworksets if disabled for course - my $ltiAutoAssignHomeworksets = $db->getSettingValue('skipLTIAutomaticAssignHomeworksets'); - if (defined($ltiAutoAssignHomeworksets) && $ltiAutoAssignHomeworksets eq "1") { + if (!$ce->{lti_advantage}{auto_assign_users_to_sets}) { return; } my @globalSetIDs = $db->listGlobalSets; my @GlobalSets = $db->getGlobalSets(@globalSetIDs); - my @results; - my $i = 0; foreach my $GlobalSet (@GlobalSets) { if (not defined $GlobalSet) { - warn "record not found for global set $globalSetIDs[$i]"; - } - elsif ($GlobalSet->visible) { - my @result = $self->assignSetToUser($userID, $GlobalSet, $db); - push @results, @result if @result; + debug("record not found for global set $globalSetIDs[$i]"); + } elsif ($GlobalSet->visible) { + $self->assignSetToUser($userID, $GlobalSet); } $i++; } - - return @results; } # Taken and modified from WeBWorK::ContentGenerator::Instructor sub assignSetToUser { - my ($self, $userID, $GlobalSet, $db) = @_; + my ($self, $userID, $GlobalSet) = @_; + my $db = $self->{db}; + my $setID = $GlobalSet->set_id; my $UserSet = $db->newUserSet; $UserSet->user_id($userID); $UserSet->set_id($setID); - my @results; - my $set_assigned = 0; - eval { $db->addUserSet($UserSet) }; if ($@) { if ($@ =~ m/user set exists/) { - push @results, "set $setID is already assigned to user $userID."; - $set_assigned = 1; + debug("set $setID is already assigned to user $userID."); } else { die $@; } @@ -373,16 +366,14 @@ sub assignSetToUser { my @GlobalProblems = grep { defined $_ } $db->getAllGlobalProblems($setID); foreach my $GlobalProblem (@GlobalProblems) { - my @result = $self->assignProblemToUser($userID, $GlobalProblem, $db); - push @results, @result if @result and not $set_assigned; + $self->assignProblemToUser($userID, $GlobalProblem); } - - return @results; } # Taken and modified from WeBWorK::ContentGenerator::Instructor sub assignProblemToUser { - my ($self, $userID, $GlobalProblem, $db) = @_; + my ($self, $userID, $GlobalProblem) = @_; + my $db = $self->{db}; my $UserProblem = $db->newUserProblem; $UserProblem->user_id($userID); @@ -394,15 +385,11 @@ sub assignProblemToUser { eval { $db->addUserProblem($UserProblem) }; if ($@) { if ($@ =~ m/user problem exists/) { - return "problem " . $GlobalProblem->problem_id - . " in set " . $GlobalProblem->set_id - . " is already assigned to user $userID."; + debug("problem " . $GlobalProblem->problem_id. " in set " . $GlobalProblem->set_id. " is already assigned to user $userID."); } else { die $@; } } - - return (); } 1; diff --git a/lib/WebworkBridge/Importer/Error.pm b/lib/LTIAdvantage/Importer/Error.pm similarity index 92% rename from lib/WebworkBridge/Importer/Error.pm rename to lib/LTIAdvantage/Importer/Error.pm index 1c0c7707a3..e7aad336fe 100644 --- a/lib/WebworkBridge/Importer/Error.pm +++ b/lib/LTIAdvantage/Importer/Error.pm @@ -3,7 +3,7 @@ use strict; use warnings; ##### Module Creation ##### -package WebworkBridge::Importer::Error; +package LTIAdvantage::Importer::Error; use Exporter; our @ISA = ('Exporter'); our @EXPORT = ('error'); diff --git a/lib/WeBWorK/Authen/LTIAdvantage/LTILaunchParser.pm b/lib/LTIAdvantage/Parser/LaunchParser.pm similarity index 93% rename from lib/WeBWorK/Authen/LTIAdvantage/LTILaunchParser.pm rename to lib/LTIAdvantage/Parser/LaunchParser.pm index 0162c05462..a0456e0fee 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/LTILaunchParser.pm +++ b/lib/LTIAdvantage/Parser/LaunchParser.pm @@ -1,4 +1,4 @@ -package WeBWorK::Authen::LTIAdvantage::LTILaunchParser; +package LTIAdvantage::Parser::LaunchParser; use strict; use warnings; @@ -40,8 +40,8 @@ sub getCourseName my $course_title; # Allow sites to customize the user - if (defined($ce->{bridge}{custom_course_title_parser})) { - $course_title = $ce->{bridge}{custom_course_title_parser}($self); + if (defined($ce->{lti_advantage}{custom_course_title_parser})) { + $course_title = $ce->{lti_advantage}{custom_course_title_parser}($self); } else { # default to context_id if custom_course_title_parser is not defined $course_title = $self->get_claim_param("context", "id"); @@ -201,8 +201,8 @@ sub get_user_identifier { my $client_id = $self->get_param("aud"); my $data_ref = $self->{data}; - if (exists($ce->{bridge}{lti_clients}{$client_id}{user_identifier_field})) { - my $user_identifier_field = $ce->{bridge}{lti_clients}{$client_id}{user_identifier_field}; + if (exists($ce->{lti_advantage}{lti_clients}{$client_id}{user_identifier_field})) { + my $user_identifier_field = $ce->{lti_advantage}{lti_clients}{$client_id}{user_identifier_field}; my @user_identifier_parts = split(/\|/, $user_identifier_field); foreach my $user_identifier_part (@user_identifier_parts) { @@ -231,8 +231,8 @@ sub get_student_number { my $client_id = $self->get_param("aud"); my $data_ref = $self->{data}; - if (exists($ce->{bridge}{lti_clients}{$client_id}{user_student_number_field})) { - my $student_number_field = $ce->{bridge}{lti_clients}{$client_id}{user_student_number_field}; + if (exists($ce->{lti_advantage}{lti_clients}{$client_id}{user_student_number_field})) { + my $student_number_field = $ce->{lti_advantage}{lti_clients}{$client_id}{user_student_number_field}; my @student_number_parts = split(/\|/, $student_number_field); foreach my $student_number_part (@student_number_parts) { diff --git a/lib/WeBWorK/Authen/LTIAdvantage/LTINamesAndRoleServiceParser.pm b/lib/LTIAdvantage/Parser/NamesAndRoleServiceParser.pm similarity index 92% rename from lib/WeBWorK/Authen/LTIAdvantage/LTINamesAndRoleServiceParser.pm rename to lib/LTIAdvantage/Parser/NamesAndRoleServiceParser.pm index 9bb850b36e..946a9bd5cc 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/LTINamesAndRoleServiceParser.pm +++ b/lib/LTIAdvantage/Parser/NamesAndRoleServiceParser.pm @@ -1,4 +1,4 @@ -package WeBWorK::Authen::LTIAdvantage::LTINamesAndRoleServiceParser; +package LTIAdvantage::Parser::NamesAndRoleServiceParser; use strict; use warnings; @@ -79,8 +79,8 @@ sub get_user_identifier { my $client_id = $self->{client_id}; - if (exists($ce->{bridge}{lti_clients}{$client_id}{user_identifier_field})) { - my $user_identifier_field = $ce->{bridge}{lti_clients}{$client_id}{user_identifier_field}; + if (exists($ce->{lti_advantage}{lti_clients}{$client_id}{user_identifier_field})) { + my $user_identifier_field = $ce->{lti_advantage}{lti_clients}{$client_id}{user_identifier_field}; my @user_identifier_parts = split(/\|/, $user_identifier_field); # check general member fields @@ -122,8 +122,8 @@ sub get_student_number { my $client_id = $self->{client_id}; - if (exists($ce->{bridge}{lti_clients}{$client_id}{user_student_number_field})) { - my $student_number_field = $ce->{bridge}{lti_clients}{$client_id}{user_student_number_field}; + if (exists($ce->{lti_advantage}{lti_clients}{$client_id}{user_student_number_field})) { + my $student_number_field = $ce->{lti_advantage}{lti_clients}{$client_id}{user_student_number_field}; my @student_number_parts = split(/\|/, $student_number_field); # check general member fields diff --git a/lib/WeBWorK/Authen/LTIAdvantage/AccessTokenRequest.pm b/lib/LTIAdvantage/Service/AccessTokenRequest.pm similarity index 93% rename from lib/WeBWorK/Authen/LTIAdvantage/AccessTokenRequest.pm rename to lib/LTIAdvantage/Service/AccessTokenRequest.pm index 1664fdd369..f1e25ef725 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/AccessTokenRequest.pm +++ b/lib/LTIAdvantage/Service/AccessTokenRequest.pm @@ -13,11 +13,11 @@ # Artistic License for more details. ################################################################################ -package WeBWorK::Authen::LTIAdvantage::AccessTokenRequest; +package LTIAdvantage::Service::AccessTokenRequest; =head1 NAME -WeBWorK::Authen::LTIAdvantage::AccessToken:: +LTIAdvantage::Service::AccessTokenRequest =cut @@ -42,7 +42,7 @@ use WeBWorK::CourseEnvironment; use WeBWorK::DB; use WeBWorK::Debug; use Data::Dumper; -use WebworkBridge::ExtraLog; +use LTIAdvantage::ExtraLog; # This package contains utilities for retrieving content membership from the LMS sub new { @@ -116,9 +116,9 @@ sub getAccessToken { my $scopes = $self->{scopes}; my $ce = $self->{ce}; - my $extralog = WebworkBridge::ExtraLog->new($ce); + my $extralog = LTIAdvantage::ExtraLog->new($ce); - if (!defined($ce->{bridge}{lti_clients}{$client_id})) + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id})) { $self->{error} = "Unknown client_id '$client_id'. "; $extralog->logAccessTokenRequest($self->{error}); @@ -134,8 +134,8 @@ sub getAccessToken { return $cached_access_token; } - my $access_token_url = $ce->{bridge}{lti_clients}{$client_id}{oauth2_access_token_url}; - my $tool_private_key = $ce->{bridge}{lti_clients}{$client_id}{tool_private_key}; + my $access_token_url = $ce->{lti_advantage}{lti_clients}{$client_id}{oauth2_access_token_url}; + my $tool_private_key = $ce->{lti_advantage}{lti_clients}{$client_id}{tool_private_key}; $extralog->logAccessTokenRequest("Requesting LTI Access Token for client: $client_id on scopes: $scopes"); debug("Requesting LTI Access Token for client: $client_id on scopes: $scopes"); diff --git a/lib/WeBWorK/Authen/LTIAdvantage/AssignmentAndGradeService.pm b/lib/LTIAdvantage/Service/AssignmentAndGradeService.pm similarity index 98% rename from lib/WeBWorK/Authen/LTIAdvantage/AssignmentAndGradeService.pm rename to lib/LTIAdvantage/Service/AssignmentAndGradeService.pm index 933a850ecf..60787ae244 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/AssignmentAndGradeService.pm +++ b/lib/LTIAdvantage/Service/AssignmentAndGradeService.pm @@ -13,11 +13,11 @@ # Artistic License for more details. ################################################################################ -package WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +package LTIAdvantage::Service::AssignmentAndGradeService; =head1 NAME -WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService +LTIAdvantage::Service::AssignmentAndGradeService =cut @@ -36,8 +36,8 @@ use JSON; use HTTP::Request::Common; use HTTP::Async; -use WeBWorK::Authen::LTIAdvantage::AccessTokenRequest; -use WebworkBridge::ExtraLog; +use LTIAdvantage::Service::AccessTokenRequest; +use LTIAdvantage::ExtraLog; #$WeBWorK::Debug::Enabled = 1; @@ -181,7 +181,7 @@ sub pushUserGradesOnSubmit { my $lti_assignment_and_grade_requests = $self->_generate_requests( \@lti_resource_links, \@lti_users, $user_grades); - # ensure there are lti rrequests to update + # ensure there are lti requests to update return if scalar(@{$lti_assignment_and_grade_requests}) == 0; $self->_performAssignmentAndGradeRequests($lti_assignment_and_grade_requests); @@ -250,7 +250,7 @@ sub _performAssignmentAndGradeRequests { my $ce = $self->{ce}; my $db = $self->{db}; - my $extralog = WebworkBridge::ExtraLog->new($ce); + my $extralog = LTIAdvantage::ExtraLog->new($ce); $extralog->logAGSRequest("Beginning LTI Assignment and Grade Service requests for Course: ".$ce->{courseName}); debug("Beginning LTI Assignment and Grade Service requests for Course: ".$ce->{courseName}); @@ -277,7 +277,7 @@ sub _performAssignmentAndGradeRequests { $scopes .= " https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"; } - my $lti_access_token_request = WeBWorK::Authen::LTIAdvantage::AccessTokenRequest->new($ce, $client_id, $scopes); + my $lti_access_token_request = LTIAdvantage::Service::AccessTokenRequest->new($ce, $client_id, $scopes); my $access_token = $lti_access_token_request->getAccessToken(); unless ($access_token) { $self->{error} = "Assignment and Grades Service request failed, unable to get an access token for scopes: $scopes"; diff --git a/lib/WeBWorK/Authen/LTIAdvantage/NamesAndRoleService.pm b/lib/LTIAdvantage/Service/NamesAndRoleService.pm similarity index 91% rename from lib/WeBWorK/Authen/LTIAdvantage/NamesAndRoleService.pm rename to lib/LTIAdvantage/Service/NamesAndRoleService.pm index 9c451365d8..27b88d7bf7 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage/NamesAndRoleService.pm +++ b/lib/LTIAdvantage/Service/NamesAndRoleService.pm @@ -13,11 +13,11 @@ # Artistic License for more details. ################################################################################ -package WeBWorK::Authen::LTIAdvantage::NamesAndRoleService; +package LTIAdvantage::Service::NamesAndRoleService; =head1 NAME -WeBWorK::Authen::LTIAdvantage::NamesAndRoleService +LTIAdvantage::Service::NamesAndRoleService =cut @@ -38,9 +38,9 @@ use WeBWorK::DB; use WeBWorK::Debug; use Data::Dumper; -use WeBWorK::Authen::LTIAdvantage::AccessTokenRequest; -use WebworkBridge::ExtraLog; -use WeBWorK::Authen::LTIAdvantage::LTINamesAndRoleServiceParser; +use LTIAdvantage::Service::AccessTokenRequest; +use LTIAdvantage::ExtraLog; +use LTIAdvantage::Parser::NamesAndRoleServiceParser; #$WeBWorK::Debug::Enabled = 1; @@ -63,7 +63,7 @@ sub getAllNamesAndRole { my $db = $self->{db}; my $course_id = $ce->{courseName}; - my $extralog = WebworkBridge::ExtraLog->new($ce); + my $extralog = LTIAdvantage::ExtraLog->new($ce); my @lti_contexts = $db->getLTIContextsByCourseID($course_id); my @lti_resource_links = $db->getAllValidLTIResourceLinks(); @@ -138,10 +138,10 @@ sub getNamesAndRole { my $ce = $self->{ce}; my $db = $self->{db}; - my $extralog = WebworkBridge::ExtraLog->new($ce); + my $extralog = LTIAdvantage::ExtraLog->new($ce); $extralog->logNRPSRequest("Beginning Names And Roles Service request for client: $client_id on context: $context_id with membership url: $context_memberships_url"); - if (!defined($ce->{bridge}{lti_clients}{$client_id})) + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id})) { $self->{error} = "Unknown client_id '$client_id'"; $extralog->logNRPSRequest($self->{error}); @@ -149,7 +149,7 @@ sub getNamesAndRole { } my $scopes = "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"; - my $lti_access_token_request = WeBWorK::Authen::LTIAdvantage::AccessTokenRequest->new($ce, $client_id, $scopes); + my $lti_access_token_request = LTIAdvantage::Service::AccessTokenRequest->new($ce, $client_id, $scopes); my $access_token = $lti_access_token_request->getAccessToken(); unless ($access_token) { $self->{error} = "Names And Roles Service request failed, unable to get an access token for scopes: $scopes"; @@ -182,7 +182,7 @@ sub getNamesAndRole { $extralog->logNRPSRequest("Names And Roles Service request successful: \n" . Dumper($data) . "\n"); # debug("Names And Roles Service request successful! \n" . Dumper($data). "\n"); - my $parser = WeBWorK::Authen::LTIAdvantage::LTINamesAndRoleServiceParser->new($client_id, $ce, $data); + my $parser = LTIAdvantage::Parser::NamesAndRoleServiceParser->new($client_id, $ce, $data); my @membership = $parser->get_members(); if (scalar(@membership) == 0) { diff --git a/lib/WeBWorK.pm b/lib/WeBWorK.pm index 627fc6468f..86bbbf12ba 100644 --- a/lib/WeBWorK.pm +++ b/lib/WeBWorK.pm @@ -55,7 +55,7 @@ use WeBWorK::URLPath; use WeBWorK::CGI; use WeBWorK::Utils qw(runtime_use writeTimingLogEntry); -use WebworkBridge::BridgeManager; +use LTIAdvantage::EntrypointManager; use mod_perl; @@ -302,16 +302,16 @@ sub dispatch($) { my $user_authen_module; - my $bridge = WebworkBridge::BridgeManager->new($r); - my $bridge_error = $bridge->run(); - if ($bridge->useAuthenModule()) { - $user_authen_module = $bridge->getAuthenModule(); - # refresh ce after running bridge ($ce might change to a different course environment when redirecting from webwork root) + my $entrypoint = LTIAdvantage::EntrypointManager->new($r); + my $entrypoint_error = $entrypoint->run(); + if ($entrypoint->useAuthenModule()) { + $user_authen_module = $entrypoint->getAuthenModule(); + # refresh ce after running entrypoint ($ce might change to a different course environment when redirecting from webwork root) $ce = $r->{ce}; } - if ($bridge_error) { - MP2 ? $r->notes->set(error_message => $bridge_error) : $r->notes('error_message' => $bridge_error); - $displayModule = $bridge->getErrorDisplayModule(); + if ($entrypoint_error) { + MP2 ? $r->notes->set(error_message => $entrypoint_error) : $r->notes('error_message' => $entrypoint_error); + $displayModule = $entrypoint->getErrorDisplayModule(); } if (!defined($user_authen_module)) { @@ -382,9 +382,9 @@ sub dispatch($) { } } - if ($bridge->useRedirect()) { + if ($entrypoint->useRedirect()) { my $q = CGI->new(); - print $q->redirect($bridge->getRedirect()); + print $q->redirect($entrypoint->getRedirect()); } } else { debug("Bad news: authentication failed!\n"); diff --git a/lib/WeBWorK/Authen/CWL/CWL.pm b/lib/WeBWorK/Authen/CWL/CWL.pm deleted file mode 100644 index b2421eeec3..0000000000 --- a/lib/WeBWorK/Authen/CWL/CWL.pm +++ /dev/null @@ -1,282 +0,0 @@ -package CWL; -use strict; - -use Carp; -use MIME::Base64; -use XMLRPC::Lite; -use WeBWorK::Debug; -use WeBWorK::Authen::CWL::Session; - -# Revision history: -# -# - Corrected max retries from 1 to 5 -# - Only retry on communication failure -# - -=head1 NAME - -CWL - Campus Wide Login Auth v2 Client - -=head1 SYNOPSIS - - # Get the login name of the user presenting a ticket - - use CWL; - - my $cwl = new CWL(service => 'webworks_psa', - password => '3ducat3m3', - url => 'https://www.auth.verf2.cwl.ubc.ca/auth/login', - ); - - my $session = $cwl->create_session($ticket); - - my $login_name = $session->get_login_name(); - -=head1 DESCRIPTION - -This module provides a connection to the CWL Auth 2 service. -Most of the CWL authtentication service API can be accessed -through the CWL::Session object that is returned by the -Lcreate_session($ticket)> -and -Lvalidate_session($ticket)> -methods. - -=head2 Methods - -The following methods are provided: - -=over - - - - -=item Bnew(%properties)> - -Create a CWL connection. - -The following keys are required in the properties hash: - -=over - -=item service - -The service account name. - -=item password - -The service account password. - -=item url - -The URL of the CWL authentication service. - -=back - -=cut - -sub new { - my $class = shift; - my $self = bless { @_ }; - - foreach my $property qw( service password url ) { - if (not defined $self->{$property}) { - croak qq[Required property '$property' not defined]; - } - } - - $self->{max_age} = 10; - $self->initialize_proxy(); - return $self; -} - - - - -=item B<$cwl-Ecreate_session($ticket)> - -Create a CWL::Session object from a session key without performing any checks. - -=item B<$cwl-Ecreate_session($username, $password)> - -Create a CWL::Session object directly from a CWL username and -password. Note: This is only to be used in special circumstances. -Most applications must obtain a session key through the login page. - -=cut - -sub create_session { - my ($self, @credentials) = @_; - my $session; - debug(@credentials); - if (scalar @credentials == 1) { - my ($ticket) = @credentials; - $session = fromTicket Session($self, $ticket); - } else { - my ($username, $password) = @credentials; - $session = fromLogin Session($self, $username, $password); - } - return $session; -} - - - - -=item B<$cwl-Ecreate_service_session( )> - -Create a CWL::Session object for the current application's service user. -This method is intended to support service to service authentication. -It returns a newly created session with the current service as its user. -This session can then be transferred to another service such as the -management API. - -=cut - -sub create_service_session { - my ($self) = @_; - my $ticket = $self->invoke('service.createServiceSession'); - return $self->create_session($ticket); -} - - - - -=item B<$cwl-Evalidate_session($ticket)> - -Create a CWL::Session object from a session key, checking its age. -See also -Lset_max_ticket_age($seconds)>. - -=cut - -sub validate_session { - my ($self, $ticket) = @_; - my $session = $self->create_session($ticket); - - my $max_age = $self->{max_age}; - my $age = $session->get_session_age(); - if ($age > $max_age) { - croak "StaleTicket: This session is older than $max_age seconds"; - } - - return $session; -} - - - - -=item B<$cwl-Eset_max_ticket_age($seconds)> - -Set the maximum age, in seconds, of ticket that this module is willing -to accept. The default is 10 seconds. - -=cut - -sub set_max_ticket_age { - my ($self, $seconds) = @_; - - my $set_seconds = ($seconds || 0) + 0; - if ($set_seconds > 0) { - $self->{max_age} = $set_seconds; - } else { - croak qq[Invalid (non-numerical or zero) max age specified: $seconds]; - } -} - - - - -=back - -=head2 Private Methods - -The following methods are used internally by this module and L. -You should not invoke these methods directly; rather, use one of the other -methods as described in the CWL API. - -=over - - - - -=item B<$cwl-Einvoke($method_name, @args)> - -B<(PRIVATE METHOD)> -Calls the RPC method and returns the result. - -=cut - -# private -sub invoke { - my ($self, $method_name, @args) = @_; - my $tries_left = 5; - CALL_ATTEMPT: while ($tries_left--) { - my $som; - debug($self->{proxy}); - eval { $som = $self->{proxy}->call($method_name, @args) }; - - if (ref $som and !$som->fault) { - return $som->result; - } - - if ($tries_left and not (defined $som and ref $som)) { - # Retry only for communication errors - sleep 1; - $self->initialize_proxy(); - next CALL_ATTEMPT; - } - - croak $@ if $@; - croak $som->faultstring if ref $som and $som->faultstring; - croak 'Unknown XMLRPC fault'; - } -} - - - - -=item B<$cwl-Einitialize_proxy( )> - -B<(PRIVATE METHOD)> -Prepares the RPC connection. - -=cut - -# private -sub initialize_proxy { - my ($self) = @_; - - my $proxy = XMLRPC::Lite->proxy($self->{url}, timeout => 60); - - # Authorization must be added manually because the httpd does not - # return a 401 result code with "WWW-Authenticate: Basic" header - # as required by RFC 1945. - my $auth = encode_base64($self->{service} . ':' . $self->{password}); - $proxy->transport->default_header('Authorization', "Basic $auth"); - - $self->{proxy} = $proxy; -} - - - - -=back - -=head1 REQUIRED MODULES - -L, L, L, L - -=head1 SEE ALSO - -I, -L - -=head1 COPYRIGHT - -Copyright (c) 2007 The University of British Columbia - -=cut - - -1; -# vim:sw=4:ts=4 diff --git a/lib/WeBWorK/Authen/CWL/Session.pm b/lib/WeBWorK/Authen/CWL/Session.pm deleted file mode 100644 index 8ca9d199b4..0000000000 --- a/lib/WeBWorK/Authen/CWL/Session.pm +++ /dev/null @@ -1,318 +0,0 @@ -package Session; -use strict; - -use WeBWorK::Authen::CWL::CWL; -use Carp; -use WeBWorK::Debug; -use XMLRPC::Lite; - -# Revision history: -# -# - Initial revision completed -# - -=head1 NAME - -CWL::Session - Campus Wide Login Auth v2 Client Session - -=head1 SYNOPSIS - - # Get the login name of the user presenting a ticket - - use CWL; - - my $cwl = new CWL(service => 'ubc_widget_service', - password => 'sprockets', - url => 'https://www.auth.verf2.cwl.ubc.ca/auth/login', - ); - - my $session = $cwl->create_session($ticket); - - my $login_name = $session->get_login_name(); - -=head1 DESCRIPTION - -This module provides access to the CWL Auth 2 service API. -You should not create CWL::Session objects directly; rather, -use CWL to create CWL::Session objects. - -=head2 Methods - -The following methods are provided: - -=over - - - - -=item B<$session-Eadd_identity($type, $value)> - -Adds an identity key to the user. - -Prior authorization is needed before a service can use this API. - -=cut - -sub add_identity { - my ($self, $type, $value) = @_; - $self->{cwl}->invoke('session.addIdentity', - XMLRPC::Data->type(string => $self->{ticket}), - XMLRPC::Data->type(string => $type), - XMLRPC::Data->type(string => $value)); -} - - - - -=item B<$session-Eget_identities( )> - -Returns all of the identity keys for the user. This returns -a hashref, the keys of which are strings representing the -identity type. - -=cut - -sub get_identities { - my ($self) = @_; - - $self->{cwl}->invoke('session.getIdentities', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_login_key( )> - -Returns the login key of the user. - -=cut - -sub get_login_key { - my ($self) = @_; - - $self->{cwl}->invoke('session.getLoginKey', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_login_name( )> - -Returns the login name of the user. - -=cut - -sub get_login_name { - my ($self) = @_; - debug($self->{cwl}); - - $self->{cwl}->invoke('session.getLoginName', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_preferred_name( )> - -Returns the preferred full name of the user. - -If no full name is available, this will return the login name instead. - -=cut - -sub get_preferred_name { - my ($self) = @_; - - $self->{cwl}->invoke('session.getPreferredName', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_roles( )> - -Returns the list of all of the user's roles. This returns an arrayref. - -=cut - -sub get_roles { - my ($self) = @_; - - $self->{cwl}->invoke('session.getRoles', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_session_age( )> - -Returns the time, in seconds, since the session was created. - -=cut - -sub get_session_age { - my ($self) = @_; - - $self->{cwl}->invoke('session.getSessionAge', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_session_key( )> - -Returns the session key for this session. - -=cut - -sub get_session_key { - my ($self) = @_; - - $self->{cwl}->invoke('session.getSessionKey', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Eget_trust_path( )> - -Get the list of services the user has traversed to arrive at this session. -This returns an arrayref. - -=cut - -sub get_trust_path { - my ($self) = @_; - - $self->{cwl}->invoke('session.getTrustPath', - XMLRPC::Data->type(string => $self->{ticket})); -} - - - - -=item B<$session-Ehas_role($role)> - -Check to see if the user has a certain role. - -Returns a true value if the user has that role, a false value otherwise. - -=cut - -sub has_role { - my ($self, $role) = @_; - - $self->{cwl}->invoke('session.hasRole', - XMLRPC::Data->type(string => $self->{ticket}), - XMLRPC::Data->type(string => $role)); -} - - - - -=item B<$session-Etransfer_session($service)> - -Create a ticket to transfer the session to another application. - -This will request a new session key from the CWL Authentication Service that -can be used by the specified application, without requiring the user to log in -a second time. - -Because tickets have relatively short lifetimes, it is important to only -request this ticket immediately before passing it to the next application. - -This returns the new ticket. - -=cut - -sub transfer_session { - my ($self, $service) = @_; - - $self->{cwl}->invoke('session.transferSession', - XMLRPC::Data->type(string => $self->{ticket}), - XMLRPC::Data->type(string => $service)); -} - - - - -=back - -=head2 Private Methods - -The following methods are used internally by this module. You should not -invoke these methods directly; rather, use one of the other methods as -described in the CWL API. - -=over - - - - -=item BfromTicket($cwl, $ticket)> - -B<(PRIVATE METHOD)> -Creates a CWL::Session object from a ticket. - -=cut - -# private -sub fromTicket { - my ($self, $cwl, $ticket) = @_; - my $self = bless { }; - - $self->{cwl} = $cwl; - $self->{ticket} = $ticket; - return $self; -} - - - - -=item BfromLogin($cwl, $username, $password)> - -B<(PRIVATE METHOD)> -Creates a CWL::Session object from a username and password. - -=cut - -# private -sub fromLogin { - my ($class, $cwl, $username, $password) = @_; - - my $ticket = $cwl->invoke('service.createSession', - XMLRPC::Data->type(string => $username), - XMLRPC::Data->type(string => $password)); - - return $class->fromTicket($cwl, $ticket); -} - - - -=back - -=head1 REQUIRED MODULES - -L, L, L - -=head1 SEE ALSO - -I, -L - -=head1 COPYRIGHT - -Copyright (c) 2007 The University of British Columbia - -=cut - - -1; -# vim:sw=4:ts=4 diff --git a/lib/WeBWorK/Authen/LTIAdvantage.pm b/lib/WeBWorK/Authen/LTIAdvantage.pm index b23d2570a0..6040621876 100644 --- a/lib/WeBWorK/Authen/LTIAdvantage.pm +++ b/lib/WeBWorK/Authen/LTIAdvantage.pm @@ -24,7 +24,7 @@ use Net::OAuth; use JSON::Validator qw(validate_json); use Crypt::JWT qw(decode_jwt); use LWP::UserAgent; -use WeBWorK::Authen::LTIAdvantage::LTILaunchParser; +use LTIAdvantage::Parser::LaunchParser; use File::Basename; use Data::Dumper; use WeBWorK::Cookie; @@ -74,7 +74,7 @@ sub get_credentials { #disable password login $self->{external_auth} = 1; - my $parser = WeBWorK::Authen::LTIAdvantage::LTILaunchParser->new($ce, $r->param("id_token")); + my $parser = LTIAdvantage::Parser::LaunchParser->new($ce, $r->param("id_token")); if ($parser->{error}) { $self->{log_error} = "Could not parse LTI launch. Error: \n".$parser->{error}; $self->{error} = "Could not parse LTI launch. Error: \n".$parser->{error}; @@ -117,10 +117,10 @@ sub get_credentials { my $version = $parser->get_claim("version"); my $dirname = dirname(__FILE__); - my $schema = $dirname."/LTIAdvantage/schema/1.3.0/LtiResourceLinkRequest.json"; + my $schema = $dirname."/LTIAdvantage/Schema/1.3.0/LtiResourceLinkRequest.json"; if ($version ne "1.3.0") { # for future, load different schemas as needed - # $schema = $dirname."/LTIAdvantage/schema/1.3.0/LtiResourceLinkRequest.json"; + # $schema = $dirname."/LTIAdvantage/Schema/1.3.0/LtiResourceLinkRequest.json"; # error out $self->{log_error} = "Invalid LTI Version. Supported Version are: 1.3.0"; @@ -140,8 +140,8 @@ sub get_credentials { } # check if valid client - if (!defined($ce->{bridge}{lti_clients}{$client_id}) || - !defined($ce->{bridge}{lti_clients}{$client_id})) + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id}) || + !defined($ce->{lti_advantage}{lti_clients}{$client_id})) { $self->{log_error} = "Unable to find a client id that matches '$client_id'."; $self->{error} = "Unable to find a client id that matches '$client_id'."; @@ -150,8 +150,8 @@ sub get_credentials { } # check if valid platform - if (!defined($ce->{bridge}{lti_clients}{$client_id}{platform_id}) || - $ce->{bridge}{lti_clients}{$client_id}{platform_id} ne $platform_id) + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id}{platform_id}) || + $ce->{lti_advantage}{lti_clients}{$client_id}{platform_id} ne $platform_id) { $self->{log_error} = "Unable to find a platform id that matches '$platform_id'."; $self->{error} = "Unable to find a platform id that matches '$platform_id'."; @@ -160,8 +160,8 @@ sub get_credentials { } # check if public key - if (!defined($ce->{bridge}{lti_clients}{$client_id}) || - !defined($ce->{bridge}{lti_clients}{$client_id}{platform_security_jwks_url})) + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id}) || + !defined($ce->{lti_advantage}{lti_clients}{$client_id}{platform_security_jwks_url})) { $self->{log_error} = "Unable to find a security jwks url for client '$client_id'."; $self->{error} = "Unable to find a security jwks url for client '$client_id'."; @@ -182,6 +182,7 @@ sub get_credentials { $self->{login_type} = "normal"; $self->{credential_source} = "LTIAdvantage"; $self->{session_key} = undef; + $self->{initial_login} = 1; # resuse session_key if possible my ($cookieUser, $cookieKey, $cookieTimeStamp) = $self->fetchCookie; @@ -200,7 +201,7 @@ sub prevent_replay { my $ce = $r->ce; my $db = $r->db; - my $parser = WeBWorK::Authen::LTIAdvantage::LTILaunchParser->new($ce, $r->param("id_token")); + my $parser = LTIAdvantage::Parser::LaunchParser->new($ce, $r->param("id_token")); my $platform_id = $parser->get_param("iss"); my $nonce = $parser->get_param("nonce"); @@ -234,7 +235,7 @@ sub authenticate { return 0; } - my $parser = WeBWorK::Authen::LTIAdvantage::LTILaunchParser->new($ce, $r->param("id_token")); + my $parser = LTIAdvantage::Parser::LaunchParser->new($ce, $r->param("id_token")); if ($parser->{error}) { $self->{log_error} = "Could not parse LTI launch. Error: \n".$parser->{error}; $self->{error} = "Could not parse LTI launch. Error: \n".$parser->{error}; @@ -243,7 +244,7 @@ sub authenticate { } my $client_id = $parser->get_param("aud"); - if (!defined($ce->{bridge}{lti_clients}{$client_id}) || !defined($ce->{bridge}{lti_clients}{$client_id}{platform_security_jwks_url})) { + if (!defined($ce->{lti_advantage}{lti_clients}{$client_id}) || !defined($ce->{lti_advantage}{lti_clients}{$client_id}{platform_security_jwks_url})) { $self->{log_error} = "Unable to find a security jwks url for client '$client_id'."; $self->{error} = "Unable to find a security jwks url for client '$client_id'."; debug($self->{log_error}); @@ -254,7 +255,7 @@ sub authenticate { my $ua = LWP::UserAgent->new(); $ua->default_header( 'Accept' => 'application/json' ); - my $jwt_keys_url = $ce->{bridge}{lti_clients}{$client_id}{platform_security_jwks_url}; + my $jwt_keys_url = $ce->{lti_advantage}{lti_clients}{$client_id}{platform_security_jwks_url}; my $jwt_keys = undef; my $retry_count = 0; while (1) { diff --git a/lib/WeBWorK/Authen/UBCCWL.pm b/lib/WeBWorK/Authen/UBCCWL.pm deleted file mode 100644 index 70c77652f6..0000000000 --- a/lib/WeBWorK/Authen/UBCCWL.pm +++ /dev/null @@ -1,191 +0,0 @@ -package WeBWorK::Authen::UBCCWL; -use base qw/WeBWorK::Authen/; - -=head1 NAME - -WeBWorK::Authen::CWL - Authentication plug in for CWL - -to use: include in global.conf or course.conf - $authen{user_module} = "WeBWorK::Authen::CWL"; -and add /webwork2 or /webwork2/courseName as a CWL Protected Location - -if $r->ce->{cosignoff} is set for a course, authentication reverts -to standard WeBWorK authentication. - -=cut - -use strict; -use warnings; -use CGI qw/:standard/; -use WeBWorK::Debug; -use WeBWorK::Authen::CWL::CWL; - -sub get_credentials { - debug("get_credentials"); - - my ($self) = @_; - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - # if we come in with a user_id, we're in the loop where we - # use WeBWorK's authentication, and so just continue - # with the superclass get_credentials method. - # if bypassCWL is submitted, using build-in authentication method. - $self->{external_auth} = 1; - if ( $r->param("bypassCWL") || ( $r->param("user") && ! $r->param("force_passwd_authen") ) ){ - if($r->param("bypassCWL")){ - $self->{external_auth} = 0; - } - return $self->SUPER::get_credentials( @_ ); - } else { - my $ticket = $r->param('ticket') || 0; - if ( $ticket ) { - my ($error, $user_id) = $self->check_cwl($ticket); - if ( $error ) { - $self->{error} = $error; - return 0; - } else { - $self->{'user_id'} = $user_id; - $self->{r}->param("user", $user_id); - # set external auth parameter so that Login.pm - # knows not to rely on internal logins if - # there's a check_user failure. - $self->{session_key} = undef; - $self->{password} = "youWouldNeverPickThisPassword"; - $self->{login_type} = "normal"; - $self->{credential_source} = "params"; - - return 1; - } - } else { - # if there's no ticket, redirect to get one - # - my $this_script = "https://" . $ENV{'SERVER_NAME'} ; - if( $ENV{"SERVER_PORT"} != 80 || $ENV{"SERVER_PORT"} != 443 ) - { - $this_script = $this_script . ":" . $ENV{"SERVER_PORT"}; - } - $this_script .= $ENV{'REQUEST_URI'}; - my $go_to = "$ce->{cwl}{cwl_server}?serviceName=$ce->{cwl}{service_name}&serviceURL=$this_script"; - $self->{redirect} = $go_to; - debug("No CWL ticket, redirect to:".$go_to); - - my $q = new CGI; - print $q->redirect($go_to); - - return 0; - } - } -} - - -sub site_checkPassword { - my ( $self, $userID, $clearTextPassword ) = @_; - # if we got here, we know we've already successfully authenticated against the CWL - return 1; -} - -sub check_cwl { - my ($self, $ticket) = @_; - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - debug($ticket); - my $cwl = CWL->new(service => $ce->{cwl}{service_name}, - password => $ce->{cwl}{service_password}, - url => $ce->{cwl}{service_url}, #$serviceURL, - ); - - my $session = $cwl->create_session($ticket); - - # check if session is too old, login failded - if($session->get_session_age() > 5) { - return q(Access Denied. Your session has expired.); - } - - my $cwl_login_name = $session->get_login_name(); - # get all ids, e.g. student id, employee id. - my $ids = $session->get_identities(); - - # get all users from database for this course - my @usernames = $db->listUsers; - my @users = $db->getUsers(@usernames); - - my @found; - my $login_name = undef; - - # search for all matches. - foreach(@users) { - debug("user_id: $_->student_id"); - while( my($key, $value) = each (%$ids) ){ - debug("identities: " . $value); - if( $value eq $_->student_id ) { - push(@found, $_); - } - } - } - - # if nothing found, no access. - # One found, use user_id from db. - # More than one found, try to match cwl login name with user_id in db. If no match, use the first one in db as default. - if(0 == scalar @found) { - $self->{external_auth} = 1; - return q(Access Denied. You have successfully logged in using your CWL account, but you do not have access to this course. - If you are experiencing any issues regarding access to WeBWorK, please contact the CTLT Support Team at - webwork.support@ubc.ca.

- You may logged in to the wrong course/section. Please make sure you clicked on the correct course and section link to login.); - }elsif( scalar @found > 1) { - foreach(@found){ - if( $_->user_id eq $cwl_login_name ) { - $login_name = $_->user_id; - last; - } - } - if(!defined $login_name){ - $login_name = $found[0]->user_id; - } - }else{ - $login_name = $found[0]->user_id; - } - return (0, $login_name); -} - -# override verify_normal_user method to customize time out handling. -sub verify_normal_user { - my $self = shift; - my $r = $self->{r}; - - my $user_id = $self->{user_id}; - my $session_key = $self->{session_key}; - - my ($sessionExists, $keyMatches, $timestampValid) = $self->check_session($user_id, $session_key, 1); - debug("sessionExists='", $sessionExists, "' keyMatches='", $keyMatches, "' timestampValid='", $timestampValid, "'"); - debug(url(-path_info=>1).$ENV{'QUERY_STRING'}); - if ($keyMatches and $timestampValid) { - return 1; - } else { - my $auth_result = $self->authenticate; - - if ($auth_result > 0) { - $self->{session_key} = $self->create_session($user_id); - $self->{initial_login} = 1; - return 1; - } elsif ($auth_result == 0) { - $self->{log_error} = "authentication failed"; - $self->{error} = $self->{GENERIC_ERROR_MESSAGE}; - return 0; - } else { # ($auth_result < 0) => required data was not present - if ($keyMatches and not $timestampValid) { - $self->{error} = qq(Your session has timed out due to inactivity. Please log in again.); - } - return 0; - } - } -} - -sub killCookie { - my ($self, @args) = @_; - return $self->SUPER::killCookie( @_ ); -} - -1; diff --git a/lib/WeBWorK/Authen/VistaLogin2.pm b/lib/WeBWorK/Authen/VistaLogin2.pm deleted file mode 100644 index 31d80a62cf..0000000000 --- a/lib/WeBWorK/Authen/VistaLogin2.pm +++ /dev/null @@ -1,140 +0,0 @@ -################################################################################ -# WeBWorK Online Homework Delivery System -# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/Authen/LDAP.pm,v 1.3 2006/11/13 16:48:39 sh002i Exp $ -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of either: (a) the GNU General Public License as published by the -# Free Software Foundation; either version 2, or (at your option) any later -# version, or (b) the "Artistic License" which comes with this package. -# -# This program 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 either the GNU General Public License or the -# Artistic License for more details. -################################################################################ - -package WeBWorK::Authen::VistaLogin2; -use base qw/WeBWorK::Authen/; - -use strict; -use warnings; -use WeBWorK::Debug; - -sub get_credentials { - my ($self) = @_; - my $r = $self->{r}; - my $ce = $r->ce; - my $db = $r->db; - - # don't allow guest login from Vista - if ($r->param("login_practice_user")) { - $self->{log_error} = "no guest logins are available"; - $self->{error} = "No guest logins are available. Please try again in a few minutes."; - return 0; - } - - my $vista_login_enable = $ce->{bridge}{vista_login_options}{"enable_param"}; - my $vista_login_course = $ce->{bridge}{vista_login_options}{"course_param"}; - my $vista_login_section = $ce->{bridge}{vista_login_options}{"section_param"}; - my $vista_login_user = $ce->{bridge}{vista_login_options}{"user_param"}; - my $vista_login_time = $ce->{bridge}{vista_login_options}{"time_param"}; - my $vista_login_mac = $ce->{bridge}{vista_login_options}{"mac_param"}; - my $vista_login_secret = $ce->{bridge}{vista_login_options}{"secret"}; - my $vista_login_time_tolerance = $ce->{bridge}{vista_login_options}{"valid_time_diff"}; - - # construct the mac message for authentication, the mac is basically concatenating all the http params together with the secret and then MD5 hash the whole string - my $mac_msg; - # note that params are alphabetically sorted by key before concatenating them together for the mac, we put the params into a hash table for easy sorting - my %macparams; - - $macparams{$vista_login_enable} = $r->param($vista_login_enable); - $macparams{$vista_login_course} = $r->param($vista_login_course); - $macparams{$vista_login_section} = $r->param($vista_login_section); - $macparams{$vista_login_user} = $r->param($vista_login_user); - $macparams{$vista_login_time} = $r->param($vista_login_time); - - # this is the mac our calculated mac should match - $vista_login_mac = $r->param($vista_login_mac); - - foreach my $key (sort keys %macparams) - { - $mac_msg .= $macparams{$key}; - } - $mac_msg .= $vista_login_secret; - debug("Vista login: MAC message: $mac_msg\n"); - -# validate timestamp, if the timestamps are too far apart, fail login - my $curtime = time(); - my $diff = abs($curtime - $macparams{$vista_login_time}); - debug("Vista login: WeBWorK time: $curtime Vista time: $macparams{$vista_login_time}\n"); - - if ($diff > $vista_login_time_tolerance) - { - $self->{log_error} = "timestamp validation failed"; - $self->{error} = "The Vista server's time does not match WeBWorK's server time."; - return 0; - } - - # at least the user ID is available in request parameters - if (defined $vista_login_user) { - $self->{user_id} = $macparams{$vista_login_user}; - $self->{mac_msg} = $mac_msg; - $self->{mac} = $vista_login_mac; -# $self->{session_key} = $r->param("key"); -# $self->{password} = $r->param("passwd"); - $self->{login_type} = "normal"; - $self->{credential_source} = "params"; - debug("params user '", $self->{user_id}, "' password '", $self->{password}, "' key '", $self->{session_key}, "'"); - return 1; - } - - # disable cookie login for Vista -# my ($cookieUser, $cookieKey) = $self->fetchCookie; -# if (defined $cookieUser) { -# $self->{user_id} = $cookieUser; -# $self->{session_key} = $cookieKey; -# $self->{login_type} = "normal"; -# $self->{credential_source} = "cookie"; -# debug("cookie user '", $self->{user_id}, "' key '", $self->{session_key}, "'"); -# return 1; -# } - return 0; -} - -# 1 == authentication succeeded -# 0 == required data was present, but authentication failed -# -1 == required data was not present (i.e. password missing) -sub authenticate { - my $self = shift; - my $r = $self->{r}; - - my $user_id = $self->{user_id}; - my $password = $self->{password}; - - my $mac_msg = $self->{mac_msg}; - my $mac = $self->{mac}; - - use Digest::MD5 qw(md5_hex); - my $digest = md5_hex($mac_msg); - debug("MAC Received: $mac\n"); - debug("MAC Calculated: $digest\n"); - - if ($digest eq $mac) - { - return 1; - } - else - { - return 0; - } - -# if (defined $password) { -# return $self->checkPassword($user_id, $password); -# } else { -# return -1; -# } -} - - -1; diff --git a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm index a4b0359131..18f1faa469 100644 --- a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm +++ b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm @@ -41,7 +41,8 @@ use WeBWorK::Utils::Tasks qw(fake_set fake_set_version fake_problem); use WeBWorK::Debug; use WeBWorK::ContentGenerator::Instructor qw(assignSetVersionToUser); use WeBWorK::Authen::LTIAdvanced::SubmitGrade; -use WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +use LTIAdvantage::Service::AssignmentAndGradeService; +use DelayedJob::Service; use PGrandom; use Caliper::Sensor; @@ -1651,13 +1652,19 @@ sub body { $LTIGradeResult = $grader->submit_set_grade($effectiveUser, $setName); } } - if ($submitAnswers && $will{recordAnswers} && $self->{ce}->{bridge}{push_grades_on_submit}) { - my $assignment_and_grade_service = WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService->new($self->{ce}, $db); - $assignment_and_grade_service->pushUserGradesOnSubmit($effectiveUser, $setName); - if ($assignment_and_grade_service->{error}) { - $LTIGradeResult = -1; - } else { + if ($submitAnswers && $will{recordAnswers} && $self->{ce}->{lti_advantage}{push_grades_on_submit}) { + if ($self->{ce}->{delayed_job}{enabled}) { + my $delayed_job_service = DelayedJob::Service->new($self->{ce}); + $delayed_job_service->pushUserGradesOnSubmit($effectiveUser, $setName); $LTIGradeResult = 1; + } else { + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($self->{ce}, $db); + $assignment_and_grade_service->pushUserGradesOnSubmit($effectiveUser, $setName); + if ($assignment_and_grade_service->{error}) { + $LTIGradeResult = -1; + } else { + $LTIGradeResult = 1; + } } } diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList2.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList2.pm index f7335501ce..2cb32719e8 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList2.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList2.pm @@ -74,6 +74,9 @@ Delete sets: - visible - selected +LTI: + - send grades to lms + =cut # FIXME: rather than having two types of boolean modes $editMode and $exportMode @@ -85,6 +88,7 @@ use warnings; use WeBWorK::CGI; use WeBWorK::Debug; use WeBWorK::Utils qw(timeToSec readFile listFilesRecursive cryptPassword sortByName jitar_id_to_seq seq_to_jitar_id x); +use LTIAdvantage::Service::AssignmentAndGradeService; use WeBWorK::Utils::DatePickerScripts; @@ -94,7 +98,7 @@ use constant DEFAULT_ENABLED_REDUCED_SCORING_STATE => 0; use constant ONE_WEEK => 60*60*24*7; use constant EDIT_FORMS => [qw(saveEdit cancelEdit)]; -use constant VIEW_FORMS => [qw(filter sort edit publish import export score create delete)]; +use constant VIEW_FORMS => [qw(filter sort edit publish import export score create delete lti)]; use constant EXPORT_FORMS => [qw(saveExport cancelExport)]; use constant VIEW_FIELD_ORDER => [ qw( set_id problems users visible enable_reduced_scoring open_date reduced_scoring_date due_date answer_date) ]; @@ -112,6 +116,7 @@ use constant FORM_PERMS => { score => "score_sets", create => "create_and_delete_problem_sets", delete => "create_and_delete_problem_sets", + lti => "create_and_delete_problem_sets", }; # permissions needed to view a given field @@ -1624,6 +1629,30 @@ sub duplicate_handler { return $r->maketext("Success"); } +sub lti_form { + my ($self, $onChange, %actionParams) = @_; + my $r = $self->r; + + return join(" ", + CGI::p($r->maketext("Send all set grades to the LMS. May take a long time to complete for larger class sizes and/or with many sets.")), + ); +} + +sub lti_handler { + my ($self, $genericParams, $actionParams, $tableParams) = @_; + my $r = $self->r; + my $ce = $r->ce; + my $db = $r->db; + + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($ce, $db); + $assignment_and_grade_service->pushAllAssignmentGrades(); + + if ($assignment_and_grade_service->{error}) { + return $r->maketext("There was an issue pushing the class grades. [_1]", $assignment_and_grade_service->{error}); + } + return $r->maketext("Successfully updated class grades."); +} + ################################################################################ # sorts ################################################################################ diff --git a/lib/WeBWorK/ContentGenerator/Instructor/UserList2.pm b/lib/WeBWorK/ContentGenerator/Instructor/UserList2.pm index b216d2bf57..f50ec10262 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/UserList2.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/UserList2.pm @@ -63,6 +63,8 @@ Export users: - to: - existing file on server (overwrite): [ list of files ] - new file on server (create): [ filename ] +LTI: + - get class roster from lms =cut @@ -73,10 +75,12 @@ use WeBWorK::CGI; use WeBWorK::File::Classlist; use WeBWorK::DB qw(check_user_id); use WeBWorK::Utils qw(readFile readDirectory cryptPassword x); +use LTIAdvantage::Service::NamesAndRoleService; +use LTIAdvantage::Importer::CourseUpdater; use constant HIDE_USERS_THRESHHOLD => 200; use constant EDIT_FORMS => [qw(saveEdit cancelEdit)]; use constant PASSWORD_FORMS => [qw(savePassword cancelPassword)]; -use constant VIEW_FORMS => [qw(filter sort edit password import export add delete)]; +use constant VIEW_FORMS => [qw(filter sort edit password import export add delete lti)]; use Encode qw(decode_utf8 encode_utf8); # permissions needed to perform a given action use constant FORM_PERMS => { @@ -88,6 +92,7 @@ use constant FORM_PERMS => { export => "modify_classlist_files", add => "modify_student_data", delete => "modify_student_data", + lti => "modify_student_data", }; # permissions needed to view a given field @@ -1331,6 +1336,35 @@ sub savePassword_handler { return $r->maketext("New passwords saved"); } +sub lti_form { + my ($self, $onChange, %actionParams) = @_; + my $r = $self->r; + + return join(" ", + CGI::p($r->maketext("Get the class roster from the LMS. May take a long time to complete for larger class sizes.")), + ); +} + +sub lti_handler { + my ($self, $genericParams, $actionParams, $tableParams) = @_; + my $r = $self->r; + my $ce = $r->ce; + my $db = $r->db; + + my $names_and_roles_service = LTIAdvantage::Service::NamesAndRoleService->new($ce, $db); + my $membership = $names_and_roles_service->getAllNamesAndRole(); + unless ($membership) { + return $r->maketext("There was an issue fetching the class roster. [_1]", $names_and_roles_service->{error}); + } + my $updater = LTIAdvantage::Importer::CourseUpdater->new($ce, $db, $membership); + my $ret = $updater->updateCourse(); + if ($ret) { + return $r->maketext("Update class roster failed: [_1]", $ret); + } + $self->{visibleUserIDs} = [ $self->{allUserIDs} ]; + return $r->maketext("Successfully updated class roster."); +} + ################################################################################ # sorts diff --git a/lib/WeBWorK/ContentGenerator/WebworkBridgeStatus.pm b/lib/WeBWorK/ContentGenerator/LTIAdvantageStatus.pm similarity index 78% rename from lib/WeBWorK/ContentGenerator/WebworkBridgeStatus.pm rename to lib/WeBWorK/ContentGenerator/LTIAdvantageStatus.pm index 55bf5d5a19..d8b8ab796c 100644 --- a/lib/WeBWorK/ContentGenerator/WebworkBridgeStatus.pm +++ b/lib/WeBWorK/ContentGenerator/LTIAdvantageStatus.pm @@ -1,9 +1,9 @@ -package WeBWorK::ContentGenerator::WebworkBridgeStatus; +package WeBWorK::ContentGenerator::LTIAdvantageStatus; use base qw(WeBWorK::ContentGenerator); =head1 NAME -WeBWorK::ContentGenerator::WebworkBridgeStatus - Webwork Bridge Status. +WeBWorK::ContentGenerator::LTIAdvantageStatus - LTI Advantage Status. =cut diff --git a/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm b/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm index 5ffba0f8f9..8536b091c7 100644 --- a/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm +++ b/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm @@ -44,7 +44,8 @@ use WeBWorK::Utils qw(readFile writeLog writeCourseLog encodeAnswers decodeAnswe use WeBWorK::DB::Utils qw(global2user user2global); use URI::Escape; use WeBWorK::Authen::LTIAdvanced::SubmitGrade; -use WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService; +use LTIAdvantage::Service::AssignmentAndGradeService; +use DelayedJob::Service; use WeBWorK::Utils::Tasks qw(fake_set fake_problem); use Email::Simple; @@ -365,13 +366,18 @@ sub process_and_log_answer{ } } } - if ($self->{ce}->{bridge}{push_grades_on_submit}) { - my $assignment_and_grade_service = WeBWorK::Authen::LTIAdvantage::AssignmentAndGradeService->new($self->{ce}, $db); - $assignment_and_grade_service->pushUserGradesOnSubmit($problem->user_id, $problem->set_id); - if ($assignment_and_grade_service->{error}) { - $scoreRecordedMessage .= $r->maketext(" Your score was not successfully sent to the LMS"); + if ($self->{ce}->{lti_advantage}{push_grades_on_submit}) { + if ($self->{ce}->{delayed_job}{enabled}) { + my $delayed_job_service = DelayedJob::Service->new($self->{ce}); + $delayed_job_service->pushUserGradesOnSubmit($problem->user_id, $problem->set_id); } else { - $scoreRecordedMessage .= $r->maketext(" Your score was successfully sent to the LMS"); + my $assignment_and_grade_service = LTIAdvantage::Service::AssignmentAndGradeService->new($self->{ce}, $db); + $assignment_and_grade_service->pushUserGradesOnSubmit($problem->user_id, $problem->set_id); + if ($assignment_and_grade_service->{error}) { + $scoreRecordedMessage .= $r->maketext(" Your score was not successfully sent to the LMS"); + } else { + $scoreRecordedMessage .= $r->maketext(" Your score was successfully sent to the LMS"); + } } } diff --git a/lib/WeBWorK/DB.pm b/lib/WeBWorK/DB.pm index 50e8fba125..d26bf7e83c 100644 --- a/lib/WeBWorK/DB.pm +++ b/lib/WeBWorK/DB.pm @@ -2334,10 +2334,9 @@ sub getLTIContextsByCourseID { return $self->{lti_contexts}->get_records_where($where); } -sub getAllLTIContextsByAutomaticUpdates { - my ($self, $automaticUpdates) = shift->checkArgs(\@_, qw/automaticUpdates/); - my $where = [automatic_updates_eq => $automaticUpdates]; - return $self->{lti_contexts}->get_records_where($where); +sub getAllLTIContexts { + my ( $self ) = shift->checkArgs(\@_); + return $self->{lti_contexts}->get_records_where(); } ################################################################################ diff --git a/lib/WeBWorK/DB/Record/DelayedJob/Error.pm b/lib/WeBWorK/DB/Record/DelayedJob/Error.pm new file mode 100644 index 0000000000..0521326e19 --- /dev/null +++ b/lib/WeBWorK/DB/Record/DelayedJob/Error.pm @@ -0,0 +1,37 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/DelayedJob/Error.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program 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 either the GNU General Public License or the +# Artistic License for more details. +################################################################################ +package WeBWorK::DB::Record::DelayedJob::Error; +use base WeBWorK::DB::Record; + +=head1 NAME + +WeBWorK::DB::Record::DelayedJob::Error + +=cut + +use strict; +use warnings; + +BEGIN { + __PACKAGE__->_fields( + jobid => { type=>"BIGINT UNSIGNED NOT NULL" }, + error_time => { type=>"INTEGER UNSIGNED NOT NULL" }, + funcid => { type=>"INT UNSIGNED NOT NULL DEFAULT 0" }, + message => { type=>"TEXT NOT NULL" }, + ); +} + +1; diff --git a/lib/WeBWorK/DB/Record/DelayedJob/Exitstatus.pm b/lib/WeBWorK/DB/Record/DelayedJob/Exitstatus.pm new file mode 100644 index 0000000000..91e8be1cfd --- /dev/null +++ b/lib/WeBWorK/DB/Record/DelayedJob/Exitstatus.pm @@ -0,0 +1,38 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/DelayedJob/Exitstatus.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program 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 either the GNU General Public License or the +# Artistic License for more details. +################################################################################ +package WeBWorK::DB::Record::DelayedJob::Exitstatus; +use base WeBWorK::DB::Record; + +=head1 NAME + +WeBWorK::DB::Record::DelayedJob::Exitstatus + +=cut + +use strict; +use warnings; + +BEGIN { + __PACKAGE__->_fields( + jobid => { type=>"BIGINT UNSIGNED PRIMARY KEY NOT NULL" }, + funcid => { type=>"INT UNSIGNED NOT NULL DEFAULT 0" }, + status => { type=>"SMALLINT UNSIGNED" }, + completion_time => { type=>"INTEGER UNSIGNED" }, + delete_after => { type=>"INTEGER UNSIGNED" }, + ); +} + +1; diff --git a/lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm b/lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm new file mode 100644 index 0000000000..02c75c5755 --- /dev/null +++ b/lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm @@ -0,0 +1,35 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/DelayedJob/Funcmap.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program 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 either the GNU General Public License or the +# Artistic License for more details. +################################################################################ +package WeBWorK::DB::Record::DelayedJob::Funcmap; +use base WeBWorK::DB::Record; + +=head1 NAME + +WeBWorK::DB::Record::DelayedJob::Funcmap + +=cut + +use strict; +use warnings; + +BEGIN { + __PACKAGE__->_fields( + funcid => { type=>"INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT" }, + funcname => { type=>"VARCHAR(255) NOT NULL", key=>1 }, + ); +} + +1; diff --git a/lib/WeBWorK/DB/Record/DelayedJob/Job.pm b/lib/WeBWorK/DB/Record/DelayedJob/Job.pm new file mode 100644 index 0000000000..28fe27664f --- /dev/null +++ b/lib/WeBWorK/DB/Record/DelayedJob/Job.pm @@ -0,0 +1,42 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/DelayedJob/Job.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program 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 either the GNU General Public License or the +# Artistic License for more details. +################################################################################ +package WeBWorK::DB::Record::DelayedJob::Job; +use base WeBWorK::DB::Record; + +=head1 NAME + +WeBWorK::DB::Record::DelayedJob::Job + +=cut + +use strict; +use warnings; + +BEGIN { + __PACKAGE__->_fields( + jobid => { type=>"BIGINT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT" }, + funcid => { type=>"INT UNSIGNED NOT NULL", key=>1 }, + arg => { type=>"MEDIUMBLOB" }, + uniqkey => { type=>"VARCHAR(255) NULL", key=>1 }, + insert_time => { type=>"INTEGER UNSIGNED" }, + run_after => { type=>"INTEGER UNSIGNED NOT NULL" }, + grabbed_until => { type=>"INTEGER UNSIGNED NOT NULL" }, + priority => { type=>"SMALLINT UNSIGNED" }, + coalesce => { type=>"VARCHAR(255)" }, + ); +} + +1; diff --git a/lib/WeBWorK/DB/Record/DelayedJob/Note.pm b/lib/WeBWorK/DB/Record/DelayedJob/Note.pm new file mode 100644 index 0000000000..6a27048fa1 --- /dev/null +++ b/lib/WeBWorK/DB/Record/DelayedJob/Note.pm @@ -0,0 +1,36 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/DelayedJob/Note.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program 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 either the GNU General Public License or the +# Artistic License for more details. +################################################################################ +package WeBWorK::DB::Record::DelayedJob::Note; +use base WeBWorK::DB::Record; + +=head1 NAME + +WeBWorK::DB::Record::DelayedJob::Note + +=cut + +use strict; +use warnings; + +BEGIN { + __PACKAGE__->_fields( + jobid => { type=>"BIGINT UNSIGNED NOT NULL", key=>1 }, + notekey => { type=>"VARCHAR(255)", key=>1 }, + value => { type=>"MEDIUMBLOB" }, + ); +} + +1; diff --git a/lib/WeBWorK/DB/Record/LTIAccessTokens.pm b/lib/WeBWorK/DB/Record/LTIAdvantage/AccessTokens.pm similarity index 77% rename from lib/WeBWorK/DB/Record/LTIAccessTokens.pm rename to lib/WeBWorK/DB/Record/LTIAdvantage/AccessTokens.pm index 3fd4c607e8..78984a305f 100644 --- a/lib/WeBWorK/DB/Record/LTIAccessTokens.pm +++ b/lib/WeBWorK/DB/Record/LTIAdvantage/AccessTokens.pm @@ -1,7 +1,7 @@ ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAccessTokens.pm,v 1.47 2017/06/08 22:59:55 wheeler Exp $ +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAdvantage/AccessTokens.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the @@ -13,12 +13,12 @@ # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ -package WeBWorK::DB::Record::LTIAccessTokens; +package WeBWorK::DB::Record::LTIAdvantage::AccessTokens; use base WeBWorK::DB::Record; =head1 NAME -WeBWorK::DB::Record::LTINonces - represent a record from the lti nonces table. +WeBWorK::DB::Record::LTIAdvantage::Nonces - represent a record from the lti nonces table. =cut diff --git a/lib/WeBWorK/DB/Record/LTIContexts.pm b/lib/WeBWorK/DB/Record/LTIAdvantage/Contexts.pm similarity index 77% rename from lib/WeBWorK/DB/Record/LTIContexts.pm rename to lib/WeBWorK/DB/Record/LTIAdvantage/Contexts.pm index ebf19fedb9..845f8b9225 100644 --- a/lib/WeBWorK/DB/Record/LTIContexts.pm +++ b/lib/WeBWorK/DB/Record/LTIAdvantage/Contexts.pm @@ -1,7 +1,7 @@ ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIContexts.pm,v 1.47 2017/06/08 22:59:55 wheeler Exp $ +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAdvantage/Contexts.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the @@ -13,12 +13,12 @@ # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ -package WeBWorK::DB::Record::LTIContexts; +package WeBWorK::DB::Record::LTIAdvantage::Contexts; use base WeBWorK::DB::Record; =head1 NAME -WeBWorK::DB::Record::LTIContexts - represent a record from the lti contexts table. +WeBWorK::DB::Record::LTIAdvantage::Contexts - represent a record from the lti contexts table. =cut @@ -30,7 +30,6 @@ BEGIN { client_id => { type=>"TINYBLOB NOT NULL", key=>1 }, context_id => { type=>"TINYBLOB NOT NULL", key=>1 }, course_id => { type=>"TEXT" }, - automatic_updates => { type=>"TINYINT" }, # names and roles provising services context_memberships_url => { type=>"TINYBLOB" } diff --git a/lib/WeBWorK/DB/Record/LTINonces.pm b/lib/WeBWorK/DB/Record/LTIAdvantage/Nonces.pm similarity index 77% rename from lib/WeBWorK/DB/Record/LTINonces.pm rename to lib/WeBWorK/DB/Record/LTIAdvantage/Nonces.pm index fb295eb210..9834a3917f 100644 --- a/lib/WeBWorK/DB/Record/LTINonces.pm +++ b/lib/WeBWorK/DB/Record/LTIAdvantage/Nonces.pm @@ -1,7 +1,7 @@ ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTINonces.pm,v 1.47 2017/06/08 22:59:55 wheeler Exp $ +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAdvantage/Nonces.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the @@ -13,12 +13,12 @@ # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ -package WeBWorK::DB::Record::LTINonces; +package WeBWorK::DB::Record::LTIAdvantage::Nonces; use base WeBWorK::DB::Record; =head1 NAME -WeBWorK::DB::Record::LTINonces - represent a record from the lti nonces table. +WeBWorK::DB::Record::LTIAdvantage::Nonces - represent a record from the lti nonces table. =cut diff --git a/lib/WeBWorK/DB/Record/LTIResourceLink.pm b/lib/WeBWorK/DB/Record/LTIAdvantage/ResourceLink.pm similarity index 81% rename from lib/WeBWorK/DB/Record/LTIResourceLink.pm rename to lib/WeBWorK/DB/Record/LTIAdvantage/ResourceLink.pm index 1c210ef83e..e5dca2c6dc 100644 --- a/lib/WeBWorK/DB/Record/LTIResourceLink.pm +++ b/lib/WeBWorK/DB/Record/LTIAdvantage/ResourceLink.pm @@ -1,7 +1,7 @@ ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIResourceLink.pm,v 1.47 2018/03/05 22:59:55 wheeler Exp $ +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAdvantage/ResourceLink.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the @@ -13,12 +13,12 @@ # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ -package WeBWorK::DB::Record::LTIResourceLink; +package WeBWorK::DB::Record::LTIAdvantage::ResourceLink; use base WeBWorK::DB::Record; =head1 NAME -WeBWorK::DB::Record::LTIResourceLink - represent a record from the lti resource link table. +WeBWorK::DB::Record::LTIAdvantage::ResourceLink - represent a record from the lti resource link table. =cut diff --git a/lib/WeBWorK/DB/Record/LTIUser.pm b/lib/WeBWorK/DB/Record/LTIAdvantage/User.pm similarity index 77% rename from lib/WeBWorK/DB/Record/LTIUser.pm rename to lib/WeBWorK/DB/Record/LTIAdvantage/User.pm index 0c16d4fe56..ff50282c24 100644 --- a/lib/WeBWorK/DB/Record/LTIUser.pm +++ b/lib/WeBWorK/DB/Record/LTIAdvantage/User.pm @@ -1,7 +1,7 @@ ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ -# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIUser.pm,v 1.47 2018/03/05 22:59:55 wheeler Exp $ +# Copyright © 2000-2021 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/lib/WeBWorK/DB/Record/LTIAdvantage/User.pm,v 1.47 2021/05/19 22:59:55 wheeler Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the @@ -13,12 +13,12 @@ # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ -package WeBWorK::DB::Record::LTIUser; +package WeBWorK::DB::Record::LTIAdvantage::User; use base WeBWorK::DB::Record; =head1 NAME -WeBWorK::DB::Record::LTIUser - represent a record from the lti resource link table. +WeBWorK::DB::Record::LTIAdvantage::User - represent a record from the lti resource link table. =cut diff --git a/lib/WeBWorK/DB/Schema/NewSQL.pm b/lib/WeBWorK/DB/Schema/NewSQL.pm index eb2980661f..95ee099ede 100644 --- a/lib/WeBWorK/DB/Schema/NewSQL.pm +++ b/lib/WeBWorK/DB/Schema/NewSQL.pm @@ -167,12 +167,6 @@ sub where_course_id_eq { return {course_id=>$course_id}; } -# can be used for lti_contexts -sub where_automatic_updates_eq { - my ($self, $flags, $automatic_update) = @_; - return {automatic_updates=>$automatic_update}; -} - # added where clauses for locations and set_locations sub where_location_id_eq { diff --git a/lib/WeBWorK/Localize.pm b/lib/WeBWorK/Localize.pm index a864f0f4ca..e0bc283454 100644 --- a/lib/WeBWorK/Localize.pm +++ b/lib/WeBWorK/Localize.pm @@ -430,6 +430,23 @@ my $ConfigStrings = [ }, ], + [x('LTI'), + { var => 'lti_advantage{auto_assign_users_to_sets}', + doc => x('Automatically assign users to sets'), + doc2 => x('By default, users are automatically assigned to all sets when they launch into WeBWorK from the LMS or when the class roster is synced.'), + type => 'boolean' + }, + { var => 'lti_advantage{cron_grade_sync}', + doc => x('Automatically send student grades to LMS via cron job'), + doc2 => x('By default, student grades are automatically sent to the LRS via a cron job.'), + type => 'boolean' + }, + { var => 'lti_advantage{cron_roster_sync}', + doc => x('Automatically get roster from LMS via cron job'), + doc2 => x('By default, class roster is automatically fetched from the LMS via a cron job.'), + type => 'boolean' + }, + ], ]; package WeBWorK::Localize::I18N; diff --git a/lib/WebworkBridge/BridgeManager.pm b/lib/WebworkBridge/BridgeManager.pm deleted file mode 100644 index af7954d0dc..0000000000 --- a/lib/WebworkBridge/BridgeManager.pm +++ /dev/null @@ -1,92 +0,0 @@ -package WebworkBridge::BridgeManager; - -##### Library Imports ##### -use strict; -use warnings; -use WeBWorK::CourseEnvironment; -use WeBWorK::DB; -use WeBWorK::Debug; -use WeBWorK::Utils qw(runtime_use); - -use WebworkBridge::Importer::Error; - -# Constructor -sub new -{ - my ($class, $r) = @_; - my $self = { - r => $r, - bridge => undef - }; - bless $self, $class; - return $self; -} - -sub run -{ - my ($self) = @_; - my $r = $self->{r}; - - debug("Importer running."); - - my @bridges = ( - "WebworkBridge::Bridges::LTILaunchBridge", - "WebworkBridge::Bridges::LTILoginBridge", - ); - - # find a compatible bridge - my $bridge; - foreach (@bridges) - { - debug("Testing bridge $_ for compatibility."); - runtime_use($_); - $bridge = $_->new($r); - last if ($bridge->accept()); - } - - if ($bridge->accept()) - { - debug("Compatible bridge found!"); - $self->{bridge} = $bridge; - return $bridge->run(); - } - # could've ended the loop without finding a compatible bridge - return 0; -} - -sub useAuthenModule -{ - my ($self) = @_; - my $bridge = $self->{bridge}; - return $bridge ? $bridge->useAuthenModule() : ""; -} - -sub getAuthenModule -{ - my ($self) = @_; - my $bridge = $self->{bridge}; - return $bridge ? $bridge->getAuthenModule() : ""; -} - -sub getErrorDisplayModule -{ - my ($self) = @_; - my $bridge = $self->{bridge}; - return $bridge ? $bridge->getErrorDisplayModule() : ""; -} - -sub useRedirect -{ - my ($self) = @_; - my $bridge = $self->{bridge}; - return $bridge ? $bridge->useRedirect() : ""; -} - -sub getRedirect -{ - my ($self) = @_; - my $bridge = $self->{bridge}; - return $bridge ? $bridge->getRedirect() : ""; -} - -1;