From 0010fb8dc59f73398e27740d2ad0e1311c5b9acd Mon Sep 17 00:00:00 2001 From: Paul Vojta Date: Sun, 9 Dec 2012 11:19:39 -0600 Subject: [PATCH] CAS authentication module, associated conf file and changes to Login.pm and Logout.pm --- conf/authen_CAS.conf.dist | 36 +++++++ lib/WeBWorK/Authen/CAS.pm | 136 +++++++++++++++++++++++++ lib/WeBWorK/ContentGenerator/Login.pm | 10 ++ lib/WeBWorK/ContentGenerator/Logout.pm | 3 + 4 files changed, 185 insertions(+) create mode 100644 conf/authen_CAS.conf.dist create mode 100644 lib/WeBWorK/Authen/CAS.pm diff --git a/conf/authen_CAS.conf.dist b/conf/authen_CAS.conf.dist new file mode 100644 index 0000000000..62417696a1 --- /dev/null +++ b/conf/authen_CAS.conf.dist @@ -0,0 +1,36 @@ +#!perl + +######################################################################################## +# authen_CAS.conf.dist +# Copy this file to authen_CAS.conf. Then configure it to match your server's CAS configuration. +# Then to activate add the following line to localOverrides.conf: +# include("conf/authen_CAS.conf") +######################################################################################## + +# Set CAS as the authentication module to use. +$authen{user_module} = { + "*" => "WeBWorK::Authen::CAS", +}; + +$authen{cas_options} = { + # Options to pass to the AuthCAS module. + # Note that this is (plain) AuthCAS, not Apache::AuthCAS + # or Apache2::AuthCAS. + # You need at least casUrl and CAFile; others can be set as well. + AuthCAS_opts => { + # URL of CAS server. Edit the host below. + casURL => '', #e.g. 'https://auth.berkeley.edu/cas', + + # Path of certificate file for CAS server. + CAFile => '', #e.g. '/etc/pki/tls/certs/ca-bundle.crt', + }, + # There are no options specific to CAS at this time. If there were, + # though, they would go here. + + # For debugging: + #su_from => '8315', + su_to => '999999', +}; + + +1; #final line of the file to reassure perl that it was read properly. diff --git a/lib/WeBWorK/Authen/CAS.pm b/lib/WeBWorK/Authen/CAS.pm new file mode 100644 index 0000000000..1cc78fea15 --- /dev/null +++ b/lib/WeBWorK/Authen/CAS.pm @@ -0,0 +1,136 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: $ +# +# 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::CAS; +use base qw/WeBWorK::Authen/; + +use strict; +use warnings; +use AuthCAS; + +use WeBWorK::Debug; +#$WeBWorK::Debug::Enabled = 1; +#$WeBWorK::Debug::Logfile = "/opt/webwork/webwork2/logs/cas-debug.log"; +#$WeBWorK::Debug::AllowSubroutineOutput = "get_credentials"; + +sub get_credentials { + my ($self) = @_; + my $r = $self->{r}; + my $ce = $r->ce; + + # if we come in with a user_id, then we've already authenticated + # through the CAS. So just check the provided user and session key. + $self->{external_auth} = 1; + if (defined $r->param('key') && defined $r->param('user')) { + # These lines were copied from the superclass get_credentials. + $self->{session_key} = $r->param('key'); + $self->{user_id} = $r->param('user'); + $self->{login_type} = 'normal'; + $self->{credential_source} = 'params'; + debug("CAS params user '", $self->{user_id}, + "' key '", $self->{session_key}, "'"); + # Check session key and user here. Otherwise, a student can + # determine the enrollment status of any other student if + # they know the userid (which is public information at + # Berkeley). That would be a privacy violation. + my $Key = $r->db->getKey($self->{user_id}); + unless (defined $Key && $Key->key eq $self->{session_key}) { + debug('undefined or invalid session key: $Key->key = ', + defined $Key ? $Key->key : undef, ', user value = ', + $self->{session_key}); + $self->{error} = "Invalid session key"; + return 0; + } + return 1; + #debug("falling back to superclass get_credentials"); + #return $self->SUPER::get_credentials( @_ ); + } else { + #my $cas_url = $ce->{authen}{cas_options}{url}; + #my $cas_certs = $ce->{authen}{cas_options}{certs}; + #my $cas = new AuthCAS(casUrl => $cas_url, + # CAFile => $cas_certs); + my $cas = new AuthCAS( + %{ $ce->{authen}{cas_options}{AuthCAS_opts} }); + + my $service = $ce->{apache_root_url} . $r->location . '/' + . $r->urlpath->arg('courseID') . '/'; + debug("service = $service"); + my $ticket = $r->param('ticket') || 0; + unless ($ticket) { + # there's no ticket, so redirect to get one + # + my $go_to = $cas->getServerLoginURL($service); + #$go_to = 'http://math.berkeley.edu/'; # for debugging + debug("no ticket. Redirecting to $go_to"); + $self->{redirect} = $go_to; + return 0; + } + # We have a ticket. Validate it. + my $user_id = $cas->validateST($service, $ticket); + if (!defined $user_id) { + my $err = $cas->get_errors(); + $err = '' unless defined $err; + $self->{error} = $err; + debug("ticket error $err"); + #return $self->SUPER::get_credentials( @_ ); + return 0; + } else { + debug("ticket is good, user is $user_id"); + if (defined $ce->{authen}{cas_options}{su_from} + && $user_id eq $ce->{authen}{cas_options}{su_from} + && defined $ce->{authen}{cas_options}{su_to}) { + $user_id = $ce->{authen}{cas_options}{su_to}; + debug("hackily changing user to $user_id"); + } + $self->{'user_id'} = $user_id; + $self->{r}->param('user', $user_id); + $self->{session_key} = undef; + $self->{password} = "not\tvalid"; + $self->{login_type} = 'normal'; + $self->{credential_source} = 'cas'; + return 1; + } + } +} + +# There's no need to provide site_checkPassword, since it's only accessed +# from checkPassword, which we're replacing. + +sub checkPassword { + my ( $self, $userID, $clearTextPassword ) = @_; + # if we got here, we know we've already successfully authenticated + # against the CAS + return 1; +} + +# Handle logout by redirecting to the relevant CAS url. + +sub logout_user { + my ($self) = @_; + + my $ce = $self->{r}->ce; + + # Using AuthCAS::getServerLogoutURL($service) would be overkill, + # and (more important) it would send us back here after logging out, + # so we'd end up back at the CAS login screen. + + my $go_to = $ce->{authen}{cas_options}{AuthCAS_opts}{casUrl} + . '/logout'; + debug("logging out. Redirecting to $go_to"); + $self->{redirect} = $go_to; +} + +1; diff --git a/lib/WeBWorK/ContentGenerator/Login.pm b/lib/WeBWorK/ContentGenerator/Login.pm index 80ea73c2a1..851913e228 100644 --- a/lib/WeBWorK/ContentGenerator/Login.pm +++ b/lib/WeBWorK/ContentGenerator/Login.pm @@ -117,6 +117,16 @@ sub links { return( @return); } +sub pre_header_initialize { + my ($self) = @_; + my $authen = $self->r->authen; + + if ( defined($authen->{redirect}) && $authen->{redirect} ) { + $self->reply_with_redirect($authen->{redirect}); + } +} + + sub body { my ($self) = @_; my $r = $self->r; diff --git a/lib/WeBWorK/ContentGenerator/Logout.pm b/lib/WeBWorK/ContentGenerator/Logout.pm index 4ea4cde833..643deed077 100644 --- a/lib/WeBWorK/ContentGenerator/Logout.pm +++ b/lib/WeBWorK/ContentGenerator/Logout.pm @@ -82,6 +82,9 @@ sub pre_header_initialize { } $self->{keyError} = $keyError; + # Do any special processing needed by external authentication + $authen->logout_user() if $authen->can('logout_user'); + # if we have an authen redirect, all of those errors may be # moot, but I think that's unavoidable (-glarose) if ( defined($authen->{redirect}) && $authen->{redirect} ) {