diff --git a/conf/authen_shibboleth.conf.dist b/conf/authen_shibboleth.conf.dist
new file mode 100644
index 0000000000..7453144c89
--- /dev/null
+++ b/conf/authen_shibboleth.conf.dist
@@ -0,0 +1,143 @@
+#!perl
+################################################################################
+# Configuration for using Shibboleth authentication.
+#
+# To enable Shibboleth authentication, copy this file to
+# conf/authen_shibboleth.conf and uncomment the appropriate lines in
+# localOverrides.conf.
+#
+################################################################################
+
+# Note that Shibboleth authentication will only work if webwork2 is proxied via
+# apache2 and a Shibboleth service provider (mod_shib) is installed and
+# configured. Instructions on how to configure the Shibboleth service provider
+# are below. These instructions are specifically for Ubuntu, and setup will be
+# slightly different on other systems.
+#
+# Install the Shibboleth service provider for apache2 by installing the Ubuntu
+# apache2-mod-shib package.
+#
+# Modify the /etc/shibboleth/shibboleth2.xml file as follows.
+#
+# Change the "entityID" attribute of the "ApplicationDefaults" tag to
+# https://your.server.edu`. Note that the Shibboleth identity provider that you
+# will use will also need to be configured to allow this "entityID" to work with
+# it.
+#
+# Change the "SSO" tag in the "Sessions" section to
+# SAML2
+#
+# Near the end of the file where example "MetadataProvider" sections are
+# located add the following "MetadataProvider" tag.
+#
+#
+# Note that further adjustments to that file may be needed depending on how your
+# Shibboleth identity provider is set up.
+#
+# Next modify the /etc/shibboleth/attribute-map.xml file by adding the attribute
+# that will be used for $shibboleth{mapping}{user_id} below. For example, if
+# you are using the "uid" as in the default value of that variable, then add
+#
+# to the Attributes section of the file.
+# Note the file already has some attributes configured, and so you may not need
+# to modify that file at all. For example, if you use "eppn" for
+# $shibboleth{mapping}{user_id}, then you don't need to change that file, since
+# "eppn" is already listed.
+#
+# Finally, configure apache2 to protect route webwork2 course URLs to the
+# Shibboleth service provider by adding one of the following to your apache2
+# site configuration file.
+#
+#
+# AuthType shibboleth
+# ShibRequestSetting requireSession 1
+# Require valid-user
+# RequestHeader unset uid
+# RequestHeader set uid %{uid}e env=uid
+#
+#
+# or
+#
+#
+# AuthType shibboleth
+# ShibRequestSetting requireSession 0
+# Require shibboleth
+# RequestHeader unset uid
+# RequestHeader set uid %{uid}e env=uid
+#
+#
+# Use the first if you want strict Shibboleth authentication. With this set up
+# the webwork2 app will never see course URL requests if the user is not first
+# authenticated with the Shibboleth identity provider. The apache2 Shibboleth
+# service provider module will redirect the user first.
+#
+# Use the second if you want lazy Shibboleth authentication. With this set up
+# the course URL requests will continue to the webwork2 app even if the user has
+# not authenticated with the Shibboleth identity provider. The webwork2 app will
+# redirect the user if authentication is needed. This allows for the usage of
+# the $shibboleth{bypass_query} parameter or the $shiboff option described
+# below.
+#
+# In both cases change all instances of "uid" to whatever you are using for the
+# value of $shibboleth{mapping}{user_id} below.
+#
+# Execute "sudo shibd -t" to test the Shibboleth service provider configuration.
+# Make sure to execute "sudo systemctl restart apache2" and
+# "sudo systemctl restart shibd" so that settings take effect.
+
+################################################################################
+
+# Set Shibboleth as the authentication module to use.
+# Comment out 'WeBWorK::Authen::Basic_TheLastOption' if bypassing Saml2
+# authentication via the bypass query option (see $shibboleth{bypass_query}
+# below) or the $shiboff option are both not allowed .
+$authen{user_module} = [
+ 'WeBWorK::Authen::Shibboleth',
+ 'WeBWorK::Authen::Basic_TheLastOption'
+];
+
+# List of authentication modules that may be used to enter the admin course.
+# This is used instead of $authen{user_module} when logging into the admin
+# course. Since the admin course provides overall power to add/delete courses,
+# access to this course should be protected by the best possible authentication
+# you have available to you.
+$authen{admin_module} = [
+ 'WeBWorK::Authen::Shibboleth'
+];
+
+# Set $shiboff to 1 to disable Shibboleth authentication. Usually this is not
+# set here, but in the course.conf file for a course for which Shibboleth
+# authentication is to be disabled.
+#$shiboff = 0;
+
+# This URL query parameter can be added to the end of a course URL to skip the
+# Shibboleth authentication module and go to the next one, for example,
+# http://your.school.edu/webwork2/courseID?bypassShib=1. Comment out the next
+# line to disable this feature.
+$shibboleth{bypass_query} = 'bypassShib';
+
+# The Shibboleth service provider login path.
+$shibboleth{login_script} = '/Shibboleth.sso/Login';
+
+# The Shibboleth service provider logout path. The default setting below
+# demonstrates how to have the user redirected back to the course login page
+# after the logout is complete.
+$shibboleth{logout_script} = '/Shibboleth.sso/Logout?return=' . $server_root_url . $webwork_url;
+
+# Set to 1 to allow Shibboleth to manage session time instead of webwork.
+$shibboleth{manage_session_timeout} = 1;
+
+# The user id hash method. The possible values are 'none' or 'MD5'. Use it when
+# you want to hide real user_ids from showing in the URL.
+$shibboleth{hash_user_id_method} = 'none';
+
+# The salt to use for the hash method.
+$shibboleth{hash_user_id_salt} = '';
+
+# Set to the Shibboleth attribute that will be used for the webwork user id.
+$shibboleth{mapping}{user_id} = 'uid';
diff --git a/conf/localOverrides.conf.dist b/conf/localOverrides.conf.dist
index 39b4a02afe..555f2f0f91 100644
--- a/conf/localOverrides.conf.dist
+++ b/conf/localOverrides.conf.dist
@@ -527,6 +527,16 @@ $mail{feedbackRecipients} = [
#include("conf/authen_ldap.conf");
+################################################################################
+# Shibboleth Authentication
+################################################################################
+
+# Uncomment the following line to enable Shibboleth authentication. You will
+# also need to copy the file authen_shibboleth.conf.dist to authen_shibboleth.conf,
+# and then edit that file to fill in the settings for your installation.
+
+#include("conf/authen_shibboleth.conf");
+
################################################################################
# Session Management
################################################################################
diff --git a/lib/WeBWorK/Authen/Shibboleth.pm b/lib/WeBWorK/Authen/Shibboleth.pm
index d29cc86707..d0d1d419cb 100644
--- a/lib/WeBWorK/Authen/Shibboleth.pm
+++ b/lib/WeBWorK/Authen/Shibboleth.pm
@@ -14,175 +14,112 @@
################################################################################
package WeBWorK::Authen::Shibboleth;
-use base qw/WeBWorK::Authen/;
+use Mojo::Base 'WeBWorK::Authen', -signatures;
=head1 NAME
WeBWorK::Authen::Shibboleth - Authentication plug in for Shibboleth.
-This is basd on Cosign.pm
-For documentation, please refer to http://webwork.maa.org/wiki/External_(Shibboleth)_Authentication
+=head1 SYNOPSIS
-to use: include in localOverrides.conf or course.conf
- $authen{user_module} = "WeBWorK::Authen::Shibboleth";
-and add /webwork2/courseName as a Shibboleth Protected
-Location or enable lazy session.
+To use this module copy C to
+C, and uncomment the line in C
+that reads C.
-if $c->ce->{shiboff} is set for a course, authentication reverts
-to standard WeBWorK authentication.
-
-add the following to localOverrides.conf to setup the Shibboleth
-
-$shibboleth{login_script} = "/Shibboleth.sso/Login"; # login handler
-$shibboleth{logout_script} = "/Shibboleth.sso/Logout?return=".$server_root_url.$webwork_url; # return URL after logout
-$shibboleth{manage_session_timeout} = 1; # allow shib to manage session time instead of webwork
-$shibboleth{hash_user_id_method} = "MD5"; # possible values none, MD5. Use it when you want to hide real user_ids from showing in url.
-$shibboleth{hash_user_id_salt} = ""; # salt for hash function
-#define mapping between shib and webwork
-$shibboleth{mapping}{user_id} = "username";
+Refer to the L
+documentation on the WeBWorK wiki and the instructions in the comments of the
+C file.
=cut
-use strict;
-use warnings;
-
-use WeBWorK::Debug;
+use Digest;
-# this is similar to the method in the base class, except that Shibboleth
-# ensures that we don't get to the address without a login. this means
-# that we can't allow guest logins, but don't have to do any password
-# checking or cookie management.
+use WeBWorK::Debug qw(debug);
-sub get_credentials {
- my ($self) = @_;
- my $c = $self->{c};
- my $ce = $c->ce;
- my $db = $c->db;
+sub request_has_data_for_this_verification_module ($self) {
+ my $c = $self->{c};
- if ($ce->{shiboff} || $c->param('bypassShib')) {
- return $self->SUPER::get_credentials(@_);
+ # Skip if shiboff is set in the course environment or the bypassShib param is set.
+ if ($c->ce->{shiboff} || ($c->ce->{shibboleth}{bypass_query} && $c->param($c->ce->{shibboleth}{bypass_query}))) {
+ debug('Shibboleth authen module bypass detected. Going to next authentication module.');
+ return 0;
}
- $c->stash(disable_cookies => 1);
+ return 1;
+}
- debug("Shib is on!");
+sub get_credentials ($self) {
+ my $c = $self->{c};
+ my $ce = $c->ce;
+ my $db = $c->db;
- # set external auth parameter so that Login.pm knows
- # not to rely on internal logins if there's a check_user
- # failure.
+ $c->stash(disable_cookies => 1);
$self->{external_auth} = 1;
- if ($c->param("user") && !$c->param("force_passwd_authen")) {
- return $self->SUPER::get_credentials(@_);
- }
+ debug('Checking for shibboleth authentication headers.');
- # This next part is necessary because some parts of webwork (e.g.,
- # WebworkWebservice.pm) need to replace the get_credentials() routine,
- # but only replace the one in the parent class (out of caution,
- # presumably). Therefore, we end up here even when authenticating
- # for WebworkWebservice.pm. This would cause authentication failures
- # when authenticating javascript web service requests (e.g., the
- # Library Browser).
-
- if ($c->{rpc}) {
- debug("falling back to superclass get_credentials (rpc call)");
- return $self->SUPER::get_credentials(@_);
- }
+ my $user_id;
+ $user_id = $c->req->headers->header($ce->{shibboleth}{mapping}{user_id}) if $ce->{shibboleth}{mapping}{user_id};
- my $user_id = "";
- my $shib_header = $ce->{shibboleth}{mapping}{user_id};
-
- if ($shib_header ne "") {
- $user_id = $c->req->headers->header($shib_header);
- }
+ if (defined $user_id && $user_id ne '') {
+ debug("Got shibboleth header ($ce->{shibboleth}{mapping}{user_id}) and user_id ($user_id)");
- if ($user_id ne "") {
- debug("Got shib header ($shib_header) and user_id ($user_id)");
if (defined($ce->{shibboleth}{hash_user_id_method})
- && $ce->{shibboleth}{hash_user_id_method} ne "none"
- && $ce->{shibboleth}{hash_user_id_method} ne "")
+ && $ce->{shibboleth}{hash_user_id_method} ne 'none'
+ && $ce->{shibboleth}{hash_user_id_method} ne '')
{
- use Digest;
my $digest = Digest->new($ce->{shibboleth}{hash_user_id_method});
- $digest->add(
- uc($user_id)
- . (defined $ce->{shibboleth}{hash_user_id_salt} ? $ce->{shibboleth}{hash_user_id_salt} : ""));
+ $digest->add(uc($user_id) . ($ce->{shibboleth}{hash_user_id_salt} // ''));
$user_id = $digest->hexdigest;
}
- $self->{'user_id'} = $user_id;
- $self->{c}->param("user", $user_id);
- # the session key isn't used (Shibboleth is managing this
- # for us), and we want to force checking against the
- # site_checkPassword
- $self->{'session_key'} = undef;
- $self->{'password'} = 1;
- $self->{login_type} = "normal";
- $self->{'credential_source'} = "params";
+ $self->{user_id} = $user_id;
+ $c->param('user', $user_id);
+ $self->{login_type} = 'normal';
+ $self->{credential_source} = 'params';
return 1;
}
- debug("Couldn't shib header or user_id");
- my $go_to = $ce->{shibboleth}{login_script} . "?target=" . $c->url_for->to_abs;
- $self->{redirect} = $go_to;
- $c->redirect_to($go_to);
+ debug('Unable to obtain user id from Shibboleth header.');
+ $self->{redirect} = $ce->{shibboleth}{login_script} . '?target=' . $c->url_for->to_abs;
+ $c->redirect_to($self->{redirect});
return 0;
}
-sub site_checkPassword {
- my ($self, $userID, $clearTextPassword) = @_;
-
- if ($self->{c}->ce->{shiboff} || $self->{c}->param('bypassShib')) {
- return $self->SUPER::checkPassword(@_);
- } else {
- # this is easy; if we're here at all, we've authenticated
- # through shib
- return 1;
- }
+sub authenticate ($self) {
+ # The Shibboleth identity provider handles authentication, so just return 1.
+ return 1;
}
-# this is a bit of a cheat, because it does the redirect away from the
-# logout script or what have you, but I don't see a way around that.
-sub forget_verification {
- my ($self, @args) = @_;
- my $c = $self->{c};
-
- if ($c->ce->{shiboff}) {
- return $self->SUPER::forget_verification(@_);
- } else {
- $self->{was_verified} = 0;
- $self->{redirect} = $c->ce->{shibboleth}{logout_script};
- }
+sub logout_user ($self) {
+ $self->{redirect} = $self->{c}->ce->{shibboleth}{logout_script};
+ return;
}
-# returns ($sessionExists, $keyMatches, $timestampValid)
-# if $updateTimestamp is true, the timestamp on a valid session is updated
-# override function: allow shib to handle the session time out
-sub check_session {
- my ($self, $userID, $possibleKey, $updateTimestamp) = @_;
+sub check_session ($self, $userID, $possibleKey, $updateTimestamp) {
my $ce = $self->{c}->ce;
my $db = $self->{c}->db;
- if ($ce->{shiboff}) {
- return $self->SUPER::check_session(@_);
- } else {
- my $Key = $db->getKey($userID); # checked
- return 0 unless defined $Key;
-
- my $keyMatches = (defined $possibleKey and $possibleKey eq $Key->key);
- my $timestampValid = (time <= $Key->timestamp() + $ce->{sessionTimeout});
- if ($ce->{shibboleth}{manage_session_timeout}) {
- # always valid to allow shib to take control of timeout
- $timestampValid = 1;
- }
+ my $Key = $db->getKey($userID);
+ return 0 unless defined $Key;
- if ($keyMatches and $timestampValid and $updateTimestamp) {
- $Key->timestamp(time);
- $db->putKey($Key);
- }
- return (1, $keyMatches, $timestampValid);
+ # This is filled in just in case it is needed somewhere, but is not used in the Shibboleth authentication process.
+ $self->{session_key} = $Key->{key};
+
+ my $currentTime = time;
+ my $timestampValid =
+ $ce->{shibboleth}{manage_session_timeout} ? 1 : time <= $Key->timestamp + $ce->{sessionTimeout};
+
+ if ($timestampValid && $updateTimestamp) {
+ $Key->timestamp($currentTime);
+ $self->{c}->stash->{'webwork2.database_session'} = { $Key->toHash };
+ $self->{c}->stash->{'webwork2.database_session'}{session}{flash} =
+ delete $self->{c}->stash->{'webwork2.database_session'}{session}{new_flash}
+ if $self->{c}->stash->{'webwork2.database_session'}{session}{new_flash};
}
+
+ return (1, 1, $timestampValid);
}
1;
diff --git a/lib/WeBWorK/ContentGenerator.pm b/lib/WeBWorK/ContentGenerator.pm
index baa9f5a24e..9b9cb538e7 100644
--- a/lib/WeBWorK/ContentGenerator.pm
+++ b/lib/WeBWorK/ContentGenerator.pm
@@ -1070,6 +1070,10 @@ session_management_via is "key" then the "key" is added.
sub hidden_authen_fields ($c, $id_prefix = undef) {
my @fields = ('user', 'effectiveUser');
push(@fields, 'key') if $c->ce->{session_management_via} ne 'session_cookie';
+
+ # Make the Shibboleth bypass_query parameter persistent if it is configured.
+ push(@fields, $c->ce->{shibboleth}{bypass_query}) if $c->ce->{shibboleth}{bypass_query};
+
return $c->hidden_fields({ id_prefix => $id_prefix }, @fields) if defined $id_prefix;
return $c->hidden_fields(@fields);
}
@@ -1106,10 +1110,11 @@ sub url_authen_args ($c) {
# When cookie based session management is in use, there should be no need
# to reveal the user and key in the URL. Putting it there makes session
# hijacking easier, in particular should a student share such a URL.
+ # If the Shibboleth authentication module is in use, then make the bypass_query parameter persistent.
if ($ce->{session_management_via} eq 'session_cookie') {
- return $c->url_args('effectiveUser');
+ return $c->url_args('effectiveUser', $c->ce->{shibboleth}{bypass_query} // ());
} else {
- return $c->url_args('user', 'effectiveUser', 'key');
+ return $c->url_args('user', 'effectiveUser', 'key', $c->ce->{shibboleth}{bypass_query} // ());
}
}
@@ -1188,6 +1193,9 @@ sub systemLink ($c, $urlpath, %options) {
}
$params{effectiveUser} = undef unless exists $params{effectiveUser};
+
+ # Make the Shibboleth bypass_query parameter persistent if it is configured.
+ $params{ $c->ce->{shibboleth}{bypass_query} } = undef if $c->ce->{shibboleth}{bypass_query};
}
my $url = $options{use_abs_url} ? $urlpath->to_abs : $urlpath;
diff --git a/lib/WeBWorK/Controller.pm b/lib/WeBWorK/Controller.pm
index 6e2f36cf08..465e326184 100644
--- a/lib/WeBWorK/Controller.pm
+++ b/lib/WeBWorK/Controller.pm
@@ -63,7 +63,7 @@ sub param ($c, @opts) {
# Override the Mojolicious::Controller session method to set the cookie parameters
# from the course environment the first time it is called.
sub session ($c, @args) {
- return if $c->stash('disable_cookies');
+ return {} if $c->stash('disable_cookies');
# Initialize the cookie session the first time this is called.
unless ($c->stash->{'webwork2.cookie_session_initialized'}) {