--- loncom/auth/lonshibauth.pm 2015/05/17 17:34:43 1.3
+++ loncom/auth/lonshibauth.pm 2023/06/02 01:20:26 1.18
@@ -1,7 +1,8 @@
# The LearningOnline Network
-# Redirect Shibboleth authentication to designated URL (/adm/sso).
+# Redirect Single Sign On authentication to designated URL:
+# /adm/sso, by default.
#
-# $Id: lonshibauth.pm,v 1.3 2015/05/17 17:34:43 raeburn Exp $
+# $Id: lonshibauth.pm,v 1.18 2023/06/02 01:20:26 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -28,14 +29,15 @@
=head1 NAME
-Apache::lonshibauth - Redirect Shibboleth authentication
+Apache::lonshibauth - Redirect Single Sign On authentication
=head1 SYNOPSIS
-Invoked when lonOtherAuthen is set to yes, and type is Shibboleth
+Invoked when an Apache config file includes:
+PerlAuthenHandler Apache::lonshibauth
If server is configured as a Shibboleth SP, the main Apache
-configuration file, e.g., /etc/httpd/conf/httpd.conf
+configuration file, e.g., /etc/httpd/conf/httpd.conf
(for RHEL/CentOS/Scentific Linux/Fedora) should contain:
LoadModule mod_shib /usr/lib/shibboleth/mod_shib_22.so
@@ -43,10 +45,99 @@ LoadModule mod_shib /usr/lib/shibboleth/
or equivalent (depending on Apache version)
before the line to include conf/loncapa_apache.conf
+If some other Apache module is in use for Single Sign On
+authentication e.g., mod_auth_cas or mod_sentinel,
+then a separate config file should be created which
+includes settings for the authentication module.
+
=head1 INTRODUCTION
-Redirects a user requiring Single Sign On via Shibboleth to a
-URL -- /adm/sso -- on the server which is configured to use that service.
+Redirects a user requiring Single Sign On to a URL on the server
+which is configured to use that service. The default URL is:
+/adm/sso.
+
+If this is to be used with a Single Sign On service other Shibboleth
+then an Apache config file needs to be loaded which:
+
+(a) loads the corresponding Apache module, and
+(b) sets appropriate values for perl vars in
+an block, and
+(c) sets an appropriate value for AuthType in a
+
+ block, which also contains
+
+require valid-user
+
+PerlAuthzHandler Apache::lonshibacc
+
+PerlAuthzHandler Apache::lonacc
+
+In the case of Shibboleth no additional file is needed
+because loncapa_apache.conf already contains:
+
+
+ PerlAuthenHandler Apache::lonshibauth
+ PerlSetVar lonOtherAuthen yes
+ PerlSetVar lonOtherAuthenType Shibboleth
+
+
+
+and
+
+
+ Header set Cache-Control "private,no-store,no-cache,max-age=0"
+
+ AuthType shibboleth
+ ShibUseEnvironment On
+ ShibRequestSetting requireSession 1
+ ShibRequestSetting redirectToSSL 443
+ require valid-user
+ PerlAuthzHandler Apache::lonshibacc
+ PerlAuthzHandler Apache::lonacc
+ ErrorDocument 403 /adm/login
+ ErrorDocument 500 /adm/errorhandler
+
+
+ PerlTypeHandler Apache::lonnoshib
+
+
+
+
+If the service is not Shibboleth, then (optionally) a URL that is
+not /adm/sso can be used as the URL for the service, e.g., /adm/cas
+or /adm/sentinel, by setting a lonOtherAuthenUrl perl var
+in an Apache config file containing (for example):
+
+PerlSetVar lonOtherAuthenUrl /adm/sentinel
+
+
+ PerlAuthenHandler Apache::lonshibauth
+ PerlSetVar lonOtherAuthen yes
+ PerlSetVar lonOtherAuthenType Sentinel
+
+
+
+
+ Header set Cache-Control "private,no-store,no-cache,max-age=0"
+
+ AuthType Sentinel
+ require valid-user
+ PerlAuthzHandler Apache::lonshibacc
+ PerlAuthzHandler Apache::lonacc
+ ErrorDocument 403 /adm/login
+ ErrorDocument 500 /adm/errorhandler
+
+
+ PerlTypeHandler Apache::lonnoshib
+
+
+
+
+In the example above for Sentinel SSO, it would also be possible to
+use /adm/sso instead of /adm/sentinel, in which case (i) there would be
+no need to define lonOtherAuthenUrl, (ii) there would be
+and (iii) the block would not be needed as
+it is already present in /etc/httpd/conf/loncapa_apache.conf.
=head1 HANDLER SUBROUTINE
@@ -54,10 +145,78 @@ This routine is called by Apache and mod
=over 4
-If $r->user defined and requested uri not /adm/sso
-redirect to /adm/sso
+If $r->user is defined and requested URL is not /adm/sso or
+other specific URL as set by a lonOtherAuthenUrl perlvar,
+then redirect to /adm/sso (or to the specific URL).
+
+Otherwise return DECLINED.
+
+In the case of redirection a query string is appended,
+which will contain either (a) the originally requested URL,
+if not /adm/sso (or lonOtherAuthenUrl URL), and
+any existing query string in the original request, or
+(b) if original request was for /tiny/domain/uniqueID,
+or if redirect is to /adm/login to support dual SSO and
+non-SSO, a query string which contains sso=tokenID, where the
+token contains information for deep-linking to course/resource.
+
+Support is included for use of LON-CAPA's standard log-in
+page -- /adm/login -- to support dual SSO and non-SSO
+authentication from that "landing" page.
+
+To enable dual SSO and non-SSO access from /adm/login
+a Domain Coordinator will use the web GUI:
+Main Menu > Set domain configuration > Display
+("Log-in page options" checked)
+and for any of the LON-CAPA domain's servers which
+will offer dual login will check "Yes" and then set:
+(a) SSO Text, Image, Alt Text, URL, Tool Tip
+(b) non-SSO: Text
+
+The value in the URL field should be /adm/sso,
+or the same URL as set for the lonOtherAuthenUrl
+perl var, e.g., /adm/sentinel.
-Otherwise return DECLINED
+=back
+
+=head1 NOTABLE SUBROUTINES
+
+=over 4
+
+=item set_token()
+
+Inputs: 2
+$r - request object
+$lonhost - hostID of current server
+
+Output: 1
+$querystring - query string to append to URL
+when redirecting.
+
+If any of the following items are present in the original query string:
+role, symb, and linkkey, then they will be stored in the token file
+on the server, for access later to support deep-linking. If the ltoken
+item is available, from successful launch from an LTI Consumer where
+LON-CAPA is the LTI Provider, but not configured to accept user
+information, and the destination is a deep-link URL /tiny/domain/uniqueiD,
+then the LTI number, type (c or d), and tiny URL will be saved as the
+linkprot item in a token file.
+
+=item set_mailtoken()
+
+Inputs: 2
+$r - request object
+$lonhost - hostID of current server
+
+Output: 1
+$querystring - query string to append to URL
+when redirecting.
+
+Called if requested URL is /adm/email, dual SSO and non-SSO login
+are supported by /adm/login and original query string contains values
+for elements: display, username and domain, which will then be
+stored in the token file on the server to support direct access
+to a specific message sent to the user.
=back
@@ -68,22 +227,67 @@ package Apache::lonshibauth;
use strict;
use lib '/home/httpd/lib/perl/';
use Apache::lonnet;
+use Apache::loncommon;
+use Apache::lonacc;
use Apache::Constants qw(:common REDIRECT);
-use LONCAPA qw(:DEFAULT);
+use LONCAPA qw(:DEFAULT :match);
sub handler {
my $r = shift;
- my $target = '/adm/sso';
- if (($r->user eq '') && ($r->uri() ne $target)) {
+ my $ssourl = '/adm/sso';
+ if ($r->dir_config('lonOtherAuthenUrl') ne '') {
+ $ssourl = $r->dir_config('lonOtherAuthenUrl');
+ }
+ my $target = $ssourl;
+ if (&Apache::lonnet::get_saml_landing()) {
+ $target = '/adm/login';
+ }
+ if (($r->user eq '') && ($r->uri ne $target) && ($r->uri ne $ssourl)) {
my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
my $hostname = &Apache::lonnet::hostname($lonhost);
if (!$hostname) { $hostname = $r->hostname(); }
my $protocol = $Apache::lonnet::protocol{$lonhost};
unless ($protocol eq 'https') { $protocol = 'http'; }
+ my $alias = &Apache::lonnet::use_proxy_alias($r,$lonhost);
+ if (($alias ne '') &&
+ (&Apache::lonnet::alias_sso($lonhost))) {
+ $hostname = $alias;
+ }
my $dest = $protocol.'://'.$hostname.$target;
- $r->subprocess_env;
- if ($ENV{'QUERY_STRING'} ne '') {
- $dest .= '?'.$ENV{'QUERY_STRING'};
+ if ($target eq '/adm/login') {
+ my $uri = $r->uri;
+ my $querystring;
+ if (($uri eq '/adm/email') && ($r->args ne '')) {
+ $querystring = &set_mailtoken($r,$lonhost);
+ } else {
+ $querystring = &set_token($r,$lonhost);
+ }
+ if ($querystring ne '') {
+ $dest .= '?'.$querystring;
+ }
+ } else {
+ my $uri = $r->uri;
+ if ($uri =~ m{^/tiny/$match_domain/\w+$}) {
+ my $querystring = &set_token($r,$lonhost);
+ if ($querystring ne '') {
+ $dest .= '?'.$querystring;
+ }
+ } elsif ((&Apache::lonnet::get_saml_landing()) &&
+ ($uri eq '/adm/email') && ($r->args ne '')) {
+ my $querystring = &set_mailtoken($r,$lonhost);
+ if ($querystring ne '') {
+ $dest .= '?'.$querystring;
+ }
+ } else {
+ if ($r->args ne '') {
+ $dest .= (($dest=~/\?/)?'&':'?').$r->args;
+ }
+ unless (($uri eq '/adm/roles') || ($uri eq '/adm/logout')) {
+ unless ($r->args =~ /origurl=/) {
+ $dest.=(($dest=~/\?/)?'&':'?').'origurl='.$uri;
+ }
+ }
+ }
}
$r->header_out(Location => $dest);
return REDIRECT;
@@ -92,5 +296,103 @@ sub handler {
}
}
+sub set_token {
+ my ($r,$lonhost) = @_;
+ my ($firsturl,$querystring,$ssotoken,@names,%token);
+ @names = ('role','symb','ltoken','linkkey');
+ map { $token{$_} = 1; } @names;
+ unless (($r->uri eq '/adm/roles') || ($r->uri eq '/adm/logout')) {
+ $firsturl = $r->uri;
+ }
+ if ($r->args ne '') {
+ &Apache::loncommon::get_unprocessed_cgi($r->args);
+ }
+ if ($r->uri =~ m{^/tiny/$match_domain/\w+$}) {
+ if ($env{'form.ttoken'}) {
+ my %info = &Apache::lonnet::tmpget($env{'form.ttoken'});
+ &Apache::lonnet::tmpdel($env{'form.ttoken'});
+ if ($info{'ltoken'}) {
+ $env{'form.ltoken'} = $info{'ltoken'};
+ } elsif ($info{'linkkey'} ne '') {
+ $env{'form.linkkey'} = $info{'linkkey'};
+ }
+ } else {
+ unless (($env{'form.ltoken'}) || ($env{'form.linkkey'})) {
+ &Apache::lonacc::get_posted_cgi($r,['linkkey']);
+ }
+ }
+ unless (($r->is_initial_req()) || ($env{'form.ltoken'}) ||
+ ($env{'form.linkkey'})) {
+ return;
+ }
+ }
+ my $extras;
+ foreach my $name (@names) {
+ if ($env{'form.'.$name} ne '') {
+ if ($name eq 'ltoken') {
+ my %info = &Apache::lonnet::tmpget($env{'form.ltoken'});
+ &Apache::lonnet::tmpdel($env{'form.ltoken'});
+ if ($info{'linkprot'}) {
+ $extras .= '&linkprot='.&escape($info{'linkprot'});
+ foreach my $item ('linkprotuser','linkprotexit','linkprotpbid','linkprotpburl') {
+ if ($info{$item} ne '') {
+ $extras .= '&'.$item.'='.&escape($info{$item});
+ }
+ }
+ last;
+ }
+ } else {
+ $extras .= '&'.$name.'='.&escape($env{'form.'.$name});
+ }
+ }
+ }
+ if (($firsturl ne '') || ($extras ne '')) {
+ $extras .= ':sso';
+ $ssotoken = &Apache::lonnet::reply('tmpput:'.&escape($firsturl).
+ $extras,$lonhost);
+ $querystring = 'sso='.$ssotoken;
+ }
+ if ($r->args ne '') {
+ foreach my $key (sort(keys(%env))) {
+ if ($key =~ /^form\.(.+)$/) {
+ my $name = $1;
+ next if (($token{$name}) || ($name eq 'ttoken'));
+ $querystring .= '&'.$name.'='.$env{$key};
+ }
+ }
+ }
+ return $querystring;
+}
+
+sub set_mailtoken {
+ my ($r,$lonhost) = @_;
+ my $firsturl = $r->uri;
+ my ($querystring,$ssotoken,$extras);
+ &Apache::loncommon::get_unprocessed_cgi($r->args);
+ my $extras;
+ if (($env{'form.display'} ne '') &&
+ ($env{'form.username'} =~ /^$match_username$/) &&
+ ($env{'form.domain'} =~ /^$match_domain$/)) {
+ $extras .= '&display='.&escape($env{'form.display'}).
+ '&mailrecip='.&escape($env{'form.username'}.':'.$env{'form.domain'});
+ }
+ if (($firsturl ne '') || ($extras ne '')) {
+ $extras .= ':sso';
+ $ssotoken = &Apache::lonnet::reply('tmpput:'.&escape($firsturl).
+ $extras,$lonhost);
+ $querystring = 'sso='.$ssotoken;
+ }
+ if ($r->args ne '') {
+ foreach my $key (sort(keys(%env))) {
+ if ($key =~ /^form\.(.+)$/) {
+ my $name = $1;
+ next if (($name eq 'display') || ($name eq 'username') || ($name eq 'domain'));
+ $querystring .= '&'.$name.'='.$env{$key};
+ }
+ }
+ }
+ return $querystring;
+}
+
1;
__END__