Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two factor authentication. #2335

Merged
merged 14 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ RUN apt-get update \
libfile-find-rule-perl-perl \
libfile-sharedir-install-perl \
libfuture-asyncawait-perl \
libgd-barcode-perl \
libgd-perl \
libhtml-scrubber-perl \
libhtml-template-perl \
Expand All @@ -113,6 +114,7 @@ RUN apt-get update \
libmail-sender-perl \
libmariadb-dev \
libmath-random-secure-perl \
libmime-base32-perl \
libmime-tools-perl \
libminion-backend-sqlite-perl \
libminion-perl \
Expand Down
2 changes: 2 additions & 0 deletions DockerfileStage1
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ RUN apt-get update \
libfile-find-rule-perl-perl \
libfile-sharedir-install-perl \
libfuture-asyncawait-perl \
libgd-barcode-perl \
libgd-perl \
libhtml-scrubber-perl \
libhtml-template-perl \
Expand All @@ -75,6 +76,7 @@ RUN apt-get update \
libmail-sender-perl \
libmariadb-dev \
libmath-random-secure-perl \
libmime-base32-perl \
libmime-tools-perl \
libminion-backend-sqlite-perl \
libminion-perl \
Expand Down
2 changes: 2 additions & 0 deletions bin/check_modules.pl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ =head1 DESCRIPTION
File::Temp
Future::AsyncAwait
GD
GD::Barcode::QRcode
Getopt::Long
Getopt::Std
HTML::Entities
Expand All @@ -117,6 +118,7 @@ =head1 DESCRIPTION
Locale::Maketext::Lexicon
Locale::Maketext::Simple
LWP::Protocol::https
MIME::Base32
MIME::Base64
Math::Random::Secure
Minion
Expand Down
19 changes: 19 additions & 0 deletions bin/reset2fa
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
warn "Pass users as additional arguments on the command line.\n"
. "Usage: wwsh $ce->{courseName} /opt/webwork/webwork2/bin/reset2fa [users]\n"
unless @ARGV;

