From f5fd929f81a444b228cde85a70382045187c986b Mon Sep 17 00:00:00 2001 From: John Hsu Date: Fri, 23 Mar 2012 14:49:33 -0700 Subject: [PATCH 1/7] Implement xmlrpc createCourse, addUser, dropUser. Same as the other xmlrpc functions, requires the same authentication credentials to initiate a session. Also has the actual actions being carried out by a separate module named CourseActions. Beside the authentication credentials, each call needs these new entries as part of the parameter. createCourse: "name": "course name" "section": 100 addUser: "firstname": "John" "lastname": "Smith" "id": "The Doctor" # username "email": "doctor@tardis" "studentid": 87492466 "userpassword": "password" # defaults to studentid if empty # if studentid also empty then no # password "permission": "professor" # valid values from %userRoles # in global.conf # defaults to student if empty dropUser: "id": "The Doctor" # username Note that dropUser does not delete the user, only marks them as dropped from the course. Because of this, addUser has to deal with two cases: an actual new user or a re-enrolling user. The method return is the same as the other xmlrpc calls, except with a 'status' entry marking the call as 'success' or 'failure'. 'failure' means the action did not go through, there will be an accompanying 'message' entry with the error message. --- lib/WebworkWebservice.pm | 59 ++++++++ lib/WebworkWebservice/CourseActions.pm | 186 +++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 lib/WebworkWebservice/CourseActions.pm diff --git a/lib/WebworkWebservice.pm b/lib/WebworkWebservice.pm index 129d99544c..82391424aa 100644 --- a/lib/WebworkWebservice.pm +++ b/lib/WebworkWebservice.pm @@ -107,6 +107,7 @@ use WebworkWebservice::RenderProblem; use WebworkWebservice::LibraryActions; use WebworkWebservice::MathTranslators; use WebworkWebservice::SetActions; +use WebworkWebservice::CourseActions; ############################################################################### package WebworkXMLRPC; @@ -401,6 +402,64 @@ sub tex2pdf { return $self->do( WebworkWebservice::MathTranslators::tex2pdf($self,$in) ); } +# Expecting a hash $in composed of the usual auth credentials +# plus the params specific to this function +#{ +# 'userID' => 'admin', # these are the usual +# 'password' => 'admin', # auth credentials +# 'courseID' => 'admin', # used to initiate a +# 'session_key' => 'key', # session. +# "name": "KEJI554", +# "section": 264, +#} +# Note that we log into the admin course to create courses. +# The course title will be a concatenation of the name and section number. +sub createCourse { + my $class = shift; + my $in = shift; + my $self = $class->initiate_session($in); + return $self->do(WebworkWebservice::CourseActions::create($self, $in)); +} + +# Expecting a hash $in composed of +#{ +# 'userID' => 'admin', # these are the usual +# 'password' => 'admin', # auth credentials +# 'courseID' => 'Math', # used to initiate a +# 'session_key' => 'key', # session. +# "firstname": "John", +# "lastname": "Smith", +# "id": "The Doctor", # required +# "email": "doctor@tardis", +# "studentid": 87492466, +# "userpassword": "password", # defaults to studentid if empty +# # if studentid also empty, then no password +# "permission": "professor", # valid values from %userRoles in global.conf +# # defaults to student if empty +#} +# This user will be added to courseID +sub addUser { + my $class = shift; + my $in = shift; + my $self = $class->initiate_session($in); + return $self->do(WebworkWebservice::CourseActions::addUser($self, $in)); +} + +# Expecting a hash $in composed of +#{ +# 'userID' => 'admin', # these are the usual +# 'password' => 'admin', # auth credentials +# 'courseID' => 'Math', # used to initiate a +# 'session_key' => 'key', # session. +# "id": "BFYM942", +#} +sub dropUser { + my $class = shift; + my $in = shift; + my $self = $class->initiate_session($in); + return $self->do(WebworkWebservice::CourseActions::dropUser($self, $in)); +} + # -- SOAP::Lite -- guide.soaplite.com -- Copyright (C) 2001 Paul Kulchenko -- # test responses diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm new file mode 100644 index 0000000000..d4581b8776 --- /dev/null +++ b/lib/WebworkWebservice/CourseActions.pm @@ -0,0 +1,186 @@ +#!/usr/local/bin/perl -w +use strict; +use warnings; + +# Course manipulation functions for webwork webservices + +package WebworkWebservice::CourseActions; + +use WebworkWebservice; + +use base qw(WebworkWebservice); +use WeBWorK::Utils qw(runtime_use cryptPassword); +use WeBWorK::Utils::CourseManagement qw(addCourse); +use WeBWorK::Debug; +use Data::Dumper; # TODO remove + + +sub create { + my ($self, $params) = @_; + my $newcourse = $params->{'name'} . '-' . $params->{'section'}; + my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $self->{ce}->{webwork_dir}, + courseName => $newcourse + }); + my $out = {}; + debug("XMLRPC course creation: " . Dumper($params)); + + # declare params + my @professors = (); + my $dbLayout = $ce->{dbLayoutName}; + my %courseOptions = ( dbLayoutName => $dbLayout ); + my %dbOptions; + my @users; + my %optional_arguments; + + my $userClass = $ce->{dbLayouts}->{$dbLayout}->{user}->{record}; + my $passwordClass = $ce->{dbLayouts}->{$dbLayout}->{password}->{record}; + my $permissionClass = $ce->{dbLayouts}->{$dbLayout}->{permission}->{record}; + + runtime_use($userClass); + runtime_use($passwordClass); + runtime_use($permissionClass); + + # configure users, only admin users + # TODO admin user password + my $admin = 'admin'; + my %record = (); + $record{status} = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; + $record{student_id} = $admin; + $record{password} = cryptPassword($admin); + $record{permission} = $ce->{userRoles}{admin}; + $record{user_id} = $admin; + $record{last_name} = 'Administrator'; + + my $User = $userClass->new(%record); + my $PermissionLevel = $permissionClass->new(user_id => $admin, permission => $record{permission}); + my $Password = $passwordClass->new(user_id => $admin, password => $record{password}); + + push @users, [ $User, $Password, $PermissionLevel ]; + + # call + eval { + addCourse( + courseID => $newcourse, + ce => $ce, + courseOptions => \%courseOptions, + dbOptions => \%dbOptions, + users => \@users, + %optional_arguments, + ); + $out->{status} = "success"; + } or do { + $out->{status} = "failure"; + $out->{message} = $@; + }; + + return $out; +} + +sub addUser { + my ($self, $params) = @_; + my $out = {}; + my $db = $self->{db}; + debug("XMLRPC Add User: " . Dumper($params)); + # Two scenarios + # 1. New user + # 2. Dropped user deciding to re-enrol + + my $olduser = $db->getUser($params->{id}); + if ($olduser) { + # a dropped user decided to re-enrol + my $enrolled = $self->{ce}->{statuses}->{Enrolled}->{abbrevs}->[0]; + $olduser->status($enrolled); + $db->putUser($olduser); + # TODO add log entry + # TODO assign sets + $out->{status} = 'success'; + } + else { + # a new user showed up + my $ce = $self->{ce}; + my $id = $params->{'id'}; + + # student record + my $enrolled = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; + my $new_student = $db->{user}->{record}->new(); + $new_student->user_id($id); + $new_student->first_name($params->{'firstname'}); + $new_student->last_name($params->{'lastname'}); + $new_student->status($enrolled); + $new_student->student_id($params->{'studentid'}); + $new_student->email_address($params->{'email'}); + + # password record + my $cryptedpassword = ""; + if ($params->{'userpassword'}) { + $cryptedpassword = cryptPassword($params->{'userpassword'}); + } + elsif ($new_student->student_id()) { + $cryptedpassword = cryptPassword($new_student->student_id()); + } + my $password = $db->newPassword(user_id => $id); + $password->password($cryptedpassword); + + # permission record + my $permission = $params->{'permission'}; + debug($params->{'permission'}); + if (defined($ce->{userRoles}{$permission})) { + $permission = $db->newPermissionLevel( + user_id => $id, + permission => $ce->{userRoles}{$permission}); + } + else { + $permission = $db->newPermissionLevel(user_id => $id, + permission => $ce->{userRoles}{student}); + } + + # commit changes to db + $out->{status} = 'success'; + eval{ $db->addUser($new_student); }; + if ($@) { + $out->{status} = 'failure'; + $out->{message} = "Add user for $id failed!\n"; + } + eval { $db->addPassword($password); }; + if ($@) { + $out->{status} = 'failure'; + $out->{message} = "Add password for $id failed!\n"; + } + eval { $db->addPermissionLevel($permission); }; + if ($@) { + $out->{status} = 'failure'; + $out->{message} = "Add permission for $id failed!\n"; + } + # TODO add log entry + # TODO assign sets + } + + return $out; +} + +sub dropUser { + my ($self, $params) = @_; + my $db = $self->{db}; + my $out = {}; + debug("XMLRPC Drop User: " . Dumper($params)); + # Mark user as dropped + my $drop = $self->{ce}->{statuses}->{Drop}->{abbrevs}->[0]; + my $person = $db->getUser($params->{'id'}); + if ($person) + { + $person->status($drop); + $db->putUser($person); + # TODO add log entry + $out->{status} = 'success'; + } + else + { + $out->{status} = 'failure'; + $out->{message} = 'Could not find user'; + } + + return $out; +} + +1; From a61659dace9f04ba0016552d70dc63e802493f4e Mon Sep 17 00:00:00 2001 From: John Hsu Date: Mon, 26 Mar 2012 15:09:28 -0700 Subject: [PATCH 2/7] Enable/disable course actions webservices and log. The course actions in webservices are now configured to be disabled by default in global.conf.dist. In case there are security concerns. There are 3 configuration options available now under the $webservices hash. 1. enableCourseActions - If set to true, course actions will proceed. Note that the course action enable check takes place after the normal auth credentials check, not before. 2. enableCourseActionsLog - Will keep a record of course creations and user add/drops in the log file. 3. courseActionsLogfile - Where the logfile is stored, defaults to courseactions.log in the default Webwork logs directory. To enable, just override in postlocal.conf e.g. add: $webservices->{enableCourseActions} = 1; $webservices->{enableCourseActionsLog} = 1; --- conf/global.conf.dist | 12 +++++ lib/WebworkWebservice/CourseActions.pm | 63 ++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/conf/global.conf.dist b/conf/global.conf.dist index 6f6d4dfdf3..df4cf6fb43 100644 --- a/conf/global.conf.dist +++ b/conf/global.conf.dist @@ -1049,6 +1049,18 @@ $pgRoot = $pg{directories}{root}; pgproblemeditor1 => 1, pgproblemeditor2 => 0, ); + +################################################################################ +# Webservices +################################################################################ +$webservices = { + enableCourseActions => 0, # enable createCourse, addUser, dropUser + enableCourseActionsLog => 0,# enable logging of course actions + # will log when courses are created and + # when students are added or dropped + courseActionsLogfile => "$webworkDirs{logs}/courseactions.log", +}; + ################################################################################ # Site wide overrides are entered into the file postlocal.conf ################################################################################ diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index d4581b8776..dd8da3000a 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -12,6 +12,10 @@ use base qw(WebworkWebservice); use WeBWorK::Utils qw(runtime_use cryptPassword); use WeBWorK::Utils::CourseManagement qw(addCourse); use WeBWorK::Debug; + +use Time::HiRes qw/gettimeofday/; # for log timestamp +use Date::Format; # for log timestamp + use Data::Dumper; # TODO remove @@ -23,6 +27,13 @@ sub create { courseName => $newcourse }); my $out = {}; + + # make sure course actions are enabled + if (!$ce->{webservices}{enableCourseActions}) { + $out->{status} = "failure"; + $out->{message} = "Course actions disabled by configuration."; + return $out + } debug("XMLRPC course creation: " . Dumper($params)); # declare params @@ -68,6 +79,7 @@ sub create { users => \@users, %optional_arguments, ); + addLog($ce, "New course created: " . $newcourse); $out->{status} = "success"; } or do { $out->{status} = "failure"; @@ -81,7 +93,16 @@ sub addUser { my ($self, $params) = @_; my $out = {}; my $db = $self->{db}; + my $ce = $self->{ce}; debug("XMLRPC Add User: " . Dumper($params)); + + # make sure course actions are enabled + if (!$ce->{webservices}{enableCourseActions}) { + $out->{status} = "failure"; + $out->{message} = "Course actions disabled by configuration."; + return $out + } + # Two scenarios # 1. New user # 2. Dropped user deciding to re-enrol @@ -92,7 +113,8 @@ sub addUser { my $enrolled = $self->{ce}->{statuses}->{Enrolled}->{abbrevs}->[0]; $olduser->status($enrolled); $db->putUser($olduser); - # TODO add log entry + addLog($ce, "User ". $olduser->user_id() . " re-enrolled in " . + $ce->{courseName}); # TODO assign sets $out->{status} = 'success'; } @@ -152,7 +174,8 @@ sub addUser { $out->{status} = 'failure'; $out->{message} = "Add permission for $id failed!\n"; } - # TODO add log entry + addLog($ce, "User ". $new_student->user_id() . " newly added in " . + $ce->{courseName}); # TODO assign sets } @@ -162,8 +185,17 @@ sub addUser { sub dropUser { my ($self, $params) = @_; my $db = $self->{db}; + my $ce = $self->{ce}; my $out = {}; debug("XMLRPC Drop User: " . Dumper($params)); + + # make sure course actions are enabled + if (!$ce->{webservices}{enableCourseActions}) { + $out->{status} = "failure"; + $out->{message} = "Course actions disabled by configuration."; + return $out + } + # Mark user as dropped my $drop = $self->{ce}->{statuses}->{Drop}->{abbrevs}->[0]; my $person = $db->getUser($params->{'id'}); @@ -171,7 +203,8 @@ sub dropUser { { $person->status($drop); $db->putUser($person); - # TODO add log entry + addLog($ce, "User ". $person->user_id() . " dropped from " . + $ce->{courseName}); $out->{status} = 'success'; } else @@ -183,4 +216,28 @@ sub dropUser { return $out; } +sub addLog { + my ($ce, $msg) = @_; + if (!$ce->{webservices}{enableCourseActionsLog}) { + return; + } + my ($sec, $msec) = gettimeofday; + my $date = time2str("%a %b %d %H:%M:%S.$msec %Y", $sec); + + $msg = "[$date] $msg\n"; + + my $logfile = $ce->{webservices}{courseActionsLogfile}; + if (open my $f, ">>", $logfile) + { + print $f $msg; + close $f; + } + else + { + debug("Error, unable to open student updates log file '$logfile' in". + "append mode: $!"); + } + return; +} + 1; From f14c099ad83bcc67a94019d4123c59a5f9a71288 Mon Sep 17 00:00:00 2001 From: John Hsu Date: Mon, 26 Mar 2012 15:44:32 -0700 Subject: [PATCH 3/7] Course creation webservice admin user conf. During course creation using webservices, an admin user is added to the course. This admin user is necessary since there isn't a classlist and we need at least one user in the course. The admin user will be created with the username and password specified in global.conf.dist. These settings can be overwritten in postlocal.conf with: $webservices->{courseActionsAdminUser} = "admin2"; $webservices->{courseActionsAdminPassword} = "blahblah"; The default admin user is "admin". It is recommended that the password be changed from the default. --- conf/global.conf.dist | 5 +++++ lib/WebworkWebservice/CourseActions.pm | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/conf/global.conf.dist b/conf/global.conf.dist index df4cf6fb43..bda85657b4 100644 --- a/conf/global.conf.dist +++ b/conf/global.conf.dist @@ -1059,6 +1059,11 @@ $webservices = { # will log when courses are created and # when students are added or dropped courseActionsLogfile => "$webworkDirs{logs}/courseactions.log", + # courses created using webservices don't have a classlist available + # but we must have at least one user in the course, this is the user + # that will be added with admin access + courseActionsAdminUser => "admin", + courseActionsAdminPassword => "qwePEOWpeioUEzSPfALj", # REMEMBER TO CHANGE }; ################################################################################ diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index dd8da3000a..209783246f 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -54,18 +54,18 @@ sub create { # configure users, only admin users # TODO admin user password - my $admin = 'admin'; + my $adminName = $ce->{webservices}{courseActionsAdminUser}; + my $adminPass = $ce->{webservices}{courseActionsAdminPassword}; my %record = (); $record{status} = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; - $record{student_id} = $admin; - $record{password} = cryptPassword($admin); + $record{password} = cryptPassword($adminPass); $record{permission} = $ce->{userRoles}{admin}; - $record{user_id} = $admin; + $record{user_id} = $adminName; $record{last_name} = 'Administrator'; my $User = $userClass->new(%record); - my $PermissionLevel = $permissionClass->new(user_id => $admin, permission => $record{permission}); - my $Password = $passwordClass->new(user_id => $admin, password => $record{password}); + my $PermissionLevel = $permissionClass->new(user_id => $adminName, permission => $record{permission}); + my $Password = $passwordClass->new(user_id => $adminName, password => $record{password}); push @users, [ $User, $Password, $PermissionLevel ]; From e54ef45691e83301876df44aa410e6dd23198779 Mon Sep 17 00:00:00 2001 From: John Hsu Date: Wed, 28 Mar 2012 16:25:23 -0700 Subject: [PATCH 4/7] Webservices option to assign homework on add user. A new student joining the course late needs to be assigned the same homework sets as the other students. There is now an option to enable assigning all visible homework sets to new and re-enrolled students. By default, this is disabled, to enable, override in postlocal.conf with: $webservices->{courseActionsAssignHomework} = 1; --- conf/global.conf.dist | 3 + lib/WebworkWebservice/CourseActions.pm | 96 ++++++++++++++++++++------ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/conf/global.conf.dist b/conf/global.conf.dist index bda85657b4..e24facdf12 100644 --- a/conf/global.conf.dist +++ b/conf/global.conf.dist @@ -1064,6 +1064,9 @@ $webservices = { # that will be added with admin access courseActionsAdminUser => "admin", courseActionsAdminPassword => "qwePEOWpeioUEzSPfALj", # REMEMBER TO CHANGE + # enable this to assign all visible homework sets to new students + # added by webservices + courseActionsAssignHomework => 0, }; ################################################################################ diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index 209783246f..5bdbe0d449 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -9,6 +9,8 @@ package WebworkWebservice::CourseActions; use WebworkWebservice; use base qw(WebworkWebservice); +use WeBWorK::DB; +use WeBWorK::DB::Utils qw(initializeUserProblem); use WeBWorK::Utils qw(runtime_use cryptPassword); use WeBWorK::Utils::CourseManagement qw(addCourse); use WeBWorK::Debug; @@ -16,9 +18,6 @@ use WeBWorK::Debug; use Time::HiRes qw/gettimeofday/; # for log timestamp use Date::Format; # for log timestamp -use Data::Dumper; # TODO remove - - sub create { my ($self, $params) = @_; my $newcourse = $params->{'name'} . '-' . $params->{'section'}; @@ -34,7 +33,7 @@ sub create { $out->{message} = "Course actions disabled by configuration."; return $out } - debug("XMLRPC course creation: " . Dumper($params)); + debug("Webservices course creation request."); # declare params my @professors = (); @@ -53,7 +52,6 @@ sub create { runtime_use($permissionClass); # configure users, only admin users - # TODO admin user password my $adminName = $ce->{webservices}{courseActionsAdminUser}; my $adminPass = $ce->{webservices}{courseActionsAdminPassword}; my %record = (); @@ -94,7 +92,7 @@ sub addUser { my $out = {}; my $db = $self->{db}; my $ce = $self->{ce}; - debug("XMLRPC Add User: " . Dumper($params)); + debug("Webservices add user request."); # make sure course actions are enabled if (!$ce->{webservices}{enableCourseActions}) { @@ -108,20 +106,21 @@ sub addUser { # 2. Dropped user deciding to re-enrol my $olduser = $db->getUser($params->{id}); + my $id = $params->{'id'}; + my $permission; # stores user's permission level if ($olduser) { # a dropped user decided to re-enrol my $enrolled = $self->{ce}->{statuses}->{Enrolled}->{abbrevs}->[0]; $olduser->status($enrolled); $db->putUser($olduser); - addLog($ce, "User ". $olduser->user_id() . " re-enrolled in " . + addLog($ce, "User ". $id . " re-enrolled in " . $ce->{courseName}); - # TODO assign sets $out->{status} = 'success'; + $permission = $db->getPermissionLevel($id); } else { # a new user showed up my $ce = $self->{ce}; - my $id = $params->{'id'}; # student record my $enrolled = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; @@ -145,8 +144,7 @@ sub addUser { $password->password($cryptedpassword); # permission record - my $permission = $params->{'permission'}; - debug($params->{'permission'}); + $permission = $params->{'permission'}; if (defined($ce->{userRoles}{$permission})) { $permission = $db->newPermissionLevel( user_id => $id, @@ -174,9 +172,20 @@ sub addUser { $out->{status} = 'failure'; $out->{message} = "Add permission for $id failed!\n"; } - addLog($ce, "User ". $new_student->user_id() . " newly added in " . + + addLog($ce, "User ". $id . " newly added in " . $ce->{courseName}); - # TODO assign sets + } + + # only students are assigned homework + if ($ce->{webservices}{courseActionsAssignHomework} && + $permission->{permission} == $ce->{userRoles}{student}) { + debug("Assigning homework."); + my $ret = assignVisibleSets($db, $id); + if ($ret) { + $out->{status} = 'failure'; + $out->{message} = "User created but unable to assign sets. $ret"; + } } return $out; @@ -187,7 +196,7 @@ sub dropUser { my $db = $self->{db}; my $ce = $self->{ce}; my $out = {}; - debug("XMLRPC Drop User: " . Dumper($params)); + debug("Webservices drop user request."); # make sure course actions are enabled if (!$ce->{webservices}{enableCourseActions}) { @@ -199,16 +208,14 @@ sub dropUser { # Mark user as dropped my $drop = $self->{ce}->{statuses}->{Drop}->{abbrevs}->[0]; my $person = $db->getUser($params->{'id'}); - if ($person) - { + if ($person) { $person->status($drop); $db->putUser($person); addLog($ce, "User ". $person->user_id() . " dropped from " . $ce->{courseName}); $out->{status} = 'success'; } - else - { + else { $out->{status} = 'failure'; $out->{message} = 'Could not find user'; } @@ -227,17 +234,62 @@ sub addLog { $msg = "[$date] $msg\n"; my $logfile = $ce->{webservices}{courseActionsLogfile}; - if (open my $f, ">>", $logfile) - { + if (open my $f, ">>", $logfile) { print $f $msg; close $f; } - else - { + else { debug("Error, unable to open student updates log file '$logfile' in". "append mode: $!"); } return; } +sub assignVisibleSets { + my ($db, $userID) = @_; + my @globalSetIDs = $db->listGlobalSets; + my @GlobalSets = $db->getGlobalSets(@globalSetIDs); + + my $i = -1; + foreach my $GlobalSet (@GlobalSets) { + $i++; + if (not defined $GlobalSet) { + debug("Record not found for global set $globalSetIDs[$i]"); + next; + } + if (!$GlobalSet->visible) { + next; + } + + # assign set to user + 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 ( $@ && !($@ =~ m/user set exists/)) { + return "Failed to assign set to user $userID"; + } + + # assign problem + my @GlobalProblems = grep { defined $_ } $db->getAllGlobalProblems($setID); + foreach my $GlobalProblem (@GlobalProblems) { + my $seed = int( rand( 2423) ) + 36; + my $UserProblem = $db->newUserProblem; + $UserProblem->user_id($userID); + $UserProblem->set_id($GlobalProblem->set_id); + $UserProblem->problem_id($GlobalProblem->problem_id); + initializeUserProblem($UserProblem, $seed); + eval { $db->addUserProblem($UserProblem) }; + if ($@ && !($@ =~ m/user problem exists/)) { + return "Failed to assign problems to user $userID"; + } + } + } + + return 0; +} + 1; From 0eee6d63d716b52fd482a58de0c11223d9585edc Mon Sep 17 00:00:00 2001 From: John Hsu Date: Mon, 2 Apr 2012 15:46:33 -0700 Subject: [PATCH 5/7] Limit webservice course creation to admin users. Course creation webservice requests will only succeed if you are successfully logged into the admin course and have instructor or above permission. This check is done in CourseActions. Note that there is a possibly duplicate check of the permission level since the session initiation function already checks for professor permissions. --- lib/WebworkWebservice/CourseActions.pm | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index 5bdbe0d449..f67d00c072 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -27,13 +27,29 @@ sub create { }); my $out = {}; + debug("Webservices course creation request."); # make sure course actions are enabled if (!$ce->{webservices}{enableCourseActions}) { + debug("Course actions disabled by configuration."); $out->{status} = "failure"; $out->{message} = "Course actions disabled by configuration."; return $out } - debug("Webservices course creation request."); + # only users from the admin course with appropriate permissions allowed + if (!($self->{ce}->{courseName} eq 'admin')) { + debug("Course creation attempt when not logged into admin course."); + $out->{status} = "failure"; + $out->{message} = "Course creation allowed only for admin course users."; + return $out + } + # prof check is actually done when initiating session, this is just in case + if (!$self->{authz}->hasPermissions($params->{'userID'}, + 'create_and_delete_courses')) { + debug("Course creation attempt with insufficient permission level."); + $out->{status} = "failure"; + $out->{message} = "Insufficient permission level."; + return $out + } # declare params my @professors = (); From b8e4efd0cc9c928b0ddbfdd0cb430e8cbe01a9b9 Mon Sep 17 00:00:00 2001 From: John Hsu Date: Mon, 2 Apr 2012 16:05:58 -0700 Subject: [PATCH 6/7] Drop section param for create course webservice. As noted by Mike, section conflict with existing vocabulary already in use by Webworks. Now the create course webservice call will just use the name param for the new course's courseID. This way, it'll be less institution specific if someone doesn't use the name-number format, e.g.: MATH100-100. --- lib/WebworkWebservice.pm | 4 +--- lib/WebworkWebservice/CourseActions.pm | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/WebworkWebservice.pm b/lib/WebworkWebservice.pm index 82391424aa..6e2c127192 100644 --- a/lib/WebworkWebservice.pm +++ b/lib/WebworkWebservice.pm @@ -409,11 +409,9 @@ sub tex2pdf { # 'password' => 'admin', # auth credentials # 'courseID' => 'admin', # used to initiate a # 'session_key' => 'key', # session. -# "name": "KEJI554", -# "section": 264, +# "name": "TEST100-100", # This will be the new course's id #} # Note that we log into the admin course to create courses. -# The course title will be a concatenation of the name and section number. sub createCourse { my $class = shift; my $in = shift; diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index f67d00c072..7b24b663cb 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -20,7 +20,7 @@ use Date::Format; # for log timestamp sub create { my ($self, $params) = @_; - my $newcourse = $params->{'name'} . '-' . $params->{'section'}; + my $newcourse = $params->{'name'}; my $ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $self->{ce}->{webwork_dir}, courseName => $newcourse From 65dd74d7f1f89b36acba54c5854a261ea8910708 Mon Sep 17 00:00:00 2001 From: John Hsu Date: Mon, 2 Apr 2012 16:37:22 -0700 Subject: [PATCH 7/7] Course creation webservice copies admin users. On the regular webwork UI, when a new course is created, the instructors from the admin course are copied over to the new course by default. This allows admins easy access to the new course. The webservice course creation call now also implements this by default. The custom admin user configuration options are now dropped as a result. E.g., thse are no longer needed in postlocal.conf: $webservices->{courseActionsAdminUser} = "admin"; $webservices->{courseActionsAdminPassword} = "admin"; --- conf/global.conf.dist | 5 ---- lib/WebworkWebservice/CourseActions.pm | 33 ++++++++++---------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/conf/global.conf.dist b/conf/global.conf.dist index e24facdf12..2ef4c5ad5a 100644 --- a/conf/global.conf.dist +++ b/conf/global.conf.dist @@ -1059,11 +1059,6 @@ $webservices = { # will log when courses are created and # when students are added or dropped courseActionsLogfile => "$webworkDirs{logs}/courseactions.log", - # courses created using webservices don't have a classlist available - # but we must have at least one user in the course, this is the user - # that will be added with admin access - courseActionsAdminUser => "admin", - courseActionsAdminPassword => "qwePEOWpeioUEzSPfALj", # REMEMBER TO CHANGE # enable this to assign all visible homework sets to new students # added by webservices courseActionsAssignHomework => 0, diff --git a/lib/WebworkWebservice/CourseActions.pm b/lib/WebworkWebservice/CourseActions.pm index 7b24b663cb..29fb661f7a 100644 --- a/lib/WebworkWebservice/CourseActions.pm +++ b/lib/WebworkWebservice/CourseActions.pm @@ -21,10 +21,13 @@ use Date::Format; # for log timestamp sub create { my ($self, $params) = @_; my $newcourse = $params->{'name'}; + # note this ce is different from $self->{ce}! my $ce = WeBWorK::CourseEnvironment->new({ webwork_dir => $self->{ce}->{webwork_dir}, courseName => $newcourse }); + my $db = $self->{db}; + my $authz = $self->{authz}; my $out = {}; debug("Webservices course creation request."); @@ -63,27 +66,17 @@ sub create { my $passwordClass = $ce->{dbLayouts}->{$dbLayout}->{password}->{record}; my $permissionClass = $ce->{dbLayouts}->{$dbLayout}->{permission}->{record}; - runtime_use($userClass); - runtime_use($passwordClass); - runtime_use($permissionClass); - - # configure users, only admin users - my $adminName = $ce->{webservices}{courseActionsAdminUser}; - my $adminPass = $ce->{webservices}{courseActionsAdminPassword}; - my %record = (); - $record{status} = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; - $record{password} = cryptPassword($adminPass); - $record{permission} = $ce->{userRoles}{admin}; - $record{user_id} = $adminName; - $record{last_name} = 'Administrator'; - - my $User = $userClass->new(%record); - my $PermissionLevel = $permissionClass->new(user_id => $adminName, permission => $record{permission}); - my $Password = $passwordClass->new(user_id => $adminName, password => $record{password}); + # copy instructors from admin course + # modified from do_add_course in WeBWorK::ContentGenerator::CourseAdmin + foreach my $userID ($db->listUsers) { + my $User = $db->getUser($userID); + my $Password = $db->getPassword($userID); + my $PermissionLevel = $db->getPermissionLevel($userID); + push @users, [ $User, $Password, $PermissionLevel ] + if $authz->hasPermissions($userID,"create_and_delete_courses"); + } - push @users, [ $User, $Password, $PermissionLevel ]; - - # call + # all data prepped, try to actually add the course eval { addCourse( courseID => $newcourse,