</IfModule>
</LocationMatch>
<Location /eg/opac>
+ # Uncomment the entries below to enable Shibboleth authentication
+ #AuthType shibboleth
+ #Require shibboleth
+
PerlSetVar OILSWebContextLoader "OpenILS::WWW::EGCatLoader"
# Expire the HTML quickly since we're loading dynamic data for each page
ExpiresActive On
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Fieldmapper;
use OpenSRF::Utils::Cache;
+use OpenILS::Event;
use DateTime::Format::ISO8601;
use CGI qw(:all -utf8);
use Time::HiRes;
use constant COOKIE_SES => 'ses';
use constant COOKIE_LOGGEDIN => 'eg_loggedin';
+use constant COOKIE_SHIB_LOGGEDOUT => 'eg_shib_logged_out';
+use constant COOKIE_SHIB_LOGGEDIN => 'eg_shib_logged_in';
use constant COOKIE_TZ => 'client_tz';
use constant COOKIE_PHYSICAL_LOC => 'eg_physical_loc';
use constant COOKIE_SSS_EXPAND => 'eg_sss_expand';
}
}
+ return $self->load_manual_shib_login if $path =~ m|opac/manual_shib_login|;
# ----------------------------------------------------------------
# Everything below here requires authentication
# ----------------------------------------------------------------
# -----------------------------------------------------------------------------
sub redirect_auth {
my $self = shift;
- my $login_page = sprintf('%s://%s%s/login',($self->ctx->{is_staff} ? 'oils' : 'https'), $self->ctx->{hostname}, $self->ctx->{opac_root});
+
+ my $sso_org = $ENV{sso_loc} || $self->get_physical_loc || $self->_get_search_lib();
+ my $sso_enabled = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.enable');
+ my $sso_native = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.allow_native');
+
+ my $login_type = ($sso_enabled and !$sso_native) ? 'manual_shib_login' : 'login';
+ my $login_page = sprintf('%s://%s%s/%s',($self->ctx->{is_staff} ? 'oils' : 'https'), $self->ctx->{hostname}, $self->ctx->{opac_root}, $login_type);
my $redirect_to = uri_escape_utf8($self->apache->unparsed_uri);
- return $self->generic_redirect("$login_page?redirect_to=$redirect_to");
+ my $redirect_url = "$login_page?redirect_to=$redirect_to";
+
+ return $self->generic_redirect($redirect_url);
}
# -----------------------------------------------------------------------------
$self->timelog("Load login begins");
+ my $sso_org = $ENV{sso_loc} || $self->get_physical_loc || $self->_get_search_lib();
+ $ctx->{sso_org} = $sso_org;
+ my $sso_enabled = $ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.enable');
+ my $sso_native = $ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.allow_native');
+ my $sso_eg_match = $ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.evergreen_matchpoint') || 'usrname';
+ my $sso_shib_match = $ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.shib_matchpoint') || 'uid';
+
$ctx->{page} = 'login';
my $username = $cgi->param('username') || '';
my $persist = $cgi->param('persist');
my $client_tz = $cgi->param('client_tz');
- # initial log form only
- return Apache2::Const::OK unless $username and $password;
+ my $sso_user_match_value;
+ my $response;
+ my $sso_logged_in;
+ $self->timelog("SSO is enabled") if ($sso_enabled);
+ if ($sso_enabled
+ and $sso_user_match_value = $ENV{$sso_shib_match}
+ and !$self->cgi->cookie(COOKIE_SHIB_LOGGEDOUT)
+ ) { # we have a shib session, and have not cleared a previous shib-login cookie
+ $self->timelog("Have an SSO user match value: $sso_user_match_value");
+
+ if ($sso_eg_match eq 'barcode') { # barcode is special
+ my $card = $self->editor->search_actor_card({barcode => $sso_user_match_value})->[0];
+ $sso_user_match_value = $card ? $card->usr : undef;
+ $sso_eg_match = 'id';
+ }
- my $auth_proxy_enabled = 0; # default false
- try { # if the service is not running, just let this fail silently
- $auth_proxy_enabled = $U->simplereq(
- 'open-ils.auth_proxy',
- 'open-ils.auth_proxy.enabled');
- } catch Error with {};
+ if ($sso_user_match_value && $sso_eg_match) {
+ my $user = $self->editor->search_actor_user({ $sso_eg_match => $sso_user_match_value })->[0];
+ if ($user) { # create a session
+ $response = $U->simplereq(
+ 'open-ils.auth_internal',
+ 'open-ils.auth_internal.session.create',
+ { user_id => $user->id, login_type => 'opac' }
+ );
+ $sso_logged_in = $response ? 1 : 0;
+ }
+ }
- $self->timelog("Checked for auth proxy: $auth_proxy_enabled; org = $org_unit; username = $username");
+ $self->timelog("Checked for SSO login");
+ }
- my $args = {
- type => ($persist) ? 'persist' : 'opac',
- org => $org_unit,
- agent => 'opac'
- };
+ if (!$sso_enabled || (!$response && $sso_native)) {
+ # initial log form only
+ return Apache2::Const::OK unless $username and $password;
- my $bc_regex = $ctx->{get_org_setting}->($org_unit, 'opac.barcode_regex');
+ my $auth_proxy_enabled = 0; # default false
+ try { # if the service is not running, just let this fail silently
+ $auth_proxy_enabled = $U->simplereq(
+ 'open-ils.auth_proxy',
+ 'open-ils.auth_proxy.enabled');
+ } catch Error with {};
- # To avoid surprises, default to "Barcodes start with digits"
- $bc_regex = '^\d' unless $bc_regex;
+ $self->timelog("Checked for auth proxy: $auth_proxy_enabled; org = $org_unit; username = $username");
- if ($bc_regex and ($username =~ /$bc_regex/)) {
- $args->{barcode} = $username;
- } else {
- $args->{username} = $username;
- }
+ my $args = {
+ type => ($persist) ? 'persist' : 'opac',
+ org => $org_unit,
+ agent => 'opac'
+ };
- my $response;
- if (!$auth_proxy_enabled) {
- my $seed = $U->simplereq(
- 'open-ils.auth',
- 'open-ils.auth.authenticate.init', $username);
- $args->{password} = md5_hex($seed . md5_hex($password));
- $response = $U->simplereq(
- 'open-ils.auth', 'open-ils.auth.authenticate.complete', $args);
+ my $bc_regex = $ctx->{get_org_setting}->($org_unit, 'opac.barcode_regex');
+
+ # To avoid surprises, default to "Barcodes start with digits"
+ $bc_regex = '^\d' unless $bc_regex;
+
+ if ($bc_regex and ($username =~ /$bc_regex/)) {
+ $args->{barcode} = $username;
+ } else {
+ $args->{username} = $username;
+ }
+
+ if (!$auth_proxy_enabled) {
+ my $seed = $U->simplereq(
+ 'open-ils.auth',
+ 'open-ils.auth.authenticate.init', $username);
+ $args->{password} = md5_hex($seed . md5_hex($password));
+ $response = $U->simplereq(
+ 'open-ils.auth', 'open-ils.auth.authenticate.complete', $args);
+ } else {
+ $args->{password} = $password;
+ $response = $U->simplereq(
+ 'open-ils.auth_proxy',
+ 'open-ils.auth_proxy.login', $args);
+ }
+ $self->timelog("Checked password");
} else {
- $args->{password} = $password;
- $response = $U->simplereq(
- 'open-ils.auth_proxy',
- 'open-ils.auth_proxy.login', $args);
+ $response ||= OpenILS::Event->new( 'LOGIN_FAILED' ); # assume failure
}
- $self->timelog("Checked password");
if($U->event_code($response)) {
# login failed, report the reason to the template
);
}
+ if ($sso_logged_in) {
+ # tells us if we're logged in via shib, so we can decide whether to try logging in again.
+ push @$cookie_list, $cgi->cookie(
+ -name => COOKIE_SHIB_LOGGEDOUT,
+ -path => '/',
+ -secure => 0,
+ -value => '0',
+ -expires => '-1h'
+ );
+ push @$cookie_list, $cgi->cookie(
+ -name => COOKIE_SHIB_LOGGEDIN,
+ -path => '/',
+ -secure => 0,
+ -value => '1',
+ -expires => $login_cookie_expires
+ );
+ }
+
return $self->generic_redirect(
$cgi->param('redirect_to') || $acct,
$cookie_list
);
}
+sub load_manual_shib_login {
+ my $self = shift;
+ my $redirect_to = shift || $self->cgi->param('redirect_to');
+
+ my $sso_org = $ENV{sso_loc} || $self->get_physical_loc || $self->_get_search_lib();
+ my $sso_entity_id = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.entityId');
+ my $sso_shib_match = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.shib_matchpoint') || 'uid';
+
+ return $self->load_login if ($ENV{$sso_shib_match});
+
+ my $url = '/Shibboleth.sso/Login?target=' . ($redirect_to || $self->ctx->{home_page});
+ if ($sso_entity_id) {
+ $url .= '&entityID=' . $sso_entity_id;
+ }
+
+ return $self->generic_redirect( $url,
+ [
+ $self->cgi->cookie(
+ -name => COOKIE_SHIB_LOGGEDOUT,
+ -path => '/',
+ -value => '0',
+ -expires => '-1h'
+ )
+ ]
+ );
+}
+
# -----------------------------------------------------------------------------
# Log out and redirect to the home page
# -----------------------------------------------------------------------------
sub load_logout {
my $self = shift;
my $redirect_to = shift || $self->cgi->param('redirect_to');
+ my $active_logout = $self->cgi->param('active_logout');
+
+ my $sso_org = $ENV{sso_loc} || $self->get_physical_loc || $self->_get_search_lib();
+ $self->ctx->{sso_org} = $sso_org;
+ my $sso_enabled = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.enable');
+ my $sso_entity_id = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.entityId');
+ my $sso_logout = $self->ctx->{get_org_setting}->($sso_org, 'opac.login.shib_sso.logout');
+ if ($sso_enabled && $sso_logout) {
+ $redirect_to = '/Shibboleth.sso/Logout?return=' . ($redirect_to || $self->ctx->{home_page});
+ if ($sso_entity_id) {
+ $redirect_to .= '&entityID=' . $sso_entity_id;
+ }
+ }
# If the user was adding anyting to an anonymous cache
# while logged in, go ahead and clear it out.
-path => '/',
-value => '',
-expires => '-1h'
+ ),
+ ($active_logout ? ($self->cgi->cookie(
+ -name => COOKIE_SHIB_LOGGEDOUT,
+ -path => '/',
+ -value => '1',
+ -expires => '2147483647'
+ )) : ()),
+ $self->cgi->cookie(
+ -name => COOKIE_SHIB_LOGGEDIN,
+ -path => '/',
+ -value => '0',
+ -expires => '-1h'
)
]
);
( 625, 'VIEW_BOOKING_RESERVATION', oils_i18n_gettext(625,
'View booking reservations', 'ppl', 'description')),
( 626, 'VIEW_BOOKING_RESERVATION_ATTR_MAP', oils_i18n_gettext(626,
- 'View booking reservation attribute maps', 'ppl', 'description'))
+ 'View booking reservation attribute maps', 'ppl', 'description')),
+ ( 627, 'SSO_ADMIN', oils_i18n_gettext(627,
+ 'Modify patron SSO settings', 'ppl', 'description'))
;
'coust', 'description'),
'bool', null);
+INSERT INTO config.org_unit_setting_type
+( name, grp, label, description, datatype, update_perm )
+VALUES
+('opac.login.shib_sso.enable',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.enable', 'Enable Shibboleth SSO for the OPAC', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.enable', 'Enable Shibboleth SSO for the OPAC', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.entityId',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.entityId', 'Shibboleth SSO Entity ID', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.entityId', 'Which configured Entity ID to use for SSO when there is more than one available to Shibboleth', 'coust', 'description'),
+ 'string', 627),
+('opac.login.shib_sso.logout',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.logout', 'Log out of the Shibboleth IdP', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.logout', 'When logging out of Evergreen, also force a logout of the IdP behind Shibboleth', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.allow_native',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.allow_native', 'Allow both Shibboleth and native OPAC authentication', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.allow_native', 'When Shibboleth SSO is enabled, also allow native Evergreen authentication', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.evergreen_matchpoint',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.evergreen_matchpoint', 'Evergreen SSO matchpoint', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.evergreen_matchpoint',
+ 'Evergreen-side field to match a patron against for Shibboleth SSO. Default is usrname. Other reasonable values would be barcode or email.',
+ 'coust', 'description'),
+ 'string', 627),
+('opac.login.shib_sso.shib_matchpoint',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.shib_matchpoint', 'Shibboleth SSO matchpoint', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.shib_matchpoint',
+ 'Shibboleth-side field to match a patron against for Shibboleth SSO. Default is uid; use eppn for Active Directory', 'coust', 'description'),
+ 'string', 627)
+;
INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
VALUES (
--- /dev/null
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- XXX Check perm number collisions, and adjust update_perm below if necessary!
+INSERT INTO permission.perm_list (id,code,description) VALUES (627,'SSO_ADMIN','Modify patron SSO settings');
+
+INSERT INTO config.org_unit_setting_type
+( name, grp, label, description, datatype, update_perm )
+VALUES
+('opac.login.shib_sso.enable',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.enable', 'Enable Shibboleth SSO for the OPAC', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.enable', 'Enable Shibboleth SSO for the OPAC', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.entityId',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.entityId', 'Shibboleth SSO Entity ID', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.entityId', 'Which configured Entity ID to use for SSO when there is more than one available to Shibboleth', 'coust', 'description'),
+ 'string', 627),
+('opac.login.shib_sso.logout',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.logout', 'Log out of the Shibboleth IdP', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.logout', 'When logging out of Evergreen, also force a logout of the IdP behind Shibboleth', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.allow_native',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.allow_native', 'Allow both Shibboleth and native OPAC authentication', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.allow_native', 'When Shibboleth SSO is enabled, also allow native Evergreen authentication', 'coust', 'description'),
+ 'bool', 627),
+('opac.login.shib_sso.evergreen_matchpoint',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.evergreen_matchpoint', 'Evergreen SSO matchpoint', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.evergreen_matchpoint',
+ 'Evergreen-side field to match a patron against for Shibboleth SSO. Default is usrname. Other reasonable values would be barcode or email.',
+ 'coust', 'description'),
+ 'string', 627),
+('opac.login.shib_sso.shib_matchpoint',
+ 'opac',
+ oils_i18n_gettext('opac.login.shib_sso.shib_matchpoint', 'Shibboleth SSO matchpoint', 'coust', 'label'),
+ oils_i18n_gettext('opac.login.shib_sso.shib_matchpoint',
+ 'Shibboleth-side field to match a patron against for Shibboleth SSO. Default is uid; use eppn for Active Directory', 'coust', 'description'),
+ 'string', 627)
+;
+
+COMMIT;
</div>
[% END %]
+[%
+ redirect = CGI.param('redirect_to');
+ # Don't use referer unless we got here from elsewhere within the TPAC
+ IF !redirect AND ctx.referer.match('^https?://' _ ctx.hostname _ ctx.opac_root);
+ redirect = ctx.referer;
+ END;
+ # If no redirect is offered or it's leading us back to the
+ # login form, redirect the user to My Account
+ IF !redirect OR redirect.match(ctx.path_info _ '$');
+ redirect = CGI.url('-full' => 1) _ '/opac/myopac/main';
+ END;
+ redirect = redirect | replace('^http:', 'https:');
+%]
+
+[% sso_enabled = ctx.get_org_setting(ctx.sso_org, 'opac.login.shib_sso.enable');
+ sso_native = ctx.get_org_setting(ctx.sso_org, 'opac.login.shib_sso.allow_native');
+%]
+
<div id='login-form-box' class='login_boxes left_brain float-left'>
<h1>[% l('Log in to Your Account') %]</h1>
+ [% IF sso_enabled %]
+ [% final_redirect = redirect | html %]
+ <div id='sso-login-notice'>[%- l('Please use our ') -%]
+ <a href="[% mkurl(ctx.opac_root _ '/manual_shib_login', { redirect_to => final_redirect }) %]">
+ [% l('Single Sign On service') %]
+ </a>
+ [%- l('to log into the catalog') -%]
+ [%- IF sso_native; l(' or use the form below'); END -%]
+ [%- l('.') -%]</div>
+ <br/><br/>
+ [% END %]
+ [% IF !sso_enabled || sso_native %]
[% l('Please enter the following information:') %]
<form method='post'>
<div class='login-form-left'>
[% END %]
</div>
<div style="clear: both; padding-top: 15px;">
- [%
- redirect = CGI.param('redirect_to');
- # Don't use referer unless we got here from elsewhere within the TPAC
- IF !redirect AND ctx.referer.match('^https?://' _ ctx.hostname _ ctx.opac_root);
- redirect = ctx.referer;
- END;
- # If no redirect is offered or it's leading us back to the
- # login form, redirect the user to My Account
- IF !redirect OR redirect.match(ctx.path_info _ '$');
- redirect = CGI.url('-full' => 1) _ '/opac/myopac/main';
- END;
- redirect = redirect | replace('^http:', 'https:');
- %]
<input type='hidden' name='redirect_to' value='[% redirect | html %]'/>
<input type="checkbox" name="persist" id="login_persist" /><label for="login_persist"> [% l('Stay logged in?') %]</label>
<input type="submit" value="[% l('Log in') %]" class="opac-button" />
</div>
<input id="client_tz_id" name="client_tz" type="hidden" />
</form>
+ [% END; # native block %]
</div>
[% INCLUDE "opac/parts/login/help.tt2" %]
class="opac-button">[% l('My Account') %]</a>
<a href="[% mkurl(ctx.opac_root _ '/myopac/lists', {}, ['single', 'message_id', 'hid', 'from_basket']) %]"
class="opac-button">[% l('My Lists') %]</a>
- <a href="[% mkurl(ctx.opac_root _ '/logout', {}, 1) %]"
+ <a href="[% mkurl(ctx.opac_root _ '/logout', {active_logout => 1}, 1) %]"
class="opac-button" id="logout_link">[% l('Logout') %]</a>
</span>
</div>