for (@ARGV) {
my $password = eval { $db->getPassword($_) };
if ($@) {
warn "Unable to retrieve password record for $_ from the database: $@\n";
next;
}

$password->otp_secret('');
eval { $db->putPassword($password) };
if ($@) {
warn "Unable to reset two factor authentication secret for $_: $@\n";
} else {
print "Successfully reset two factor authentication for $_.\n";
}
}
61 changes: 61 additions & 0 deletions conf/defaults.config
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ $authen{admin_module} = ['WeBWorK::Authen::Basic_TheLastOption'];
%permissionLevels = (
login => "guest",
navigation_allowed => "guest",
use_two_factor_auth => "student",
report_bugs => "ta",
submit_feedback => "student",
change_password => "student",
Expand Down Expand Up @@ -944,6 +945,66 @@ $CookieSecure = 0;
# when the browser session ends. The default value is 7 days.
$CookieLifeTime = 604800;

################################################################################
# Two Factor Authentication
################################################################################

# The following variables enable two factor authentication and control how it
# works. Two factor authentication only applies to courses that use password
# authentication, i.e., the Basic_TheLastOption user authentication module
# without an external authentication approach (like LTI, CAS, Shibboleth, etc.).
# It is recommended that two factor authentication be enabled for all courses
# that use password authentication. It is extremely highly recommended that this
# be enabled for the admin course. Two factor authentication works with an
# authenticator app on a mobile device (such as Google Authenticator,
# Microsoft authenticator, Twilio Authy, etc.).

# $twoFA{enabled} determines if two factor authentication is enabled for a
# course. If this is set to 0, then two factor authentication is disabled for
# all courses. If this is 1 (the default), then two factor authentication is
# enabled for all courses that use password authentication. If this is a string
# course name like 'admin', then two factor authentication is enabled only for
# that course. If this is an array of string course names, then two factor
# authentication is enabled only for those courses listed. This can also be set
# in a course's course.conf file. Note that only the values of 0 and 1 make
# sense there.
$twoFA{enabled} = 1;

# There are two methods that can be used to setup two factor authentication when
# a user signs in for the first time. The setup information can be emailed to
# the user, or can be directly displayed in the browser on the next page that is
# shown after password verification succeeds.
#
# If $twoFA{email_sender} is set, then the email approach will be used. In this
# case, after a user signs in and the password is verified, the user will be
# sent an email containing a QR code and instructions on how to set up a OTP
# generator app. This is probably a more secure way to set up two factor
# authentication, as it ensures the user setting it up is the correct user. Note
# that if a user does not have an email address, then the browser method below
# will be used as a fallback.
#
# If $twoFA{email_sender} is not set, then after a user signs in and the
# password is verified, the QR code, OTP link, and instructions will be
# displayed directly on the page in the browser. This is potentially less secure
# because a hacker could guess a username and password before a user has setup
# two factor authentication (particularly if the username and password are
# initially the same), and then the hacker would gain access to that user's
# account, and the actual user would be locked out. Note that you will need to
# use this option if your server can not send emails. Also note that no-reply
# addresses may be blocked by the email server or marked as spam. So it may be
# better to find a valid email address to use for this.
$twoFA{email_sender} = '';

# When a user signs in and enters the two factor authentication code, the user
# has the option to skip two factor verification on a given device for
# subsequent logins. That will only last for the amount of time set as the
# skip_verification_code_interval. By default this is set to one year. However,
# good security practices most likely recommend a shorter time interval for
# this. So change this value if you want to require a shorter and thus more
# secure time interval before users will need to enter the two factor
# authentication code again.
$twoFA{skip_verification_code_interval} = 3600 * 24 * 365;

################################################################################
# WeBWorK Caliper
################################################################################
Expand Down
71 changes: 70 additions & 1 deletion conf/localOverrides.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ $mail{feedbackRecipients} = [
# $permissionLevels{login} = "guest";

# The above code would give the permission to login to any user with permission
# level guest or higher.
# level guest or higher (which is the default).

# By default answers for all users are logged to the past_answers table in the database
# and the myCourse/logs/answer_log file. If you only want answers logged for users below
Expand Down Expand Up @@ -574,6 +574,75 @@ $mail{feedbackRecipients} = [
#$CookieLifeTime = 604800;
#$CookieLifeTime = "session";

################################################################################
# Two Factor Authentication
################################################################################

# The following variables enable two factor authentication and control how it
# works. Two factor authentication only applies to courses that use password
# authentication, i.e., the Basic_TheLastOption user authentication module
# without an external authentication approach (like LTI, CAS, Shibboleth, etc.).
# It is recommended that two factor authentication be enabled for all courses
# that use password authentication. It is extremely highly recommended that this
# be enabled for the admin course. Two factor authentication works with an
# authenticator app on a mobile device (such as Google Authenticator,
# Microsoft authenticator, Twilio Authy, etc.).

# $twoFA{enabled} determines if two factor authentication is enabled for a
# course. If this is set to 0, then two factor authentication is disabled for
# all courses. If this is 1 (the default), then two factor authentication is
# enabled for all courses that use password authentication. If this is a string
# course name like 'admin', then two factor authentication is enabled only for
# that course. If this is an array of string course names, then two factor
# authentication is enabled only for those courses listed. This can also be set
# in a course's course.conf file. Note that only the values of 0 and 1 make
# sense there.
#$twoFA{enabled} = $admin_course_id; # Use this at the very least.
#$twoFA{enabled} = [$admin_course_id, 'another_courseID', 'another_courseID_3'];

# There are two methods that can be used to setup two factor authentication when
# a user signs in for the first time. The setup information can be emailed to
# the user, or can be directly displayed in the browser on the next page that is
# shown after password verification succeeds.
#
# If $twoFA{email_sender} is set, then the email approach will be used. In this
# case, after a user signs in and the password is verified, the user will be
# sent an email containing a QR code and instructions on how to set up a OTP
# generator app. This is probably a more secure way to set up two factor
# authentication, as it ensures the user setting it up is the correct user. Note
# that if a user does not have an email address, then the browser method below
# will be used as a fallback.
#
# If $twoFA{email_sender} is not set, then after a user signs in and the
# password is verified, the QR code, OTP link, and instructions will be
# displayed directly on the page in the browser. This is potentially less secure
# because a hacker could guess a username and password before a user has setup
# two factor authentication (particularly if the username and password are
# initially the same), and then the hacker would gain access to that user's
# account, and the actual user would be locked out. Note that you will need to
# use this option if your server can not send emails. Also note that no-reply
# addresses may be blocked by the email server or marked as spam. So it may be
# better to find a valid email address to use for this.
#$twoFA{email_sender} = '[email protected]';

# When a user signs in and enters the two factor authentication code, the user
# has the option to skip two factor verification on a given device for
# subsequent logins. That will only last for the amount of time set as the
# skip_verification_code_interval. By default this is set to one year. However,
# good security practices most likely recommend a shorter time interval for
# this. So change this value if you want to require a shorter and thus more
# secure time interval before users will need to enter the two factor
# authentication code again.
#$twoFA{skip_verification_code_interval} = 3600 * 24 * 7;

# By default all users with the role of "student" or higher are required to use
# two factor authentication when signing in with a username and password. If
# you want to disable two factor authentication for students, but require it for
# instructors then set the permission level below to "login_proctor" (or
# higher).

#$permissionLevels{use_two_factor_auth} = "login_proctor";

################################################################################
# Searching for set.def files to import
################################################################################
Expand Down
7 changes: 7 additions & 0 deletions conf/webwork2.mojolicious.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,10 @@ debug:
hardcopy:
# If 1, don't delete temporary files created when a hardcopy is generated.
preserve_temp_files: 0

# Set this to 1 to allow the html2xml and render_rpc endpoints to disable
# cookies and thus skip two factor authentication. This should never be enabled
# for a typical webwork server. This should only be enabled if you want to
# allow serving content via these endpoints to links in external websites with
# usernames and passwords embedded in them such as for PreTeXt textbooks.
allow_unsecured_rpc: 0
8 changes: 4 additions & 4 deletions htdocs/js/UserList/userlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@
e.stopPropagation();
show_errors(['export_file_err_msg'], [export_filename, export_select_target]);
}
} else if (action === 'delete') {
const delete_confirm = document.getElementById('delete_select');
} else if (action === 'delete' || action === 'reset_2fa') {
const action_confirm = document.getElementById(`${action}_select`);
if (!is_user_selected()) {
e.preventDefault();
e.stopPropagation();
} else if (delete_confirm.value != 'yes') {
} else if (action_confirm.value != 'yes') {
e.preventDefault();
e.stopPropagation();
show_errors(['delete_confirm_err_msg'], [delete_confirm]);
show_errors([`${action}_confirm_err_msg`], [action_confirm]);
}
}
});
Expand Down
18 changes: 13 additions & 5 deletions lib/WeBWorK.pm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use WeBWorK::Debug;
use WeBWorK::Upload;
use WeBWorK::Utils qw(runtime_use);
use WeBWorK::ContentGenerator::Login;
use WeBWorK::ContentGenerator::TwoFactorAuthentication;
use WeBWorK::ContentGenerator::LoginProctor;

our %SeedCE;
Expand Down Expand Up @@ -90,12 +91,13 @@ async sub dispatch ($c) {
if ($c->current_route =~ /^(render_rpc|instructor_rpc|html2xml)$/) {
$c->{rpc} = 1;

$c->stash(disable_cookies => 1) if $c->current_route eq 'render_rpc' && $c->param('disableCookies');
$c->stash(disable_cookies => 1)
if $c->current_route eq 'render_rpc' && $c->param('disableCookies') && $c->config('allow_unsecured_rpc');

# This provides compatibility for legacy html2xml parameters.
# This should be deleted when the html2xml endpoint is removed.
if ($c->current_route eq 'html2xml') {
$c->stash(disable_cookies => 1);
$c->stash(disable_cookies => 1) if $c->config('allow_unsecured_rpc');
for ([ 'userID', 'user' ], [ 'course_password', 'passwd' ], [ 'session_key', 'key' ]) {
$c->param($_->[1], $c->param($_->[0])) if defined $c->param($_->[0]) && !defined $c->param($_->[1]);
}
Expand Down Expand Up @@ -268,9 +270,15 @@ async sub dispatch ($c) {
# If the user is logging out and authentication failed, still logout.
return 1 if $displayModule eq 'WeBWorK::ContentGenerator::Logout';

debug("Bad news: authentication failed!\n");
debug("Rendering WeBWorK::ContentGenerator::Login\n");
await WeBWorK::ContentGenerator::Login->new($c)->go();
if ($c->authen->session->{two_factor_verification_needed}) {
debug("Login succeeded but two factor authentication is needed.\n");
debug("Rendering WeBWorK::ContentGenerator::TwoFactorAuthentication\n");
await WeBWorK::ContentGenerator::TwoFactorAuthentication->new($c)->go();
} else {
debug("Bad news: authentication failed!\n");
debug("Rendering WeBWorK::ContentGenerator::Login\n");
await WeBWorK::ContentGenerator::Login->new($c)->go();
}
return 0;
}
}
Expand Down
Loading